IT/Kubernetes

[Kubernetes] Kubernetes Study: "컨트롤러 - Deployment (feat. Canary)"

wookiist 2021. 7. 11. 18:50

Prologue

본 포스트는 인프런 쿠버네티스 스터디 그룹에서 진행하는 스터디 자료의 일환으로 작성하였습니다. 기본적으론 [대세는 쿠버네티스] 강의를 보고 내용을 정리합니다. 그리고 제 경험이나 이해를 곁들여 포스트를 작성했습니다. 그동안 공식 문서나 블로그 포스트, CKA 강의 등 다양한 경로로 쿠버네티스를 익혀왔지만, 한국어로 잘 정리된 강좌를 한번 듣고 깔끔하게 다듬어보는 시간을 가지면 좋겠다는 생각이 들었습니다.

강의와는 조금 다를 수 있지만, 쿠버네티스 공식 문서를 보고 내용을 정리해보겠습니다.

이번 포스트에선 지난 시간에 소개해드린 ReplicaSet에 이어 "컨트롤러 - Deployment"를 다뤄봅니다.

Deployment

디플로이먼트는 지난 시간에 소개드린 ReplicaSet을 추상화한 컨트롤러입니다. 디플로이먼트를 생성하게 되면 해당 컨트롤러 매니페스트에 명시된 Selector, Replicas, Template 내용을 바탕으로 ReplicaSet을 생성하게 됩니다. 즉, 파드를 실제로 생성하는 것은 Deployment가 생성한 ReplicaSet인 것이죠. 그렇다면 왜 이런 구조를 가지게 되었을까요? 강의 내용을 정리해보면서 알아보겠습니다.

강의 내용

ReCreate

ReCreate 방식은 모든 파드를 동시에 내리고, 한 번에 업그레이드를 진행하는 업그레이드 방식입니다. 이 방식의 단점은 모든 파드가 동시에 내려가고, 동시에 업그레이드가 이루어지기 때문에 서비스의 다운 타임이 발생한다는 점입니다.

Rolling Update

Rolling Update 방식으로 업그레이드를 수행하면, 디플로이먼트는 우선 업그레이드된 버전의 파드를 하나 생성합니다. 물론 이렇게 되면 기존의 자원 사용량보다 더 많은 자원을 사용하게 됩니다. 파드가 하나 더 배포된 상태이기 때문입니다. 그리고 이 상황에서 사용자는, 어떤 경우에는 이전 버전의 서비스를 제공받을 수도 있고, 때로는 최신 버전의 서비스를 제공받을 수도 있습니다. 이전 버전과 최신 버전의 파드가 공존하고 있기 때문입니다.

이 상황에서 디플로이먼트는 최신 버전의 파드가 하나 생성되었으면, 이전 버전의 파드를 하나 삭제합니다. 마찬가지로 이전 버전의 파드가 더 있는 경우, 같은 방식으로 최신 버전의 파드를 하나 생성하고, 그 이후에 이전 버전의 파드를 지우는 작업을 반복합니다. 이 방식의 장점은 ReCreate 방식과는 달리 서비스의 다운 타임이 없다는 점입니다.

Blue / Green

Blue/Green 업그레이드 방식은 디플로이먼트에 한정되는 방식은 아닙니다. 이전 포스트에서 다뤘던 ReplicaSet을 통해서도 이용할 수 있는 방식입니다.

컨트롤러를 이용해 파드를 생성하면, 컨트롤러에 적힌 레이블을 토대로 파드에도 레이블이 붙게 됩니다. 그리고 이 파드와 연결하는 서비스도 이 레이블을 갖게 되는데요. 이렇게 운영되는 상태에서 이전 컨트롤러와는 별도로 새로운 컨트롤러를 만듭니다. 이 컨트롤러는 새로운 버전임을 표시하는 레이블이 붙어있는 상태입니다. 이전 Rolling Update 방식에 비해 자원 사용량이 좀 더 많아진다는 점이 단점입니다. 이전 컨트롤러에 속한 파드의 개수만큼 새로운 컨트롤러에 속한 파드도 생성되기 때문입니다.

이런 상황에서 서비스의 레이블을 새로운 버전의 컨트롤러 레이블로 변경해주면 서비스에 연결되어 있던 기존 파드와의 연결 관계는 끊어지고 새로운 버전의 파드와의 연결이 이루어집니다.

이 방식을 이용하면 이전 버전의 컨트롤러를 지우지 않았기 때문에, 새로운 버전에서 문제가 발생했을 때, 서비스의 레이블을 이전 버전으로 바꿔주면, 다시 이전 상태의 서비스를 제공할 수 있다는 장점이 있습니다.

Canary 🐤

카나리아라는 새는 1초에 17번 정도 심장이 뛴다고 합니다. 이 새는 공기 중에 오염 물질이 있으면 금방 죽게 되는데, 이런 이유로 예전에 광산에 들어갈 때 이 광산에서 일산화탄소 등의 유해 물질이 공기 중에 있는지 확인하기 위해 데려갔다고 합니다. 카나리 배포는 유래가 된 카나리아 새처럼, 실험체를 통해서 위험을 검증하고, 위험이 없다는 것이 확인되면 정식으로 배포를 하는 방식입니다.

강의중에는 카나리 배포의 두 가지 소개가 이루어졌는데요. 첫 번째는 불특정 다수의 파드를 검증할 때 사용하는 방식입니다. 우선 이전 Blue/Green 방식처럼 이전 버전과 최신 버전의 컨트롤러 두 대를 모두 운용합니다. 다만 다른 점은, 서비스에 적힌 레이블이 버전이 아니라는 점입니다. 대신 이 서비스가 가리켜야 할 애플리케이션을 레이블링하여, 최신 버전과 이전 버전 모두 서비스가 연결되어 있도록 만듭니다. 처음에는 최신 컨트롤러의 replicas를 낮게 잡고, 시간이 지날 수록 점점 늘려가는 방식입니다. 종국에는 구 버전의 컨트롤러 replicas가 0이 되고, 최신 버전의 컨트롤러 replicas가 정상 개수만큼 배포되는 방식이죠.

또 다른 방식의 카나리 배포는 이렇습니다. 서비스가 컨트롤러마다 각각 연결되는 방식입니다. 그리고 이렇게 각각 만들어진 서비스를 Ingress Controller로 묶어줍니다. 유입되는 트래픽을 URL Path에 따라서 서비스에 연결해주는 역할을 수행하는 Ingress Controller는 /app 으로 오면 이전 버전의 서비스로, /v2/app 으로 오면 최신 버전의 서비스로 연결해주는 작업을 수행할 수 있습니다. 강의 중에 예를 들어주신 걸 그대로 가져오자면, 글로벌한 서비스를 운용할 때 새로 배포될 서비스는 특정 지역을 대상으로 테스트를 진행하기로 결정할 수 있습니다. 특정 지역이 미국이라면, 미국에서 접속해오는 트래픽에는 en을 URL에 붙이도록 강제합니다. 그러면 Ingress Controller가 미국에서 접근하는 트래픽만 v2 서비스로 연결을 해줍니다. 이렇게 특정 타켓을 정해놓고 테스팅을 수행할 수도 있습니다. 이렇게 해서 테스팅 기간이 종료되고 문제가 없음이 확실시 되면, 최신 버전의 컨트롤러 replicas를 증가시키고, Ingress Controller의 설정을 변경해 기존의 이전 버전 서비스로 연결해주던 /app URL Path를 최신 버전의 서비스로 연결하도록 수정해주기만 하면 됩니다. 이렇게 하면 역시 서비스의 다운 타임이 발생하지 않고, 자원 사용량도 테스트를 할 파드의 수 정도만큼만 증가하게 됩니다.

카나리 배포는 추후 Ingress Controller 강좌에서 다뤄질 예정이라고 하니, 그 때 좀 더 다뤄보도록 하겠습니다.

Deployment: ReCreate

파드 업그레이드 방식이 ReCreate 방식인 디플로이먼트는 우선 생성하면, Selector, Replicas, Template을 바탕으로 ReplicaSet을 생성합니다. 그러면 ReplicaSet은 다시 파드를 생성하겠죠.

이때, 버전을 업그레이드하고자 Template을 업데이트 하게 되면,

  • 기존 ReplicaSet의 Replicas를 0으로 만들어서 기존 파드를 모두 지웁니다. (서비스 다운 타임 발생)
  • 이후 새로운 버전의 Template을 이용해 새로운 ReplicaSet을 생성하는데, 이 때 Replicas는 디플로이먼트의 Replicas와 동일합니다.
  • 여기서 생성된 파드는 자연스럽게 이전 서비스 오브젝트와 연결되고, 여기서부터는 서비스가 다시 정상적으로 동작하게 됩니다.

그럼 ReCreate 방식으로 업그레이드를 하는 디플로이먼트를 만들려면 매니페스트를 어떻게 작성해야할지 알아보겠습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-1
spec:
  selector:
    matchLabels:
      type: app
  replicas: 2
  strategy:
    type: Recreate
  revisionHistoryLimit: 1
  template:
    metadata:
      labels:
        type: app
    spec:
      containers:
        - name: container
          image: 'busybox:1.33'

위 매니페스트 파일에서 spec.strategy.type 을 주목해주세요. 일반적으로 업그레이드 방식을 수정하지 않는 경우, 아래에서 설명드릴 Rolling Update 방식으로 업그레이드를 수행하게 됩니다. 그러나 이렇게 특정 방식을 사용하고자 하는 경우 업그레이드 전략 방식을 명시적으로 나타내주어야 합니다.

또한 spec.revisionHistoryLimit은 업그레이드를 한 이후에도 기존 버전의 ReplicaSet을 지우고 싶지 않은 경우에 설정하게 됩니다. 여기서는 1로 설정했으므로, 1개까지의 이전 ReplicaSet이 남아있게 되는 것이죠. 이 값은 기본적으로 10으로 설정되어 있습니다.

Deployment: Rolling Update (default)

Rolling Update 방식은 앞선 내용에서 소개해드린 것처럼, 새로운 버전의 파드를 하나 생성하고, 이전 파드를 하나 지우고, 또 새로운 파드 생성, 이전 파드 삭제. 이 과정을 반복하는 업그레이드 방식입니다. 디플로이먼트는 이러한 방식을 구현하기 위해 최초에 Replicas가 1인 새로운 버전의 ReplicaSet을 생성합니다. 이 상황에선 서비스가 구버전과 신버전에 모두 연결되어 있기 때문에 서비스가 분산되어 전달됩니다. 이 다음에 구버전의 ReplicaSet의 Replicas를 하나 낮춥니다. 그러면 이전 버전의 파드가 하나 줄어들겠죠. 마찬가지로 신버전의 ReplicaSet을 하나 더 증가시키고, 이전 버전의 Replicas를 하나 낮춥니다. 이런 방식으로 디플로이먼트를 Rolling Update 할 수 있습니다.

이번에도 매니페스트 파일을 보도록 하겠습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-2
spec:
  selector:
    matchLabels:
      type: app
  replicas: 2
  minReadySeconds: 10
  template:
    metadata:
      labels:
        type: app
    spec:
      containers:
        - name: container
          image: 'busybox:1.33'

이번 디플로이먼트 매니페스트에는 spec.strategy 필드가 없음에 주목해주세요. 디플로이먼트의 기본 업그레이드 방식은 Rolling Update이기 때문에 명시적으로 나타내주지 않아도 자동으로 이 방식으로 업그레이드가 진행됩니다.

spec.minReadySeconds 필드를 살펴보겠습니다. 이 값이 없으면 구버전과 신버전의 파드가 생성되고 삭제되는 과정이 순식간에 진행됩니다. 이 값을 넣어주게 되면 넣어준 값만큼 파드가 준비 과정을 거친 뒤에 활성화됩니다. 이 값은 후에 강의에서 나올 Readiness Probe와 연관이 있다는 점을 인지해주시기 바랍니다.

추가적으로, template.metadata.labels 를 보면 type: app 으로 파드가 생성되는데요. 디플로이먼트가 Rolling Update를 할 때, 이전 버전의 ReplicaSet도 type: app 이라는 레이블 셀렉터를 가지니, 자연스럽게 이전 버전의 파드가 새로운 ReplicaSet과도 연결될 수도 있지 않았을까 하는 의문이 들 수도 있습니다.

(저는 아무런 생각이 없었는데, 강사님께서 집어주시더군요... 아직 내공이 부족합니다 ㅠㅠ)

이것은 디플로이먼트가 새로운 ReplicaSet을 생성할 때 우리가 적어준 레이블 외에도 추가적인 레이블을 붙여줌으로서 해결하게 됩니다.

참고

마무리

이 포스트에서는 ReplicaSet의 상위 개념인 Deployment를 다뤄보았습니다. Deployment는 기존 ReplicaSet을 이용해 파드를 업그레이드 하거나 다운그레이드 하는 등의 작업을 더 간편하게 수행할 수 있게 도와줍니다. 즉,얼핏 보면 같아 보이는 개념같지만, 실상은 그렇지 않았던 것이죠.

내용이 많이 부실하고, 설명이 어렵게 되어 있는 등의 문제가 있을 것으로 보입니다. 추후 수정을 통해 다듬어보겠습니다.

여기까지 따라오시느라 고생이 많으셨습니다. 만약 이 글이 도움이 되셨다면 글 좌측 하단의 하트❤를 눌러주시면 감사하겠습니다.

혹시라도 글에 이상이 있거나, 이해가 가지 않으시는 부분, 또는 추가적으로 궁금하신 내용이 있다면 주저 마시고 댓글💬을 남겨주세요! 빠른 시간 안에 답변을 드리겠습니다 😊

반응형