데이터베이스와 같이 stateful 한 워크로드를 위한 리소스이다. 데이터를 영구적으로 저장하기 위해 사용한다.

 

1. 스테이트풀셋 생성

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset
spec:
  serviceName: sample-statefulset
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.16
          ports:
            - containerPort: 80
          volumeMounts:
            - name: www
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
    - metadata:
        name: www
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 1G

 

위 내용으로 스테이트풀셋을 생성하고, 아래 커맨드로 파드명 규칙을 확인해보면, 레플리카셋이나 데몬셋과는 다르게 파드명 suffix 로 incremental index 가 붙는것을 알 수 있다.

$ kubectl get pods -o wide -l app=sample-app

 

매니페스트에서 주요하게 보아야 할 내용은 아래와 같다.

  • spec.template.spec.containers[].volumeMounts[]: 컨테이너에서 사용할 볼륨명과 마운트 경로
  • spec.volumeClaimTemplates[]: 스테이트풀셋이 생성할 영구 볼륨 요청 템플릿

즉, 스테이트풀셋이 volumeClaimTemplates 에 정의된 볼륨 리소스를 생성하여 영구 볼륨을 확보하고, 이를 컨테이너에서 요청하는 것이다. 생성된 persistentvolumes 과 persistentvolumeclaims 는 다음 커맨드로 확인할 수 있다.

$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                              STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
pvc-66206fda-ac99-4923-9b7c-eff0e3a3f677   1G         RWO            Delete           Bound    default/www-sample-statefulset-0   standard       <unset>                          22m
pvc-922ab66a-e5cc-447c-99d6-99d2f476345e   1G         RWO            Delete           Bound    default/www-sample-statefulset-1   standard       <unset>                          22m
pvc-9ecd3989-6435-4cbc-9e94-146866a59ddf   1G         RWO            Delete           Bound    default/www-sample-statefulset-2   standard       <unset>                          22m

$ kubectl get pvc
NAME                       STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
www-sample-statefulset-0   Bound    pvc-66206fda-ac99-4923-9b7c-eff0e3a3f677   1G         RWO            standard       <unset>                 25m
www-sample-statefulset-1   Bound    pvc-922ab66a-e5cc-447c-99d6-99d2f476345e   1G         RWO            standard       <unset>                 22m
www-sample-statefulset-2   Bound    pvc-9ecd3989-6435-4cbc-9e94-146866a59ddf   1G         RWO            standard       <unset>                 22m

 

PV(persistentvolumes)와 PVC(persistentvolumeclaims) 에 대한 내용은 추후에 영구 볼륨을 따로 다룰 때 자세히 설명하고, 여기서는 간단히 정리하면 다음과 같다.

  • PV: 물리적인 스토리지. namespaced: false. 즉, cluster 전역 스토리지
  • PVC: 물리적인 스토리지를 namespace 에서 사용하기 위한 객체. namespaced: true
  • PVC 에서 PV 의 사용을 요청하기 위해서는 PVC 와 PV 의 아래 조건이 맞아야 한다.(바인딩 조건)
    • PVC 에 selector 가 설정되어 있으면 PV 중에서 selector 에 일치하는 key=value 를 찾는다.(optional)
    • spec.accessModes 가 일치하여야 한다.
    • PV 의 spec.capacity.storage 용량이 PVC spec.resources.request.storage 용량 이상이어야 한다.
    • PV 와 PVC 둘 다 storageClassName 이 명시되어 있으면 그 값이 일치하여야 한다.(둘 다 명시되어있지 않으면 빈문자열로 취급되어 일치함. 한쪽만 명시되면 불일치함)
  • PVC 는 파드에 설정되고, 파드는 PVC 를 통해서 PV 를 인식하고 사용한다.
  • PVC 한개가 여러개의 PV 에 바인딩 될 수는 없다.
  • 한번 바인딩이 되었다가 풀리면 PV 가 released 상태가 되며, 바인딩 되었던 PVC 에서는 다시 바인딩이 불가능하기 때문에, PV 를 삭제 후 다시 생성해야 한다. PV 를 조건에 맞게 생성하면 자동으로 바인딩이 이루어진다.

 

2. 스테이트풀셋 스케일링의 특이점

레플리카셋이나 데몬셋은 scale out 또는 scale in 시에 생성/삭제되는 파드가 무작위이지만, 스테이트풀셋은 아래와 같은 규칙이 있다.

  • scale out: pod 명 suffix index 가 1씩 증분하면서 생성되며, 하나의 파드가 생성 후 ready 상태가 되면 다음 파드가 생성된다.
  • scale in: pod 명의 suffix 가 큰 것부터 하나씩 삭제된다. 삭제될 때도 하나씩 순차적으로 삭제된다.

이러한 특징 때문에 0번째 파드가 항상 가장 오래 남아있게 되는데, 그 특징으로 0번째 파드를 마스터로 사용하는 이중화 애플리케이션에 적합하다.

 

3. 파드 관리 정책

위에서 스테이트풀셋의 파드는 생성/삭제시 순차적으로 진행된다고 하였는데, 이를 변경하는 파라미터가 있다. spec.podManagementPolicy 필드로, 기본값이 OrderedReady 이고, 이를 Parallel 로 설정하면 레플리카셋과 동일하게 파드 생성/삭제가 병렬로 이루어진다.

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

데몬셋은 쿠버네티스 클러스터 내의 모든 "노드"에 1개씩의 파드 실행을 보장하는 컨트롤러 리소스이다. 노드를 추가했을 때에도 자동으로 해당 노드에 파드가 기동된다. 아래의 용도로 주로 사용한다.

  • 파드가 출력하는 로그를 노드 단위로 수집하는 로그 프로세스를 띄울 때
  • 파드 리소스 사용 현황 및 노드 상태를 모니터링하는 프로세스를 띄울 때

즉, 모든 노드에서 반드시 동작해야 하는 프로세스를 데몬셋으로 올려 사용한다.

 

1. 데몬셋 생성

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: sample-ds
spec:
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.16
          ports:
            - containerPort: 80

 

위 내용으로 데몬셋을 생성한 후, 파드를 확인해보면 각 노드에 1개씩 파드가 생성된 것을 알 수 있다.

현재 클러스터에 desktop-worker, desktop-worker2 노드가 있고, 각각의 노드에 하나씩 파드가 생성되었다.

$ kubectl get pods -o wide
NAME                                          READY   STATUS    RESTARTS   AGE     IP             NODE              NOMINATED NODE   READINESS GATES
sample-ds-b9l7s                               1/1     Running   0          3m37s   10.244.2.101   desktop-worker2   <none>           <none>
sample-ds-gjbfk                               1/1     Running   0          3m37s   10.244.1.93    desktop-worker    <none>           <none>

 

2. 데몬셋 업데이트 전략

Deployment 와 마찬가지로 spec.updateStrategy 필드에 지정하며, 아래 표의 전략에 해당하는 값을 지정한다.

전략 설명 비고
OnDelete 매니페스트 변경 후 apply 할 때 변경사항이 적용된 파드로 교체되는것이 아니라, 파드가 삭제되고 다시 생성될 때 변경사항이 적용된다. - 업데이트를 적용하려면 파드를 수동으로 삭제해주어야 함
RollingUpdate 매니페스트 변경 후 apply 할 때 변경사항이 적용된다. - 노드 단위의 rolling update 는 아니고, Deployment 와 동일하게 클러스터 단위의 rolling update 임

 

 

2.1. OnDelete

어떤 이유에서든, 파드가 정지되고 다시 생성되기 전까지는 업데이트되지 않는다. 따라서 임의의 시점에 파드를 업데이트 하려면, 데몬셋이 관리하는 파드를 kubectl delete pod 커맨드로 직접 삭제해야 한다.(데몬셋이 파드를 자동으로 새로 생성해줌)

 

위에서 생성한 데몬셋을 삭제하고, spec.updateStrategy: OnDelete 로 변경하여 다시 생성한 후, 파드 템플릿에서 nginx 버전을 1.16 -> 1.17 로 변경하여 apply 하면서 상태를 관찰해보자.

$ kubectl get daemonsets.apps -o wide --watch

# nginx:1.16 버전 확인
NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE     CONTAINERS        IMAGES       SELECTOR
ds-ondelete   2         2         2       2            2           <none>          9m42s   nginx-container   nginx:1.16   app=sample-app

# 데몬셋 매니페스트 파일의 파드 템플릿에서 컨테이너 이미지를 nginx:1.17 버전으로 수정 후
# 다른 쉘 세션을 열고 변경된 파드 템플릿을 apply 한다.

# 아래처럼 UP-TO-DATE 항목이 2 -> 0으로 바뀐 것을 볼 수 있다.
# 즉, image 는 nginx:1.17 로 변경되었지만, 파드가 최신 버전이 아니라는 뜻이다.
ds-ondelete   2         2         2       2            2           <none>          10m     nginx-container   nginx:1.17   app=sample-app
ds-ondelete   2         2         2       0            2           <none>          10m     nginx-container   nginx:1.17   app=sample-app

$ kubectl get pods -l app=sample-app -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}'
# 또는 jq가 있다면
$ kubectl get pods -l app=sample-app -o json | jq '.items[].spec.containers[].image'
# nginx:1.16 버전이 유지되는지 확인

 

이제 nginx:1.17 버전을 적용하려면 아래 커맨드를 이용해 파드를 삭제하면, 데몬셋이 업데이트된 버전의 파드를 자동으로 생성해준다.

$ kubectl delete pod -l app=sample-app

# 파드 2개가 모두 삭제된 후, 다시 생성되어 올라온다.
# UP-TO-DATE 가 2로 변경된 것을 알 수 있다.
NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE     CONTAINERS        IMAGES       SELECTOR
ds-ondelete   2         1         0       1            0           <none>          29m     nginx-container   nginx:1.17   app=sample-app
ds-ondelete   2         2         0       2            0           <none>          29m     nginx-container   nginx:1.17   app=sample-app
ds-ondelete   2         2         2       2            2           <none>          29m     nginx-container   nginx:1.17   app=sample-app

$ kubectl get pods -l app=sample-app -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}'
# nignx:1.17 확인

 

 

2.2. RollingUpdate

매니페스트 변경 후 apply 할 때 변경사항이 적용된다. rolling update 형태로 무중단 업데이트 배포가 이루어진다. 주의할 점은, 데몬셋에서는 하나의 노드에 동일한 파드를 2개 이상 생성할 수 없으므로, 동시에 추가 생성될 수 있는 최대 파드 수인 maxSurge 필드를 설정할 수 없다.

 

위에서 생성한 데몬셋을 삭제하고, spec.updateStrategy: RollingUpdate 로 변경하여 다시 생성한 후, 파드 템플릿에서 nginx 버전을 1.16 -> 1.17 로 변경하여 apply 하면서 상태를 관찰해보자.

$ kubectl get daemonsets.apps -o wide --watch

# nginx:1.16 버전 확인
NAME               DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE   CONTAINERS        IMAGES       SELECTOR
ds-rollingupdate   2         2         2       2            2           <none>          1s    nginx-container   nginx:1.16   app=sample-app

# 데몬셋 매니페스트 파일의 파드 템플릿에서 컨테이너 이미지를 nginx:1.17 버전으로 수정 후
# 다른 쉘 세션을 열고 변경된 파드 템플릿을 apply 한다.

# 아래처럼 UP-TO-DATE 항목이 2 -> 0 -> 2으로 바뀐 것을 볼 수 있다.
# 파드 2개가 동시에 내려가지 않고, 한개씩 삭제된 후 재생성된 것을 알 수 있다.
ds-rollingupdate   2         2         2       2            2           <none>          28s   nginx-container   nginx:1.17   app=sample-app
ds-rollingupdate   2         2         2       0            2           <none>          28s   nginx-container   nginx:1.17   app=sample-app
ds-rollingupdate   2         2         1       1            1           <none>          28s   nginx-container   nginx:1.17   app=sample-app
ds-rollingupdate   2         2         2       1            2           <none>          29s   nginx-container   nginx:1.17   app=sample-app
ds-rollingupdate   2         2         1       2            1           <none>          29s   nginx-container   nginx:1.17   app=sample-app
ds-rollingupdate   2         2         2       2            2           <none>          30s   nginx-container   nginx:1.17   app=sample-app

# kubectl get pods -o wide --watch
# pod 도 watch 해보면, desktop-worker2 의 파드가 먼저 종료 -> 생성된 후, desktop-worker 의 파드가 종료 -> 생성된 것을 알 수 있다.
# AGE 필드를 보면 알겠지만, Terminating Completed 는 순서에 맞지 않게 이벤트가 발생했으므로 비동기적으로 수행되었음을 짐작할 수 있다.
NAME                     READY   STATUS              RESTARTS   AGE   IP             NODE              NOMINATED NODE   READINESS GATES
ds-rollingupdate-ql2lw   1/1     Terminating         0          28s   10.244.2.105   desktop-worker2   <none>           <none>
ds-rollingupdate-ql2lw   0/1     Completed           0          28s   10.244.2.105   desktop-worker2   <none>           <none>
ds-rollingupdate-kblv8   0/1     Pending             0          0s    <none>         <none>            <none>           <none>
ds-rollingupdate-kblv8   0/1     Pending             0          0s    <none>         desktop-worker2   <none>           <none>
ds-rollingupdate-kblv8   0/1     ContainerCreating   0          0s    <none>         desktop-worker2   <none>           <none>
ds-rollingupdate-ql2lw   0/1     Completed           0          28s   10.244.2.105   desktop-worker2   <none>           <none>
ds-rollingupdate-ql2lw   0/1     Completed           0          28s   10.244.2.105   desktop-worker2   <none>           <none>
ds-rollingupdate-kblv8   1/1     Running             0          1s    10.244.2.106   desktop-worker2   <none>           <none>
ds-rollingupdate-9zvrh   1/1     Terminating         0          29s   10.244.1.97    desktop-worker    <none>           <none>
ds-rollingupdate-9zvrh   0/1     Completed           0          29s   10.244.1.97    desktop-worker    <none>           <none>
ds-rollingupdate-rd65j   0/1     Pending             0          0s    <none>         <none>            <none>           <none>
ds-rollingupdate-rd65j   0/1     Pending             0          0s    <none>         desktop-worker    <none>           <none>
ds-rollingupdate-rd65j   0/1     ContainerCreating   0          0s    <none>         desktop-worker    <none>           <none>
ds-rollingupdate-rd65j   1/1     Running             0          1s    10.244.1.98    desktop-worker    <none>           <none>
ds-rollingupdate-9zvrh   0/1     Completed           0          30s   10.244.1.97    desktop-worker    <none>           <none>
ds-rollingupdate-9zvrh   0/1     Completed           0          30s   10.244.1.97    desktop-worker    <none>           <none>

 

추가로 공부해야 할 점: 데몬셋이 노드당 1개의 파드를 보장해주는 것이라면, rolling update 시에도 노드 내에서 1개의 추가 파드를 생성해서 교체해야 하는 것이 맞아보이는데, 실제로 그렇게 동작하지 않는다. 노드 단위로 항시 서비스를 유지해야 하는 프로세스라면, 무중단 배포라고 볼 수 없다는 점이 의아하다.

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

디플로이먼트 업데이트시 몇가지 상세 설정할 수 있는 필드가 있다.

  • minReadySeconds: 파드가 ready 상태가 된 후 디플로이먼트가 파드 기동이 완료되었다고 판단하기까지의 추가 최소 시간(초)
    • 이 시간이 지난 후에야 다음 파드의 교체가 이루어진다.
  • revisionHistoryLimit: 디플로이먼트가 유지할 레플리카 수
    • 레플리카 수는 파드 템플릿의 갯수와 동일하므로, 유지할 revision 갯수와도 같은 의미이다.
    • 롤백 가능한 이력 수와도 같은 의미이다.
  • progressDeadlineSeconds: 업데이트 타임아웃(초)
    • 이 시간이 경과했는데도 업데이트가 정상적으로 완료되지 않았으면 자동으로 롤백한다.

아래는 매니페스트 예시이다.

apiVersion: apps/v1
kind: Deployment
...
spec:
  minReadySeconds: 10
  revisionHistoryLimit: 5
  progressDeadlineSeconds: 3600 # 60분
...

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,