Argo Rollouts AnalysisTemplate — Prometheus·Datadog·Webhook으로 카나리 배포 자동 롤백 구현하기
카나리 배포를 처음 도입했을 때, 저는 꽤 오랫동안 Grafana 대시보드를 뚫어지게 바라보는 것이 배포 프로세스의 일부였습니다. 트래픽 10%를 카나리로 흘리고, 에러율 그래프가 잠잠한 것을 확인한 뒤 수동으로 argo rollouts promote 명령을 날리는 방식이었는데요. 어느 날 밤, P99 레이턴시가 슬금슬금 올라가는 걸 놓쳤습니다. 그래프가 워낙 완만하게 상승해서 "잠깐의 노이즈겠지" 하고 promote를 눌렀고, 30분 뒤 Slack 온콜 채널이 터졌습니다. 그때 "이걸 코드로 만들면 되지 않나?" 라는 생각이 들었고, 그게 Argo Rollouts의 AnalysisTemplate을 본격적으로 파고들게 된 계기였습니다.
이 글에서는 Prometheus, Datadog, Webhook 세 가지 메트릭 프로바이더를 조합해 카나리 배포마다 다층 자동 검증 게이트를 구성하고, 분석 실패 시 자동 롤백이 트리거되도록 설정하는 전체 패턴을 다룹니다. 실제 YAML 예시와 함께, 어디서 실수하기 쉬운지와 저도 한 번씩 밟았던 함정들을 솔직하게 풀어보려 합니다. Kubernetes를 운영 중이며 카나리 배포를 도입했거나 도입을 고려 중인 분이라면 바로 적용해볼 수 있는 내용으로 구성했습니다. Prometheus·Datadog을 이미 쓰고 있다는 전제로 PromQL 쿼리 등은 별도 설명 없이 사용하며, Istio를 사용하는 경우의 예시이므로 메트릭 구조는 서비스 메시 환경마다 달라질 수 있다는 점도 참고해두시면 좋습니다.
핵심 개념
AnalysisTemplate이 하는 일
Argo Rollouts의 AnalysisTemplate은 카나리나 블루-그린 배포 도중 메트릭을 자동으로 평가해서 "계속 배포할지, 아니면 되돌릴지"를 결정하는 CRD입니다. AnalysisTemplate을 참조하면 AnalysisRun 인스턴스가 생성되고, 이 Run이 지정된 프로바이더에서 데이터를 수집한 뒤 성공/실패 조건을 평가합니다.
구조를 단순하게 표현하면 이렇습니다:
Rollout → AnalysisRun ← AnalysisTemplate
↓
┌───────────┼───────────┐
Prometheus Datadog Webhook
└───────────┼───────────┘
↓
Success → Promote
Failure → Abort & Rollback프로모션 게이트: 배포 단계(step)마다 자동으로 실행되는 품질 검문소입니다. 분석이 성공해야만 다음 단계로 진행되고, 실패하면 자동 롤백이 트리거됩니다.
세 가지 분석 유형
분석을 언제 실행하느냐에 따라 세 가지로 나뉩니다. 처음 봤을 때 저도 좀 헷갈렸는데, 실제로 써보니 각각 쓰임새가 명확합니다.
| 유형 | 설명 | 주요 용도 |
|---|---|---|
| Background Analysis | 롤아웃 진행과 병렬로 지속 실행, 단계를 블로킹하지 않음 | 인프라 메트릭 지속 모니터링 |
| Inline Analysis | 롤아웃 step 내에 선언, 분석 완료 전까지 다음 step 진입 불가 | 단계별 명시적 게이트 |
| Pre/Post Analysis | 배포 직전 또는 직후 실행 | 사전 검증, 배포 후 헬스체크 |
한 가지 미리 짚어두면, ClusterAnalysisTemplate이라는 유형도 있습니다. 네임스페이스 범위가 아닌 클러스터 전체에서 공유되는 버전인데요. 팀 공통 게이트(에러율, 레이턴시 임계값)는 ClusterAnalysisTemplate으로, 서비스별 특화 게이트는 AnalysisTemplate으로 분리하는 패턴이 나중에 운영하기 훨씬 편합니다. 이 차이는 장단점 섹션에서 다시 다룹니다.
실전 적용
예시 1: Prometheus로 HTTP 성공률 게이트 구성하기
가장 기본적이면서도 효과적인 패턴입니다. Istio 메트릭을 기반으로 HTTP 5xx 에러율이 임계값을 넘으면 자동으로 롤백됩니다. 이하 모든 AnalysisTemplate 예시는 동일 네임스페이스에 배포한다고 가정합니다.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: http-success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 5m # 5분마다 측정
count: 10 # 총 10회 측정 (= 최대 50분 분석)
# Prometheus는 벡터를 반환하므로 result[0]으로 첫 번째 값을 참조합니다
successCondition: result[0] >= 0.95
failureLimit: 3 # 3회 실패 측정 시 분석 실패 판정
provider:
prometheus:
address: http://prometheus.monitoring.svc:9090
query: |
sum(irate(
istio_requests_total{
reporter="source",
destination_service=~"{{args.service-name}}",
response_code!~"5.*"
}[5m]
)) /
sum(irate(
istio_requests_total{
reporter="source",
destination_service=~"{{args.service-name}}"
}[5m]
)){{args.service-name}}처럼 템플릿 파라미터를 쿼리 안에 그대로 쓸 수 있어서, 동일한 AnalysisTemplate을 여러 서비스에 재사용할 수 있다는 게 상당히 편합니다.
Prometheus 프로바이더는 PromQL 실행 결과로 벡터를 반환합니다.
successCondition: result[0] >= 0.95에서result[0]은 반환된 시계열의 첫 번째 값을 참조합니다. 단일 값이 예상되는 쿼리라면 항상result[0]을 사용하는 것이 안전합니다.
이쯤에서 자주 등장하는 파라미터들을 한번 정리해두겠습니다.
| 파라미터 | 설명 |
|---|---|
interval |
메트릭 측정 주기 (예: 5m) |
count |
측정 횟수 (0이면 롤아웃이 끝날 때까지 무한 반복) |
successCondition |
성공 조건 표현식 |
failureCondition |
실패 조건 표현식 |
failureLimit |
허용 가능한 실패 횟수 (이 횟수를 넘으면 분석 실패 판정) |
inconclusiveLimit |
결론 미확정 허용 횟수 (메트릭 미반환 등) |
failureLimit과 inconclusiveLimit은 미묘하게 다릅니다. failureLimit은 조건 평가 결과가 "실패"인 횟수를, inconclusiveLimit은 메트릭 자체가 반환되지 않거나 평가할 수 없는 경우의 허용 횟수입니다. 네트워크 순간 장애나 메트릭 수집 지연 때문에 멀쩡한 배포가 롤백되는 걸 방지하려면 inconclusiveLimit을 적절히 설정해두는 게 좋습니다.
예시 2: Datadog으로 P99 레이턴시 게이트 구성하기
비즈니스 메트릭은 Datadog에 쌓이는 경우가 많죠. P99 응답 레이턴시가 200ms를 넘으면 프로모션을 막는 예시입니다.
솔직히 말하면 Datadog 프로바이더에서 처음에 두 가지를 삽질했습니다. 하나는 nil 처리, 다른 하나는 레이턴시 메트릭에 잘못된 집계 함수를 쓴 것이었습니다. .as_count()는 rate 기반 메트릭을 개수로 변환할 때 쓰는 함수인데, duration처럼 레이턴시 메트릭에 붙이면 의미가 없습니다. P99 레이턴시는 percentile 집계 방식을 써야 합니다.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: datadog-latency-gate
spec:
args:
- name: service-name
metrics:
- name: p99-latency
interval: 5m
count: 5
# default()로 nil 처리 필수 — 이유는 아래 callout 참고
successCondition: "default(result, 0) < 200"
failureLimit: 2
provider:
datadog:
apiVersion: v2
# percentile 집계로 P99 레이턴시 조회
# .as_count()는 rate 변환용이므로 duration 메트릭에는 사용하지 않습니다
query: |
p99:trace.web.request.duration.by.resource_service{
service:{{args.service-name}}
}Datadog 인증 정보는 반드시 Secret으로 분리해야 합니다. 네임스페이스를 argo-rollouts로 맞춰줘야 컨트롤러가 인식합니다.
apiVersion: v1
kind: Secret
metadata:
name: datadog
namespace: argo-rollouts
stringData:
address: https://api.datadoghq.com
api-key: <DD_API_KEY>
app-key: <DD_APP_KEY>
default()함수가 왜 필요한가: Datadog은 해당 시간대에 데이터가 없으면nil을 반환합니다.successCondition에서nil < 200을 평가하면 예외가 발생해 분석이 Inconclusive 상태가 됩니다.default(result, 0)으로 기본값을 지정해두면 이 상황을 안전하게 처리할 수 있습니다.
메트릭 단위는 Datadog APM 설정에 따라 달라질 수 있습니다. 배포 전에 Datadog UI에서 실제 값 범위를 먼저 확인하고 임계값을 설정하는 것을 권장합니다.
예시 3: Webhook으로 E2E 테스트 게이트 연결하기
Prometheus나 Datadog이 인프라/APM 레벨의 신호를 담당한다면, E2E 테스트는 실제 사용자 시나리오를 검증합니다. 외부 테스트 서비스(Testkube, k6, 자체 QA API 등)를 Webhook으로 연결하면 됩니다.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: e2e-test-gate
spec:
args:
- name: canary-url
- name: api-token
metrics:
- name: e2e-smoke-test
interval: 10m
count: 1 # E2E는 보통 1회 실행으로 충분
successCondition: result.status == "passed"
failureCondition: result.status == "failed"
provider:
web:
method: POST
url: "https://qa-service.internal/run-tests"
timeoutSeconds: 300 # 외부 서비스 응답 대기 시간, 반드시 설정
headers:
- key: Authorization
# {{args.api-token}}은 Argo Rollouts 파라미터 치환 문법 (이중 중괄호)
value: "Bearer {{args.api-token}}"
- key: Content-Type
value: application/json
body: |
{
"targetUrl": "{{args.canary-url}}",
"testSuite": "smoke"
}
# jsonPath는 JSONPath 표준 문법 (단일 중괄호) — 응답 JSON에서 평가할 필드 경로
jsonPath: "{$.result}"jsonPath: "{$.result}"와 {{args.canary-url}}의 중괄호 개수가 달라 처음 보면 헷갈릴 수 있습니다. {$.result}는 JSONPath 표준 문법이고, {{args.xxx}}는 Argo Rollouts의 파라미터 치환 문법입니다. 두 가지 문법이 같은 YAML 안에 섞여 있다는 점을 기억해두시면 좋습니다.
예시 4: 세 가지 프로바이더를 Rollout에 통합하기
지금까지 각각 독립적으로 만든 세 가지 AnalysisTemplate을 이제 하나의 Rollout에 엮어보겠습니다. 10% → 50% → 100% 단계적 트래픽 증가에 다층 게이트를 결합한 구성입니다.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-service
namespace: default
spec:
strategy:
canary:
# Background Analysis: spec.strategy.canary 하위에 선언합니다
# steps 전체에 걸쳐 병렬로 실행되며, 단계를 블로킹하지 않습니다
analysis:
templates:
- templateName: http-success-rate
args:
- name: service-name
value: my-service.default.svc.cluster.local
steps:
- setWeight: 10
- pause: {duration: 5m}
# Inline Analysis: steps 안에 선언합니다
# 세 가지 게이트가 모두 통과해야 setWeight: 50으로 진행됩니다
- analysis:
templates:
- templateName: http-success-rate # Prometheus: 에러율 체크
- templateName: datadog-latency-gate # Datadog: 레이턴시 체크
- templateName: e2e-test-gate # Webhook: E2E 테스트
args:
- name: service-name
value: my-service.default.svc.cluster.local
- name: canary-url
value: http://my-service-canary.internal
- name: api-token
valueFrom:
secretKeyRef:
name: qa-credentials
key: token
- setWeight: 50
- pause: {duration: 10m}
- setWeight: 100
progressDeadlineSeconds: 600
progressDeadlineAbort: true여기서 analysis 블록이 두 곳에 나옵니다. spec.strategy.canary.analysis(steps 바깥)는 Background Analysis로 롤아웃 전 과정을 병렬 모니터링하고, steps 안의 analysis는 Inline Analysis로 해당 단계를 블로킹합니다. 들여쓰기 레벨이 다르다는 점을 꼭 확인해두시면 좋습니다. 저는 처음에 analysis:를 steps: 다음에 같은 레벨로 놓는 실수를 해서 파싱 오류가 났었습니다.
예시 5: 자동 롤백이 트리거되는 메커니즘
분석 실패 시 내부적으로 어떤 일이 벌어지는지 이해하면 디버깅이 훨씬 수월합니다.
spec:
strategy:
canary:
# Abort 이후 카나리 ReplicaSet이 즉시 삭제되지 않고 대기하는 시간입니다
# 진행 중인 요청이 완료될 때까지 기다리는 graceful termination 용도이므로
# 0으로 설정하면 처리 중인 요청이 끊길 수 있습니다
abortScaleDownDelaySeconds: 30
steps:
- setWeight: 20
- analysis:
templates:
- templateName: http-success-rate
# failureLimit 초과
# → AnalysisRun: Failed
# → Rollout: Abort 상태 전환
# → canary weight: 0으로 복원
# → stable ReplicaSet으로 트래픽 복구롤아웃 상태를 실시간으로 확인하는 명령어도 익혀두시면 좋습니다:
# 롤아웃 전체 상태 및 분석 결과 실시간 확인
kubectl argo rollouts get rollout my-service -n default --watch
# AnalysisRun 목록 조회
kubectl get analysisrun -n default
# 특정 AnalysisRun 상세 확인 (실패 원인 포함)
kubectl describe analysisrun <analysisrun-name> -n default장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 자동 프로모션/롤백 | 사람의 개입 없이 메트릭 기반으로 배포 진행 여부가 결정됩니다 |
| 높은 재사용성 | ClusterAnalysisTemplate으로 팀 전체가 공통 게이트 템플릿을 공유할 수 있습니다 |
| 멀티 프로바이더 | Prometheus, Datadog, New Relic, Webhook, Kubernetes Job 등 다양한 소스를 하나의 Rollout에 조합할 수 있습니다 |
| 점진적 위험 감소 | 트래픽을 단계적으로 증가시키며 이상 징후를 조기에 감지할 수 있습니다 |
| GitOps 친화적 | AnalysisTemplate을 Git에서 버전 관리하고 코드 리뷰 프로세스에 포함할 수 있습니다 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| Cold Start 문제 | 카나리 트래픽이 매우 낮은 초기 단계에서 메트릭이 통계적으로 불충분해 오판 가능 | 초기 pause 시간을 충분히 두거나 느슨한 임계값으로 시작 |
| Datadog nil 반환 | 메트릭이 없는 시간 구간에서 nil 반환 시 분석 오류 발생 | successCondition에서 default(result, 0) 패턴 필수 적용 |
| progressDeadlineAbort 버그 | 배포 중이 아닌 상태에서도 Rollout이 Degraded로 표시되는 Known Issue 존재 (GitHub Issue #1624) | 모니터링 알람을 Rollout 상태가 아닌 AnalysisRun 상태 기준으로 설정 |
| 임계값 설정 난이도 | 너무 엄격하면 잦은 오탐 롤백, 너무 느슨하면 무의미한 게이트 | 처음엔 느슨한 임계값으로 시작해 실제 데이터를 보며 점진적으로 강화 |
| Webhook 타임아웃 | 외부 서비스 응답 지연이 분석 자체의 실패로 이어질 수 있음 | timeoutSeconds를 E2E 테스트 최대 실행 시간보다 넉넉하게 설정 |
| 운영 복잡성 | 멀티 프로바이더 조합 시 실패 원인 파악이 까다로워짐 | kubectl describe analysisrun으로 프로바이더별 결과를 개별 확인 |
ClusterAnalysisTemplate: 네임스페이스 범위가 아닌 클러스터 전체에서 공유되는 AnalysisTemplate입니다. 팀 공통 게이트는 ClusterAnalysisTemplate으로, 서비스별 특화 게이트는 AnalysisTemplate으로 분리하는 패턴이 운영하기 편합니다.
⚠️ 실무에서 자주 하는 실수 TOP 3
1. 첫 배포부터 엄격한 임계값 적용
successCondition: result[0] >= 0.999 같은 설정으로 시작하면 통계적 노이즈에도 계속 롤백됩니다. 저희 팀도 처음에 이 실수를 해서 멀쩡한 배포가 세 번 연속으로 롤백되었고, 결국 팀원 모두가 AnalysisTemplate 자체를 불신하게 되는 상황이 벌어졌습니다. autoPromotionEnabled: false로 수동 검증을 먼저 진행하고, 실제 메트릭 분포를 확인한 뒤 임계값을 설정하는 것을 권장합니다.
2. interval과 count의 총 분석 시간을 고려하지 않음
interval: 5m, count: 10이면 분석이 최대 50분 걸립니다. E2E 테스트를 Inline Analysis로 걸어두고 count: 3, interval: 10m으로 설정하면 배포 하나에 30분이 소요될 수 있습니다. 배포 주기와 SLO를 함께 고려해서 설계해볼 만합니다.
3. Webhook 응답 구조와 jsonPath 불일치
successCondition: result.status == "passed"로 설정했는데 jsonPath: "{$.data.result}"를 잘못 지정하면 항상 Inconclusive가 됩니다. Webhook 프로바이더를 처음 설정할 때는 kubectl describe analysisrun으로 실제 result 값이 어떻게 평가되는지 먼저 확인해보시면 좋습니다. 이건 제가 배포 도중 Slack 알람이 쏟아지고 나서야 발견했던 문제이기도 합니다.
마치며
도입부에서 이야기한 것처럼, "사람이 지켜봐야 안심되는 배포"는 결국 사람을 소모합니다. AnalysisTemplate은 에러율·레이턴시·기능 검증을 코드로 표현해서 배포 자체가 스스로 안전을 검증하도록 만들어줍니다. 이 구성을 도입한 이후 저희 팀은 온콜 중 배포 관련 호출이 약 70% 줄었고, 카나리 단계에서 조용히 자동 롤백된 배포가 두 건 있었는데 그 두 건 모두 사람이 놓쳤을 이슈였다는 걸 나중에 확인했습니다. 복잡해 보이는 설정이지만 한 번 구성해두면 야간 배포나 금요일 오후 배포에서도 훨씬 마음이 편해집니다.
지금 바로 시작해볼 수 있는 3단계:
-
Argo Rollouts를 Helm으로 설치하고 기존 Deployment를 Rollout으로 전환해볼 수 있습니다.
helm install argo-rollouts argo/argo-rollouts --namespace argo-rollouts --create-namespace명령으로 설치한 뒤, 기존 Deployment YAML을kind: Rollout으로 바꾸고autoPromotionEnabled: false로 시작해 수동 promote 흐름부터 먼저 익혀두시면 좋습니다. -
Prometheus 연동 AnalysisTemplate 하나를 느슨한 임계값으로 붙여볼 수 있습니다. 현재 서비스의 HTTP 성공률 평균이 99.5%라면,
successCondition: result[0] >= 0.90처럼 여유 있는 값으로 시작해서 실제 분석 결과가 어떻게 나오는지kubectl argo rollouts get rollout <name> --watch로 관찰해볼 수 있습니다. -
안정적으로 동작한다고 확인되면 Datadog이나 Webhook 게이트를 추가하고 임계값을 점진적으로 강화해볼 수 있습니다. 멀티 프로바이더 조합은 각각 검증된 이후에 연결하는 것이 디버깅 측면에서 훨씬 수월합니다.
참고 자료
- Analysis & Progressive Delivery | Argo Rollouts 공식 문서
- Prometheus Provider | Argo Rollouts 공식 문서
- Datadog Provider | Argo Rollouts 공식 문서
- Web (HTTP) Provider | Argo Rollouts 공식 문서
- Argo Rollouts Integration | Datadog 공식 문서
- Progressive Delivery with Argo Rollouts: Canary with Analysis | InfraCloud
- Argo Rollouts Canary Monitoring: Metrics, Gotchas, and Automated Gates | Last9
- Automated Deployments With Argo Rollouts + Datadog | DZone
- Zero-Touch Safety: Automated Canary Rollbacks with Argo Rollouts & Istio | Medium
- Automating Blue-Green & Canary Deployments with Argo Rollouts | Akuity Blog
- argoproj/argo-rollouts | GitHub
- rollouts-plugin-metric-sample-prometheus | GitHub