디플로이먼트 (Deployment)

디플로이먼트 (Deployment)

디플로이먼트(Deployment)는 애플리케이션 워크로드를 실행하기 위한 파드(Pod) 집합을 관리하는 리소스예요. 주로 상태를 유지하지 않는 애플리케이션에 사용해요.

디플로이먼트는 파드와 레플리카셋(ReplicaSet)에 선언적 업데이트를 제공해요. 원하는 상태(desired state)를 디플로이먼트에 기술해 두면, 디플로이먼트 컨트롤러(Controller)가 실제 상태를 통제된 속도로 원하는 상태에 맞춰 바꿔 줘요. 디플로이먼트를 만들어서 새 레플리카셋을 만들 수도 있고, 기존 디플로이먼트를 제거하면서 그 리소스를 새 디플로이먼트가 이어받게(adopt) 할 수도 있어요.

참고: 디플로이먼트가 소유한 레플리카셋은 직접 관리하지 마세요. 아래에서 다루지 않는 사용 사례가 있다면 쿠버네티스 메인 저장소에 이슈를 열어 봐요.

사용 사례

디플로이먼트의 대표적인 사용 사례는 이래요.

  • 디플로이먼트를 만들어 레플리카셋을 롤아웃(rollout)해요. 레플리카셋은 백그라운드에서 파드를 만들어요. 롤아웃 상태를 확인해서 성공했는지 실패했는지 판단해요.
  • 디플로이먼트의 PodTemplateSpec을 업데이트해서 파드의 새 상태를 선언해요. 그러면 새 레플리카셋이 생기고, 디플로이먼트가 새 레플리카셋을 점점 늘리면서(old 레플리카셋은 줄이며) 통제된 속도로 파드를 교체해요. 새 레플리카셋이 생길 때마다 디플로이먼트의 리비전(revision)이 갱신돼요.
  • 현재 디플로이먼트 상태가 안정적이지 않으면 이전 디플로이먼트 리비전으로 롤백(rollback)해요. 롤백할 때마다 디플로이먼트 리비전이 갱신돼요.
  • 디플로이먼트를 스케일 업해서 더 많은 부하를 처리해요.
  • 디플로이먼트의 롤아웃을 일시 중지(pause)해서 PodTemplateSpec에 여러 수정을 적용한 뒤 다시 재개(resume)해서 새 롤아웃을 시작해요.
  • 롤아웃이 막혔는지(stuck) 알아내는 지표로 디플로이먼트 상태를 사용해요.
  • 더 이상 필요 없는 오래된 레플리카셋을 정리해요.

디플로이먼트 만들기

다음은 디플로이먼트 예시예요. nginx 파드 3개를 띄우는 레플리카셋을 만들어요.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

이 예시에서 눈여겨볼 점을 짚어 볼게요.

  • .metadata.name 필드로 이름이 nginx-deployment인 디플로이먼트가 만들어져요. 이 이름은 나중에 만들어지는 레플리카셋과 파드 이름의 기준이 돼요. 자세한 내용은 "디플로이먼트 스펙 작성"을 봐요.
  • 디플로이먼트는 .spec.replicas 필드가 가리키는 만큼, 즉 복제된 파드 3개를 만드는 레플리카셋을 만들어요.
  • .spec.selector 필드는 만들어진 레플리카셋이 어떤 파드를 관리할지를 정해요. 이 경우 파드 템플릿에 정의된 라벨(app: nginx)을 선택해요. 파드 템플릿 자체가 규칙을 만족하기만 하면 더 정교한 선택 규칙도 쓸 수 있어요.

참고: .spec.selector.matchLabels 필드는 {key, value} 쌍의 맵이에요. matchLabels 맵에 있는 하나의 {key, value}는 matchExpressions의 한 요소와 같아요. 그 요소의 key 필드는 "key", operator는 "In", values 배열에는 "value" 하나만 들어 있어요. 매치하려면 matchLabelsmatchExpressions 양쪽의 모든 요구사항을 다 충족해야 해요.

  • .spec.template 필드는 다음 하위 필드를 포함해요.
    • .metadata.labels 필드로 파드에 app: nginx 라벨을 붙여요.
    • 파드 템플릿의 스펙인 .spec 필드는 파드가 nginx 컨테이너 하나를 실행한다는 뜻이에요. 그 컨테이너는 Docker Hubnginx 이미지 1.14.2 버전을 돌려요.
    • .spec.containers[0].name 필드로 컨테이너 하나를 만들고 이름을 nginx로 지어요.

시작하기 전에 쿠버네티스 클러스터가 실행 중인지 확인해요. 위 디플로이먼트를 만들려면 아래 단계를 따라 해요.

  1. 다음 명령으로 디플로이먼트를 만들어요.
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
  1. kubectl get deployments를 실행해서 디플로이먼트가 만들어졌는지 확인해요.

디플로이먼트가 아직 생성 중이라면 출력이 다음과 비슷해요.

NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   0/3     0            0           1s

클러스터의 디플로이먼트를 조회하면 다음 필드들이 표시돼요. .spec.replicas 필드에 따라 원하는 레플리카 수가 3이라는 점에 주목해요.

  • NAME은 네임스페이스 안의 디플로이먼트 이름을 나열해요.
  • READY는 사용자에게 제공할 수 있는 애플리케이션 레플리카 수를 보여줘요. ready/desired 패턴을 따라가요.
  • UP-TO-DATE는 원하는 상태에 도달하도록 업데이트된 레플리카 수를 보여줘요.
  • AVAILABLE은 사용자에게 제공할 수 있는 애플리케이션 레플리카 수를 보여줘요.
  • AGE는 애플리케이션이 실행된 시간을 보여줘요.
  1. 디플로이먼트 롤아웃 상태를 보려면 kubectl rollout status deployment/nginx-deployment를 실행해요.

출력은 다음과 비슷해요.

Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out
  1. 몇 초 뒤 다시 kubectl get deployments를 실행해요. 출력은 다음과 비슷해요.
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           18s

디플로이먼트가 세 레플리카를 모두 만들었고, 모든 레플리카가 최신 상태(가장 최신 파드 템플릿을 담고 있음)이며 사용 가능한 상태임을 확인할 수 있어요.

  1. 디플로이먼트가 만든 레플리카셋(rs)을 보려면 kubectl get rs를 실행해요. 출력은 다음과 비슷해요.
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-75675f5897   3         3         3       18s

레플리카셋 출력은 다음 필드를 보여줘요. 레플리카셋 이름이 항상 [DEPLOYMENT-NAME]-[HASH] 형식이라는 점에 주목해요. 이 이름은 나중에 만들어질 파드 이름의 기준이 돼요. HASH 문자열은 레플리카셋의 pod-template-hash 라벨과 같아요.

  • NAME은 네임스페이스 안의 레플리카셋 이름을 나열해요.
  • DESIRED는 디플로이먼트를 만들 때 정의한 애플리케이션의 레플리카 수를 보여줘요. 이것이 원하는 상태예요.
  • CURRENT는 현재 실행 중인 레플리카 수를 보여줘요.
  • READY는 사용자에게 제공할 수 있는 애플리케이션 레플리카 수를 보여줘요.
  • AGE는 애플리케이션이 실행된 시간을 보여줘요.
  1. 각 파드에 자동 생성된 라벨을 보려면 kubectl get pods --show-labels를 실행해요. 출력은 다음과 비슷해요.
NAME                                READY     STATUS    RESTARTS   AGE       LABELS
nginx-deployment-75675f5897-7ci7o   1/1       Running   0          18s       app=nginx,pod-template-hash=75675f5897
nginx-deployment-75675f5897-kzszj   1/1       Running   0          18s       app=nginx,pod-template-hash=75675f5897
nginx-deployment-75675f5897-qqcnn   1/1       Running   0          18s       app=nginx,pod-template-hash=75675f5897

만들어진 레플리카셋이 nginx 파드 3개를 보장해 줘요.

참고: 디플로이먼트에는 적절한 셀렉터와 파드 템플릿 라벨을 지정해야 해요(여기서는 app: nginx). 다른 컨트롤러(다른 디플로이먼트와 스테이트풀셋 포함)와 라벨·셀렉터가 겹치지 않게 해요. 쿠버네티스가 겹치는 것을 막아 주지는 않아요. 그런데 여러 컨트롤러의 셀렉터가 겹치면 서로 충돌해서 예상치 못하게 동작할 수 있어요.

pod-template-hash 라벨

주의: 이 라벨은 변경하지 마세요.

pod-template-hash 라벨은 디플로이먼트 컨트롤러가 자신이 만들거나 이어받은 모든 레플리카셋에 추가해요. 이 라벨은 디플로이먼트의 자식 레플리카셋들이 서로 겹치지 않게 해 줘요. 레플리카셋의 PodTemplate을 해싱하고, 그 결과 해시 값을 라벨 값으로 써서 레플리카셋 셀렉터, 파드 템플릿 라벨, 그리고 그 레플리카셋이 가질 기존 파드들에 추가해요.

디플로이먼트 업데이트하기

참고: 디플로이먼트의 롤아웃은 파드 템플릿(.spec.template)이 바뀔 때에만, 즉 템플릿의 라벨이나 컨테이너 이미지가 업데이트될 때만 발생해요. 스케일링 같은 다른 업데이트는 롤아웃을 발생시키지 않아요.

아래 단계에 따라 디플로이먼트를 업데이트해요.

  1. nginx 파드가 nginx:1.14.2 대신 nginx:1.16.1 이미지를 쓰도록 업데이트해 볼게요.
kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.16.1

또는 다음 명령을 써도 돼요.

kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1

여기서 deployment/nginx-deployment는 디플로이먼트를, nginx는 업데이트 대상 컨테이너를, nginx:1.16.1은 새 이미지와 그 태그를 뜻해요.

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment image updated

또는 디플로이먼트를 edit해서 .spec.template.spec.containers[0].imagenginx:1.14.2에서 nginx:1.16.1로 바꿔도 돼요.

kubectl edit deployment/nginx-deployment

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment edited
  1. 롤아웃 상태를 보려면 다음을 실행해요.
kubectl rollout status deployment/nginx-deployment

출력은 다음과 비슷해요.

Waiting for rollout to finish: 2 out of 3 new replicas have been updated...

또는 이렇게 돼요.

deployment "nginx-deployment" successfully rolled out

업데이트된 디플로이먼트에 대한 자세한 내용을 확인해 볼게요.

  • 롤아웃이 성공한 뒤 kubectl get deployments로 디플로이먼트를 조회해요. 출력은 다음과 비슷해요.
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           36s
  • kubectl get rs를 실행해 보면 디플로이먼트가 새 레플리카셋을 만들고 3개로 늘리면서, 옛 레플리카셋을 0개로 줄여 파드를 업데이트한 걸 볼 수 있어요.
kubectl get rs

출력은 다음과 비슷해요.

NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   3         3         3       6s
nginx-deployment-2035384211   0         0         0       36s
  • get pods를 실행하면 이제 새 파드만 보여요.
kubectl get pods

출력은 다음과 비슷해요.

NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-1564180365-khku8   1/1       Running   0          14s
nginx-deployment-1564180365-nacti   1/1       Running   0          14s
nginx-deployment-1564180365-z9gth   1/1       Running   0          14s

다음에 이 파드들을 업데이트하고 싶다면 디플로이먼트의 파드 템플릿만 다시 업데이트하면 돼요.

디플로이먼트는 업데이트 중에 정해진 개수만큼만 파드가 내려가 있도록 보장해요. 기본적으로 원하는 파드 수의 75% 이상은 떠 있게 합니다(최대 25% unavailable). 또 디플로이먼트는 원하는 파드 수보다 정해진 개수만큼만 파드를 더 만들게 하는데, 기본적으로 원하는 파드 수의 최대 125%까지만 떠 있게 해요(최대 25% max surge).

예를 들어 위 디플로이먼트를 자세히 보면, 새 파드 하나를 만든 다음 옛 파드 하나를 삭제하고 다시 새 파드를 만드는 걸 볼 수 있어요. 새 파드가 충분히 떠오르기 전에는 옛 파드를 죽이지 않고, 옛 파드가 충분히 죽기 전에는 새 파드를 만들지 않아요. 그래서 항상 최소 3개 파드가 사용 가능하고, 최대 4개 파드가 유지돼요. 레플리카가 4개인 디플로이먼트라면 파드 수는 3~5개 사이가 돼요.

  • 디플로이먼트의 자세한 내용을 가져오려면 다음과 같이 실행해요.
kubectl describe deployments

출력은 다음과 비슷해요.

Name:                   nginx-deployment
Namespace:              default
CreationTimestamp:      Thu, 30 Nov 2017 10:56:25 +0000
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision=2
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
   Containers:
    nginx:
      Image:        nginx:1.16.1
      Port:         80/TCP
      Environment:  <none>
      Mounts:       <none>
    Volumes:        <none>
  Conditions:
    Type           Status  Reason
    ----           ------  ------
    Available      True    MinimumReplicasAvailable
    Progressing    True    NewReplicaSetAvailable
  OldReplicaSets:  <none>
  NewReplicaSet:   nginx-deployment-1564180365 (3/3 replicas created)
  Events:
    Type    Reason             Age   From                   Message
    ----    ------             ----  ----                   -------
    Normal  ScalingReplicaSet  2m    deployment-controller  Scaled up replica set nginx-deployment-2035384211 to 3
    Normal  ScalingReplicaSet  24s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 1
    Normal  ScalingReplicaSet  22s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 2
    Normal  ScalingReplicaSet  22s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 2
    Normal  ScalingReplicaSet  19s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 1
    Normal  ScalingReplicaSet  19s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 3
    Normal  ScalingReplicaSet  14s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 0

여기서 디플로이먼트를 처음 만들 때 레플리카셋(nginx-deployment-2035384211)을 만들자마자 3개로 늘린 걸 볼 수 있어요. 디플로이먼트를 업데이트하면 새 레플리카셋(nginx-deployment-1564180365)을 만들고 1개로 올린 뒤 파드가 뜨기를 기다려요. 그다음 옛 레플리카셋을 2개로 줄이고 새 레플리카셋을 2개로 올려서, 항상 최소 3개 파드를 사용 가능하게, 최대 4개 파드만 만들어지게 해요. 같은 롤링 업데이트 전략으로 새·옛 레플리카셋을 계속 위아래로 조절하다가, 결국 새 레플리카셋에 3개의 사용 가능한 레플리카를 확보하고 옛 레플리카셋은 0으로 줄여요.

참고: 쿠버네티스는 availableReplicas를 계산할 때 종료 중(terminating) 파드는 세지 않아요. 이 값은 replicas - maxUnavailablereplicas + maxSurge 사이여야 해요. 그래서 롤아웃 중에는 예상보다 더 많은 파드가 보일 수 있고, 종료 중인 파드의 terminationGracePeriodSeconds가 만료되기 전까지 디플로이먼트가 소비하는 리소스 총량이 replicas + maxSurge보다 많아 보일 수도 있어요.

롤오버 (여러 업데이트가 진행 중일 때)

디플로이먼트 컨트롤러가 새 디플로이먼트를 관찰할 때마다 원하는 파드를 띄우기 위해 레플리카셋을 하나 만들어요. 디플로이먼트가 업데이트되면, 그때까지 파드를 제어하던 레플리카셋(라벨이 .spec.selector와 일치하지만 템플릿이 .spec.template과 일치하지 않는 파드를 제어하는 레플리카셋)은 스케일 다운돼요. 결국 새 레플리카셋은 .spec.replicas로 스케일되고, 모든 옛 레플리카셋은 0으로 스케일돼요.

기존 롤아웃이 진행 중인 동안 디플로이먼트를 업데이트하면, 디플로이먼트는 업데이트에 맞춰 새 레플리카셋을 만들고 그걸 스케일 업하기 시작해요. 그리고 이전에 스케일 업 중이던 레플리카셋은 롤오버(roll over)시키는데, 그걸 옛 레플리카셋 목록에 추가하고 스케일 다운하기 시작해요.

예를 들어 nginx:1.14.2 5개를 만들도록 디플로이먼트를 만들었는데, nginx:1.14.2 3개만 만들어졌을 때 디플로이먼트를 nginx:1.16.1 5개로 업데이트했다고 해 볼게요. 이 경우 디플로이먼트는 만든 nginx:1.14.2 파드 3개를 즉시 죽이기 시작하고 nginx:1.16.1 파드를 만들기 시작해요. nginx:1.14.2 5개가 만들어질 때까지 기다렸다가 방향을 바꾸지 않아요.

라벨 셀렉터 업데이트

라벨 셀렉터 업데이트는 일반적으로 권장하지 않아요. 셀렉터는 처음부터 계획해 두라고 권해요. 디플로이먼트의 라벨 셀렉터는 생성 후 **불변(immutable)**이라서 kubectl patch, kubectl edit, kubectl apply, helm upgrade 같은 도구로도 업데이트할 수 없어요.

셀렉터를 바꿔야 한다면 디플로이먼트를 삭제하고 다시 만들어야 해요. 기본적으로 디플로이먼트를 삭제하면 실행 중인 파드도 함께 삭제되어 다운타임이 생겨요. 디플로이먼트를 다시 만드는 동안 파드가 계속 실행되길 원한다면 --cascade=orphan을 써요(아래 영향을 참고). 큰 주의를 기울이고 다음 영향들을 반드시 이해해 두세요.

  • 추가(Additions): 더 좁은 셀렉터로 새 디플로이먼트를 만들 때, 새 디플로이먼트는 반드시 적절한 파드 템플릿도 가져야 해요. 기존 매니페스트가 있다면 그걸 수정해서 셀렉터를 좁히려면, 매니페스트의 파드 템플릿 메타데이터를 수정해 매치할 새 라벨을 추가해야 해요. 그렇지 않으면 API 서버가 검증 오류를 반환해요. 이는 겹치지 않는 변경이에요. 새 디플로이먼트는 옛 파드(새 라벨이 없음)를 "보지" 못해서 옛 레플리카셋은 **고아(orphan)**가 되고 완전히 새로운 레플리카셋이 만들어져요.
  • 값 업데이트(Value Updates): 셀렉터 키의 기존 값을 바꾸는 것(예: v1에서 v2로)은 추가(additions)와 같은 동작(고아화와 재생성)을 일으켜요.
  • 제거(Removals): 디플로이먼트 셀렉터에서 기존 키를 제거하는 것은 파드 템플릿 라벨에 어떤 변경도 요구하지 않아요. 이는 겹치는 변경이에요. 더 넓어진 새 셀렉터가 옛 파드를 매치할 거예요. 기존 레플리카셋은 고아가 되지 않고 새 레플리카셋도 만들어지지 않아요. 다만 제거된 라벨이 기존 파드와 레플리카셋에는 여전히 남아 있다는 점에 주의해요. 디플로이먼트 롤아웃을 트리거해서 정리하면 돼요.

디플로이먼트 롤백하기

디플로이먼트가 안정적이지 않을 때(예: 크래시 루프) 롤백하고 싶을 때가 있어요. 기본적으로 디플로이먼트의 롤아웃 기록 전체가 시스템에 보관돼서 원할 때 언제든 롤백할 수 있어요(리비전 기록 한도를 바꿔서 조절할 수도 있어요).

참고: 디플로이먼트의 리비전은 롤아웃이 트리거될 때 만들어져요. 즉 새 리비전은 디플로이먼트의 파드 템플릿(.spec.template)이 바뀔 때에만, 예를 들어 템플릿의 라벨이나 컨테이너 이미지를 업데이트할 때만 생겨요. 스케일링 같은 다른 업데이트는 리비전을 만들지 않아서 수동·자동 스케일링을 동시에 할 수 있어요. 그래서 이전 리비전으로 롤백하면 디플로이먼트의 파드 템플릿 부분만 롤백돼요.

  • 디플로이먼트를 업데이트할 때 이미지 이름을 nginx:1.16.1 대신 nginx:1.161로 잘못 입력했다고 해 볼게요.
kubectl set image deployment/nginx-deployment nginx=nginx:1.161

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment image updated
  • 롤아웃이 막혀 버려요. 롤아웃 상태를 확인해서 그걸 검증할 수 있어요.
kubectl rollout status deployment/nginx-deployment

출력은 다음과 비슷해요.

Waiting for rollout to finish: 1 out of 3 new replicas have been updated...
  • 위 롤아웃 상태 감시를 멈추려면 Ctrl-C를 눌러요. 막힌 롤아웃에 대한 자세한 내용은 이 문서를 읽어요.
  • 옛 레플리카 수(nginx-deployment-1564180365nginx-deployment-2035384211의 레플리카 수를 더한 값)는 3이고, 새 레플리카 수(nginx-deployment-3066724191에서)는 1인 걸 볼 수 있어요.
kubectl get rs

출력은 다음과 비슷해요.

NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   3         3         3       25s
nginx-deployment-2035384211   0         0         0       36s
nginx-deployment-3066724191   1         1         0       6s
  • 만들어진 파드를 보면 새 레플리카셋이 만든 파드 1개가 이미지 풀 루프에 걸려 있는 걸 볼 수 있어요.
kubectl get pods

출력은 다음과 비슷해요.

NAME                                READY     STATUS             RESTARTS   AGE
nginx-deployment-1564180365-70iae   1/1       Running            0          25s
nginx-deployment-1564180365-jbqqo   1/1       Running            0          25s
nginx-deployment-1564180365-hysrc   1/1       Running            0          25s
nginx-deployment-3066724191-08mng   0/1       ImagePullBackOff   0          6s

참고: 디플로이먼트 컨트롤러는 나쁜 롤아웃을 자동으로 멈추고 새 레플리카셋의 스케일 업을 중단해요. 이는 지정한 rollingUpdate 파라미터(특히 maxUnavailable)에 달려 있어요. 쿠버네티스는 기본값을 25%로 설정해요.

  • 디플로이먼트의 설명을 가져와요.
kubectl describe deployment

출력은 다음과 비슷해요.

Name:           nginx-deployment
Namespace:      default
CreationTimestamp:  Tue, 15 Mar 2016 14:48:04 -0700
Labels:         app=nginx
Selector:       app=nginx
Replicas:       3 desired | 1 updated | 4 total | 3 available | 1 unavailable
StrategyType:       RollingUpdate
MinReadySeconds:    0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.161
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    ReplicaSetUpdated
OldReplicaSets:     nginx-deployment-1564180365 (3/3 replicas created)
NewReplicaSet:      nginx-deployment-3066724191 (1/1 replicas created)
Events:
  FirstSeen LastSeen    Count   From                    SubObjectPath   Type        Reason              Message
  --------- --------    -----   ----                    -------------   --------    ------              -------
  1m        1m          1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-2035384211 to 3
  22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 1
  22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 2
  22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 2
  21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 1
  21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 3
  13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 0
  13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-3066724191 to 1

이를 고치려면 안정적인 이전 디플로이먼트 리비전으로 롤백해야 해요.

디플로이먼트의 롤아웃 기록 확인하기

아래 단계에 따라 롤아웃 기록을 확인해요.

  1. 먼저 이 디플로이먼트의 리비전을 확인해요.
kubectl rollout history deployment/nginx-deployment

출력은 다음과 비슷해요.

deployments "nginx-deployment"
REVISION    CHANGE-CAUSE
1           <none>
2           <none>
3           <none>

CHANGE-CAUSE는 디플로이먼트 어노테이션 kubernetes.io/change-cause에서 리비전 생성 시 복사돼요. CHANGE-CAUSE 메시지를 지정하는 방법은 아래와 같아요.

참고: 이전 쿠버네티스 버전에서는 kubectl 명령에 --record 플래그를 써서 CHANGE-CAUSE 필드를 자동으로 채울 수 있었어요. 이 플래그는 더 이상 사용되지 않으며(deprecated) 향후 릴리스에서 제거될 예정이에요.

  • kubectl annotate deployment/nginx-deployment kubernetes.io/change-cause="image updated to 1.16.1"로 디플로이먼트에 어노테이션을 추가하는 방법
  • 리소스 매니페스트를 직접 수정하는 방법
  • 어노테이션을 자동으로 설정하는 도구를 쓰는 방법
  1. 각 리비전의 세부 내용을 보려면 다음을 실행해요.
kubectl rollout history deployment/nginx-deployment --revision=2

출력은 다음과 비슷해요.

deployments "nginx-deployment" revision 2
  Labels:       app=nginx
          pod-template-hash=1159050644
  Containers:
   nginx:
    Image:      nginx:1.16.1
    Port:       80/TCP
     QoS Tier:
        cpu:      BestEffort
        memory:   BestEffort
    Environment Variables:      <none>
  No volumes.

이전 리비전으로 롤백하기

아래 단계에 따라 디플로이먼트를 현재 버전에서 이전 버전(버전 2)으로 롤백해요.

  1. 이제 현재 롤아웃을 되돌리고 이전 리비전으로 롤백하기로 결정했다고 해 볼게요.
kubectl rollout undo deployment/nginx-deployment

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment rolled back

또는 --to-revision으로 특정 리비전을 지정해서 롤백할 수도 있어요.

kubectl rollout undo deployment/nginx-deployment --to-revision=2

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment rolled back

롤아웃 관련 명령에 대한 자세한 내용은 kubectl rollout을 읽어요.

디플로이먼트는 이제 이전의 안정적인 리비전으로 롤백됐어요. 보다시피 리비전 2로 롤백하는 DeploymentRollback 이벤트가 디플로이먼트 컨트롤러에서 생성돼요.

  1. 롤백이 성공했고 디플로이먼트가 예상대로 실행 중인지 확인하려면 다음을 실행해요.
kubectl get deployment nginx-deployment

출력은 다음과 비슷해요.

NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           30m
  1. 디플로이먼트의 설명을 가져와요.
kubectl describe deployment nginx-deployment

출력은 다음과 비슷해요.

Name:                   nginx-deployment
Namespace:              default
CreationTimestamp:      Sun, 02 Sep 2018 18:17:55 -0500
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision=4
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.16.1
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-c4747d96c (3/3 replicas created)
Events:
  Type    Reason              Age   From                   Message
  ----    ------              ----  ----                   -------
  Normal  ScalingReplicaSet   12m   deployment-controller  Scaled up replica set nginx-deployment-75675f5897 to 3
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 1
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 2
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 2
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 1
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 3
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 0
  Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-595696685f to 1
  Normal  DeploymentRollback  15s   deployment-controller  Rolled back deployment "nginx-deployment" to revision 2
  Normal  ScalingReplicaSet   15s   deployment-controller  Scaled down replica set nginx-deployment-595696685f to 0

디플로이먼트 스케일링

다음 명령으로 디플로이먼트를 스케일할 수 있어요.

kubectl scale deployment/nginx-deployment --replicas=10

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment scaled

클러스터에서 horizontal Pod autoscaling이 활성화되어 있다면, 디플로이먼트용 오토스케일러를 설정하고 기존 파드의 CPU 사용률에 따라 실행할 최소·최대 파드 수를 정할 수 있어요.

kubectl autoscale deployment/nginx-deployment --min=10 --max=15 --cpu-percent=80%

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment scaled

비례 스케일링

RollingUpdate 디플로이먼트는 애플리케이션의 여러 버전을 동시에 실행하는 것을 지원해요. 사용자나 오토스케일러가 롤아웃이 진행 중인(진행 중이거나 일시 중지된) RollingUpdate 디플로이먼트를 스케일할 때, 디플로이먼트 컨트롤러는 리스크를 줄이기 위해 추가 레플리카를 기존 활성 레플리카셋(파드를 가진 레플리카셋)에 분배해요. 이것을 비례 스케일링(proportional scaling) 이라고 불러요.

예를 들어 레플리카 10개, maxSurge=3, maxUnavailable=2인 디플로이먼트를 실행 중이라고 해 볼게요.

  • 디플로이먼트의 레플리카 10개가 실행 중인지 확인해요.
kubectl get deploy

출력은 다음과 비슷해요.

NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment     10        10        10           10          50s
  • 클러스터 안에서 해석할 수 없는 새 이미지로 업데이트한다고 해 볼게요.
kubectl set image deployment/nginx-deployment nginx=nginx:sometag

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment image updated
  • 이미지 업데이트가 레플리카셋 nginx-deployment-1989198191로 새 롤아웃을 시작하지만, 앞서 말한 maxUnavailable 요구사항 때문에 막혀요. 롤아웃 상태를 확인해 볼게요.
kubectl get rs

출력은 다음과 비슷해요.

NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   5         5         0         9s
nginx-deployment-618515232    8         8         8         1m
  • 그다음 디플로이먼트에 대한 새 스케일 요청이 들어와요. 오토스케일러가 디플로이먼트 레플리카를 15로 올려요. 디플로이먼트 컨트롤러는 이 새 5개 레플리카를 어디에 추가할지 정해야 해요. 비례 스케일링을 쓰지 않았다면 5개 모두 새 레플리카셋에 추가됐을 거예요. 비례 스케일링에서는 추가 레플리카를 모든 레플리카셋에 분산해요. 레플리카가 가장 많은 레플리카셋에 더 큰 비율이, 적은 레플리카셋에 더 낮은 비율이 가요. 남은 것은 레플리카가 가장 많은 레플리카셋에 추가돼요. 레플리카가 0인 레플리카셋은 스케일 업되지 않아요.

위 예시에서는 옛 레플리카셋에 3개, 새 레플리카셋에 2개가 추가돼요. 새 레플리카가 정상이 된다고 가정하면 롤아웃 과정은 결국 모든 레플리카를 새 레플리카셋으로 옮겨요. 이를 확인하려면 다음을 실행해요.

kubectl get deploy

출력은 다음과 비슷해요.

NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment     15        18        7            8           7m

롤아웃 상태는 각 레플리카셋에 레플리카가 어떻게 추가됐는지 확인해 줘요.

kubectl get rs

출력은 다음과 비슷해요.

NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   7         7         0         7m
nginx-deployment-618515232    11        11        11        7m

디플로이먼트 롤아웃 일시 중지·재개

디플로이먼트를 업데이트하고 있거나 업데이트할 계획이라면, 하나 이상의 업데이트를 트리거하기 전에 해당 디플로이먼트의 롤아웃을 일시 중지할 수 있어요. 변경 사항을 적용할 준비가 되면 롤아웃을 재개해요. 이렇게 하면 불필요한 롤아웃을 트리거하지 않고 일시 중지와 재개 사이에 여러 수정을 적용할 수 있어요.

  • 예를 들어 만들어진 디플로이먼트가 있다고 해 볼게요.

디플로이먼트 상세 정보를 가져와요.

kubectl get deploy

출력은 다음과 비슷해요.

NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx     3         3         3            3           1m

롤아웃 상태를 가져와요.

kubectl get rs

출력은 다음과 비슷해요.

NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   3         3         3         1m
  • 다음 명령으로 일시 중지해요.
kubectl rollout pause deployment/nginx-deployment

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment paused
  • 그런 다음 디플로이먼트의 이미지를 업데이트해요.
kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment image updated
  • 새 롤아웃이 시작되지 않았다는 걸 확인해요.
kubectl rollout history deployment/nginx-deployment

출력은 다음과 비슷해요.

deployments "nginx"
REVISION  CHANGE-CAUSE
1   <none>
  • 기존 레플리카셋이 바뀌지 않았는지 롤아웃 상태로 확인해요.
kubectl get rs

출력은 다음과 비슷해요.

NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   3         3         3         2m
  • 원하는 만큼 업데이트를 할 수 있어요. 예를 들어 사용할 리소스를 업데이트해 볼게요.
kubectl set resources deployment/nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment resource requirements updated

롤아웃을 일시 중지하기 전 디플로이먼트의 초기 상태는 계속 동작하지만, 롤아웃이 일시 중지된 동안에는 디플로이먼트에 대한 새 업데이트가 아무 효과도 없어요.

  • 마지막으로 디플로이먼트 롤아웃을 재개하고 모든 새 업데이트를 담은 새 레플리카셋이 떠오르는 걸 관찰해요.
kubectl rollout resume deployment/nginx-deployment

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment resumed
kubectl get rs --watch

출력은 다음과 비슷해요.

NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   2         2         2         2m
nginx-3926361531   2         2         0         6s
nginx-3926361531   2         2         1         18s
nginx-2142116321   1         2         2         2m
nginx-2142116321   1         2         2         2m
nginx-3926361531   3         2         1         18s
nginx-3926361531   3         2         1         18s
nginx-2142116321   1         1         1         2m
nginx-3926361531   3         3         1         18s
nginx-3926361531   3         3         2         19s
nginx-2142116321   0         1         1         2m
nginx-2142116321   0         1         1         2m
nginx-2142116321   0         0         0         2m
nginx-3926361531   3         3         3         20s
  • 최신 롤아웃 상태를 가져와요.
kubectl get rs

출력은 다음과 비슷해요.

NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   0         0         0         2m
nginx-3926361531   3         3         3         28s

참고: 일시 중지된 디플로이먼트는 재개하기 전에는 롤백할 수 없어요.

디플로이먼트 상태

디플로이먼트는 생명주기 동안 여러 상태를 거쳐요. 새 레플리카셋을 롤아웃하는 동안에는 진행 중(progressing) 이 될 수 있고, 완료(complete) 상태가 될 수도 있으며, 진행에 실패할 수도 있어요.

진행 중인 디플로이먼트

쿠버네티스는 다음 작업 중 하나가 수행되면 디플로이먼트를 진행 중 으로 표시해요.

  • 디플로이먼트가 새 레플리카셋을 만든다.
  • 디플로이먼트가 최신 레플리카셋을 스케일 업한다.
  • 디플로이먼트가 더 오래된 레플리카셋을 스케일 다운한다.
  • 새 파드가 ready 또는 available 상태가 된다(최소 MinReadySeconds 동안 ready 상태 유지).

롤아웃이 "진행 중"이 되면 디플로이먼트 컨트롤러는 디플로이먼트의 .status.conditions에 다음 속성을 가진 조건을 추가해요.

  • type: Progressing
  • status: "True"
  • reason: NewReplicaSetCreated | reason: FoundNewReplicaSet | reason: ReplicaSetUpdated

kubectl rollout status로 디플로이먼트의 진행 상황을 모니터링할 수 있어요.

완료된 디플로이먼트

쿠버네티스는 디플로이먼트가 다음 특성을 가질 때 완료 로 표시해요.

  • 디플로이먼트에 연결된 모든 레플리카가 지정한 최신 버전으로 업데이트됐다. 즉 요청한 업데이트가 모두 완료됐다.
  • 디플로이먼트에 연결된 모든 레플리카가 사용 가능(available)하다.
  • 디플로이먼트의 옛 레플리카가 하나도 실행 중이지 않다.

롤아웃이 "완료"가 되면 디플로이먼트 컨트롤러는 디플로이먼트의 .status.conditions에 다음 속성을 가진 조건을 설정해요.

  • type: Progressing
  • status: "True"
  • reason: NewReplicaSetAvailable

Progressing 조건은 새 롤아웃이 시작될 때까지 "True" 값을 유지해요. 레플리카의 사용 가능 상태가 바뀌어도 이 조건은 유지돼요(그 변경은 Available 조건에 영향을 줘요).

kubectl rollout status로 디플로이먼트가 완료됐는지 확인할 수 있어요. 롤아웃이 성공적으로 완료되면 kubectl rollout status는 종료 코드 0을 반환해요.

kubectl rollout status deployment/nginx-deployment

출력은 다음과 비슷해요.

Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deployment" successfully rolled out

그리고 kubectl rollout의 종료 상태는 0(성공)이에요.

echo $?
0

실패한 디플로이먼트

디플로이먼트가 최신 레플리카셋을 배포하려다 완료하지 못한 채 막힐 수 있어요. 이는 다음 요인들 때문에 발생할 수 있어요.

  • 할당량(quota) 부족
  • 준비성 프로브(readiness probe) 실패
  • 이미지 풀 오류
  • 권한 부족
  • limit range
  • 애플리케이션 런타임 설정 오류

이 상태를 감지하는 방법 중 하나는 디플로이먼트 스펙에 데드라인 파라미터를 지정하는 거예요(.spec.progressDeadlineSeconds). .spec.progressDeadlineSeconds는 디플로이먼트 진행이 막혔음을 디플로이먼트 상태에 표시하기 전에 디플로이먼트 컨트롤러가 기다리는 시간(초)을 뜻해요.

다음 kubectl 명령은 progressDeadlineSeconds로 스펙을 설정해서, 롤아웃 진행 부족을 10분 후에 보고하도록 컨트롤러에 지시해요.

kubectl patch deployment/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'

출력은 다음과 비슷해요.

deployment.apps/nginx-deployment patched

데드라인이 지나면 디플로이먼트 컨트롤러는 디플로이먼트의 .status.conditions에 다음 속성을 가진 DeploymentCondition을 추가해요.

  • type: Progressing
  • status: "False"
  • reason: ProgressDeadlineExceeded

이 조건은 일찍 실패할 수도 있는데, 그 경우 ReplicaSetCreateError 같은 이유로 "False" 상태로 설정돼요. 또한 디플로이먼트 롤아웃이 완료되면 데드라인은 더 이상 고려되지 않아요.

상태 조건에 대한 자세한 내용은 Kubernetes API conventions를 참고해요.

참고: 쿠버네티스는 막힌 디플로이먼트에 대해 reason: ProgressDeadlineExceeded 상태 조건을 보고하는 것 외에는 아무 조치도 취하지 않아요. 상위 수준 오케스트레이터가 이를 활용해서 예를 들어 디플로이먼트를 이전 버전으로 롤백하는 식으로 대응할 수 있어요.

참고: 디플로이먼트 롤아웃을 일시 중지하면 쿠버네티스는 지정된 데드라인에 대해 진행을 확인하지 않아요. 롤아웃 중간에 디플로이먼트 롤아웃을 안전하게 일시 중지했다가, 데드라인 초과 조건을 트리거하지 않고 재개할 수 있어요.

디플로이먼트에서 일시적인 오류를 겪을 수 있어요. 낮게 설정한 타임아웃 때문이거나, 일시적(transient)으로 처리할 수 있는 다른 종류의 오류 때문일 수 있어요. 예를 들어 할당량이 부족하다고 가정해 볼게요. 디플로이먼트를 describe하면 다음 섹션을 볼 수 있어요.

kubectl describe deployment nginx-deployment

출력은 다음과 비슷해요.

<...>
Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     True    ReplicaSetUpdated
  ReplicaFailure  True    FailedCreate
<...>

kubectl get deployment nginx-deployment -o yaml을 실행하면 디플로이먼트 상태는 다음과 비슷해요.

status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: Replica set "nginx-deployment-4262182780" is progressing.
    reason: ReplicaSetUpdated
    status: "True"
    type: Progressing
  - lastTransitionTime: 2016-10-04T12:25:42Z
    lastUpdateTime: 2016-10-04T12:25:42Z
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:
      object-counts, requested: pods=1, used: pods=3, limited: pods=2'
    reason: FailedCreate
    status: "True"
    type: ReplicaFailure
  observedGeneration: 3
  replicas: 2
  unavailableReplicas: 2

결국 디플로이먼트 진행 데드라인이 지나면 쿠버네티스가 상태를 업데이트하고 Progressing 조건의 reason을 바꿔요.

Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     False   ProgressDeadlineExceeded
  ReplicaFailure  True    FailedCreate

할당량 부족 문제는 디플로이먼트를 스케일 다운하거나, 실행 중인 다른 컨트롤러를 스케일 다운하거나, 네임스페이스의 할당량을 늘려서 해결할 수 있어요. 할당량 조건을 충족하고 디플로이먼트 컨트롤러가 디플로이먼트 롤아웃을 완료하면, 디플로이먼트 상태가 성공 조건(status: "True", reason: NewReplicaSetAvailable)으로 업데이트되는 걸 볼 수 있어요.

Conditions:
  Type          Status  Reason
  ----          ------  ------
  Available     True    MinimumReplicasAvailable
  Progressing   True    NewReplicaSetAvailable

type: Availablestatus: "True"가 있으면 디플로이먼트가 최소 가용성을 확보했다는 뜻이에요. 최소 가용성은 배포 전략에 지정된 파라미터에 따라 정해져요. type: Progressingstatus: "True"가 있으면 디플로이먼트가 롤아웃 중이라 진행 중이거나, 진행을 성공적으로 완료해서 필요한 최소한의 새 레플리카가 사용 가능하다는 뜻이에요(자세한 내용은 조건의 Reason 참고 — 여기서는 reason: NewReplicaSetAvailable이 디플로이먼트가 완료됐다는 뜻이에요).

kubectl rollout status로 디플로이먼트가 진행에 실패했는지 확인할 수 있어요. 디플로이먼트가 진행 데드라인을 초과했다면 kubectl rollout status는 0이 아닌 종료 코드를 반환해요.

kubectl rollout status deployment/nginx-deployment

출력은 다음과 비슷해요.

Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline

그리고 kubectl rollout의 종료 상태는 1(오류를 나타냄)이에요.

echo $?
1

완료된 디플로이먼트에 적용되는 모든 작업은 실패한 디플로이먼트에도 적용돼요. 스케일 업/다운, 이전 리비전으로 롤백, 또는 디플로이먼트 파드 템플릿에 여러 수정을 적용해야 한다면 일시 중지하는 것도 가능해요.

정리 정책

디플로이먼트의 .spec.revisionHistoryLimit 필드를 설정해서 이 디플로이먼트의 옛 레플리카셋을 몇 개까지 유지할지 지정할 수 있어요. 나머지는 백그라운드에서 가비지 컬렉션돼요. 기본값은 10이에요.

참고: 이 필드를 명시적으로 0으로 설정하면 디플로이먼트의 모든 기록이 정리돼서 더 이상 롤백할 수 없게 돼요.

정리는 디플로이먼트가 완료 상태에 도달한 뒤에만 시작돼요. .spec.revisionHistoryLimit을 0으로 설정해도, 쿠버네티스가 옛 레플리카셋을 제거하기 전에 어떤 롤아웃이든 새 레플리카셋 생성을 유발해요.

리비전 기록 한도가 0이 아니더라도, 설정한 한도보다 더 많은 레플리카셋이 있을 수 있어요. 예를 들어 파드가 크래시 루프 중이고 시간이 지나면서 롤링 업데이트 이벤트가 여러 번 발생하면, 디플로이먼트가 완료 상태에 도달하지 못해서 .spec.revisionHistoryLimit보다 많은 레플리카셋이 생길 수 있어요.

카나리아 디플로이먼트

디플로이먼트를 사용해서 일부 사용자나 서버에 릴리스를 롤아웃하고 싶다면, 리소스 관리에 설명된 카나리아 패턴에 따라 릴리스별로 디플로이먼트 하나씩, 여러 디플로이먼트를 만들 수 있어요.

디플로이먼트 스펙 작성

다른 모든 쿠버네티스 구성과 마찬가지로 디플로이먼트에는 .apiVersion, .kind, .metadata 필드가 필요해요. 구성 파일 작업에 대한 일반적인 정보는 애플리케이션 배포, 컨테이너 구성, kubectl로 리소스 관리하기 문서를 참고해요.

컨트롤 플레인이 디플로이먼트용 새 파드를 만들 때, 디플로이먼트의 .metadata.name은 그 파드들의 이름을 붙이는 기준의 일부가 돼요. 디플로이먼트 이름은 유효한 DNS 서브도메인 값이어야 해요. 그런데 이는 파드 호스트네임에 예상치 못한 결과를 낼 수 있어요. 최상의 호환성을 위해 이름은 DNS 라벨의 더 제한적인 규칙을 따라야 해요.

디플로이먼트에는 .spec 섹션도 필요해요.

파드 템플릿

.spec.template.spec.selector.spec의 유일한 필수 필드예요.

.spec.template파드 템플릿이에요. 파드와 정확히 같은 스키마를 갖되, 중첩되어 있고 apiVersion이나 kind가 없어요.

파드에 필요한 필드 외에도 디플로이먼트의 파드 템플릿은 적절한 라벨과 적절한 재시작 정책을 지정해야 해요. 라벨은 다른 컨트롤러와 겹치지 않게 해요. 셀렉터를 참고하세요.

.spec.template.spec.restartPolicyAlways만 허용돼요. 지정하지 않으면 기본값이 Always예요.

레플리카

.spec.replicas는 선택 필드로, 원하는 파드 수를 지정해요. 기본값은 1이에요.

디플로이먼트를 수동으로 스케일했다면(예: kubectl scale deployment deployment --replicas=X), 그다음 매니페스트 기반으로 디플로이먼트를 업데이트하면(예: kubectl apply -f deployment.yaml 실행) 그 매니페스트가 이전에 한 수동 스케일링을 덮어써요.

HorizontalPodAutoscaler(또는 수평 스케일링을 위한 비슷한 API)가 디플로이먼트의 스케일링을 관리한다면 .spec.replicas를 설정하지 마세요. 대신 쿠버네티스 컨트롤 플레인.spec.replicas 필드를 자동으로 관리하게 두세요.

셀렉터

.spec.selector는 필수 필드로, 이 디플로이먼트가 대상으로 하는 파드의 라벨 셀렉터를 지정해요.

.spec.selector.spec.template.metadata.labels와 일치해야 해요. 그렇지 않으면 API가 거부해요.

API 버전 apps/v1에서 .spec.selector.metadata.labels는 설정하지 않으면 .spec.template.metadata.labels로 기본값이 정해지지 않아요. 그래서 명시적으로 설정해야 해요. 또한 apps/v1에서 .spec.selector는 디플로이먼트 생성 후 불변(immutable)이라는 점도 주의해요.

디플로이먼트는 셀렉터와 라벨이 일치하는 파드라도 템플릿이 .spec.template과 다르거나, 그런 파드의 총 수가 .spec.replicas를 초과하면 종료할 수 있어요. 파드 수가 원하는 수보다 적으면 .spec.template으로 새 파드를 띄워요.

참고: 셀렉터와 라벨이 일치하는 다른 파드를 직접 만들거나 다른 디플로이먼트를 만들거나 레플리카셋·레플리케이션컨트롤러 같은 다른 컨트롤러를 만들어서 만들지 마세요. 그렇게 하면 첫 번째 디플로이먼트가 이 파드들을 자신이 만들었다고 생각해요. 쿠버네티스가 이를 막아 주지는 않아요.

겹치는 셀렉터를 가진 컨트롤러가 여러 개 있으면 그 컨트롤러들은 서로 싸우며 제대로 동작하지 않아요.

전략

.spec.strategy는 새 파드로 옛 파드를 교체하는 전략을 지정해요. .spec.strategy.type은 "Recreate" 또는 "RollingUpdate"가 될 수 있어요. "RollingUpdate"가 기본값이에요.

Recreate 디플로이먼트

.spec.strategy.type==Recreate이면 모든 기존 파드가 새 파드가 만들어지기 전에 종료돼요.

참고: 이는 업그레이드 시 파드 종료가 생성보다 먼저 일어남을 보장할 뿐이에요. 디플로이먼트를 업그레이드하면 옛 리비전의 모든 파드가 즉시 종료돼요. 새 리비전의 파드가 하나라도 만들어지기 전에 제거가 성공하기를 기다려요. 파드를 수동으로 삭제하면 생명주기는 레플리카셋이 제어해서, 옛 파드가 여전히 Terminating 상태여도 교체 파드가 즉시 만들어져요. 파드에 대해 "at most" 보장이 필요하다면 스테이트풀셋(StatefulSet) 사용을 고려해요.

Rolling Update 디플로이먼트

.spec.strategy.type==RollingUpdate이면 디플로이먼트는 롤링 업데이트 방식으로 파드를 업데이트해요(옛 레플리카셋을 점차 줄이고 새 레플리카셋을 점차 늘려요). 롤링 업데이트 과정을 제어하려면 maxUnavailablemaxSurge를 지정할 수 있어요.

Max Unavailable

.spec.strategy.rollingUpdate.maxUnavailable는 선택 필드로, 업데이트 과정에서 사용할 수 없게(불가용) 될 파드의 최대 개수를 지정해요. 값은 절대 수(예: 5) 또는 원하는 파드 수의 백분율(예: 10%)일 수 있어요. 백분율은 내림(rounding down)으로 절대 수를 계산해요. .spec.strategy.rollingUpdate.maxSurge가 0이면 이 값은 0이 될 수 없어요. 기본값은 25%예요.

예를 들어 이 값이 30%로 설정되면 롤링 업데이트가 시작될 때 옛 레플리카셋을 원하는 파드 수의 70%까지 즉시 스케일 다운할 수 있어요. 새 파드가 준비되면 옛 레플리카셋을 더 스케일 다운하고, 이어서 새 레플리카셋을 스케일 업해서, 업데이트 중 언제든 사용 가능한 파드 총 수가 원하는 파드 수의 최소 70%가 되도록 보장해요.

Max Surge

.spec.strategy.rollingUpdate.maxSurge는 선택 필드로, 원하는 파드 수보다 추가로 만들 수 있는 파드의 최대 개수를 지정해요. 값은 절대 수(예: 5) 또는 원하는 파드 수의 백분율(예: 10%)일 수 있어요. maxUnavailable이 0이면 이 값은 0이 될 수 없어요. 백분율은 올림(rounding up)으로 절대 수를 계산해요. 기본값은 25%예요.

예를 들어 이 값이 30%로 설정되면 롤링 업데이트가 시작될 때 새 레플리카셋을 즉시 스케일 업해서, 옛·새 파드의 총 수가 원하는 파드 수의 130%를 넘지 않도록 해요. 옛 파드가 종료되면 새 레플리카셋을 더 스케일 업해서, 업데이트 중 언제든 실행 중인 파드 총 수가 원하는 파드 수의 최대 130%가 되도록 보장해요.

다음은 maxUnavailablemaxSurge를 사용하는 Rolling Update 디플로이먼트 예시들이에요. (Max Unavailable, Max Surge, Hybrid 탭)

apiVersion: apps/v1

kind: Deployment

metadata:

  name: nginx-deployment

  labels:

    app: nginx

spec:

  replicas: 3

  selector:

    matchLabels:

      app: nginx

  template:

    metadata:

      labels:

        app: nginx

    spec:

      containers:

      - name: nginx

        image: nginx:1.14.2

        ports:

        - containerPort: 80

  strategy:

    type: RollingUpdate

    rollingUpdate:

      maxUnavailable: 1
Progress Deadline Seconds

.spec.progressDeadlineSeconds는 선택 필드로, 디플로이먼트가 진행에 실패했다고 시스템이 보고하기 전에(리소스 상태에 type: Progressing, status: "False", reason: ProgressDeadlineExceeded 조건으로 표면화됨) 진행되기를 기다릴 시간(초)을 지정해요. 디플로이먼트 컨트롤러는 디플로이먼트를 계속 재시도해요. 기본값은 600이에요.

지정한다면 이 필드는 .spec.minReadySeconds보다 커야 해요.

Min Ready Seconds

.spec.minReadySeconds는 선택 필드로, 새로 만들어진 파드가 사용 가능(available)하다고 간주되려면 컨테이너가 하나도 크래시하지 않은 채 ready 상태로 유지해야 하는 최소 시간(초)을 지정해요. 기본값은 0이에요(파드는 ready가 되는 즉시 사용 가능한 것으로 간주돼요). 파드가 언제 ready로 간주되는지에 대한 자세한 내용은 컨테이너 프로브(Container Probes)를 참고해요.

종료 중인 파드

기능 상태: Kubernetes v1.35부터 Beta, 기본 활성화

종료 중인 파드는 API 서버와 kube-controller-manager에서 DeploymentReplicaSetTerminatingReplicas 기능 게이트를 활성화한 경우에만 볼 수 있어요.

삭제나 스케일 다운으로 인해 종료 중이 된 파드는 종료되는 데 시간이 오래 걸릴 수 있고, 그동안 추가 리소스를 소비할 수 있어요. 그래서 모든 파드의 총 수가 일시적으로 .spec.replicas를 초과할 수 있어요. 종료 중인 파드는 디플로이먼트의 .status.terminatingReplicas 필드로 추적할 수 있어요.

Revision History Limit

디플로이먼트의 리비전 기록은 그 디플로이먼트가 제어하는 레플리카셋에 저장돼요.

.spec.revisionHistoryLimit는 선택 필드로, 롤백을 위해 유지할 옛 레플리카셋 수를 지정해요. 이 옛 레플리카셋들은 etcd의 리소스를 소비하고 kubectl get rs의 출력을 어지럽혀요. 각 디플로이먼트 리비전의 구성은 그 레플리카셋에 저장돼요. 그래서 옛 레플리카셋이 삭제되면 그 디플로이먼트 리비전으로 롤백할 능력을 잃어요. 기본적으로 옛 레플리카셋 10개가 유지돼요. 다만 이상적인 값은 새 디플로이먼트의 빈도와 안정성에 따라 달라져요.

좀 더 구체적으로, 이 필드를 0으로 설정하면 레플리카가 0인 옛 레플리카셋이 모두 정리돼요. 이 경우 새 디플로이먼트 롤아웃은 리비전 기록이 정리됐으므로 되돌릴 수 없어요.

Paused

.spec.paused는 디플로이먼트를 일시 중지·재개하는 선택 불리언 필드예요. 일시 중지된 디플로이먼트와 그렇지 않은 디플로이먼트의 유일한 차이는, 일시 중지된 동안에는 PodTemplateSpec에 대한 변경이 새 롤아웃을 트리거하지 않는다는 거예요. 디플로이먼트는 생성될 때 기본적으로 일시 중지되지 않아요.

다음 내용

  • 파드에 대해 더 알아보기.
  • 디플로이먼트를 사용해서 무상태(stateless) 애플리케이션 실행하기.
  • 디플로이먼트 API를 이해하려면 Deployment API 읽기.
  • PodDisruptionBudget과 디플로이먼트를 사용해 장애 상황에서 애플리케이션 가용성을 관리하는 방법 알아보기.
  • kubectl로 디플로이먼트 만들기.