티스토리 뷰

 

2022.03.06 - [Devops 2회차/K8S Study] - k8s-06_워크로드 API(Pod, Replicaset, Deployment)

위 본문을 통해 쿠버네티스 워크로드 API 종류 3개를 먼저 공부해봤다.
지난회차에 다룬 것들이 훨씬 사용 빈도도 높고 대부분 일반적인 서비스에서는 보다 필수적으로 알아야 한다.
특히 기타 다른 워크로드 API들의 기본이 되는 POD는 조금 더 친해지고 더 많이 알아보길 바란다.

시작하기전에
서비스 별로 환경을 구분짓기위해서는 크게 아래와 같은 방법들이 있다.
- 클러스터를 서비스 별로 나누거나 (무모하고 낭비도 큰 방법일수있다.)

- 하나의 클러스터 안에서 네임스페이스 별로 나누거나 (흔한 방법.)

 

참조

쿠버네티스 완벽 가이드 (마사야 아오야마)

실무를 통한 경험

 

Daemonset

앞서 다룬 것처럼 하나의 클러스터 안에서 여러 서비스를 생성하고 관리하기 위해 논리적으로 선을 긋는다.

그 방법이 클러스터 자체일수도 있고 네임스페이스가 될 수도 있다.
만일 네임스페이스로 구분하였다 한다면 하나의 클러스터에 있는 노드 20개(예시)는 각기 다른 파드들이 떠 있을 것이다.

그런데 이런 개념과 잠시 동 떨어져서 
한 클러스터에 있는 모든 노드에 동일한 파드를 띄우고 싶다면 어떡할까?

그럴때 사용하는 것이 데몬셋이다.

 

모든 노드에 원하는 파드를 띄우는 것 뿐만 아니라 앞으로 추가되는 노드에도 해당 데몬셋 파드가 같이 뜬다.

데몬셋은 쉽게 풀어말하면 모든 노드에 이걸 띄워주세요. 라는 것이다.
그렇기 때문에 replicas 개수를 정할수도 없다. "동일한 조건으로 2대씩!" 이라는 커맨드가 안 먹히는 것이다.

언제쓸까?
일반적으로 서비스를 제공하기 위해 데몬셋 워크로드를 사용하는 경우는 드물다.
데몬셋은 보통 모든 노드에 붙어 상태를 체크하는 모니터링 시스템에서 많이 사용된다.
프로메테우스의 node exporter, 혹은 로그를 수집하는 fluentd와 같은 agent들이 데몬셋으로 뜨곤 한다.

아래는 Daemonset 예제이다.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      # these tolerations are to have the daemonset runnable on control plane nodes
      # remove them if your control plane nodes should not run pods
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

toleration은 추후에 다뤄보고 우선 구성만 보면 모든 워크로드(deployment, daemonsest, statefulset) API spec레벨은 거의 동일하다. volume을 선언하고 container 정보를 설정하고 적용되는 각 옵션도 다 동일하다.
하지만 각 워크로드마다 조금씩 차이가 있을 수 있기에 항상 공식 매뉴얼을 잘 참조해야한다.

 

Daemonset의 특징은 배포(업데이트)이다.

 

데몬셋을 수정하고자 할 때 두 가지중 하나를 선택할 수 있다.

  • Ondelete : 데몬셋 메니페스트를 수정하여도 업데이트가 바로 적용되지 않고 다른 이유로 파드가 재시작될때 신규 버전이 뜨게 된다.
  • RollingUpdate : 순차적으로 정해진 룰에 따라 바로 업데이트를 시작하는 것. 단, 위에서 강조한 것처럼 데몬셋은 동일한 매니페스트에서 replicas 개수를 설정할 수 없다. 새로운 버전이 업데이트 되었다고 한들 신규버전파드를 하나 더 띄울 수 없다.
    그래서 데몬셋의 RollingUpdate에는 maxSurge(최대 몇대를 신규로 띄우며 배포할까?)없다.
    오로지 maxUnavailable(몇 대를 제거하며 배포할까?)만 지정이 가능하다. (기본값은 1이며 0으로 지정하는 것 또한 불가하다.)

나중에 Daemonset을 적용시키고 싶지 않은 노들드은 taint-toleration 설정을 추가하게 되는데 이 부분은 실무 기록을 기록할 때 다루도록 하겠다.

Statefulset

스테이트풀셋도 데몬셋과 마찬가지로 레플리카셋의 특수한 형태이다.
제목에서 그 역할을 유추해볼 수 있는데 정말 statefule한 워크로드에 사용학 위한 리소스이다.

 

스테이트풀셋은 아래와 같은 특징이 있다.

  • 생성되는 파드명의 접미사는 숫자 인덱스가 부여된 것이다.
    ex) junebee-sample-sts-0, junebee-sample-sts-1, junebee-sample-sts-2
  • 파드명이 바뀌지 않는다.
  • 데이터를 영구적으로 저장하기 위한 구조로 되어 있다. 
    여기서는 volumeClaimTemplates를 지정할 수 있다. 즉, 영구 볼륨을 연결하여 파드가 재기동되어도 데이터를 동일하게 바라보고 보존할 수 있다.

아래는 statfulset의 예시이다.

disk연결과 service까지 생성하여 nginx를 하나 띄우는 예제이다.

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # has to match .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # by default is 1
  minReadySeconds: 10 # by default is 0
  template:
    metadata:
      labels:
        app: nginx # has to match .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

하지만 statefulset이 업데이트 되는 방식은 조금 복잡하다.

데몬셋과 다르게 replicas를 컨트롤하여 파드의 스케일 아웃이 가능하나 기본적으로 파드를 동시에 하나씩만 생성하고 삭제한다.

  • 스케일 아웃
    인덱스가 가장 작은 것부터 파드를 하나씩 생성하고 이전에 생성된 파드가 Ready 상태가 되고 나서 다음 파드를 생성한다.
    즉, replicas=3 이라고 설정한다면 한번에 파드 3개가 뜨는것이 아니라 0번,1번,2번 순서로 생성이 된다.
  • 스케일 인
    스케일 아웃과 반대로 인덱스가 가장 큰 녀석부터 하나씩 삭제가 된다.

하지만 이 방식은 절대적인 것은 아니며 podManagementPolicy 옵션으로 동작을 바꿀 수 있다.
선택은 OrderedReady, Parallel 둘 중에 하나를 선택하여 사용하면 된다.

 

스테이트풀셋은 0번째 파드가 항상 가장 먼저 생성되고 가장 늦게 삭제되기 때문에 이를 마스터 노드로 사용하는 구조의 애플리케이션에 사용되기도 한다.


스테이트풀셋 배포(업데이트)는 데몬셋과 동일하다.
위에서 말한 것처럼 만일 데이터베이스의 목적으로 sts를 사용한다면 수동으로 업데이트를 위해 OnDelete 옵션으로 진행하면 되고, 그렇지 않다면 RollingUpdate 옵션으로 진행하면 된다.

Rollingupdate에서 동일하게 maxSurge는 사용할 수 없으며, podManagementPolicy가 Parallel이라고 한들 배포는 하나씩 진행된다.

 

그리고!

Statefulset의 RollingUpdate에서는 partition이라는 특정 값을 설정할 수 있다.

partition을 설정하면 전체 파드 중에 어떤 파드까지 업데이트할지를 지정할 수 있다. 
이 설정을 사용하면 부분 업데이트가 가능하여 안전하게 업데이트를 진행할 수 있다. (partition 값보다 작은 인덱스를 가진 파드는 업데이트 되지 않는다.)