디플로이먼트 (Deployment)

niyu·2022년 7월 28일
0

쿠버네티스 기초

목록 보기
11/15
post-thumbnail

파드가 어플리케이션의 첫 번째 버전을 실행하고 이 이미지의 태그가 v1이라고 가정할 때, 이후에 새로운 버전의 어플리케이션을 개발해 v2로 태그가 지정된 새 이미지를 이미지 스토리지에 푸시한다면 모든 파드를 새로운 버전으로 바꾸는 것이 좋다.

이때 파드를 생성한 후에는 해당 파드를 만들 때 사용했던 기존 파드의 이미지를 이전 파드를 제거하고 새로운 이미지를 실행하는 새로운 파드로 교체해야 한다.

파드에서 실행 중인 어플리케이션을 어떻게 업데이트할까? 🧐

파드에서 실행 중인 어플리케이션 업데이트


k8s에서 실행 중인 어플리케이션의 기본적인 구성 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

k8s에서 실행되는 어플리케이션 기본 구성은 위와 같다. 클라이언트가 파드에 액세스하기 위해 서비스가 존재하고, 레플리카셋이 파드를 관리한다.

오래된 파드를 삭제하고 새로운 파드로 교체

기존의 모든 파드를 먼저 삭제한 후 새로운 파드를 시작한다.


레플리케이션컨트롤러의 파드 템플릿 변경 및 이전 파드 삭제로 파드 업데이트 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

레플리케이션컨트롤러의 파드 템플릿은 언제든지 업데이트될 수 있다. 레플리케이션컨트롤러가 새 인스턴스를 만들면 업데이트된 파드 템플릿을 사용해 인스턴스를 만든다.

버전 v1의 파드 세트를 관리하는 레플리케이션컨트롤러가 있는 경우 버전 v2 이미지를 참조하도록 파드 템플릿을 수정한 후 이전 파드 인스턴스를 삭제한다. 레플리케이션컨트롤러는 해당 라벨 셀렉터와 일치하는 파드를 발견하지 못하고 새로운 인스턴스를 스핀업시킨다.

하지만 이 방법을 사용하면, 어플리케이션을 사용할 수 없는 짧은 시간이 발생한다. 오래된 파드를 새로운 파드로 변경하는 데 있어서 잠깐의 다운타임을 허용할 수 있는 경우라면 이 방법을 통해 어플리케이션을 업데이트할 수 있다.

새 파드의 기동과 오래된 파드 삭제

새로운 것을 시작하고 일단 끝나면 오래된 것을 지운다.

어떤 다운타임도 발생하지 않지만, 짧은 시간 동안 두 배의 파드를 실행해야 하기 때문에 더 많은 하드웨어 리소스가 필요하다. 또한 두 가지 버전의 어플리케이션 실행을 동시에 처리해야 한다.

한 번에 기존 버전에서 새로운 버전으로 전환

새 파드를 추가하고 한 번에 기존 파드를 모두 삭제한다.


이전 파드에서 새로운 파드로 서비스 전환 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

새 버전을 실행하는 파드를 가져오는 동안 초기 버전의 파드만 서비스와 연결된다. 그런 다음 새 파드가 모두 올라오면 서비스의 라벨 셀렉터를 변경해 서비스를 새 파드로 전환한다. 이를 블루그린(blue-green) 배포 라고 한다. 이전 버전을 blue 환경으로, 새 버전은 green 환경으로 부른다. 새 버전이 올바르게 동작하는지 확인하고 이전 레플리케이션컨트롤러를 삭제하면 기존 파드를 자유롭게 삭제할 수 있다.

롤링 업데이트 (Rolling Update)

새 파드를 순차적으로 추가하고 이전 파드를 점차 제거한다. 뒤에 나오는 디플로이먼트의 기본 전략이다.


두 개의 레플리케이션컨트롤러를 사용한 파드의 롤링 업데이트 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

이전의 레플리케이션컨트롤러를 천천히 축소하고 새 레플리케이션컨트롤러를 확장한다. 서비스의 파드 셀렉터에 이전 파드와 새 파드를 모두 포함시키고 이 두 요청의 집합으로 요청을 직접 전달한다.

디플로이먼트

디플로이먼트는 상태가 없는(stateless) 없는 앱을 배포할 때 사용하는 가장 기본적인 컨트롤러다.

🔎 컨트롤러: 기본 오브젝트를 생성하고 이를 관리하는 역할로, 대표적으로 ReplicaSet, DeamonSet, StatefulSet, Job, Deployment 등이 있다.


디플로이먼트, 레플리카셋, 파드 관계 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

디플로이먼트를 만들면 레플리카셋 리소스가 아래에 만들어진다. 실제 파드는 디플로이먼트가 아니라 레플리카셋에 의해 생성되고 관리된다.

디플로이먼트 생성

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 3  # 레플리카 수 설정
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
      - image: luksa/kubia:v1
        name: nodejs
  selector:
    matchLabels:
      app: kubia
# 디플로이먼트 생성
$ kubectl create -f kubia-deployment-v1.yaml --record

deployment.apps/kubia created

--record 옵션은 디플로이먼트에 대한 변경 이력을 기록한다. 나중에 필요한 경우 리비전 번호를 사용해 롤백할 수 있다.

디플로이먼트 상태 확인

# 디플로이먼트의 롤링 업데이트 상태 감시
$ kubectl rollout status deployment kubia

deployment "kubia" successfully rolled out
# 디플로이먼트 변경 이력 출력
$ kubectl rollout history deployment kubia

deployment.apps/kubia
REVISION  CHANGE-CAUSE
1         kubectl create --filename=kubia-deployment-v1.yaml --record=true
# 파드 목록 보기
$ kubectl get po

NAME                    READY   STATUS    RESTARTS      AGE
kubia-6459db4dc-4rxg2   1/1     Running   0             2m27s
kubia-6459db4dc-8snvt   1/1     Running   0             2m27s
kubia-6459db4dc-j2f9w   1/1     Running   0             2m27s

디플로이먼트에서 만든 3개의 파드에는 이름 중간에 숫자 값이 추가로 포함됐다. 이 숫자는 디플로이먼트와 이 파드를 관리하는 레플리카셋의 파드 템플릿의 해시 값이다. 디플로이먼트는 파드를 직접 관리하지 않고 그 대신 레플리카셋을 생성하고 레플리카셋이 관리를 하도록 위임한다.

# 레플리카셋 보기
$ kubectl get replicasets

NAME              DESIRED   CURRENT   READY   AGE
kubia-6459db4dc   3         3         3       2m56s
...

레플리카셋의 이름도 해당 파드 템플릿의 해시 값을 포함한다. 디플로이먼트는 여러 버전의 레플리카셋을 만든다. 파드 템플릿의 각 버전마다 레플리카셋이 생성된다. 파드 템플릿의 해시 값을 사용하면 디플로이먼트에서 항상 파드 템플릿의 주어진 버전에 동일한 레플리카셋을 사용할 수 있게 된다.

# 디플로이먼트 상태 확인
$ kubectl get deploy -o yaml
apiVersion: v1
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    annotations:
      deployment.kubernetes.io/revision: "1"
      kubernetes.io/change-cause: kubectl create --filename=kubia-deployment-v1.yaml
        --record=true
    creationTimestamp: "2022-07-28T02:56:40Z"
    generation: 2
    name: kubia
    namespace: default
    resourceVersion: "194399"
    uid: 71ffad1d-32a5-4fae-a30e-4322007aeea5
  spec:
    minReadySeconds: 10
    progressDeadlineSeconds: 600
    replicas: 3
    revisionHistoryLimit: 10
    selector:
      matchLabels:
        app: kubia
    strategy:
      rollingUpdate:
        maxSurge: 25%
        maxUnavailable: 25%
      type: RollingUpdate
    template:
      metadata:
        creationTimestamp: null
        labels:
          app: kubia
        name: kubia
      spec:
        containers:
        - image: luksa/kubia:v1
          name: nodejs 
...

디플로이먼트 업데이트

업데이트를 위해 필요한 일은 디플로이먼트 리소스에 정의된 파드 템플릿을 수정하는 것 뿐이다.

# 이미지 변경
$ kubectl set image deployment kubia nodejs=luksa/kubia:v2

deployment.apps/kubia image updated
# 디플로이먼트 상태 확인
$ kubectl get deploy -o yaml
apiVersion: v1
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    annotations:
      deployment.kubernetes.io/revision: "2"
      kubernetes.io/change-cause: kubectl set image deployment kubia nodejs=luksa/kubia:v2
    creationTimestamp: "2022-07-28T02:56:40Z"
    generation: 3
    name: kubia
    namespace: default
    resourceVersion: "194842"
    uid: 71ffad1d-32a5-4fae-a30e-4322007aeea5
  spec:
    minReadySeconds: 10
    progressDeadlineSeconds: 600
    replicas: 3
    revisionHistoryLimit: 10
    selector:
      matchLabels:
        app: kubia
    strategy:
      rollingUpdate:
        maxSurge: 25%
        maxUnavailable: 25%
      type: RollingUpdate
    template:
      metadata:
        creationTimestamp: null
        labels:
          app: kubia
        name: kubia
      spec:
        containers:
        - image: luksa/kubia:v2
          name: nodejs
...


디플로이먼트의 파드 템플릿 업데이트 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

kubia 디플로이먼트의 파드 템플릿이 업데이트돼 nodejs 컨테이너에서 사용하고 있는 이미지가 v1에서 luksa/kubia:v2로 변경된다.


롤링 업데이트 시작과 종료 시점의 디플로이먼트 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

추가 레플리카셋이 생성되고 이전 레플리카셋이 0으로 축소되는 동안 느리게 확장된다.

# 레플리카셋 출력
$ kubectl get rs

NAME               DESIRED   CURRENT   READY   AGE
kubia-6459db4dc    0         0         0       75m
kubia-75c4ff7786   3         3         3       11m

레플리카셋의 리스트를 보면 이전 레플리카셋을 여전히 볼 수 있다.

디플로이먼트 롤백

버전 3에서는 어플리케이션 (참고: app.js) 이 처음의 네 개의 요청만 제대로 처리하고 다섯 번째 이후의 모든 요청은 내부 서버 오류를 반환한다.

# 이미지 변경
$ kubectl set image deployment kubia nodejs=luksa/kubia:v3

deployment.apps/kubia image updated

롤아웃 되돌리기

잘못된 롤아웃을 되돌리자. 디플로이먼트를 사용하면 k8s에게 디플로이먼트의 마지막 롤아웃을 취소하도록 해서 이전 버전으로 쉽게 롤백할 수 있다.

# 이전 버전으로 롤백
$ kubectl rollout undo deployment kubia

deployment.apps/kubia rolled back

🧩 디플로이먼트의 롤아웃 히스토리 보여주기

디플로이먼트는 리비전 히스토리를 유지하기 때문에 롤아웃 롤백이 가능하다. 히스토리는 레플리카셋의 내부에 저장된다. 롤아웃이 완료되면 이전 레플리카셋은 삭제되지 않고 이전 레플리카셋은 이전 버전뿐만 아니라 모든 리비전으로 롤백할 수 있다.

$ kubectl rollout history deployment kubia

deployment.apps/kubia
REVISION  CHANGE-CAUSE
2         kubectl set image deployment kubia nodejs=luksa/kubia:v2
3         kubectl set image deployment kubia nodejs=luksa/kubia:v3

🧩 특정 디플로이먼트 리비전으로 롤백하기

# 첫 번째 버전으로 롤백
$ kubectl rollout undo deployment kubia --to-revision=1

deployment.apps/kubia rolled back


디플로이먼트의 레플리카셋 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

처음 디플로이먼트를 수정했을 때 비활성 레플리카셋이 디플로이먼트의 첫 번째 리비전을 나타낸다. 디플로이먼트에 의해 생성된 모든 레플리카셋은 전체 리비전 히스토리를 나타낸다. 레플리카셋은 해당 특정 리비전의 디플로이먼트의 전체 정보를 저장하기 때문에 수동으로 삭제하면 안된다. 삭제하면 디플로이먼트 히스토리에서 특정 리비전을 잃어버리게 되고 롤백할 수 없다.

롤아웃 속도 통제

새 파드가 생성되고 이전 파드가 삭제되는 방식은 롤링 업데이트 전략의 maxSurgemaxUnavailable 속성을 통해 구성할 수 있다. 두 속성은 롤링 업데이트 중 한 번에 대체되는 파드의 수에 영향을 미친다.

  • maxSurge : 레플리카 수보다 얼마나 많은 파드 인스턴스 수를 허용할지를 나타낸다. 기본값은 25%이다. 레플리카 수가 4로 설정되면 4의 25%인 1개만큼 더 허용될 수 있다. 즉, 5개의 파드까지 업데이트 중에 동시에 실행된다.
  • maxUnavailable : 업데이트 중 레플리카 수를 기준으로 사용할 수 없는 파드 인스턴스 수를 나타낸다. 기본값은 25%이다. 레플리카 수가 4인 경우 백분율이 25%이면 하나의 파드만 사용할 수 없다. 즉, 업데이트 과정에서 사용할 수 없는 파드는 최대 1개여야 하며 최소 3개의 파드는 항상 가용한 상태를 유지하면서 롤링 업데이트가 수행되어야 한다는 의미이다.

** extensions/v1beta1 버전은 두 속성의 기본값을 25% 대신 1로 설정한다.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 3
  strategy:
    rollingUpdate:
      maxSurge: 1                  
      maxUnavailable: 0
    type: RollingUpdate

레플리카 수가 3이고 maxSurge가 1이고 maxUnavailable을 0으로 설정했다.


3개의 복제본, 기본 maxSurge, maxUnavailable 사용한 디플로이먼트의 롤링 업데이트 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

maxSurge가 1이기 때문에 모든 파드 수는 4개가 되는 것까지 허용했으며, maxUnavailable은 0이기 때문에 available한 파드는 레플리카 수와 동일한 3개여야 한다. 항상 3개의 파드를 사용할 수 있어야 한다.


위 예제에서 maxUnavailable만 1로 수정해보자.


maxSurge=1, maxUnavailable=1과 함께 디플로이먼트의 롤링 업데이트 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

maxUnavailable가 1로 설정되어 1개의 복제본을 사용할 수 없기 때문에 원하는 레플리카 수가 3개인 경우 2개만 사용할 수 있어야 한다. 롤아웃 프로세스는 즉시 하나의 파드를 삭제하고 2개의 파드를 새로 만든다. 2개의 파드를 사용할 수 있게 되면 나머지 2개의 오래된 파드는 삭제된다.

롤아웃 프로세스 일시 중지

롤아웃 프로세스 중에 일시 중지해서 나머지 롤아웃을 진행하기 전에 새 버전이 제대로 작동하는지 확인할 수 있다.

# 이미지 변경
$ kubectl set image deploy kubia nodejs=luksa/kubia:v4

deployment.apps/kubia image updated

# 롤아웃 일시 정지
$ kubectl rollout pause deploy kubia

deployment.apps/kubia paused

# 파드 확인
$ kubectl get po

NAME                    READY   STATUS    RESTARTS        AGE
kubia-6459db4dc-5x4tm   1/1     Running   0               28m
kubia-6459db4dc-9476w   1/1     Running   0               27m
kubia-6459db4dc-z297h   1/1     Running   0               27m
kubia-f797964fb-pqh6z   1/1     Running   0               26s

파드 목록을 보면 v4로 구동된 파드 (kubia-f797964fb-pqh6z) 1개가 추가로 구동 중인것을 확인할 수 있다. 새 파드가 가동되면 서비스에 대한 모든 요청의 일부가 새 파드로 리다이렉션된다.

🔎 카나리 배포
롤아웃 프로세스를 일시정지해서 일종의 카나리 배포를 실행할 수 있다.


카나리 배포 (출처: rollbar.com/blog/deployment-strategies/)

카나리 배포는 잘못된 버전의 어플리케이션을 롤아웃하고 모든 사용자에게 영향을 미칠 위험을 최소화하기 위한 기술이다. 새 버전을 모든 사람에게 롤아웃하는 대신, 한 개 또는 소수의 이전 포드를 새 파드로 바꾼다. 이렇게 하면 소수의 사용자만 초기에 새 버전을 사용하게 된다. 그런 다음 새 버전이 제대로 작동하는지 확인한 후 나머지 모든 파드를 통해 롤아웃을 계속하거나 이전 버전으로 롤백할 수 있다.

롤아웃 재개

롤아웃 프로세스를 일시 중지하면 클라이언트 요청 중 일부만 v4 파드로 전달되고 대부분은 여전히 v3 파드로 전달된다. 새 버전이 제대로 작동하면 디플로이먼트를 다시 시작해 모든 이전 파드를 새 파드로 교체할 수 있다.

# 롤아웃 재개
$ kubectl rollout resume deploy kubia

deployment.apps/kubia resumed

# 파드 확인
$ kubectl get po

NAME                    READY   STATUS        RESTARTS        AGE
kubia-f797964fb-hfd29   1/1     Running       0               24s
kubia-f797964fb-pqh6z   1/1     Running       0               7m11s
kubia-f797964fb-sw78t   1/1     Running       0               35s

잘못된 버전의 롤아웃 방지

minReadySeconds 속성으로 롤아웃 속도를 늦춰 롤링 업데이트 과정을 직접 볼 수 있는데, 이 속성의 주요 기능은 오작동하는 버전의 배포를 방지하는 것이다.

minReadySeconds는 파드를 사용 가능한 것으로 취급하기 전에 새로 만든 파드를 준비할 시간을 지정하는 속성이다. 파드를 사용할 수 있게 될 때까지는 롤아웃 프로세스가 진행되지 않는다.

이것과 레디니스 프로브를 함께 이용하여 오작동 버전의 롤아웃을 효과적으로 차단할 수 있다. 모든 파드의 레디니스 프로브가 성공하면 파드가 준비상태가 되는데, minReadySeconds 에 설정된 시간이 지나가기 전에 레디니스 프로브가 실패하기 시작하면 새 버전의 롤아웃은 차단이 된다.

🧩 적절한 minReadySeconds와 레디니스 프로브 정의

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 3
  minReadySeconds: 10  # minReadySeconds를 10초로 설정
  selector:
    matchLabels:
      app: kubia
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0  # 파드를 하나씩만 교체하도록 설정
    type: RollingUpdate
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
      - image: luksa/kubia:v3
        name: nodejs
        readinessProbe:  # 레디네스 프로브 정의
          periodSeconds: 1  # 매초마다 레디네스 프로브 수행
          httpGet:  # 컨테이너에 HTTP GET 요청 수행
            path: /
            port: 8080
# 디플로이먼트 업데이트 
$ kubectl apply -f kubia-deployment-v3-with-readinesscheck.yaml

deployment.apps/kubia configured
# 롤아웃 상태 확인
$ kubectl rollout status deploy kubia

Waiting for deployment "kubia" rollout to finish: 1 out of 3 new replicas have been updated...
error: deployment "kubia" exceeded its progress deadline
# 파드 확인
$ kubectl get po
NAME                     READY   STATUS    RESTARTS        AGE
kubia-695dd8898f-cxhjl   0/1     Running   0               118s
kubia-f797964fb-hfd29    1/1     Running   0               15m
kubia-f797964fb-pqh6z    1/1     Running   0               22m
kubia-f797964fb-sw78t    1/1     Running   0               15m

하나의 새 레플리카만 시작됐음을 보여주며, 파드는 준비되지 않은 것으로 표시된다.


새로운 파드에서 레디네스 프로브가 실패함으로써 차단된 디플로이먼트 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/9)

새 파드가 시작되자마자 레디네스프로브가 매초 시작된다. 어플리케이션에서 5번째 요청 이후에 HTTP 상태 코드 500을 반환했기 시작했기 때문에 5번째 요청에서 어플리케이션 준비가 실패했다. 파드는 결과적으로 서비스로의 엔드포인트가 제거되어 파드는 준비되지 않은 것으로 표시된다.

잘못된 버전은 레디니스 프로브 단계에서 차단되어 파드가 생성되지 않는다. 사용 가능한 것으로 간주되려면 10초 이상 준비돼 있어야 하기 때문에 해당 파드가 사용 가능할 때까지 롤아웃 프로세스는 새 파드를 만들지 않는다. 또한 maxUnavailable 속성이 0으로 설정되었기 때문에 원래 파드도 제거되지 않는다.

🧩 롤아웃 데드라인 설정

기본적으로 롤아웃이 10분 동안 진행되지 않으면 실패한 것으로 간주된다. 이 값은 스펙의 progressDeadlineSeconds 속성을 통해 설정할 수 있다. progressDeadlineSeconds에 지정된 시간이 초과되면 롤아웃이 자동으로 중단된다.

# 디플로이먼트 정보 확인
$ kubectl describe deploy kubia

Name:                   kubia
...
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    False   ProgressDeadlineExceeded

Progressing의 Status 항목이 False로 실패한 것을 확인할 수 있다. Reason에도 배포해야 할 기준 시간이 지나서 실패했다는 것을 볼 수 있다.

잘못된 롤아웃은 중지하도록 한다.

# 롤아웃 중지
$ kubectl rollout undo deployment kubia

deployment.apps/kubia rolled back

디플로이먼트를 이용해 앱을 배포할 때 롤링 업데이트하거나, 앱 배포 도중 잠시 멈췄다가 다시 배포할 수 있다. 또한 앱 배포 후 이전 버전으로 롤백할 수 있다.


References

0개의 댓글