Argo Rollouts Canary 배포: Prometheus 메트릭이 트래픽 비율을 대신 결정하게 두기
야간에 배포를 올리고 다음 날 아침에 Slack 알림을 확인하는 게 무섭다면, 아마 저와 비슷한 경험을 해보셨을 겁니다. "이번 릴리즈는 아무것도 건드리지 않았는데 왜 5xx가 터지는 거지?" 싶어서 오전 9시부터 롤백하다 보면, 사람이 자는 사이에 이 판단을 자동으로 해줬으면 싶다는 생각이 자연스럽게 납니다.
Argo Rollouts의 Canary 전략과 AnalysisTemplate이 그 아이디어를 실제로 구현해줍니다. 새 버전을 전체 트래픽의 5~10%에만 먼저 노출하고, Prometheus 메트릭 기준을 통과하면 자동으로 다음 단계로 넘어가고, 실패하면 사람이 개입하기도 전에 롤백을 실행합니다. 이 글에서는 그 동작 원리를 뜯어보고, 실제로 작동하는 YAML 예시와 함께 흔한 함정까지 짚어보겠습니다.
대상은 이미 Kubernetes를 운영 중이고, kubectl rollout undo 를 몇 번 이상 써봤으며, "다음번엔 더 똑똑하게 배포하고 싶다"고 생각하는 분들입니다.
기본 Deployment의 한계와 Argo Rollouts가 채우는 틈
기본 롤링 업데이트는 무엇을 못 하는가
쿠버네티스 기본 Deployment의 롤링 업데이트는 잘 만들어진 기능입니다. 하지만 두 가지 근본적인 한계가 있습니다.
첫째, 트래픽 비율과 Pod 비율이 연동됩니다. 새 버전 Pod가 30% 올라오면 트래픽도 대략 30%가 들어갑니다. Replica 수가 적은 서비스라면 1개 Pod가 전체의 33%가 되어, 미세한 트래픽 조절이 불가능합니다.
둘째, 메트릭 기반 판단 로직이 없습니다. 새 Pod가 Ready 상태라면 쿠버네티스 입장에서는 배포 성공입니다. 내부적으로 응답 속도가 두 배가 됐어도, 5xx 에러가 2% 늘어났어도, 상태 체크는 통과합니다.
Argo Rollouts는 Rollout CRD로 Deployment를 대체하면서 이 두 가지 문제를 해결합니다. stable과 canary ReplicaSet을 동시에 운영하고, Ingress 컨트롤러나 서비스 메시와 연동해 트래픽을 Pod 수와 독립적으로 제어합니다.
GitOps 파이프라인에서의 위치
ArgoCD + Argo Rollouts 조합이 GitOps 파이프라인에서 널리 채택되고 있습니다. ArgoCD가 Rollout 매니페스트를 동기화하고, Argo Rollouts가 점진적 배포를 실행하는 구성입니다. 사용 중인 Argo Rollouts 버전과 지원 매트릭스는 공식 GitHub Releases에서 확인하시길 권장드립니다. 트래픽 라우팅 프로바이더(Istio, NGINX, ALB 등)와 메트릭 프로바이더(Prometheus, Datadog 등) 호환 정보가 릴리즈 노트에 정리되어 있습니다.
Rollout 리소스 구조 — steps로 배포 시나리오를 선언하기
기본 Rollout 매니페스트
Deployment를 Rollout으로 전환하는 건 생각보다 간단합니다. apiVersion과 kind를 바꾸고 spec.strategy를 추가하면 됩니다. 단, Deployment와 Rollout을 같은 이름으로 동시에 운영하면 안 됩니다. 기존 Deployment를 삭제하고 Rollout을 생성하는 순서가 중요합니다.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-service
spec:
replicas: 10
selector:
matchLabels:
app: my-service
template:
metadata:
labels:
app: my-service
spec:
containers:
- name: my-service
image: my-service:v2.1.0
strategy:
canary:
canaryService: my-service-canary
stableService: my-service-stable
steps:
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 30
- pause: {duration: 10m}
- setWeight: 60
- analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: my-service
- setWeight: 100steps가 핵심입니다. setWeight는 canary로 보낼 트래픽 비율, pause는 대기 시간, analysis는 AnalysisTemplate 실행을 선언합니다. 이 순서대로 컨트롤러가 자동으로 실행합니다.
트래픽 관리 연동 — NGINX와 Istio
트래픽 가중치 제어 방식은 환경에 따라 다릅니다. NGINX Ingress를 쓴다면 어노테이션 기반으로 동작합니다.
strategy:
canary:
canaryService: my-service-canary
stableService: my-service-stable
trafficRouting:
nginx:
stableIngress: my-service-ingressArgo Rollouts가 배포 단계마다 Ingress의 nginx.ingress.kubernetes.io/canary-weight 어노테이션을 자동으로 업데이트합니다.
Istio를 쓰는 환경이라면 VirtualService의 가중치를 직접 조정합니다. Pod 수 비율로만 트래픽을 나누는 방식보다 Istio로 직접 조정하는 방식이 훨씬 정밀합니다. 예를 들어 Replica가 2개인 서비스에서 5% 트래픽만 canary에 보내고 싶다면, Pod 수 기반으로는 구현이 불가능합니다. Istio VirtualService 기반이 사실상 표준으로 자리 잡은 이유가 여기에 있습니다.
trafficRouting:
istio:
virtualService:
name: my-service-vsvc
routes:
- primary
destinationRule:
name: my-service-destrule
canarySubsetName: canary
stableSubsetName: stableAnalysisTemplate — 메트릭이 배포의 진행을 결정하는 방식
동작 원리
analysis step에 도달하면 Argo Rollouts가 AnalysisRun 리소스를 생성합니다. AnalysisRun은 지정된 interval마다 Prometheus에 PromQL 쿼리를 날리고, 결과가 successCondition을 만족하는지 확인합니다. failureLimit 횟수 이상 실패하면 자동으로 canary 가중치를 0으로 되돌리고 롤백합니다.
에러율 기반 AnalysisTemplate
가장 흔하게 쓰이는 패턴입니다. HTTP 5xx 에러율이 5%를 초과하면 실패로 판단합니다.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{
service="{{args.service-name}}",
status!~"5.."
}[2m])) /
sum(rate(http_requests_total{
service="{{args.service-name}}"
}[2m]))failureLimit: 3이 중요합니다. 일시적인 트래픽 스파이크로 한 번 실패했다고 바로 롤백하면 오탐(false positive)이 너무 많아집니다. failureLimit은 successCondition을 만족하지 못한 측정 횟수의 상한이므로, 3회까지는 실패해도 허용되고 4회째부터 롤백이 트리거됩니다. 참고로 consecutiveErrorLimit이라는 별도 필드가 있는데, 이건 successCondition 판정과는 관계없이 PromQL 쿼리 자체가 실패하는 provider 오류(네트워크 타임아웃, 쿼리 파싱 실패 등)의 연속 상한입니다. 두 필드는 역할이 다르니 헷갈리지 않도록 조심하시길 바랍니다.
P99 레이턴시 기반 분석
성능 회귀를 배포 초기에 차단하고 싶다면 레이턴시 기반 조건을 추가할 수 있습니다.
metrics:
- name: latency-p99
interval: 1m
successCondition: result[0] < 0.3
failureLimit: 3
provider:
prometheus:
address: http://prometheus:9090
query: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{
service="my-service"
}[5m])) by (le)
)successCondition: result[0] < 0.3은 P99 레이턴시가 300ms 미만일 때 성공으로 판단한다는 뜻입니다. 이 기준을 통과하지 못하면 60%까지 트래픽을 올리지 않고 멈춥니다.
하나의 AnalysisTemplate에 여러 메트릭을 선언할 수도 있습니다. 에러율과 레이턴시를 동시에 모니터링하고, 둘 다 통과해야 다음 단계로 진행하도록 구성하는 게 일반적입니다.
전체 배포 흐름 시각화
실전 시나리오별 구성 패턴
야간 무인 배포 — 에러율 기반 자동 롤백
SRE 팀에서 가장 먼저 도입하는 패턴입니다. 사람이 모니터링하지 않는 시간대에 배포를 올리면서도 품질을 보장할 수 있습니다.
strategy:
canary:
steps:
- setWeight: 5
- pause: {duration: 10m}
- analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: my-service
- setWeight: 25
- pause: {duration: 15m}
- analysis:
templates:
- templateName: success-rate
- templateName: latency-p99
args:
- name: service-name
value: my-service
- setWeight: 100각 weight 단계 이후 analysis를 반복 배치하는 것이 포인트입니다. 처음 5% 단계에서는 에러율만 보고, 25% 단계에서는 에러율과 레이턴시를 함께 검증합니다.
헤더 기반 라우팅 — 내부 QA 팀 선노출
특정 HTTP 헤더를 가진 요청만 canary로 보내는 패턴은 전체 트래픽을 건드리지 않고 내부 테스터 그룹에게만 신규 버전을 먼저 노출할 수 있어 유용합니다. Istio 기반이라면 VirtualService에 두 개의 라우트(헤더 매칭 라우트와 기본 라우트)를 정의하고, Rollout 스펙에서 두 라우트 이름을 모두 참조합니다.
먼저 VirtualService에서 X-Canary: true 헤더가 있는 요청은 canary subset으로, 나머지는 가중치 기반으로 나눕니다.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-service-vsvc
spec:
hosts:
- my-service
http:
- name: qa-internal
match:
- headers:
X-Canary:
exact: 'true'
route:
- destination:
host: my-service
subset: canary
weight: 100
- destination:
host: my-service
subset: stable
weight: 0
- name: primary
route:
- destination:
host: my-service
subset: stable
weight: 100
- destination:
host: my-service
subset: canary
weight: 0이제 Rollout에서는 routes 배열에 두 라우트를 모두 지정해, Argo Rollouts가 배포 진행 시 양쪽 라우트의 가중치를 함께 조정하도록 합니다.
trafficRouting:
istio:
virtualService:
name: my-service-vsvc
routes:
- primary
- qa-internal
destinationRule:
name: my-service-destrule
canarySubsetName: canary
stableSubsetName: stableprimary 라우트는 가중치 기반 canary를 담당하고, qa-internal 라우트는 헤더가 매칭될 때 항상 canary subset으로 직행합니다. QA 팀이 개별적으로 새 버전을 검증하는 동안 일반 사용자에겐 안정적인 stable이 서빙됩니다.
kubectl 플러그인으로 실시간 상태 확인
kubectl argo rollouts get rollout my-service --watch
kubectl argo rollouts promote my-service
kubectl argo rollouts abort my-service
kubectl argo rollouts undo my-serviceget rollout --watch 명령이 실시간으로 canary 가중치, AnalysisRun 상태, 각 단계 진행 여부를 보여줍니다. 처음 설정을 확인할 때는 이 명령을 열어두고 단계가 진행되는 걸 보면 직관적으로 이해가 됩니다.
트레이드오프와 흔한 실수
얻는 것
| 항목 | 내용 |
|---|---|
| 리스크 최소화 | 전체 사용자 대신 일부에게 먼저 노출해 프로덕션 버그의 영향 반경을 제한 |
| 완전한 자동화 | 메트릭 기반 판단으로 사람 개입 없이 프로모션과 롤백 결정 |
| 선언적 관리 | 모든 배포 정책이 YAML로 Git에 저장되고 ArgoCD와 자연스럽게 연동 |
| 풍부한 메트릭 소스 | Prometheus, Datadog, New Relic, CloudWatch, 커스텀 웹훅까지 지원 |
감수해야 할 것
| 항목 | 내용 |
|---|---|
| 초기 설정 복잡도 | 컨트롤러 설치, 스크레이핑 설정, Rollout 전환, AnalysisTemplate 작성, 트래픽 관리 연동을 모두 준비해야 함 |
| 저트래픽 서비스의 불안정성 | 샘플이 적으면 P99 레이턴시나 성공률 계산이 부정확해 오탐이 발생 가능 |
| Deployment 마이그레이션 비용 | 기존 Deployment를 Rollout으로 전환해야 하며 동일 이름 동시 운영 불가 |
| 배포 시간 증가 | pause와 analysis 단계로 인해 단일 배포 소요 시간이 기본 롤링 업데이트보다 길어짐 |
실무에서 자주 마주치는 실수
failureLimit을 너무 낮게 설정하는 경우: failureLimit: 1로 설정하면 일시적인 스파이크 하나에 롤백이 트리거됩니다. 처음에는 3~5 정도로 시작해보시면 좋습니다.
저트래픽 서비스에 P99 레이턴시를 쓰는 경우: canary가 5% 가중치인 상태에서 서비스 전체 RPS가 10이라면, canary에 들어오는 요청은 분당 30건 수준입니다. 이 샘플로 P99를 계산하면 의미가 없습니다. 저트래픽 서비스에서는 에러율만 보거나, 최소 30% 이상 가중치를 올린 뒤 레이턴시 분석을 시작하는 편이 낫습니다.
임계값을 평상시 메트릭 없이 설정하는 경우: successCondition: result[0] >= 0.95라고 설정했는데, 서비스 평상시 성공률이 98%라면 5% 마진이 너무 넉넉합니다. 반대로 외부 의존성 때문에 평상시에도 97%라면 0.95 기준이 항상 통과돼 의미가 없어집니다. 최소 2주치 Prometheus 데이터를 보고 기준선(baseline)을 잡는 것을 권장드립니다.
Deployment와 Rollout을 동시에 운영하는 경우: 같은 이름의 Deployment와 Rollout이 공존하면 두 컨트롤러가 같은 ReplicaSet을 두고 충돌합니다. Deployment를 먼저 정리하거나, Argo Rollouts 공식 마이그레이션 가이드의 절차를 따르시면 됩니다.
어디서부터 시작하면 좋을까
처음 도입할 때 공식 Best Practices에서도 권장하는 순서가 있습니다. Blue/Green 배포로 먼저 시작해 메트릭과 애플리케이션 특성을 파악한 뒤 Canary로 전환하는 흐름입니다. Canary 자체도 처음엔 AnalysisTemplate 없이 단순히 가중치와 pause만으로 시작하고, 핵심 KPI가 확인되면 분석 단계를 점진적으로 추가하는 게 현실적입니다.
메트릭 기준선(baseline)을 확보하고, 트래픽 라우터(NGINX 또는 Istio)를 결정하고, 첫 서비스는 트래픽이 충분한 것으로 고르시길 바랍니다. 이 세 가지만 준비되면 나머지는 매니페스트로 표현 가능한 영역입니다.
결국 이 모든 설정의 목적은 하나입니다. 야간 배포 다음 날 아침, Slack 알림이 아니라 "새 버전이 자동으로 100%까지 올라갔습니다" 메시지를 확인하며 커피를 마실 수 있게 되는 것. 롤백 판단을 사람이 아니라 메트릭이 내리도록 위임하는 순간, 배포 다음 날 아침의 긴장도가 눈에 띄게 달라집니다.
참고 자료
- Argo Rollouts 공식 문서 — Analysis & Progressive Delivery
- Argo Rollouts 공식 문서 — Prometheus 메트릭 프로바이더
- Argo Rollouts 공식 문서 — Canary 전략
- Argo Rollouts 공식 문서 — Best Practices
- Argo Rollouts 공식 문서 — Migrating from Deployments
- Argo Rollouts GitHub Releases
- Canary Deployment in Kubernetes Part 3 — Smart Canary with Argo Rollouts and Prometheus
- Progressive Canary Deployments on Kubernetes with Argo Rollouts and Istio
- Canary Deployment in Kubernetes Using Argo Rollouts and Istio — Deckhouse Blog
- Progressive Canary Releases with Argo Rollouts Analysis and Linkerd Metrics
- Automating Blue-Green & Canary Deployments with Argo Rollouts — Akuity