왜 "배포 성공 = 정상"이 아닌가
CI가 초록불이고 파드가 Running이어도 서비스가 정상이라는 보장은 없다. 새 버전이 뜬 직후에는 커넥션 풀 워밍업, JIT 컴파일, 캐시 미스, 다운스트림 재연결이 겹쳐 지연과 에러율이 일시적으로 튄다. 문제는 이 구간이 지나면 안정화되는 '일시적 스파이크'인지, 아니면 롤백해야 할 '실제 회귀'인지 구분이 어렵다는 점이다. 배포 직후 곧바로 트래픽을 100% 전환하면, 초기 노이즈에 놀라 멀쩡한 배포를 롤백하거나 반대로 진짜 장애를 놓친다.
베이크 타임의 정의와 역할
베이크 타임(bake time)은 새 버전에 일부 또는 전체 트래픽을 흘린 뒤, 자동 승격이나 완전 전환을 하기 전에 지표를 관찰하며 대기하는 구간이다. 목적은 두 가지다. 첫째, 워밍업 노이즈가 가라앉을 시간을 준다. 둘째, 그 시간 동안 에러율·지연·비즈니스 지표를 관측 윈도우로 모아 통계적으로 판단한다. 핵심은 "몇 초 기다린다"가 아니라 "기다리는 동안 무엇을 어떤 기준으로 볼 것인가"다.
헬스 체크는 세 층으로 나눈다
단일 /health로 모든 걸 판단하면 안 된다. 목적에 따라 분리해야 오탐이 줄어든다.
| 종류 | 판단 대상 | 실패 시 동작 |
|---|---|---|
| Liveness | 프로세스 데드락 여부 | 컨테이너 재시작 |
| Readiness | 트래픽 수용 준비(DB·캐시 연결) | 트래픽 제외 |
| Bake 검증 | 실사용자 지표(에러율·p99) | 승격 중단/롤백 |
쿠버네티스 프로브 예시
readiness에는 실제 의존성 확인을, liveness에는 가벼운 자기 진단만 둔다. liveness에 DB 체크를 넣으면 DB 장애 시 전 파드가 재시작되며 장애가 증폭된다.
livenessProbe:
httpGet: { path: /livez, port: 8080 }
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 2
베이크 구간 자동 검증 스크립트
승격 판단은 스냅샷 한 번이 아니라 윈도우 누적으로 한다. 아래는 관측 창 동안 에러율과 p99를 주기적으로 확인해, 임계 초과가 연속으로 잡히면 실패를 반환하는 예시다.
import time, sys, requests
WINDOW, INTERVAL = 300, 15 # 5분 관측, 15초 간격
ERR_MAX, P99_MAX = 0.01, 800 # 1%, 800ms
strikes, need = 0, 3
deadline = time.time() + WINDOW
while time.time() < deadline:
m = requests.get("http://prometheus/api/v1/query",
params={"query": "canary:err_rate,canary:p99"}).json()
err, p99 = parse(m) # 배포 파이프라인 라벨 기준 집계
if err > ERR_MAX or p99 > P99_MAX:
strikes += 1
print(f"breach err={err:.3f} p99={p99:.0f} strike={strikes}")
if strikes >= need:
sys.exit(1) # 연속 위반 -> 롤백 트리거
else:
strikes = 0
time.sleep(INTERVAL)
print("bake passed")
단발 스파이크로 롤백되지 않도록 need번 연속 위반을 조건으로 둔 점이 중요하다. 반대로 임계를 너무 관대하게 잡으면 실제 회귀를 통과시키므로, 평소 트래픽의 p99와 에러율을 먼저 측정해 여유(예: 평소값 × 1.5)로 기준을 정한다.
파이프라인 연결
카나리 배포라면 트래픽 비율을 올리는 사이사이에 이 검증을 넣는다. 예: 10% 전환 → 5분 베이크 → 통과 시 50% → 재검증 → 100%. 검증 스크립트가 비정상 종료하면 파이프라인이 이전 리비전으로 되돌린다.
steps:
- shift-traffic: { canary: 10 }
- run: python bake_check.py # 실패 시 exit 1
- shift-traffic: { canary: 50 }
- run: python bake_check.py
- promote: { canary: 100 }
on-failure:
- rollback: previous
주의점
몇 가지 흔한 함정이 있다. 트래픽이 적은 시간대에는 요청 수 자체가 적어 에러율 분모가 작아지므로, 최소 요청 수 조건(예: 창당 500건 미만이면 판단 보류)을 함께 둔다. 또 비교 기준을 '고정 임계값'만 쓰면 평소 지표가 나쁜 시스템에서 무의미해지므로, 가능하면 기존 버전(baseline)과 동시 비교하는 방식이 안전하다. 배포 라벨로 신·구 버전 지표를 분리 집계하지 않으면 두 버전이 섞여 판단이 오염된다. 마지막으로 베이크 타임은 길수록 안전하지만 배포 리드타임을 늘리므로, 서비스 워밍업 특성에 맞춰 3~10분 범위에서 실측으로 조정한다. 롤백은 반드시 자동화하되, 롤백 자체도 헬스 검증을 거치게 해 '롤백했더니 더 나빠지는' 상황을 감지해야 한다.