[Kubernetes in Action] Deployments: Updating Applications Declaratively

2021. 12. 19. 18:03DevOps/Docker & Kubernetes

 

 

Updating Applications Running in Pods

 

 

기본적으로 위의 구조와 같이 여러 개의 인스턴스를 통해 서비스되고 있는 애플리케이션을 업데이트하는 방법에는 두 가지가 있습니다. 일단 파드가 생성되고 나면 그 내부의 컨테이너 이미지를 수정할 수 없기 때문에 새로운 버전의 이미지를 서비스하고 싶다면 기존의 파드를 제거한 후에 새로운 버전의 이미지를 포함한 파드를 새로 생성해야 합니다.

 

  • 우선 존재하는 파드를 모두 지운 후에, 새로운 파드들을 시작한다.
  • 새로운 파드들을 시작한 이후에, 이들이 정상적으로 동작하기 시작하면 이전의 파드들을 제거한다.

 

Deleting old pods and replacing them with new ones

ReplicationController를 통해서 파드를 배포한 경우, 해당 ReplicationController의 템플릿을 변경할 수 있습니다. [Replication Controller 관련 글 참조] 해당 템플릿에서 사용하는 파드의 이미지를 새로운 버전으로 변경한 뒤에 적용하면 ReplicationController(혹은 ReplicaSet)이 해당 변경사항을 감지한 이후에(Label Selector를 통해) 기존 버전의 파드를 모두 제거하고, 새로운 파드 템플릿으로부터 새로운 파드들을 생성합니다. 

 

이 경우, 기존 파드를 모두 제거한 뒤에 새로운 파드를 생성하므로 기존 파드가 제거되고 새로운 파드가 준비될 때까지 서비스가 중단되는 시점이 생기게 됩니다. 

 

 

 

Spinning up new pods and then deleting the old ones

애플리케이션이 잠시라도 중단되기를 원하지 않는 경우, 그리고 동일한 버전의 애플리케이션이 동시에 서비스되어도 서비스 제공에 큰 문제가 없는 경우에는 새로운 파드가 완전히 시작되기 전까지 기존의 파드를 제거하지 않도록 할 수 있습니다. 단, 이 경우에는 당연히 두 버전의 파드가 모두 떠 있는 시간이 있으므로 위에서 설명한 방법에 비해서 추가적인 하드웨어 리소스가 필요합니다.

 

Switching from the old to the new version at once

위의 방법이 ReplicationController(or ReplicaSet)의 Template 업데이트를 통해 간단하게 수행되었다면 이 방식은 추가적으로 Kubernetes Service에 대한 고려가 필요합니다. 새로운 버전의 이미지를 포함한 파드의 ReplicationController가 배포되고 나면 Service의 label selector를 새로운 버전의 파드를 가리키도록 업데이트하면, 한 번에 모든 서비스가 새로운 버전의 파드를 가리키게 됩니다. kubectl set selector 커맨드를 사용하면 이를 쉽게 업데이트할 수 있습니다.

 

 

 

Performing a rolling update

쿠버네티스를 사용해서 한 번에 새로운 버전으로 모두 업데이트하는 방법 이외에 Rolling Update (새로운 파드가 생성되는 대로 순차적으로 하나씩 교체하는 방식)를 수행할 수도 있습니다. 이는 이전 버전의 ReplicationController를 scale down 하고 새로운 버전을 scale up 하는 식으로 수행하게 됩니다. 이는 따분한 작업이고 실수할 수 있으므로 자동화하는 것이 좋으며 쿠버네티스는 이를 자동화하는 방법을 제공합니다. (rolling update에 대한 설명이 나오지만 해당 기능은 더 이상 쿠버네티스에서 지원하지 않으므로 이 부분은 생략하고 바로 Deployments에 대해 다음 섹션에서 설명합니다.)

 

 

 

Using Deployments for updating apps declaratively

Deployment는 명시적으로 애플리케이션을 배포하고 업데이트하고 롤백하는 기능을 제공하기 위한 High-Level의 쿠버네티스 리소스입니다. (Higher Level이라는 표현을 사용하는 이유는 내부적으로는 ReplicaSet를 사용하여 한 단계 높은 추상성을 제공하기 때문입니다.) Deployment를 생성하면, 내부적으로 ReplicaSet이 생성되며 실제로 파드를 운영하고 관리하는 책임은 이 ReplicaSet이 가지게 됩니다.

 

 

뒤에서 자세히 살펴보겠지만 파드를 생성하는 것 자체만 놓고 보았을 때는 그냥 ReplicaSet만 사용하는 것이 더 편할 수 있겠지만, 애플리케이션을 업데이트하고 롤백하는 상황들에 있어서는 여러 개의 ReplicaSet이 상호작용해야 할 필요성이 생기게 됩니다. (특정 ReplicaSet이 Scale down 할 때, 다른 ReplicaSet은 Scale up 해야 하는 등의 상황) 이 상황들을 잘 컨트롤하기 위한 주체로서의 Deployment가 존재하는 것이고, Deployment 덕분에 사용자는 원하는 상태에 대한 명시적인 기술만 적어두면, 나머지는 쿠버네티스가 알아서 관리해 준다는 장점이 있습니다. 

 

 

Creating a Deployment

Deployment를 생성하는 것은 ReplicaSet을 생성하는 것과 매우 비슷합니다. 다음과 같이 yaml 파일을 작성하고 kubectl apply 커맨드를 사용해서 Deployment를 생성할 수 있습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: yeoul
spec:
  replicas: 3
  template:
    metadata:
      name: yeoul
      labels:
        app: yeoul
    spec:
      containers:
      - image: luksa/kubia:v1
        name: nodejs
  selector:
    matchLabels:
      app: yeoul

 

Creating the Deployment Resource

kubectl apply -f deployment.yaml --record

--record flag를 사용하면 애플리케이션의 revision history에 대한 정보를 알 수 있습니다. (이후 섹션에서 설명합니다.)

 

 

 

Displaying the Status of the Deployment Rollout

kubectl rollout status deployment yeoul

rollout 커맨드를 사용하면 해당 Deployment가 성공적으로 수행되었는지를 조회할 수 있습니다. 실제로 확인해보면 deployment.yaml 파일에 명시한 것과 같이 3개의 파드가 잘 생성된 것을 확인할 수 있습니다.

 

 

 

Understanding how Deployments create ReplicaSets which then create the pods

Deployment는 그 자체로 직접 파드를 관리하지 않습니다. 앞서 언급했던 것처럼, Deployment는 ReplicaSets를 만들고 파드의 관리는 ReplicaSet이 수행하도록 둡니다. 따라서 아래와 같이 Deployment가 생성되면 ReplicaSet이 생성되고 ReplicaSet의 이름뒤에 임의로 해시된 스트링이 붙어있는 것을 확인할 수 있습니다. (Deployment가 여러 개의 ReplicaSet를 관리해야 하기 때문입니다.)

 

 

 

Updating a Deployment

Deployment를 사용하면 새로운 RC를 생성하고, Scaling을 설정하고 중간에 문제가 생기지는 않았는지 계속해서 rolling-update 명령어를 체크하면서 수동을 확인할 필요가 없어집니다. 그저 쿠버네티스가 지향하는 "원하는 상태 선언"을 YAML파일을 통해 해 주면 알아서 RS도 생성하고, 스케일링도 하고 새로운 파드도 생성하고 업데이트도 해주게 됩니다.

 

 

Deployment Strategies

Deployment를 사용한 배포 전략에는 크게 2가지가 있습니다. RollingUpdate(Default)방식과 Recreate 방식인데 Recreate방식은 위에서 살펴본 것처럼 두 가지 이상의 버전이 동시에 서비스되면 안 되는 경우에 사용하는 방식으로, 어쩔 수 없이 서비스가 잠시 중단되게 됩니다. (기존 파드를 모두 제거한 뒤에 새로운 파드를 생성하는 방식이기 때문입니다.) RollingUpdate방식은 하나의 파드를 제거하고 새로운 버전의 파드를 하나씩 추가하는 식으로 업데이트가 진행됩니다. 따라서 동시에 여러 버전의 파드가 떠있을 수 있게 되지만, 서비스가 중단되지 않고 계속 운영됩니다.

 

 

Slowing Down the Rolling Update for Demo Purposes

RollingUpdate를 테스트하기 위해 Update Process를 확인할 수 있도록 minReadySeconds 필드를 Deployment에 추가합니다. 

minReadySeconds는 쿠버네티스가 해당 파드가 available 한 상태인지를 판단하기 위한 최소한의 Ready 시간입니다. 이 시간 동안 파드 내의 모든 컨테이너가 살아있어야 해당 파드가 available 한 상태로 간주합니다.

yaml파일을 업데이트해도 되지만 하나의 필드만 업데이트하는 것이므로 kubectl patch 명령어를 사용해도 됩니다.

 

kubectl patch deployment yeoul -p '{"spec": {"minReadySeconds": 10}}'

 

 

Triggering The Rolling Update

minReadySeconds를 업데이트하였으므로 이제 Rolling Update를 직접 실행해보겠습니다. 기존 deployment.yaml의 컨테이너 이미지를 v2로 변경해보겠습니다. 

kubectl set image deployment yeoul nodejs=luksa/kubia:v2

 

 

위와 같이 하나씩 파드가 Update되면서 새로운 RS의 파드로 옮겨가고 있는 것을 확인할 수 있습니다.

 

Understanding the awesomeness of Deployments

Deployments를 사용하면 사용자는 pod template의 수정만으로 rolling update를 안정적으로 수행할 수 있게 됩니다. 이 프로세스는 kubectl client에서 일어나는 것이 아니라 kubernetes control plane에서 일어난 것입니다. 

 

이 전체 과정은 rolling-update 명령어와 동일합니다. 다만, Deployment에서 업데이트가 일어난 후에, 기존의 ReplicaSet은 지워지지 않고 Scale to zero (파드의 개수 0개)의 상태로 그대로 남아있게 됩니다. 이는 뒤에 나오는 롤백의 처리를 위해서도 사용할 수 있습니다. 해당 ReplicaSet은 사용자가 생성한 것이 아니라 Deployment를 생성하면서 Deployment가 생성한 ReplicaSet이므로 직접적으로 해당 설정을 건드릴 필요는 없습니다. 

 

 

Rolling Back a Deployment

애플리케이션을 운영하다보면 신규 기능이 들어간 새로운 버전의 이미지에 예상하지 못한 버그가 발생해서 해당 이미지를 롤백해야 하는 경우가 종종 생기게 됩니다. 이 경우 Deployments의 rollout 기능을 사용하면 아주 간단하게 해당 애플리케이션을 이전 버전으로 롤백할 수 있습니다. (위에서 신규 버전 배포 시에 이전 ReplicaSet이 지워지지 않았다는 것과, 위의 테스트 deployment생성 시에 커맨드에 추가로 --record flag를 사용해서 revision history를 남겨두었다는 사실을 기억해야 합니다.)

 

Undoing a Rollout

애플리케이션을 롤백하는 것은 Deployment를 사용했다면 간단하게 처리할 수 있습니다.

kubectl rollout undo deployment yeoul

해당 커맨드를 사용하면 바로 이전 버전의 이미지로 롤백할 수 있습니다. 이 undo 커맨드는 애플리케이션이 배포되는 도중에도 사용 가능하며, 배포되는 도중에 해당 커맨드 사용지 신규로 배포되고 있던 버전들이 취소되고 이전 버전의 파드들이 다시 생성되게 됩니다.

 

 

 

Displaying a Deployment's Rollout history

kubectl rollout history deployment yeoul

해당 커맨드를 사용하면 deployment의 rollout history를 확인할 수 있습니다. 이 rollout history를 사용해서 특정 Revision을 선택한 다음, 해당 버전으로 롤백할 수도 있습니다.

kubectl rollout undo deployment yeoul --to-revision=1

 

 

 

 

 

ReplicaSet의 이전 버전의 기록을 무한정 저장하는 것은 비효율적인 일이 될 수 있기 때문에 해당 deployment에서 저장할 수 있는  revision history의 최대 갯수는 revisionHistoryLimit을 통해 명시적으로 설정할 수 있습니다. (defaults to 10)

 

 

Controlling the rate of the rollout

앞선 rollout process에서는 파드가 하나씩 제거되고 하나씩 생성되는 식으로 프로세스가 진행되었습니다. 하지만 maxSurge와 maxUnavailable Property를 사용하면 이를 사용자가 명시적으로 설정할 수 있습니다. 

 

  • maxSurge: Replica Count보다 추가로 존재할 수 있는 pod의 개수의 최댓값 입니다. (defaults to 25%). 소수점은 올림
  • maxUnavailable: Replica Count중 동작이 중지된 pod의 개수의 최댓값입니다. (defaults to 25%). 소수점은 버림

두 값 모두 백분율로 default가 설정되어 있지만 정수로도 설정할 수 있습니다.

 

즉 실제로 하나씩 생성하고 하나씩 제거하는 방식으로 동작하는 것이 아니며, 예제에서 생성한 파드의 개수(3개)에 각각 defaults 값을 설정했기 때문에 다음과 같이 동작한 것입니다.

 

  • maxSurge: (3 * 0.25 = 0.75 = 1(소수점 올림)) 따라서 최대 한개의 파드를 추가로 생성 가능
  • maxUnavailable: (3* 0.25 = 0.75 = 0(소수점 내림)) 3개의 파드가 항상 동작하고 있어야 하므로 하나가 제대로 생성된 다음에야
  • 기존의 파드를 제거할 수 있었던 것.

 

 

 

 

반응형