왜 수동 롤백은 늦는가
새 버전을 100%로 한 번에 배포하면 장애가 전체 트래픽을 덮친다. 카나리 배포는 소수 트래픽(예: 5%)만 신 버전으로 보내 위험을 격리한다. 하지만 사람이 대시보드를 보고 판단하면 문제가 있다. 지표가 나빠지는 순간과 사람이 알아채고 롤백 버튼을 누르는 순간 사이에 수 분의 지연이 생기고, 그동안 에러가 누적된다. 새벽 배포라면 아무도 보고 있지 않다. 핵심은 판정과 롤백을 자동화해, 카나리 그룹의 지표가 기준선 대비 나빠지면 사람 개입 없이 되돌리는 것이다.
무엇을 메트릭으로 삼을 것인가
롤백 판정은 배포 성공 여부가 아니라 사용자 관점의 나빠짐을 봐야 한다. 실무에서 자주 쓰는 4대 신호는 다음과 같다.
- 에러율: HTTP 5xx 비율, gRPC 에러 코드 비율
- 지연: p95/p99 응답 시간 (평균은 꼬리 지연을 숨긴다)
- 포화도: CPU/메모리, GC pause, 커넥션 풀 고갈
- 비즈니스 신호: 결제 성공률, 주문 전환율 등 도메인 지표
주의할 점은 절댓값이 아니라 같은 시간대 기준선(baseline)과의 상대 비교를 해야 한다는 것이다. 트래픽 자체가 튀는 시간대엔 신 버전이 아니어도 지연이 오른다. 그래서 안정 버전 그룹과 카나리 그룹을 동시에 관측해 차이를 본다.
판정 로직: 절대 임계 vs 상대 비교
| 방식 | 판정 기준 | 장점 | 약점 |
|---|---|---|---|
| 절대 임계 | 카나리 5xx > 1% | 단순, 이해 쉬움 | 정상 배경 노이즈에 오탐 |
| 상대 비교 | 카나리 5xx > 기준선 × 2 | 시간대 변동에 강함 | 기준선도 나쁘면 못 잡음 |
| 조합 | 둘 다 초과 시 실패 | 오탐/미탐 균형 | 임계 튜닝 필요 |
실전에서는 조합을 권한다. 절대 하한(예: 최소 5xx 0.5%)을 두어 기준선 이상반응을 막고, 그 위에서 상대 배수를 본다.
Argo Rollouts로 선언적 판정
쿠버네티스라면 Argo Rollouts의 AnalysisTemplate이 표준적이다. Prometheus 쿼리로 카나리 파드의 성공률을 주기적으로 측정하고, 실패 횟수가 쌓이면 자동 롤백한다.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service
metrics:
- name: success-rate
interval: 30s
count: 5 # 5회 관측
failureLimit: 2 # 2회 실패 시 롤백
successCondition: result[0] >= 0.99
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{
service="{{args.service}}",status!~"5.."}[1m]))
/
sum(rate(http_requests_total{
service="{{args.service}}"}[1m]))
failureLimit을 1로 두지 마라. 단발성 스크레이프 오류로 롤백되면 배포가 늘 실패한다. 여러 번 연속/누적 실패를 요구해 노이즈를 흡수한다.
직접 구현하는 판정 스크립트
Argo가 없는 환경(EC2, ECS 등)에서는 CI 파이프라인 단계에서 직접 판정한다. 아래는 카나리와 기준선을 동시에 조회해 상대·절대 조건을 함께 검사하는 예시다.
import requests, sys
PROM = "http://prometheus:9090/api/v1/query"
def err_rate(group):
q = (f'sum(rate(http_requests_total'
f'{{group="{group}",status=~"5.."}}[3m]))'
f' / sum(rate(http_requests_total{{group="{group}"}}[3m]))')
r = requests.get(PROM, params={"query": q}, timeout=5).json()
res = r["data"]["result"]
return float(res[0]["value"][1]) if res else 0.0
canary = err_rate("canary")
base = err_rate("stable")
# 절대 하한 0.5% + 기준선 2배 초과 시 실패
if canary > 0.005 and canary > base * 2:
print(f"FAIL canary={canary:.4f} base={base:.4f}")
sys.exit(1)
print(f"OK canary={canary:.4f} base={base:.4f}")
이 스크립트를 배포 후 여러 분에 걸쳐 반복 실행하고, 종료 코드 1이면 파이프라인이 이전 버전으로 되돌리도록 연결한다.
운영에서 놓치기 쉬운 함정
- 워밍업 지연: 신 버전은 JIT/캐시가 비어 초기 지연이 높다. 판정 전 30~60초 유예를 둬라.
- 표본 부족: 카나리 트래픽이 너무 적으면 요청 몇 건으로 비율이 요동친다. 최소 요청 수 조건을 함께 걸어라.
- 롤백 자체의 실패: 롤백이 동작하는지 정기적으로 게임데이로 검증해야 한다. 쓰지 않는 자동화는 정작 필요할 때 망가져 있다.
- DB 마이그레이션: 스키마 변경은 롤백이 안 된다. 신·구 버전이 공존 가능하도록 확장-수축(expand/contract) 방식으로 나눠야 카나리 롤백이 성립한다.
자동 롤백의 목표는 배포를 빠르게 하는 게 아니라 나쁜 배포의 영향 범위와 지속 시간을 최소화하는 것이다. 임계값은 한 번 정하고 끝내지 말고, 오탐·미탐 사례가 나올 때마다 조정해 나가야 한다.