쿠버네티스의 기본 Deployment는 롤링 업데이트를 제공하지만, 실무에서 배포를 두려워하게 만드는 문제는 여기서 시작된다. 롤링 업데이트는 새 버전 파드를 순차 교체할 뿐 “새 버전이 정말 건강한가”를 판단하지 못한다. readiness 프로브가 통과하면 트래픽이 흘러가고, 5xx가 치솟거나 지연이 폭증해도 롤아웃은 묵묵히 진행된다. 문제를 알아챘을 땐 이미 전체 사용자에게 나쁜 버전이 퍼진 뒤다.

프로그레시브 딜리버리(progressive delivery)는 이 간극을 메운다. 새 버전을 한 번에 100%로 밀지 않고 트래픽의 5%·20%·50%로 점진적으로 늘리면서 각 단계마다 실제 지표(에러율·지연)를 확인하고, 이상하면 자동으로 되돌린다. Argo Rollouts는 쿠버네티스에서 이 패턴을 구현하는 컨트롤러로, 카나리(canary)와 블루-그린(blue-green) 전략을 선언적으로 다루고 지표 분석을 배포에 내장한다. 이 글에서는 Rollout 설계, 트래픽 분할, 자동 분석과 롤백, 실무 함정까지 정리한다.

왜 Deployment로는 부족한가

기본 Deployment의 롤링 업데이트에서 배포 성공의 기준은 오직 파드가 Ready 상태가 되는가이다. 하지만 파드가 Ready라는 것은 프로세스가 떠서 포트를 열었다는 의미일 뿐, 그 응답이 옳다는 보장은 아니다. 절반의 요청이 500을 뱉어도, 캐시 키가 바뀌어 응답이 5배 느려져도 파드는 여전히 Ready다.

게다가 기본 Deployment는 롤아웃을 일시정지하고 관찰할 지점을 제공하지 않는다. “여기서 멈추고 지표를 본 다음 계속할지 결정한다”는 단계적 게이트가 없다. Argo Rollouts는 Deployment를 대체하는 Rollout CRD를 도입해 이 단계와 게이트를 1급 개념으로 만든다.

# rollout.yaml — Deployment 대신 Rollout 리소스 사용
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: checkout-api
spec:
  replicas: 10
  selector:
    matchLabels: { app: checkout-api }
  template:               # Deployment 와 동일한 파드 스펙
    metadata:
      labels: { app: checkout-api }
    spec:
      containers:
        - name: app
          image: registry.int4.io/checkout-api:1.8.0
          readinessProbe:
            httpGet: { path: /healthz, port: 8080 }
  strategy:
    canary:              # 카나리 전략 — 아래에서 단계 정의
      steps:
        - setWeight: 5   # 우선 5% 트래픽만 새 버전으로
        - pause: {}      # 무기한 대기 (수동 승인)

주의할 점은 하나의 워크로드를 Deployment와 Rollout이 동시에 관리하면 안 된다는 것이다. 마이그레이션 시에는 기존 Deployment를 삭제하거나 workloadRef로 참조하고 replicas를 0으로 내려 Rollout이 스케일을 넘겨받게 한다.

카나리 전략: 단계와 가중치 설계

카나리의 핵심은 steps 배열이다. setWeight로 새 버전에 보낼 트래픽 비율을 정하고 pause로 그 상태를 유지한다. pause에 duration을 주면 그만큼 대기 후 자동으로 다음 단계로 넘어가고, 인자 없이 pause: {}로 두면 사람이 kubectl argo rollouts promote를 실행할 때까지 무기한 멈춘다.

strategy:
  canary:
    steps:
      - setWeight: 5
      - pause: { duration: 5m }    # 5% 유지하며 관찰
      - setWeight: 20
      - pause: { duration: 10m }
      - setWeight: 50
      - pause: { duration: 10m }   # 마지막 100% 는 생략 가능

단계 설계에서 흔한 실수는 초기 가중치를 너무 크게 잡는 것이다. 첫 단계에서 30%를 보내면 나쁜 버전이 이미 사용자 3분의 1을 때린다. 트래픽이 충분하다면 첫 단계는 유의미한 최소 표본을 얻을 만큼만(예: 5%) 잡고 안정적일 때만 키운다. 반대로 트래픽이 적으면 5%로는 요청이 수십 건뿐이라 판단이 무의미하니 관찰 시간을 늘려야 한다. 또한 replicas가 10인데 setWeight를 5로 두면, 정밀 분할을 지원하지 않는 환경에서는 반올림으로 실제 비율이 10%(파드 1개)가 되기도 한다.

트래픽 분할: 서비스 메시와 인그레스 연동

Argo Rollouts 자체는 트래픽을 분할하지 않는다. 정확한 퍼센트 라우팅은 트래픽 라우터인 인그레스 컨트롤러나 서비스 메시에 위임한다. Rollout은 두 개의 서비스(안정 버전용, 카나리용)를 두고 라우터에게 가중치를 지시한다.

# 인그레스 기반 트래픽 분할 예시
strategy:
  canary:
    canaryService: checkout-api-canary   # 카나리 파드로 향하는 Service
    stableService: checkout-api-stable   # 안정 파드로 향하는 Service
    trafficRouting:
      nginx:
        stableIngress: checkout-api      # 복제해 카나리 인그레스 자동 생성
    steps:
      - setWeight: 10
      - pause: { duration: 5m }
      - setWeight: 40

서비스 메시를 쓰면 헤더·경로 기반의 세밀한 카나리도 가능하다. 예컨대 setHeaderRoute로 X-Canary 헤더를 매칭해 내부 QA 트래픽만 항상 카나리로 보내면서, 일반 트래픽은 setWeight로 소수만 흘린다. 참고로 setWeight는 트래픽 비율과 파드 스케일을 함께 조정하지만 setCanaryScale은 파드 수만 별도로 고정한다.

AnalysisTemplate: 지표로 자동 판단하기

프로그레시브 딜리버리의 진짜 가치는 사람이 대시보드를 노려보지 않아도 되는 데 있다. AnalysisTemplate은 배포 중 조회할 지표와 성공/실패 조건을 선언한다. 각 단계에서 분석을 실행해 조건을 벗어나면 롤아웃을 자동으로 중단하고 되돌린다.

# analysis-template.yaml — 프로메테우스 지표로 성공률 검사
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  args:
    - name: service-name        # 실행 시 Rollout 이 주입
  metrics:
    - name: success-rate
      interval: 1m              # 조회 주기
      count: 5                  # 5회 측정
      successCondition: result[0] >= 0.99   # 성공률 99% 이상이어야 통과
      failureLimit: 2           # 실패 2회 누적 시 중단
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            sum(rate(http_requests_total{service="{{args.service-name}}", code!~"5.."}[2m]))
            /
            sum(rate(http_requests_total{service="{{args.service-name}}"}[2m]))

이 템플릿을 Rollout 단계에 연결하면 분석이 백그라운드로 돌며 진행 여부를 게이트한다. failureLimit은 일시적 스파이크로 인한 오판을 막는 완충 장치다. 한 번의 순간적 5xx로 되돌리면 배포가 불안정해지므로, 누적 실패 횟수로 판단하는 것이 안전하다.

strategy:
  canary:
    steps:
      - setWeight: 20
      - pause: { duration: 2m }
      - analysis:               # 이 지점에서 분석 실행
          templates: [{ templateName: success-rate }]
          args:
            - { name: service-name, value: checkout-api-canary }
      - setWeight: 50

successCondition과 failureCondition은 둘 중 하나만 써도 된다. 지연 시간(p95)이나 에러 예산 소모율 같은 여러 지표를 조합할 때는 metrics 배열에 항목을 늘리면 되고, 하나라도 실패하면 전체 분석이 실패한다.

자동 롤백은 어떻게 동작하나

분석이 실패로 판정되면 Argo Rollouts는 즉시 롤아웃을 중단(abort)하고 트래픽 가중치를 0으로 되돌린다. 안정 버전 파드는 그대로 살아 있으므로 사용자는 곧바로 이전 버전으로 복귀한다. 이 롤백은 이미 떠 있는 안정 파드로 트래픽을 되돌리는 것이라 몇 초 안에 끝난다.

# 롤아웃 상태 실시간 관찰 — 단계·가중치·분석 결과가 보인다
kubectl argo rollouts get rollout checkout-api --watch

# 문제를 확인했을 때 즉시 안정 버전으로 강제 롤백
kubectl argo rollouts abort checkout-api

# 이전 안정 리비전으로 완전히 복귀
kubectl argo rollouts undo checkout-api --to-revision=3

주의할 대목은 abort 후의 상태다. GitOps로 관리한다면 같은 이미지 태그가 매니페스트에 남아 있어, 컨트롤러가 다시 롤아웃을 시도하는 롤백 루프에 빠질 수 있다. 나쁜 버전의 태그를 커밋에서 걷어내고 안정 태그로 되돌리는 것까지가 진짜 롤백이다.

블루-그린 전략은 언제 쓰나

카나리가 “트래픽을 점진적으로 옮기는” 방식이라면, 블루-그린은 새 버전 전체를 미리 띄워 놓고 한 번에 전환하는 방식이다. 새 버전(green)을 프로덕션과 동일한 규모로 배포한 뒤 프리뷰 서비스로 검증하고, 문제없으면 액티브 서비스를 green으로 스위치한다. 점진 노출이 없는 대신 전환·롤백이 즉각적이다.

블루-그린은 두 버전을 동시에 풀 스케일로 띄우므로 리소스를 두 배 쓴다. prePromotionAnalysis로 전환 전 프리뷰를 검증하고, scaleDownDelaySeconds로 전환 후에도 구버전을 잠시 유지해 즉시 롤백에 대비한다. 두 버전이 섞이면 정합성 문제가 생기는 서비스(스키마 전환 등)나 순간 전환이 필요한 서비스에 적합하고, 리소스가 부담되면 카나리가 낫다.

실무 함정과 운영 체크리스트

도구를 도입한 뒤 겪는 문제는 대개 설정이 아니라 전제의 어긋남에서 온다.

  • 지표 표본이 부족함: 트래픽이 적은 서비스에서 5% 카나리를 1분만 관찰하면 요청이 몇 건뿐이라 성공률이 노이즈에 지배된다. 관찰 창을 늘리거나 비율을 키워야 한다.
  • 분석 쿼리가 카나리만 겨냥하지 못함: 쿼리가 안정 버전과 카나리를 구분하지 못하면 나쁜 카나리의 에러가 전체 평균에 희석돼 탐지에 실패한다. 버전 라벨로 카나리 트래픽만 필터링해야 한다.
  • 롤백해도 나쁜 상태가 남음: 스키마를 앞서 바꾼 마이그레이션은 앱을 롤백해도 되돌아가지 않는다. 확장-축소(expand-contract) 방식으로 스키마 변경을 코드 배포와 분리해야 안전하다.
  • readiness와 분석의 혼동: 프로브는 “이 파드에 트래픽을 보내도 되는가”를, 분석은 “이 버전을 더 확대해도 되는가”를 본다. 둘을 같게 여기면 분석을 생략하게 된다.

마지막으로 운영에서 반드시 준비할 것은 알림 연동이다. 롤아웃 abort를 사람이 늦게 아는 것은 곤란하다. Argo Rollouts의 알림 컨트롤러로 on-analysis-run-failed 같은 트리거에 훅을 걸어 abort 이벤트를 슬랙 등으로 즉시 전파하고, 나쁜 리비전 확인을 담당자에게 연결해 둔다.

마무리

프로그레시브 딜리버리는 “배포를 무섭지 않게 만드는” 실천이다. 핵심은 소수 트래픽으로 시작해 실제 지표로 검증하고, 나쁘면 사람의 개입 없이 되돌리는 것이다. Argo Rollouts는 이를 Rollout CRD의 카나리·블루-그린 전략과 AnalysisTemplate의 지표 분석으로 선언적으로 구현한다.

다만 도구가 안전을 보장하지는 않는다. 분석 쿼리가 카나리를 정확히 겨냥하는지, 표본이 충분한지, 스키마 변경이 롤백을 가로막지 않는지 같은 전제가 무너지면 자동 롤백은 헛돈다. 카나리 단계와 가중치는 트래픽 규모에 맞춰 설계하고, 리소스 여유와 전환 특성에 따라 카나리와 블루-그린을 고르며, abort 이벤트는 반드시 사람에게 닿게 만든다. 이 세 가지를 갖추면 배포는 특별한 이벤트가 아니라 일상이 된다.

자주 묻는 질문

Q. 트래픽 라우터 없이 Argo Rollouts만으로 카나리가 되나요?
A. 됩니다. 다만 트래픽 분할이 파드 수 비율로 근사되어, replicas가 적으면 실제 비율이 크게 어긋납니다. 파드 4개에서 setWeight 10은 실제로 25%(1개)가 됩니다. 정밀한 분할이 필요하면 인그레스나 서비스 메시 연동이 사실상 필수입니다.

Q. 카나리와 블루-그린 중 기본으로 무엇을 택해야 하나요?
A. 실사용자 지표로 서서히 검증하고 리소스를 아끼려면 카나리가 기본입니다. 두 버전이 섞이면 곤란하거나(스키마 전환 등) 순간 전환·즉각 롤백이 더 중요하고 리소스 두 배를 감당할 수 있다면 블루-그린이 낫습니다.