카나리 배포

카나리 배포 (Canary Deployment)

이 튜토리얼에서는 쿠버네티스에서 카나리 릴리스를 배포하는 과정을 함께 살펴봐요. 카나리 배포를 사용하면 새 애플리케이션 버전을 전체로 롤아웃하기 전에 소량의 프로덕션 트래픽으로 안전하게 테스트할 수 있습니다. 이 접근 방식은 위험을 최소화하고 실제 사용자로부터 빠른 피드백을 얻도록 도와줘요.

카나리 배포의 개요는 Managing Workloads 개념 페이지의 카나리 배포를 참고하세요.

출처: 문서

본문

목표 (Objectives)

  • 애플리케이션의 안정 버전 배포하기
  • 안정 버전 옆에 카나리 버전 배포하기
  • Service로 두 버전에 모두 트래픽 라우팅하기
  • 카나리 배포 모니터링하기
  • 카나리를 스케일링하고 안정 버전을 제거해 롤아웃 완료하기

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 명령줄 도구가 클러스터와 통신하도록 설정돼 있어야 해요.

카나리 배포 이해하기

카나리 배포는 기존 버전 옆에 애플리케이션의 새 버전을 배포하는 전략이에요. 새 버전(카나리)은 소량의 트래픽을 받아 다음을 할 수 있게 해줍니다:

  • 실제 프로덕션 트래픽으로 새 버전 테스트하기
  • 오류, 성능 문제, 예상치 못한 동작 모니터링하기
  • 문제가 감지되면 빠르게 롤백하기
  • 성능이 좋으면 새 버전으로 트래픽을 점진적으로 늘리기

이 튜토리얼에서는 track 라벨을 사용해 안정 버전과 카나리 릴리스를 구분해요. 안정 릴리스는 track: stable을, 카나리 릴리스는 track: canary를 사용합니다. 두 배포는 Service가 두 파드 집합 모두에 트래픽을 라우팅할 수 있게 하는 공통 라벨(app.kubernetes.io/name: rollout-demo)을 공유합니다.

안정 버전을 3개의 레플리카로, 카나리 버전을 1개의 레플리카로 배포할 거예요. 두 버전이 같은 Service 선택자(app.kubernetes.io/name: rollout-demo)를 공유하므로, 쿠버네티스는 4개 파드 전체에 트래픽을 로드밸런싱합니다. 이 비율로 약 25%의 트래픽이 카나리로, 75%가 안정 버전으로 갑니다.

안정 버전 배포하기

먼저 애플리케이션의 안정 버전을 배포해요:

안정 Deployment를 적용해요:

kubectl apply -f https://k8s.io/examples/application/canary/app-v1-deployment.yaml

Deployment가 생성되고 파드가 실행 중인지 확인해요:

kubectl get deployments -l app.kubernetes.io/name=rollout-demo

출력은 다음과 비슷합니다:

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
rollout-demo-stable    3/3     3            3           10s

파드를 확인해요:

kubectl get pods -l app.kubernetes.io/name=rollout-demo

출력은 다음과 비슷합니다:

NAME                                  READY   STATUS    RESTARTS   AGE
rollout-demo-stable-7d4b9b8c5d-abc12   1/1     Running   0          15s
rollout-demo-stable-7d4b9b8c5d-def34   1/1     Running   0          15s
rollout-demo-stable-7d4b9b8c5d-ghi56   1/1     Running   0          15s

서비스 만들기

애플리케이션을 노출할 Service를 만들어요. Service 선택자는 공통 라벨(app.kubernetes.io/name: rollout-demo)을 사용하고 track 라벨을 생략하므로, 안정 파드와 카나리 파드 모두에 트래픽을 라우팅할 수 있습니다:

Service를 적용해요:

kubectl apply -f https://k8s.io/examples/application/canary/app-service.yaml

Service가 생성되었는지 확인해요:

kubectl get service rollout-demo-service

출력은 다음과 비슷합니다:

NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
rollout-demo-service   ClusterIP   10.96.123.45    <none>        80/TCP    5s

임시 파드를 만들고 요청을 보내 Service를 테스트해요:

kubectl run curl-test --image=curlimages/curl:latest --rm -it --restart=Never -- curl http://rollout-demo-service

안정 파드의 응답이 보여야 해요. 이 명령을 여러 번 실행해 서로 다른 파드 호스트 이름을 확인하세요. 모든 응답은 버전 v1을 보여줘야 합니다.

이 시점에서 애플리케이션은 안정 상태예요. 실제 상황에서는 보통 여기서 멈추고, 배포할 새 버전이 있을 때까지 안정 버전을 운영할 거예요. 다음 단계는 카나리 버전을 도입하는 방법을 보여줍니다.

카나리 버전 배포하기

카나리 Deployment를 만들려면 안정 Deployment 매니페스트를 복사하고 몇 가지를 바꾸면 돼요:

  • metadata.name을 변경한다(예: rollout-demo-canary)
  • metadata.labelsspec.selector.matchLabels/spec.template.metadata.labels 모두에서 track 라벨을 stable에서 canary로 변경한다
  • 더 낮은 레플리카 수로 설정한다(예: 1)
  • 컨테이너 이미지를 새 버전으로 업데이트한다

업데이트된 매니페스트는 다음과 같아야 해요:

kubectl으로 로컬 매니페스트를 적용할 수 있고, 원한다면 이 체크된 예시를 사용할 수도 있어요:

kubectl apply -f https://k8s.io/examples/application/canary/app-v2-deployment.yaml

두 Deployment가 실행 중인지 확인해요:

kubectl get deployments -l app.kubernetes.io/name=rollout-demo

출력은 다음과 비슷합니다:

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
rollout-demo-stable    3/3     3            3           2m
rollout-demo-canary    1/1     1            1           10s

모든 파드를 확인해요:

kubectl get pods -l app.kubernetes.io/name=rollout-demo -o wide

출력은 다음과 비슷합니다:

NAME                                  READY   STATUS    RESTARTS   AGE   IP           NODE
rollout-demo-stable-7d4b9b8c5d-abc12   1/1     Running   0          2m    10.244.1.5   node1
rollout-demo-stable-7d4b9b8c5d-def34   1/1     Running   0          2m    10.244.1.6   node1
rollout-demo-stable-7d4b9b8c5d-ghi56   1/1     Running   0          2m    10.244.2.7   node2
rollout-demo-canary-8e5c0d9f6a-xyz78   1/1     Running   0          15s   10.244.2.8   node2

이제 총 4개의 파드(안정 파드 3개, 카나리 파드 1개)가 있음을 주목하세요.

트래픽 분산 테스트하기

두 Deployment가 같은 Service 선택자(app.kubernetes.io/name: rollout-demo)를 사용하므로 Service는 모든 파드에 트래픽을 라우팅해요. 안정 파드 3개와 카나리 파드 1개로 약 25%의 요청이 카나리로 갑니다.

트래픽 분산을 보려면 Service를 여러 번 테스트해요:

kubectl run curl-test --image=docker.io/library/curlimages/curl:latest --rm -it --restart=Never -- \
  sh -c 'for i in $(seq 1 10); do curl -s http://rollout-demo-service; echo; done'

안정 버전과 카나리 버전 모두의 응답이 보여야 해요. 비율은 달라질 수 있지만, 안정 응답에 카나리 응답이 섞여 보여야 합니다.

두 버전 모두 트래픽을 받고 있는지 Service EndpointSlices를 확인해요:

kubectl get endpointslices -l kubernetes.io/service-name=rollout-demo-service

출력은 다음과 비슷합니다(주소 패밀리당 EndpointSlice 하나, ENDPOINTS 열은 기본적으로 잘림):

NAME                          ADDRESSTYPE   PORTS   ENDPOINTS                              AGE
rollout-demo-service-abc12    IPv4          8080    10.244.1.5,10.244.1.6,10.244.2.7 + 1 more...   3m

모든 엔드포인트 주소를 보려면 -o yaml을 사용하세요.

카나리 배포 모니터링하기

카나리 배포에서 오류, 성능 문제, 예상치 못한 동작이 없는지 모니터링해요:

카나리의 파드 로그를 확인해요:

kubectl logs -l app.kubernetes.io/name=rollout-demo,track=canary --tail=50

파드 상태를 모니터링해요:

kubectl get pods -l app.kubernetes.io/name=rollout-demo -w

계속 보려면 Ctrl+C를 눌러 중지하세요.

리소스 사용량을 확인해요:

kubectl top pods -l app.kubernetes.io/name=rollout-demo

kubectl top 명령은 클러스터에 metrics-server가 설치되어 있어야 해요. 사용할 수 없다면 Prometheus나 클라우드 제공자 모니터링 도구 같은 다른 방법으로 모니터링할 수 있습니다.

트래픽 분산 조정하기

카나리가 잘 동작한다면 스케일업해 점진적으로 트래픽을 늘릴 수 있어요:

카나리를 2개의 레플리카로 스케일링한다(이제 트래픽의 40%):

kubectl scale deployment/rollout-demo-canary --replicas=2

새 파드가 실행 중인지 확인해요:

kubectl get pods -l app.kubernetes.io/name=rollout-demo

모니터링을 계속해요. 모든 것이 좋아 보인다면 카나리를 더 스케일업하고 안정 버전을 스케일다운할 수 있습니다.

롤아웃 완료하기

모니터링으로 카나리에서 문제가 발견됐다면 대신 롤백할 거예요. 문제 시나리오를 연습하려면 카나리 배포 롤백하기로 건너뛸 수 있습니다.

단일 Deployment로 롤아웃을 완료하려면 먼저 안정 Deployment를 다시 스케일업해 새 이미지 가져오기가 실패해도 용량을 잃지 않게 하세요. 그런 다음 이미지와 VERSION 환경 변수를 새 버전에 맞게 업데이트하고, 롤아웃이 완료될 때까지 기다린 뒤 카나리 Deployment를 제거합니다:

kubectl scale deployment/rollout-demo-stable --replicas=3

# Outside of this tutorial, you wouldn't set the
# environment variable $VERSION.
# However, your new application code might need
# other configuration changes after an upgrade.
kubectl set image deployment/rollout-demo-stable rollout-demo=gcr.io/google-samples/hello-app:2.0
kubectl set env deployment/rollout-demo-stable VERSION=v2

# Wait for the rollout to complete
kubectl rollout status deployment/rollout-demo-stable

kubectl delete deployment rollout-demo-canary

이것이 성공했으므로 이제 정리 섹션으로 넘어갈 준비가 되었어요. 바로 다음의 롤백 섹션을 따라갈 필요는 없습니다.

대신 정리를 시작하기 전에 페이지의 나머지를 읽을 수도 있어요. 다룰 주제가 몇 개 더 있습니다.

카나리 배포 롤백하기

카나리 버전에서 문제를 발견하면 빠르게 롤백할 수 있어요:

카나리 배포를 스케일다운한다:

kubectl scale deployment/rollout-demo-canary --replicas=0

0으로 스케일링하면 조사하는 동안 구성을 살펴볼 수 있도록 카나리 Deployment를 보존해요. 조사가 끝나면 kubectl delete deployment rollout-demo-canary로 삭제하세요.

필요하면 안정 버전을 다시 스케일업한다:

kubectl scale deployment/rollout-demo-stable --replicas=3

다시 카나리 배포를 시도하기 전에 카나리의 문제를 조사해요.

HTTPRoute로 트래픽 분할하기 (선택 사항)

Gateway API를 사용 중이라면 HTTPRoute로 안정 버전과 카나리 버전 사이의 트래픽 분할을 더 정밀하게 제어할 수 있어요. 이 접근 방식은 레플리카 수에 의존하지 않고 트래픽 분산의 정확한 비율을 지정할 수 있게 해줍니다.

먼저 안정 버전과 카나리 버전용 별도 Service를 만들어요. 이 접근 방식은 Gateway API를 사용하지 않더라도 디버깅에 유용해요. 별도 Service를 가지면 각 버전을 독립적으로 직접 테스트하거나 모니터링할 수 있습니다.

apiVersion: v1
kind: Service
metadata:
  name: rollout-demo-stable-service
spec:
  selector:
    app.kubernetes.io/name: rollout-demo
    track: stable
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: rollout-demo-canary-service
spec:
  selector:
    app.kubernetes.io/name: rollout-demo
    track: canary
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080

그런 다음 두 Service 사이에 트래픽을 분할하는 HTTPRoute를 만들어요:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: rollout-demo-route
spec:
  parentRefs:
  - name: example-gateway
  hostnames:
  - "rollout-demo.example.com"
  rules:
  - backendRefs:
    - name: rollout-demo-stable-service
      port: 80
      weight: 90
    - name: rollout-demo-canary-service
      port: 80
      weight: 10

이 구성은 레플리카 수와 관계없이 트래픽의 90%를 안정 Service로, 10%를 카나리 Service로 라우팅해요.

헤더 기반 라우팅으로 특정 트래픽을 카나리로 보낼 수도 있어요:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: rollout-demo-route
spec:
  parentRefs:
  - name: example-gateway
  hostnames:
  - "rollout-demo.example.com"
  rules:
  # Rule 1: Route traffic with canary header to canary service
  - matches:
    - headers:
      - type: Exact
        name: env
        value: canary
    backendRefs:
    - name: rollout-demo-canary-service
      port: 80
  # Rule 2: Split remaining traffic 90/10
  - backendRefs:
    - name: rollout-demo-stable-service
      port: 80
      weight: 90
    - name: rollout-demo-canary-service
      port: 80
      weight: 10

이 구성은 env: canary 헤더가 있는 모든 트래픽을 카나리 Service로 보내고, 다른 트래픽은 안정과 카나리 사이에 90/10으로 분할해요.

Gateway API는 클러스터에 Gateway 컨트롤러가 설치되어 있어야 해요. 설치 지침과 지원되는 구현은 Gateway API 문서를 참고하세요.

카나리 롤아웃 자동화하기

프로덕션 환경에서 카나리 롤아웃은 보통 트래픽 이동·모니터링·승격·롤백을 자동화하는 컨트롤러나 CI/CD 시스템이 관리해요. Flux, Argo Rollouts, GitLab 같은 도구와 많은 다른 도구들이 점진적 전달·분석·롤백을 처리할 수 있습니다. 이는 수동 단계를 줄이고 안전하고 반복 가능한 배포를 보장하는 데 도움이 됩니다. 자세한 내용은 선택한 배포 도구나 컨트롤러의 문서를 참고하세요.

정리하기

이 튜토리얼에서 만든 리소스를 삭제해요:

kubectl delete deployment rollout-demo-stable rollout-demo-canary
kubectl delete service rollout-demo-service

HTTPRoute용으로 별도 Service를 만들었다면 그것들도 삭제해요:

kubectl delete service rollout-demo-stable-service rollout-demo-canary-service
kubectl delete httproute rollout-demo-route

더 알아보기 (Learn more)

  • Managing Workloads 개념 페이지의 카나리 배포에 대해 더 알아보기.
  • Deployments와 애플리케이션 라이프사이클을 관리하는 방법 읽어보기.
  • Services와 서비스 디스커버리·로드밸런싱을 활성화하는 방법 읽어보기.
  • 고급 트래픽 관리 기능의 Gateway API 탐색하기.
  • 메트릭 기반으로 레플리카 수를 자동 조정하는 Horizontal Pod Autoscaling 고려하기.