Kubernetes Argo Rollouts AnalysisTemplate과 Datadog으로 구현하는 번 레이트 SLO 기반 카나리 자동 롤백
새벽 3시에 PagerDuty 알림으로 눈을 뜬 적이 있나요? 저는 있습니다. 그것도 여러 번. 그때마다 로그를 뒤지다 보면 결국 같은 생각이 들더라고요. "카나리 배포를 하고 있었는데 왜 배포 시점에 이걸 못 잡았지?" 에러율이 1% 미만이었으니 시스템 입장에선 아무 문제가 없었던 거죠. 에러율 0.9%가 30분 동안 유지되고 있어도 아무도, 어떤 시스템도 개입하지 않았습니다.
문제의 원인은 명확했지만, 해결책은 생각보다 정교한 판단을 요구했습니다. 단순히 "에러율 1% 이하"라는 고정 임계값 대신, 에러 예산(Error Budget)이 얼마나 빠르게 소진되고 있는지—번 레이트(Burn Rate)—를 기준으로 카나리 프로모션과 롤백을 자동화하는 패턴이 필요했습니다. 이 방식으로 바꾼 뒤로는 새벽 전화가 오는 대신, 다음 날 아침에 Slack에서 "카나리 자동 롤백 완료, 영향 없음" 메시지를 확인하는 경험을 여러 번 했습니다.
이 글은 Kubernetes 기본 지식을 가진 개발자를 대상으로 합니다. SLO가 처음이어도 괜찮습니다. Argo Rollouts의 AnalysisTemplate을 Datadog과 연결해서 SLO 기반 판단 로직을 Kubernetes 리소스로 표현하는 과정을 단계별로 살펴볼게요. 실제 패턴에서 영감을 얻을 수 있는 YAML 예시와 함께, 이 접근 방식의 함정과 주의사항도 솔직하게 짚어드리겠습니다.
핵심 개념
SLO, 에러 예산, 번 레이트 — 배포 판단 기준으로 쓸 수 있는 이유
처음 이 개념을 접했을 때 가장 헷갈렸던 게 "결국 에러율 아닌가?"라는 의문이었습니다. 저도 처음엔 그랬어요. 그런데 번 레이트 개념을 이해하고 나서 생각이 완전히 바뀌었습니다.
먼저 용어부터 정리해봅시다.
| 개념 | 의미 | 예시 |
|---|---|---|
| SLI (Service Level Indicator) | 측정 지표 | HTTP 5xx 비율, p95 레이턴시 |
| SLO (Service Level Objective) | SLI의 목표치 | 30일 창에서 99.9% 가용성 |
| 에러 예산 (Error Budget) | 1 - SLO만큼의 허용 오류량 |
30일 × 0.1% = 43.2분 |
| 번 레이트 (Burn Rate) | 에러 예산 소진 속도 | 번 레이트 1 = 30일 후 예산 소진 |
번 레이트 공식은 단순합니다.
번 레이트 = 현재 에러율 / (1 - SLO)SLO가 99.9%라면 허용 에러율은 0.1%(= 0.001)입니다. 지금 에러율이 0.1%이면 번 레이트는 1.0 — 30일이 지나면 예산이 딱 다 떨어집니다. 에러율이 1.44%라면?
번 레이트 = 0.0144 / 0.001 = 14.4번 레이트 14.4는 정상 소진 속도의 14.4배입니다. 이 속도가 유지되면 약 2일 만에 30일치 에러 예산이 소진됩니다(30일 ÷ 14.4 ≈ 2.1일). 카나리 배포 중 이 수치가 관측된다면 즉각 대응이 필요하다는 신호입니다.
왜 단순 에러율보다 번 레이트인가? 에러율 1%가 "위험하다"는 건 서비스 특성에 따라 다릅니다. SLO 99%인 서비스에서 1%는 번 레이트 1.0 — 정상 소진 속도입니다. SLO 99.99%인 서비스에서 1%는 번 레이트 100 — 즉각 위험입니다. 번 레이트는 이 맥락을 자동으로 담아냅니다.
AnalysisTemplate이 하는 일
AnalysisTemplate은 Kubernetes CRD로, Argo Rollouts가 카나리 배포 중 외부 메트릭 기반으로 "계속 진행할지, 멈출지, 롤백할지"를 결정할 수 있게 해줍니다.
Rollout Controller
│ (AnalysisTemplate 인스턴스화)
▼
AnalysisRun (분석 실행)
│ (Datadog Metrics API v2 쿼리)
▼
Datadog
│ (번 레이트 계산 결과 반환)
▼
successCondition / failureCondition 평가
│
├─ Successful → 다음 스텝 자동 프로모션
├─ Failed → 자동 롤백
└─ Inconclusive → 일시정지 (수동 개입 대기)판정 결과 세 가지 중 Inconclusive가 중요합니다. successCondition도 failureCondition도 충족하지 못할 때 반환되며, Rollout은 자동으로 일시정지되고 수동 검토를 기다립니다. 완전 자동화의 위험을 줄이는 안전장치입니다.
Datadog과의 연결 방식
Argo Rollouts가 Datadog과 통신하려면 API 키와 App 키를 Kubernetes Secret으로 먼저 준비해야 합니다.
apiVersion: v1
kind: Secret
metadata:
name: datadog-api-key
namespace: argo-rollouts
stringData:
api-key: "<DATADOG_API_KEY>"
app-key: "<DATADOG_APP_KEY>"stringData는 평문으로 저장되므로 실무에서는 Sealed Secrets나 External Secrets Operator와 연동하는 방식이 권장됩니다. 네임스페이스 격리(namespaced: true)도 명시적으로 설정해두시면 좋습니다 — 멀티테넌트 환경으로 확장할 때 훨씬 편해집니다.
전체 구조 한눈에 보기
코드 예시를 보기 전에 전체 그림을 한번 잡고 가는 게 좋을 것 같습니다. AnalysisTemplate, Datadog, Rollout Controller가 어떻게 연결되는지입니다.
┌─────────────────────────────────────────────────────────────┐
│ Git Repository │
│ ┌──────────────────┐ ┌─────────────────────────────┐ │
│ │ Rollout.yaml │ │ AnalysisTemplate.yaml │ │
│ └────────┬─────────┘ └──────────────┬──────────────┘ │
└───────────┼───────────────────────────┼────────────────────┘
│ GitOps 배포 │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌──────────────────┐ 인스턴스화 ┌─────────────────────┐ │
│ │ Rollout │ ──────────→ │ AnalysisRun │ │
│ │ Controller │ ←────────── │ (메트릭 쿼리 + 평가) │ │
│ │ │ 판정 결과 └──────────┬──────────┘ │
│ └──────────────────┘ │ │
│ │ Datadog API 쿼리 │
│ ┌──────▼───────────┐ │ │
│ │ Canary Pods │ ▼ │
│ │ (5%→20%→50%→100%)│ ┌──────────────────────┐ │
│ └──────────────────┘ │ Datadog Metrics v2 │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘AnalysisTemplate은 메트릭 쿼리 방법과 판정 기준을 정의하는 "템플릿"이고, AnalysisRun은 배포 시마다 생성되는 실제 실행 인스턴스입니다. Rollout Controller는 AnalysisRun의 판정 결과를 받아 카나리 스텝 진행 여부를 결정합니다.
실전 적용
기본 — 에러율로 카나리 품질 검증하기
Argo Rollouts + Datadog 조합을 처음 써본다면 복잡한 번 레이트 계산 없이 단순 에러율 체크부터 시작해보시면 좋습니다. Datadog Metrics API v2를 써서 5분 에러율을 쿼리하고, 1% 이상이면 실패로 판정하는 기본 패턴입니다.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: datadog-error-rate
spec:
args:
- name: service-name
metrics:
- name: error-rate
interval: 5m
successCondition: default(result, 0) <= 0.01
failureLimit: 3
provider:
datadog:
apiVersion: v2
queries:
errors: sum:requests.errors{service:{{args.service-name}}}.as_count()
total: sum:requests{service:{{args.service-name}}}.as_count()
formula: "moving_rollup(errors, 300, 'sum') / moving_rollup(total, 300, 'sum')"| 설정 | 설명 |
|---|---|
interval: 5m |
5분마다 Datadog에 쿼리 실행 |
default(result, 0) |
쿼리 결과가 nil일 때(트래픽 없음) 0으로 처리. 없으면 카나리 초기에 분석 자체가 에러로 처리됩니다 |
failureLimit: 3 |
연속 3번 실패해야 롤백. 일시적 스파이크 오탐 방지 |
moving_rollup(errors, 300, 'sum') |
5분(300초) 윈도우 합산 집계 |
failureCondition이 없다는 점이 눈에 띄실 겁니다. 이 경우 Argo Rollouts는 successCondition이 충족되지 않은 측정을 "실패"로 카운트하고, failureLimit에 도달하면 롤백합니다. failureCondition이 없으면 Inconclusive 상태 없이 성공 아니면 실패만 있는 이진 판정이 됩니다.
핵심 — 번 레이트 기반 SLO 자동 롤백
이제 본론입니다. failureCondition 임계값을 이 패턴에서 가장 고민했던 게 failureCondition의 번 레이트 숫자였는데, 결국 Google SRE Workbook 기준을 따르는 게 가장 합리적이었습니다. Datadog Formula에서 에러율을 허용 에러율로 나눠서 번 레이트를 실시간으로 계산합니다.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: slo-burn-rate-check
spec:
args:
- name: service-name
- name: slo-error-rate # 예: "0.001" (SLO 99.9%의 허용 에러율)
metrics:
- name: error-budget-burn-rate
interval: 5m
successCondition: result <= 2.0 # 번 레이트 2배 이하 → 안전
failureCondition: result > 14.4 # 번 레이트 14.4 초과 → 즉시 롤백
failureLimit: 1
provider:
datadog:
apiVersion: v2
queries:
errors: sum:requests.errors{service:{{args.service-name}}}.as_count()
total: sum:requests{service:{{args.service-name}}}.as_count()
formula: >-
(moving_rollup(errors, 300, 'sum') / moving_rollup(total, 300, 'sum'))
/ {{args.slo-error-rate}}successCondition과 failureCondition 사이, 즉 번 레이트 2.0~14.4 구간은 Inconclusive로 처리됩니다. Rollout은 자동으로 일시정지되고 수동 개입을 기다립니다. 완전 자동화와 사람 검토 사이의 안전장치로 활용할 수 있습니다.
번 레이트 14.4는 어디서 왔나요? Google SRE Workbook에서 유래한 값입니다. 번 레이트 14.4가 1시간 동안 지속되면 30일치 에러 예산의 약 2%가 소진됩니다(14.4 ÷ 720시간 = 2%). 소진량 자체가 크지는 않지만, 카나리라는 짧은 관측 창에서 이미 이 속도가 관측된다면 프로덕션 전체에 영향을 미칠 문제가 잠재해 있다는 신호입니다. 즉각적인 대응이 필요한 경보 수준으로 봐야 합니다.
권장 패턴 — 멀티윈도우 번 레이트로 오탐 최소화
솔직히 저도 처음엔 단일 윈도우 번 레이트로만 운영했습니다. 그러다 10분짜리 플랫폼 이슈 때문에 멀쩡한 배포가 여러 번 롤백되는 걸 겪고 나서 이 패턴으로 바꿨어요. 장기 창(5분)과 단기 창(1분)을 동시에 체크해서 둘 다 임계값을 넘을 때만 실패로 판정하는 방식입니다.
한 가지 중요한 포인트가 있습니다. 아래 예시에서 count를 생략했습니다. count: 5처럼 측정 횟수를 제한하면 interval: 1m과 조합했을 때 분석이 5분 뒤에 종료되어버립니다. Background Analysis로 배포 전 구간을 감시하려는 목적과 완전히 어긋납니다. count를 생략하면 Rollout이 완료될 때까지 분석이 계속 돌아갑니다.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: multiwindow-burn-rate
spec:
args:
- name: service
- name: slo-threshold
metrics:
# 장기 창: 5분 집계 기준 번 레이트
- name: burn-rate-5m
interval: 1m
successCondition: default(result, 0) <= 2.0
failureCondition: default(result, 0) > 14.4
failureLimit: 1
provider:
datadog:
apiVersion: v2
queries:
e: sum:http.errors{service:{{args.service}},version:canary}.as_count()
r: sum:http.requests{service:{{args.service}},version:canary}.as_count()
formula: >-
(moving_rollup(e, 300, 'sum') / moving_rollup(r, 300, 'sum'))
/ {{args.slo-threshold}}
# 단기 창: 1분 집계 기준 번 레이트 (빠른 급등 감지)
- name: burn-rate-1m
interval: 1m
successCondition: default(result, 0) <= 14.4
failureCondition: default(result, 0) > 14.4
failureLimit: 2
provider:
datadog:
apiVersion: v2
queries:
e: sum:http.errors{service:{{args.service}},version:canary}.as_count()
r: sum:http.requests{service:{{args.service}},version:canary}.as_count()
formula: >-
(moving_rollup(e, 60, 'sum') / moving_rollup(r, 60, 'sum'))
/ {{args.slo-threshold}}쿼리에서 version:canary 태그를 명시한 부분에 주목해 주세요. 카나리가 전체 트래픽의 5%만 처리하는 상황에서 버전 구분 없는 글로벌 메트릭을 쓰면, 나머지 95%의 안정적인 구버전 트래픽이 번 레이트를 희석시켜 실제 문제를 놓치게 됩니다. 카나리 파드에 version: canary 레이블을 붙이고, Datadog에서 이를 태그로 수집하도록 설정하면 카나리 슬라이스만의 번 레이트를 정확하게 측정할 수 있습니다.
burn-rate-1m의 successCondition과 failureCondition이 동일한 임계값(14.4)을 사용한다는 점도 보이실 겁니다. Inconclusive 구간이 없는 이진 판정이지만, failureLimit: 2로 연속 2회를 초과해야 실패로 처리하기 때문에 1분짜리 노이즈는 자연스럽게 걸러집니다. 더 세밀한 제어가 필요하다면 successCondition: result <= 10.0처럼 Inconclusive 구간을 명시적으로 두는 것도 고려할 수 있습니다.
Rollout에 연결 — Background Analysis로 전 구간 감시
앞서 만든 AnalysisTemplate을 실제 Rollout에 붙이는 방법입니다. Background Analysis를 쓰면 모든 카나리 스텝 동안 지속적으로 분석이 돌아갑니다.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: payment-service
spec:
replicas: 10
strategy:
canary:
analysis:
templates:
- templateName: multiwindow-burn-rate
startingStep: 1 # setWeight: 5 이후 즉시 분석 시작
args:
- name: service
value: payment-service
- name: slo-threshold
value: "0.001" # SLO 99.9%
steps:
- setWeight: 5
- pause: {duration: 10m}
- setWeight: 20
- pause: {duration: 10m}
- setWeight: 50
- pause: {duration: 15m}
- setWeight: 100startingStep: 1로 설정하면 첫 번째 setWeight 직후부터 분석이 시작됩니다. 카나리 트래픽이 5%일 때부터 실시간으로 SLO 영향을 감시하는 셈이죠.
배포 중 상태 확인 — AnalysisRun 디버깅
처음 이 패턴을 도입할 때 가장 먼저 막히는 지점이 "뭔가 잘못됐는데 어디서 확인하지?"입니다. 저도 처음엔 이 부분에서 한참 헤맸습니다.
# AnalysisRun 목록 및 현재 상태 확인
kubectl get analysisrun -n <namespace>
# 특정 AnalysisRun 상세 보기
kubectl describe analysisrun <analysisrun-name> -n <namespace>
# status 필드만 추출해서 보기
kubectl get analysisrun <analysisrun-name> -n <namespace> -o json \
| jq '.status | {phase, message, metricResults}'status.phase는 Running, Successful, Failed, Inconclusive 중 하나입니다. status.metricResults에서 각 메트릭별 최근 측정값과 판정 결과를 확인할 수 있습니다. Inconclusive 상태라면 측정값이 어느 임계값 사이에 있는지, Datadog 쿼리가 nil을 반환하지는 않는지 확인해보시면 대부분 원인을 찾을 수 있습니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 자동화된 안전망 | 사람이 개입하지 않아도 SLO 위반 즉시 자동 롤백되어 MTTR이 단축됩니다 |
| 비즈니스 임팩트 연계 | 단순 기술 지표가 아닌 에러 예산 소진 속도로 판단하므로 비즈니스 SLA와 직결됩니다 |
| 오탐 감소 | 멀티윈도우 번 레이트 패턴으로 일시적 스파이크에 의한 불필요한 롤백을 줄일 수 있습니다 |
| 점진적 위험 노출 | 5% → 20% → 50% 단계적 트래픽 이동으로 문제 발생 시 영향 반경을 최소화합니다 |
| GitOps 통합 | AnalysisTemplate을 코드로 관리하므로 배포 정책의 감사(Audit)와 재현이 가능합니다 |
단점 및 주의사항
| 항목 | 무엇이 문제인가 | 대응 방안 |
|---|---|---|
| Cold Start | 카나리 초기에 요청 수가 적어 번 레이트 통계가 불안정합니다 | default(result, 0) 함수 사용, 초기 pause를 충분히 확보 |
| 저트래픽 서비스 | 요청 1건이 에러율 100%로 계산될 수 있습니다 | failureLimit를 넉넉히 설정하거나 최소 요청 수 조건을 추가 |
| 카나리 슬라이스 격리 | 서비스 메시 없이는 카나리 파드만의 메트릭 격리가 어렵습니다 | Istio / Linkerd 등 서비스 메시 결합을 권장합니다 |
| Datadog 쿼리 비용 | 분석 interval마다 Datadog API 호출이 발생합니다 | interval을 너무 짧게 설정하지 않도록 주의 |
| SLO 기간 불일치 | 30일 SLO 창과 카나리 15분 관측 창의 통계적 신뢰도 간 괴리가 있습니다 | 번 레이트 판단을 절대적 기준이 아닌 신호로 이해하고 Inconclusive 구간을 활용 |
실무에서 가장 흔한 실수
-
default()함수를 빠뜨리는 것 — Datadog 쿼리가 빈 결과(nil)를 반환할 때successCondition표현식 자체가 에러로 처리되어 AnalysisRun이Failed로 끝납니다. 카나리 초기 5분이 가장 위험한 구간입니다. 모든 메트릭에default(result, 0)패턴을 기본으로 적용해두는 게 좋습니다. -
count를 잘못 설정해서 분석이 너무 일찍 끝나는 것 —count: 5와interval: 1m을 함께 쓰면 분석이 5분 뒤에 종료됩니다. Background Analysis로 배포 전 구간을 감시하려는 목적과 완전히 어긋납니다.count를 생략하면 Rollout이 완료될 때까지 분석이 계속 돌아갑니다. -
글로벌 메트릭으로 카나리 품질을 판단하는 것 — 카나리가 전체 트래픽의 5%만 처리할 때, 나머지 95%의 안정적인 구버전 트래픽이 번 레이트를 희석시킵니다. Datadog 쿼리에
version:canary같은 태그를 명시하거나, 서비스 메시로 트래픽을 명확하게 분리하는 것이 이 함정을 피하는 가장 확실한 방법입니다.
마치며
처음 이야기한 새벽 3시 상황을 다시 떠올려보면, 번 레이트 기반 롤백이 작동하고 있었다면 그 알림은 아마 오지 않았을 겁니다. 에러율 0.9%가 30분 동안 지속됐을 때 번 레이트는 9.0이었고, failureCondition: result > 14.4에는 걸리지 않지만 successCondition: result <= 2.0에도 해당하지 않는 Inconclusive 상태였을 테니까요. Rollout이 자동으로 일시정지되고, 저는 다음 날 아침에 여유롭게 상황을 검토할 수 있었을 겁니다.
SLO 기반 카나리 자동 롤백의 핵심은 "에러율 1% 이하"라는 단순 임계값에서 벗어나, 비즈니스 신뢰도 지표인 에러 예산 소진 속도와 배포 파이프라인을 직접 연결하는 것입니다.
처음부터 멀티윈도우 번 레이트와 Background Analysis를 모두 구성하면 복잡하게 느껴질 수 있습니다. 아래 순서로 접근해보시면 훨씬 수월합니다.
-
Datadog에서 서비스의 SLO를 먼저 정의해두시면 좋습니다.
Service Level Objectives > New SLO로 SLI와 목표치를 설정하면,1 - SLO값(예:0.001)을slo-threshold인자로 바로 활용할 수 있습니다. -
위의 기본 에러율
AnalysisTemplate부터 기존 Rollout에 붙여보시는 것을 권장합니다. 번 레이트 계산 없이 Datadog 쿼리 결과가 AnalysisRun에 잘 도달하는지 먼저 확인하는 단계입니다.kubectl get analysisrun으로 판정 결과를 실시간으로 확인할 수 있습니다. -
안정성이 확인되면 번 레이트 포뮬라로 교체하고, 이후 멀티윈도우 패턴으로 발전시켜 나갈 수 있습니다.
slo-threshold를 Rollout 인자로 외부화해두면 서비스별로 다른 SLO를 유연하게 적용할 수 있습니다.
참고 자료
- Argo Rollouts 공식 문서: Analysis & Progressive Delivery
- Argo Rollouts 공식 문서: Datadog 메트릭 프로바이더
- Datadog 공식 문서: Burn Rate Alerts
- Datadog 블로그: Burn rate is a better error rate
더 읽기
- Datadog 공식 문서: Argo Rollouts 통합
- Datadog 공식 문서: Error Budget Alerts
- InfraCloud 블로그: Progressive Delivery with Argo Rollouts: Canary with Analysis
- Mario Fernandez 블로그: Multiwindow, Multi-Burn-Rate Alerts in DataDog
- Google SRE Workbook: Canarying Releases
- Argo Rollouts GitHub 예제: rollout-analysis-step.yaml