여기서는 Kubernetes의 컨트롤러 중 하나인 Deployment에 대해서 이런저런 간단한 이야기를 해 보자. 자세한 내용을 쓰려면 아예 따로 써야 할 것 같다.

Deployment: 배포를 잘 할수 있도록 하는 컨트롤러

A Deployment provides declarative updates for Pods and ReplicaSets.

공식 페이지에서는 위와 같은 말로 Deployment를 설명하고 있는데, 이게 무슨 의미인지 조금 더 알아보는 것이 좋겠다.

우선 답부터 말하면 Deployment는 replicaset에 비해 배포와 관련된 편의성을 제공했다고 보면 된다. 그래서 이런 배포에 관련한 기능을 어떻게 제공하는지 한번 확인해 보자.

Deployment의 관리 대상: ReplicaSet

먼저 간단한 Deployment를 다시 한 번 생성해 보자. 내용은 아래와 같다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 6
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx

이를 생성하면,

$ kubectl create -f deployment.yaml
deployment.apps/nginx-deployment created

$ kubectl get pod
NAME                               READY   STATUS              RESTARTS   AGE
nginx-deployment-f89759699-2cbl4   0/1     ContainerCreating   0          2s
nginx-deployment-f89759699-hsk6w   0/1     ContainerCreating   0          2s
nginx-deployment-f89759699-7vr75   0/1     ContainerCreating   0          2s
nginx-deployment-f89759699-smcgr   0/1     ContainerCreating   0          2s
nginx-deployment-f89759699-f2r6j   0/1     ContainerCreating   0          2s
nginx-deployment-f89759699-p98dh   0/1     ContainerCreating   0          2s

6개의 replicas를 가지는 nginx-deployment라는 deployment를 생성하였다. 이름을 보면 nginx-deployment 이외에 f89759699라는 이름이 추가로 붙었다는 것을 확인할 수 있다. 뒤의 5자리 문자열이야 generateName으로 붙었을 테니. 그럼 f89759699는 어디서 왔는가?

$ kubectl get replicaset
NAME                         DESIRED   CURRENT   READY   AGE
nginx-deployment-f89759699   6         6         6       87s

replicaset의 이름에 해당 스트링이 붙어 있다. 이게 그런데 왜 붙어있을까? 왜냐 하면 replicaset에서 팟을 라벨과 셀렉터를 통해 관리하고 있으므로, 마찬가지로 Deployment 또한 라벨과 셀렉터로 관리하면 되는 것이 아닐까? 왜 굳이 이름을 새로 추가했을까?

첫 번째로 관리 대상을 명확히 파악해 보자. deployment의 spec.selector.matchLabelsapp: nginx로 되어 있는데, replicaset의 metadata.labels에서 app: nginx2로 변경하면 다음과 같은 광경을 확인할 수 있다.

# replicaset의 라벨을 "app: nginx2"로 변경하였다.
$ kubectl edit rs nginx-deployment-f89759699
replicaset.apps/nginx-deployment-f89759699 edited

$ kubectl get pod
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-f89759699-7vr75    1/1     Running             0          4m4s
nginx-deployment-f89759699-hsk6w    1/1     Running             0          4m4s
nginx-deployment-f89759699-f2r6j    1/1     Running             0          4m4s
nginx-deployment-f89759699-2cbl4    1/1     Running             0          4m4s
nginx-deployment-f89759699-smcgr    1/1     Running             0          4m4s
nginx-deployment-f89759699-p98dh    1/1     Running             0          4m4s
nginx-deployment-6f657c7746-574c8   0/1     ContainerCreating   0          2s
nginx-deployment-6f657c7746-5kk5n   0/1     ContainerCreating   0          2s
nginx-deployment-6f657c7746-sgm9t   0/1     ContainerCreating   0          2s
nginx-deployment-6f657c7746-krfkj   0/1     ContainerCreating   0          2s
nginx-deployment-6f657c7746-z8bpc   0/1     ContainerCreating   0          2s
nginx-deployment-6f657c7746-xh54c   0/1     ContainerCreating   0          2s

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-f89759699    6         6         6       4m49s
nginx-deployment-6f657c7746   6         6         6       47s

앗, 라벨을 변경했더니 새로운 팟이 등장하였다. 거기다 replicaset을 조회해 보니 하나의 replicaset이 더 뜬 것을 확인할 수 있다. 그러니까, deployment는 replicaset을 관리하는 것이고, 라벨을 변경했더니 변경된 라벨의 replicaset이 없으므로 이를 생성한 것이다. 거기다가 새로 생성된 replicaset은 자신이 관리하는 팟이 없었으므로, replicas만큼 다시 팟을 생성했다고 봐도 되겠다.

즉 deployment는 단순히 replicaset을 관리하는 주체라고 봐도 되겠다.

Deployment의 업데이트 방식: ReplicaSet의 교체

그러면 이번에는 라벨 이외에 deployment의 정보를 변경해 보자. nginx의 버전을 변경해 보려고 한다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 6
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14
        # image를 nginx에서 nginx:1.14로 변경하였다.

그리고 이를 실행하면,

$ kubectl apply -f deployment.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment.apps/nginx-deployment configured

$ kubectl get pod
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-6f657c7746-sgm9t   1/1     Running             0          17m
nginx-deployment-6f657c7746-xh54c   1/1     Running             0          17m
nginx-deployment-6f657c7746-5kk5n   1/1     Running             0          17m
nginx-deployment-6f657c7746-krfkj   1/1     Running             0          17m
nginx-deployment-6f657c7746-z8bpc   1/1     Running             0          17m
nginx-deployment-7bcc8c844-x757g    0/1     ContainerCreating   0          3s
nginx-deployment-7bcc8c844-8lgzb    0/1     ContainerCreating   0          3s
nginx-deployment-7bcc8c844-dpj6j    0/1     ContainerCreating   0          3s
nginx-deployment-6f657c7746-574c8   0/1     Terminating         0          17m

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-6f657c7746   5         5         5       17m
nginx-deployment-7bcc8c844    3         3         0       5s
$ kubectl get pod
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-6f657c7746-sgm9t   1/1     Running             0          17m
nginx-deployment-6f657c7746-z8bpc   1/1     Running             0          17m
nginx-deployment-7bcc8c844-x757g    1/1     Running             0          10s
nginx-deployment-7bcc8c844-8lgzb    1/1     Running             0          10s
nginx-deployment-7bcc8c844-dpj6j    1/1     Running             0          10s
nginx-deployment-7bcc8c844-9cttr    0/1     ContainerCreating   0          3s
nginx-deployment-7bcc8c844-qllp8    0/1     ContainerCreating   0          3s
nginx-deployment-7bcc8c844-28sxd    0/1     ContainerCreating   0          2s
nginx-deployment-6f657c7746-krfkj   0/1     Terminating         0          17m
nginx-deployment-6f657c7746-5kk5n   0/1     Terminating         0          17m
nginx-deployment-6f657c7746-xh54c   0/1     Terminating         0          17m

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-6f657c7746   2         2         2       17m
nginx-deployment-7bcc8c844    6         6         3       12s

$ kubectl get pod
NAME                                READY   STATUS        RESTARTS   AGE
nginx-deployment-7bcc8c844-x757g    1/1     Running       0          21s
nginx-deployment-7bcc8c844-8lgzb    1/1     Running       0          21s
nginx-deployment-7bcc8c844-dpj6j    1/1     Running       0          21s
nginx-deployment-7bcc8c844-9cttr    1/1     Running       0          14s
nginx-deployment-7bcc8c844-qllp8    1/1     Running       0          14s
nginx-deployment-7bcc8c844-28sxd    1/1     Running       0          13s
nginx-deployment-6f657c7746-z8bpc   0/1     Terminating   0          18m
nginx-deployment-6f657c7746-sgm9t   0/1     Terminating   0          18m

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-7bcc8c844    6         6         6       23s
nginx-deployment-6f657c7746   0         0         0       18m

$ kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-7bcc8c844-x757g   1/1     Running   0          28s
nginx-deployment-7bcc8c844-8lgzb   1/1     Running   0          28s
nginx-deployment-7bcc8c844-dpj6j   1/1     Running   0          28s
nginx-deployment-7bcc8c844-9cttr   1/1     Running   0          21s
nginx-deployment-7bcc8c844-qllp8   1/1     Running   0          21s
nginx-deployment-7bcc8c844-28sxd   1/1     Running   0          20s

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-7bcc8c844    6         6         6       29s
nginx-deployment-6f657c7746   0         0         0       18m
$ kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-7bcc8c844-x757g   1/1     Running   0          35s
nginx-deployment-7bcc8c844-8lgzb   1/1     Running   0          35s
nginx-deployment-7bcc8c844-dpj6j   1/1     Running   0          35s
nginx-deployment-7bcc8c844-9cttr   1/1     Running   0          28s
nginx-deployment-7bcc8c844-qllp8   1/1     Running   0          28s
nginx-deployment-7bcc8c844-28sxd   1/1     Running   0          27s

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-7bcc8c844    6         6         6       37s
nginx-deployment-6f657c7746   0         0         0       18m

앗, 갑자기 수많은 팟이 생성되었다! 그런데 잘 보면 replicaset이 추가되었으므로, replicaset이 관리할 새로운 팟이 생성되었다고 볼 수 있다. 거기다 지속적으로 get을 찍어 보니 기존 nginx-deployment-6f657c7746에서 생성한 팟은 숫자가 줄어들고 있고, 새로운 nginx-deployment-7bcc8c844에서 관리하는 팟의 숫자가 늘어나는 것을 볼 수 있다. 거기에 추가적으로, 기존 replicaset은 지워지지 않고 단지 DESIRED가 0으로 바뀐 채로 남아 있는 것을 확인할 수 있다.

이런 시나리오를 생각해 보면 다음과 같이 사용할 수 있음을 알 수 있다. 만약 어떤 어플리케이션의 버전이 변경된 경우, Deployment에 변경된 내용을 기록하기만 해도 업데이트가 된다는 말이다. 특히 업데이트는 위에서 봤듯 한 번에 진행되는 것이 아니라, 새로운 요구사항에 맞는 replicaset을 생성한 후 기존 팟을 지워나감과 동시에 새로운 팟을 생성해 나간다. 따라서 모든 팟이 꺼지는 상황이 생기지는 않게 되는 것이다. 즉, Deployment는 replicaset을 우아하게 교체하여 서비스에 지장이 없는 업데이트를 진행한다.

한 가지 더 이야기할 것이 있다면, 이런 업데이트는 spec.template이 변경될 때만 가능하다. 즉 metadata같은 것들을 변경해 봤자 전혀 소용이 없다는 것. 생각해 보면 당연한 것이, Deployment의 관리 대상이 업데이트되는 것이므로 spec을 고쳐야 하는 것이 당연하기도 하다. 그래서 보통 강제 업데이트를 진행할 때는 보통 template에 이런저런 값들을 넣어 업데이트하는 식으로 진행한다. 예를 들면 이런 식이다.

$ kubectl patch deployment nginx-deployment -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"`date +'%s'`\"}}}}}"
deployment.apps/nginx-deployment patched

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-6f657c7746   0         0         0       36m
nginx-deployment-7bcc8c844    0         0         0       18m
nginx-deployment-949db48d4    6         6         6       14s

왜 이전 replicaset은 지워지지 않는가

kubectl rollout이라는 명령어가 있다.

$ kubectl rollout
Manage the rollout of a resource.

 Valid resource types include:

  *  deployments
  *  daemonsets
  *  statefulsets

Examples:
  # Rollback to the previous deployment
  kubectl rollout undo deployment/abc

  ...
  ...

rollout명령어를 통해 이전 버전의 deployment로 되돌아갈 수 있다. 그래서 이걸 한번 간단히 실행해 보면,

$ kubectl rollout history deployment/nginx-deployment
deployment.apps/nginx-deployment
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         <none>

우선 위의 강제 업데이트까지 해서 3번의 업데이트가 있었다는 것을 알 수 있다. 그럼 실제로 이전 버전으로 돌리려면,

$ kubectl rollout undo deployment/nginx-deployment
deployment.apps/nginx-deployment rolled back
$ kubectl get pod
NAME                               READY   STATUS              RESTARTS   AGE
nginx-deployment-949db48d4-v8bdd   1/1     Running             0          6m36s
nginx-deployment-949db48d4-5qtzn   1/1     Running             0          6m36s
nginx-deployment-949db48d4-78fxf   1/1     Running             0          6m36s
nginx-deployment-949db48d4-kwvb6   1/1     Running             0          6m29s
nginx-deployment-949db48d4-mvjwm   1/1     Running             0          6m30s
nginx-deployment-949db48d4-v2hl9   1/1     Terminating         0          6m29s
nginx-deployment-7bcc8c844-btwt9   0/1     ContainerCreating   0          2s
nginx-deployment-7bcc8c844-phpjv   0/1     ContainerCreating   0          2s
nginx-deployment-7bcc8c844-jn55n   0/1     ContainerCreating   0          1s
$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-6f657c7746   0         0         0       42m
nginx-deployment-949db48d4    5         5         5       6m38s
nginx-deployment-7bcc8c844    3         3         0       25m
$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-6f657c7746   0         0         0       42m
nginx-deployment-949db48d4    2         2         2       6m45s
nginx-deployment-7bcc8c844    6         6         3       25m
$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-6f657c7746   0         0         0       42m
nginx-deployment-7bcc8c844    6         6         4       25m
nginx-deployment-949db48d4    1         1         1       6m52s
$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-6f657c7746   0         0         0       43m
nginx-deployment-7bcc8c844    6         6         5       25m
nginx-deployment-949db48d4    0         0         0       7m

kubectl rollout undo를 통해 되돌아갈 수 있다. 되돌아가는 방법은 별게 아니고, 현재의 replicaset의 수를 줄이면서, DESIRED 0인 상태로 유지되던 replicaset의 수를 그냥 늘리는 것 뿐이다. 마찬가지로 우아하게 전환할 뿐. --revision을 통해 특정 버전을 선택하여 업데이트할 수 있으므로 이것도 알아두면 좋겠다.

undo 후에는 새로운 리비전 번호를 받는다. 3번 리비전에서 undo를 통해 2번 리비전으로 이동했다고 해서 현재 리비전이 2로 바뀌는 것은 아니고, 새로운 리비전 4를 받는다는 것이다.

$ kubectl rollout history deployment/nginx-deployment
deployment.apps/nginx-deployment
REVISION  CHANGE-CAUSE
1         <none>
3         <none>
4         <none>

$ kubectl describe deployment nginx-deployment
Name:                   nginx-deployment
Namespace:              playground
CreationTimestamp:      Sun, 21 Jun 2020 13:37:12 +0900
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision: 4
                        kubectl.kubernetes.io/last-applied-configuration:
                        ...
                        ...
                        ...

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-6f657c7746   0         0         0       46m
nginx-deployment-949db48d4    0         0         0       10m
nginx-deployment-7bcc8c844    6         6         6       29m

2번 리비전이 없어지고 새로운 4번 리비전이 생긴 것을 볼 수 있다. 따라서 redo는 없다.