스테이트풀셋을 삭제한 경우에는(kubectl delete -f 든, kubectl delete sts 든) PV, PVC 는 삭제되지 않고 남아있게 되며, PVC 와 PV 의 바인딩 관계도 그대로 유지되게 된다. 때문에 다시 스테이트풀셋을 생성한 경우에 남아있던 영구 볼륨 데이터 그대로 파드가 기동되어 재사용하게 된다. 레플리카수를 scale in 하였다가 다시 scale out 한 경우도 마찬가지로 동작한다.

$ kubectl apply -f sample-statefulset.yaml
$ kubectl get pv
$ kubectl get pvc

# sample-statefulset 을 수동으로 삭제한 후에도 PV, PVC 가 유지되는지 확인

$ kubectl delete statefulset sample-statefulset
$ kubectl get pv
$ kubectl get pvc

 

직접 PVC 를 삭제하면, PV 와 바인딩이 풀리면서 PV 도 자동으로 삭제되게 된다.(영구볼륨의 ClaimPolicy 설정 기본값일 때)

$ kubectl delete pvc www-statefulset-{0..2}

 

블로그 이미지

망원동똑똑이

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

,

데몬셋과 동일하게 OnDelete, RollingUpdate 를 지정할 수 있지만 몇가지 차이점이 있다.

 

1. RollingUpdate 시 데몬셋과 다른 점

OnDelete 는 데몬셋의 그것과 동일하게 동작하지만, RollingUpdate 의 경우에는 좀 다르다. 스테이트풀셋은 영속성 데이터를 다루는 리소스 컨트롤러 이므로, 추가 파드를 생성해서 롤링 업데이트를 할 수 없으며, 마찬가지로 maxUnavailable 를 지정하여 업데이트 중 동시에 여러 파드를 중지시킬 수도 없다. 파드마다 ready 상태 여부를 확인하고 순차적으로 업데이트하게 된다.(spec.podManagementPolicy: Parallel 이어도 마찬가지)

2. RollingUpdate 시 파티션을 나누어 업데이트

스테이트풀셋의 경우 spec.updateStrategy.rollingUpdate.partition 필드에 RollingUpdate 를 적용할 파티션을 지정할 수 있다.(아래 참고) partition 을 설정하면, 해당 값 이상의 인덱스 값을 가진 파드들만 업데이트 대상이 된다. 수동으로 재기동한 경우에도 partition 의 영향을 받는다.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: statefulset-rollingupdate
spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 3
  serviceName: statefulset-rollingupdate
  replicas: 5
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.16

 

 

위 매니페스트를 사용하여 스테이트풀셋을 만들고, 파드를 업데이트하면서 관찰해보면, 실제로 partition 에 해당하는 인덱스 이상의 파드만 업데이트 되는 것을 볼 수 있다.

$ kubectl apply -f statefulset-rollingupdate.yaml
# statefulset-rollingupdate-0 ~ 4 까지 5개의 파드가 순차적으로 생성됨

# nginx:1.16 -> nginx:1.17 로 이미지 수정 후 다시 apply 를 진행
# watch 를 이용하여 파드 상태를 관찰하면 아래와 같이 출력됨
$ kubectl get pods -l app=sample-app -o wide --watch
NAME                          READY   STATUS              RESTARTS   AGE   IP            NODE              NOMINATED NODE   READINESS GATES
statefulset-rollingupdate-4   1/1     Terminating         0          83s   10.244.2.11   desktop-worker2   <none>           <none>
statefulset-rollingupdate-4   0/1     Completed           0          83s   10.244.2.11   desktop-worker2   <none>           <none>
statefulset-rollingupdate-4   0/1     Completed           0          84s   10.244.2.11   desktop-worker2   <none>           <none>
statefulset-rollingupdate-4   0/1     Completed           0          84s   10.244.2.11   desktop-worker2   <none>           <none>
statefulset-rollingupdate-4   0/1     Pending             0          0s    <none>        <none>            <none>           <none>
statefulset-rollingupdate-4   0/1     Pending             0          0s    <none>        desktop-worker2   <none>           <none>
statefulset-rollingupdate-4   0/1     ContainerCreating   0          0s    <none>        desktop-worker2   <none>           <none>
statefulset-rollingupdate-4   1/1     Running             0          1s    10.244.2.12   desktop-worker2   <none>           <none>
statefulset-rollingupdate-3   1/1     Terminating         0          86s   10.244.2.10   desktop-worker2   <none>           <none>
statefulset-rollingupdate-3   1/1     Terminating         0          86s   10.244.2.10   desktop-worker2   <none>           <none>
statefulset-rollingupdate-3   0/1     Completed           0          86s   10.244.2.10   desktop-worker2   <none>           <none>
statefulset-rollingupdate-3   0/1     Completed           0          87s   10.244.2.10   desktop-worker2   <none>           <none>
statefulset-rollingupdate-3   0/1     Completed           0          87s   10.244.2.10   desktop-worker2   <none>           <none>
statefulset-rollingupdate-3   0/1     Pending             0          0s    <none>        <none>            <none>           <none>
statefulset-rollingupdate-3   0/1     Pending             0          0s    <none>        desktop-worker2   <none>           <none>
statefulset-rollingupdate-3   0/1     ContainerCreating   0          0s    <none>        desktop-worker2   <none>           <none>
statefulset-rollingupdate-3   1/1     Running             0          1s    10.244.2.13   desktop-worker2   <none>           <none>

 

즉, 0번~2번 인덱스의 파드는 영향을 받지 않고, 3~4번 인덱스의 파드가 하나씩 순차적으로 Termination -> Completed 로 종료된 후, Pending -> ContainerCreating -> Running 으로 새로 실행된 것을 알 수 있다. 여기서 이번에는 partition 을 1로 변경 후 apply 하게 되면 1~2번 파드만 추가로 업데이트를 하게 된다.(기존 업데이트 완료되었던 3~4번 파드는 업데이트되지 않음)

블로그 이미지

망원동똑똑이

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

,

데이터베이스와 같이 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분
...

 

블로그 이미지

망원동똑똑이

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

,

디플로이먼트 업데이트 전략의 기본값은 RollingUpdate 로, 매니페스트 파일의 spec.strategy.type 필드에서 설정을 변경할 수 있다.

전략 설명 비고
Recreate 모든 파드를 일괄 삭제하고 새로운 레플리카셋을 생성한 후 그 하위에 새로운 파드를 일괄 생성 - 서비스 다운타임 발생
- 전환 빠름
RollingUpdate 레플리카셋의 점진적 scaling 을 통해 기존 버전 레플리카 내 파드 수를 줄이고, 신규 버전 레플리카 내 파드 수를 늘리는 액션을 모든 파드가 전환될 때 까지 반복 수행 - 무중단 배포
- 전환 느림

 

1. Recreate

apiVersion: apps/v1
kind: Deployment
...
spec:
  strategy:
    type: Recreate
...

 

위와 같이 매니페스트의 spec.strategy.type: Recreate 만 지정해주면 된다. 업데이트시 추가적인 리소스 확보 필요 없이 일괄로 업데이트가 되지만, 서비스 다운타임이 몇초간 생기므로, 사용에 주의하여야 한다.

 

2. RollingUpdate

apiVersion: apps/v1
kind: Deployment
...
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  replicas: 3
...

 

위와 같이 매니페스트의 spec.strategy.type: RollingUpdate 와 spec.strategy.rollingUpdate 에 maxUnavailable, maxSurge 값을 지정해준다. 자세한 설명은 아래와 같다.

  • maxUnavailable: 업데이트중 동시에 정지상태가 될 수 있는 최대 파드 갯수
  • maxSurge: 업데이트중 동시에 추가 생성될 수 있는 최대 파드 갯수

즉, 이 두 값을 통해서 업데이트중에 최소 살아있어야 하는 pod 갯수와 over 하여 생성될 수 있는 pod 갯수를 제어할 수 있다. 두 값 모두 동시에 0으로 설정할 수는 없다. 설명이 조금 어려운데, 아래와 같이 이해하면 된다.

  • 업데이트 중 최소 파드 갯수: $replicas - $maxUnavailable
  • 업데이트 중 최대 파드 갯수: $replicase + $maxSurge

replicas: 3, maxUnavailable: 0, maxSurge: 1 로 설정한 경우 아래의 그림과 같이 업데이트된다.

  • 최소 파드 갯수: 3
  • 최대 파드 갯수: 4

 

replicas: 3, maxUnavailable: 1, maxSurge: 0 로 설정한 경우 아래의 그림과 같이 업데이트된다.

  • 최소 파드 갯수: 2
  • 최대 파드 갯수: 3

 

maxUnavailable, maxSurge 는 백분율 값으로도 지정할 수 있으며, 지정하지 않을 경우 기본값은 각각 25% 이다. 백분율로 설정 시 실제 pod 수 계산은 replicas 에 대한 백분율로 계산 되는데, 계산된 값이 정수로 맞아 떨어지지 않는 경우 아래와 같이 계산된다.

 

  • maxUnavailable: 소수점 내림(round down)
  • maxSurge: 소수점 올림(round up)

공식문서 참고

 

Deployments

A Deployment manages a set of Pods to run an application workload, usually one that doesn't maintain state.

kubernetes.io

이는 백분율 값 설정시 서비스가 너무 적은 리소스를 할당받아 제대로 서빙되지 못할 것을 우려한 보수적인 정책인 것으로 보인다.

블로그 이미지

망원동똑똑이

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

,

파드를 업데이트 한다는 것은, 파드에 배포된 컨테이너의 내용을 변경하여 새로운 버전을 배포한다는 것과 동일한 의미이다. https://secjong.tistory.com/57 에서 다루었듯, Deployment 를 이용해 파드를 업데이트하면, 이전 배포 버전의 Replicaset 는 삭제되지 않고 살아있게 된다. 디플로이먼트의 롤백은 실제로는 이전 버전의 레플리카셋으로 전환하는 것이다.

 

롤백을 하기 위해 어떤 버전으로 롤백을 하고자 하는지 정해야 하므로, 아래 커맨드를 이용해 디플로이먼트의 변경 이력을 확인한다. 리비전이 곧 버전이 된다.

$ kubectl rollout history deployment sample-deployment
REVISION  CHANGE-CAUSE
3         <none>
4         <none>
5         <none>

 

하지만, 디플로이먼트 revision 인 3, 4, 5 값만으로는 어떤 배포버전을 의미하는 것인지 알기 힘들다. 실제로 실무에서는 커맨드로 직접 롤백하지 않고 형상관리 툴과 연결된 CI/CD 도구를 이용해서 git commit message 등을 조회하여 어떤 revision 이 어떤 배포 건인지 확인할 수 있기 때문에 직접 확인하지 않지만, 공부를 위해 확인해보자.

 

아래 커맨드를 이용해 특정 리비전이 가진 파드 템플릿 정보를 알 수 있다.

$ kubectl rollout history deployment sample-deployment --revision 3
deployment.apps/sample-deployment with revision #3
Pod Template:
  Labels:	app=sample-app
	pod-template-hash=7457fb97b6
  Containers:
   nginx-container:
    Image:	nginx:1.17
    Port:	80/TCP
    Host Port:	0/TCP
    Environment:	<none>
    Mounts:	<none>
  Volumes:	<none>
  Node-Selectors:	<none>
  Tolerations:	<none>

 

또한 아래 커맨드를 통해 replicaset 의 자세한 정보에서도 revision 을 확인할 수 있다.

$ kubectl describe replicasets.apps sample-deployment-7457fb97b6
Name:           sample-deployment-7457fb97b6
Namespace:      default
Selector:       app=sample-app,pod-template-hash=7457fb97b6
Labels:         app=sample-app
                pod-template-hash=7457fb97b6
Annotations:    deployment.kubernetes.io/desired-replicas: 3
                deployment.kubernetes.io/max-replicas: 4
                deployment.kubernetes.io/revision: 3
Controlled By:  Deployment/sample-deployment
Replicas:       0 current / 0 desired
Pods Status:    0 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=sample-app
           pod-template-hash=7457fb97b6
  Containers:
   nginx-container:
    Image:         nginx:1.17
...

 

Annotations 의 deployment.kubernetes.io/revision 에서 revision 번호를, Pod Template 에서 컨테이너 이미지 버전 등을 확인할 수 있다. 우리는 이렇게 조회된 nginx 1.17 버전을 원한다고 가정하고, revision 3 으로 롤백을 진행하고 결과를 확인해보자.

$ kubectl rollout undo deployment sample-deployment --to-revision 3
deployment.apps/sample-deployment rolled back

$ kubectl get deployments.apps sample-deployment -o wide
롤백된 버전 확인

$ kubectl rollout history deployment sample-deployment
deployment.apps/sample-deployment
REVISION  CHANGE-CAUSE
4         <none>
5         <none>
6         <none>

$ kubectl get rs -l app=sample-app
# 해당 리비전의 replicaset 이 사용중인지 확인
NAME                           DESIRED   CURRENT   READY   AGE
sample-deployment-5d6c4c4b76   0         0         0       48m
sample-deployment-5f6db9bfb9   0         0         0       49m # 이전 레플리카셋
sample-deployment-7457fb97b6   3         3         3       47m # 현재 레플리카셋

 

위에 언급했듯, 실제 서비스 환경에서는 rollout undo 를 사용하지 않고, 매니페스트 파일의 형상을 이전 버전으로 되돌린 후에 다시 apply 하는 방식을 더 많이 쓴다. 이 측면이 형상관리 이력을 남긴다는 측면에서 더 이롭고, 동일한 파드 정의로 되돌리는 것이므로 파드 템플릿 해시값이 동일하게 적용되기 때문에 해당 리비전의 레플리카셋이 재활용된다는 것은 완벽히 동일하기 때문이다.

블로그 이미지

망원동똑똑이

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

,

Deployment 를 사용한 컨테이너 운영은 kubernetes 에서 가장 권장하는 방법이다.

컨테이너를 기동하는 방법에는

1. pod 를 직접 사용하는 방법(단순 기동)

2. replicaset 으로 파드의 replica 갯수를 설정된 상태에 맞도록 유지하도록 유지하는 방법(자동복구 + scaling 가능)

3. deployment 로 n개의 replicaset 을 제어하면서 전체 파드의 갯수는 설정된 상태에 맞도록 유지하는 방법(rolling update 가능)

 

pod < replicaset < deployment 순서로 포함하는 구조이며, 따라서 상위에서는 하위의 기능을 사용할 수 있다.

 

1. 디플로이먼트 생성

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  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

 

위 내용으로 디플로이먼트를 생성한 후, 아래 커맨드로 디플로이먼트 및 디플로이먼트가 관리하는 레플리카셋, 그리고 그 레플리카셋이 관리하는 파드를 조회할 수 있다.

$ kubectl get deployments.apps sample-deployment
sample-deployment

$ kubectl get replicasets.apps -l app=sample-app
sample-deployment-79cd77b9d6

$ kubectl get pods -l app=sample-app
sample-deployment-79cd77b9d6-767b4
sample-deployment-79cd77b9d6-7j2zh
sample-deployment-79cd77b9d6-ksjpf

 

조회 결과를 보면, 디플로이먼트 > 레플리카셋 > 파드 순서로 네이밍 규칙을 이어받아 따르는 것을 알 수 있다.

  • deployment 명: {deployment 명}
    • ex) sample-deployment
  • replicaset 명: {deployment 명}-{10자리 랜덤hash 문자열}
    • ex) sample-deployment-79cd77b9d6
  • pod 명: {replicaset 명}-{5자리 랜덤hash 문자열}
    • ex) sample-deployment-79cd77b9d6-767b4

 

2. 디플로이먼트 업데이트

이제 deployment 매니페스트 파일의 컨테이너 이미지의 nginx 버전을 1.16 -> 1.17로 변경하고 apply 하면, 자동으로 새로운 replicaset 이 생성되며 rolling update 가 진행된다. 즉, 디플로이먼트가 알아서 rolling update 방식으로 무중단 배포를 진행해준다.

# 매니페스트 파일 수정 후
$ kubectl apply -f sample-deployment.yaml
$ kubectl get replicasets.apps -l app=sample-app
# rolling update 를 위한 새로운 replicaset 이 추가로 생성되었는지 확인

 

참고로, 다시 nginx 버전을 1.17 -> 1.16 으로 변경하여 rolling update 를 진행한 후 pod 명을 확인해보면, replicaset 과는 다르게 pod 는 재활용되지 않고 새로 생성된 것을 알 수 있다.(파드명이 다름)

  • replicaset 은 업데이트시 삭제되지 않고 유지되며, 다시 동일한 spec.template 으로 업데이트시 재사용된다.
    • 즉, spec.template 내용의 변경이 있으면 해당 내용으로 레플리카셋이 새로 생성된다.
    • 실제로 레플리카셋명의 hash 값은 spec.template 아래의 값으로부터 생성된다.(파드 템플릿 해시)
    • nginx 버전을 1.16 -> 1.17 -> 1.16 으로 변경한 경우에 1.16 버전 파드 템플릿 해시값이 동일할 것이므로, 레플리카셋이 새로 생성되지 않고 재사용되는 것이다.
  • pod 는 업데이트시 삭제되고 새롭게 생성된다.
# 최초 상태
$ kubectl get rs
sample-deployment-558978b4c5   3         3         3       2m40s
sample-deployment-79cd77b9d6   0         0         0       17m

$ kubectl get po
sample-deployment-558978b4c5-6d9gw   1/1     Running   0          3m56s
sample-deployment-558978b4c5-c7qjx   1/1     Running   0          3m57s
sample-deployment-558978b4c5-tffhz   1/1     Running   0          3m55s

# 첫번째 rolling update 후 => 새로운 replicaset 이 생성되고, 기존 replicaset 는 유지됨. 새로운 replicaset 하위로 pod 가 생성됨.
$ kubectl get rs
sample-deployment-558978b4c5   0         0         0       8m26s
sample-deployment-79cd77b9d6   3         3         3       23m

$ kubectl get po
sample-deployment-79cd77b9d6-56vlj   1/1     Running   0          4s
sample-deployment-79cd77b9d6-rznvh   1/1     Running   0          2s
sample-deployment-79cd77b9d6-tlk5f   1/1     Running   0          4s

# 두번째 rolling update 후 => replicaset 은 유지되나, 그 하위 pod 는 새로 생성됨
$ kubectl get rs
sample-deployment-558978b4c5   3         3         3       8m50s
sample-deployment-79cd77b9d6   0         0         0       24m

$ kubectl get po
sample-deployment-558978b4c5-c4dwd   1/1     Running   0          20s
sample-deployment-558978b4c5-hkb7p   1/1     Running   0          19s
sample-deployment-558978b4c5-tcrtx   1/1     Running   0          18s

 

블로그 이미지

망원동똑똑이

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

,

replicaset 을 이용해 파드를 관리할 때, 파드 수에 대한 수평적 확장 즉, 파드 스케일 아웃을 수행하는 방법은 두가지가 있다.

먼저 아래 내용으로 파드 레플리카를 3개 구동시킨다.

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: sample-rs
spec:
  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

 

아래 커맨드를 실행해 파드 갯수를 확인한다.

$ kubectl describe replicasets.apps sample-rs
# replicaset 의 정보 확인

$ kubectl get pods -o wide -L app
# pod 가 3개 띄워져 있는지 확인

 

1. kubectl scale 명령어를 이용하는 방법

아래 커맨드를 실행해 pod 의 갯수를 5개로 조정 후 다시 파드의 갯수를 확인한다.

$ kubectl scale replicaset sample-rs --replicas 5
# replica 갯수를 5개로 조정

$ kubectl get pods -o wide -L app
# pod 의 갯수가 5개로 변경되었는지 확인

 

kubectl scale 명령어를 이용한 이 방법은 커맨드로 간편하게 스케일링을 할 수 있으나, 현재 상태가 매니페스트 파일과 동일하지 않게 되기 때문에 실무에서는 권장하지 않는 방법이다.

참고로, scale 명령어는 ReplicationController, Deployment, StatefulSet, Job, CronJob 에 대해서도 사용할 수 있다.

 

2. 매니페스트를 동적으로 수정 후 kubectl apply 하는 방법

아래 커맨드를 실행해 replica 수를 3개에서 5개로 수정하여 저장한 후, 매니페스트를 apply 한다.

$ sed -i -e 's|replicas: 3|replicas: 5|' sample-rs.yaml
# sed 명령어를 이용해 sample-rs.yaml 파일의 내용 중 "replicas: 3" 를 "replicas: 5" 로 치환

$ kubectl apply -f sample-rs.yaml
# 변경된 매니페스트 적용

$ kubectl get pods -o wide -L app
# pod 의 갯수가 5개로 변경되었는지 확인

 

이를 원시적으로 응용하면 원격에서 쉘스크립트를 호출하며 인자를 넘겨 replica 갯수를 조정할 수 있을 것이다. IaC 를 구현하기 위해서라도 이러한 형태로 사용하는 것이 권장된다.

블로그 이미지

망원동똑똑이

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

,

Dockerfile 에서 WORKDIR 지시자를 통해 컨테이너 내부에서의 작업 디렉터리를 설정하는 것처럼, 파드 매니페스트 설정에서도 작업 디렉터리를 설정할 수 있다. 이 설정은 Dockerfile 의 WORKDIR 을 덮어쓰게 된다.

apiVersion: v1
kind: Pod
metadata:
  name: workingdir
spec:
  containers:
  - name: nginx-container
    image: nginx:1.16
    workingDir: /tmp

 

위 내용으로 파드를 생성한 후, 아래 커맨드로 프로세스가 실행되는 디렉터리가 변경되었음을 확인할 수 있다.

 

$ kubectl exec -it workingdir -- pwd
/tmp

 

블로그 이미지

망원동똑똑이

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

,