Flagger Webhook으로 불필요한 롤백 없는 카나리 배포 게이팅 구현하기 — Mann-Whitney 통계 검증 서비스 완전 가이드
부제: Kubernetes 카나리 배포 · Python FastAPI · Mann-Whitney U 검정 · Prometheus · A/B 테스트 게이팅
Kubernetes에서 카나리 배포를 운영해본 개발자라면 한 번쯤 이런 경험을 했을 것이다. 에러율이 0.8%에서 1.2%로 올랐다. 롤백해야 할까? 아니면 트래픽 급증이나 일시적인 노이즈일까?
단순 임계값 기반 게이팅은 이 질문에 답하지 못한다. 전체 트래픽의 10%만 카나리로 흘릴 때, 작은 표본은 자연 변동폭(natural variation)—동일한 두 버전을 비교해도 항상 발생하는 랜덤 노이즈—에 취약하다. 실제로 표준편차가 ±30ms인 P99 레이턴시 서비스에서 카나리 표본 50개만으로 판단하면, 실제로 아무 문제가 없어도 임계값을 넘는 확률이 30%를 넘는다. 그 결과 멀쩡한 배포가 롤백되거나(오탐), 실제로 나쁜 배포가 통과되는(미탐) 상황이 반복된다.
이 글에서는 Flagger의 webhooks 인터페이스에 통계적 유의성 검증 서비스를 연결해, Python FastAPI와 Mann-Whitney U 검정으로 자연 변동폭에 강건한 A/B 테스트 게이팅 레이어를 구현하는 방법을 다룬다. 이 글 끝에서 당신의 Flagger CRD에 바로 연결할 수 있는 완성 코드를 가져갈 수 있다.
핵심 개념
이 글을 읽기 전에
이 글은 Flagger를 이미 운영 중이거나 도입을 검토하는 팀을 대상으로 한다. Kubernetes Deployment, Canary CRD, PromQL에 대한 기본 이해를 전제한다. Flagger 자체 입문은 공식 문서를 먼저 참고하길 권한다.
Flagger Webhook 게이팅 메커니즘
Flagger는 카나리 분석 사이클마다 외부 HTTP 엔드포인트를 호출하고, 응답 코드만으로 배포 진행 여부를 결정한다.
HTTP 2xx → 배포 계속 진행 (트래픽 가중치 증가)
HTTP 4xx/5xx → 실패 카운트 증가 → threshold 초과 시 롤백Webhook 타입은 호출 시점과 용도에 따라 구분된다.
| 타입 | 호출 시점 | 주요 용도 |
|---|---|---|
pre-rollout |
트래픽 이동 전 | 사전 품질 확인 (k6 부하 테스트 등) |
rollout |
매 분석 사이클마다 | 실시간 메트릭 기반 게이팅 ← 이 글의 핵심 |
confirm-rollout |
단계 진행 전 수동 승인 | 경계 구간에서 인간 개입 |
rollback |
롤백 발생 시 | 알림·사후 처리 |
핵심 설계 원칙: Flagger는 webhook 응답의 HTTP 상태 코드만 본다. 통계 서비스 내부에서 어떤 복잡한 연산을 수행해도 무방하며, Flagger는 결과를 단순히 200 또는 500으로 받는다. 이 단순한 인터페이스가 외부 통계 서비스 연결을 가능하게 하는 핵심이다.
자연 변동폭 문제와 통계적 해법
P99 레이턴시가 평소 180ms ± 30ms인 서비스에서 카나리의 순간 P99가 210ms를 기록했다면, 이것은 문제일까? 1-시그마 범위 안의 자연 변동일 수도 있다. 통계적 유의성 검증은 이 질문에 수학적으로 답한다.
- p-value: 귀무가설(두 분포가 동일하다)이 참이라면, 이 정도 혹은 더 극단적인 차이가 우연히 관측될 확률. p < 0.05는 "귀무가설이 참일 때 이런 결과가 관측될 확률이 5% 미만"을 의미하며, 이것이 낮을수록 실제 차이가 존재할 가능성이 높다고 볼 수 있다
- 신뢰 구간(CI): 효과 크기의 불확실성 범위. 샘플이 클수록 CI가 좁아진다
- 통계 검력(Statistical Power): 실제 차이가 있을 때 이를 탐지할 확률. 트래픽이 적으면 낮아진다
어떤 검정 방법을 선택할까
레이턴시, 에러율 같은 인프라 메트릭은 정규 분포를 따르지 않는다. 긴 꼬리(long tail)가 있고 이상값(outlier)에 민감하다.
| 검정 방법 | 분포 가정 | 적합한 메트릭 | 특징 |
|---|---|---|---|
| Mann-Whitney U | 없음 (비모수) | 레이턴시, 에러율 | Netflix Kayenta 채택, 이상값에 강건 |
| Welch's t-test | 정규 분포 | 응답 크기 등 | 등분산 가정 불필요, 표준 t-test보다 안전 |
| Bayesian A/B | 없음 | 전환율, CTR | "카나리가 더 좋을 확률"로 직관적 해석 |
Mann-Whitney U 검정: 두 독립 표본의 분포를 원래 값이 아닌 순위(rank) 기반으로 비교하는 비모수 검정이다. 정규성 가정이 없어 레이턴시처럼 왜곡된 분포에 적합하며, 이상값 하나에 결과가 크게 흔들리지 않는다. Netflix Kayenta가 이 방법을 핵심 검정으로 채택한 이유다.
Bayesian A/B 테스트 간략 소개: 전환율·에러율처럼 이진 결과를 다루는 메트릭에는 Beta-Bernoulli 모델이 잘 맞는다. 사전 분포로 Beta(1,1)(균등 사전, 사전 지식 없음)을 두고, 성공/실패 횟수를 관측할수록 Beta(α+성공, β+실패)로 사후 분포가 업데이트된다. "카나리의 에러율이 베이스라인보다 낮을 확률"을 직접 계산할 수 있어, p-value 없이 직관적인 의사결정이 가능한 것이 강점이다.
아키텍처 개요
통계 게이팅 레이어의 전체 흐름은 다음과 같다.
flowchart LR
F[Flagger Controller] -->|"POST /check\n{name, namespace, metadata}"| S["Statistical\nSignificance Service\n(FastAPI)"]
S -->|PromQL 쿼리| P[(Prometheus)]
P -->|분위수 시계열 반환| S
S -->|"HTTP 200: pass\n{p_value, effect_size}"| F
S -->|"HTTP 500: fail\n{degradation_detected}"| F
F -->|트래픽 증가| C[카나리 Pod]
F -->|롤백| B[베이스라인 Pod]각 컴포넌트의 역할:
| 컴포넌트 | 역할 | 구현 |
|---|---|---|
| Flagger Controller | 카나리 사이클 관리, webhook 호출 | Flagger CRD |
| Statistical Significance Service | 메트릭 수집 + 통계 검정 + 판정 | Python FastAPI |
| Prometheus | 카나리/베이스라인 메트릭 저장 | 기존 모니터링 스택 |
실전 적용
구현 옵션 A: Python + FastAPI 기반 Mann-Whitney 게이트
Flagger가 호출할 /check 엔드포인트를 FastAPI로 구현한다.
Prometheus 데이터 수집의 현실적 제약: Prometheus는 개별 요청 레이턴시를 저장하지 않는다. rate(http_request_duration_seconds_bucket[5m])은 히스토그램 버킷의 누적 비율을 반환할 뿐, Mann-Whitney에 직접 넣을 수 있는 개별 요청 샘플을 반환하지 않는다. 실제 요청 단위 샘플이 필요하다면 Loki 같은 로그 기반 파이프라인을 함께 사용해야 한다. 여기서는 현실적인 대안으로 히스토그램의 여러 분위수를 근사 분포 샘플로 수집하는 방법을 사용한다.
# stat_gate/main.py
import os
import logging
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from scipy.stats import mannwhitneyu # 비모수 검정 라이브러리
from prometheus_api_client import PrometheusConnect # Prometheus HTTP API 클라이언트
from prometheus_client import Gauge, start_http_server # 커스텀 메트릭 노출용
import numpy as np
app = FastAPI()
logger = logging.getLogger(__name__)
# PROMETHEUS_URL: Kubernetes 클러스터 내부에서는 서비스 FQDN을 사용하세요.
# 형식: http://<서비스명>.<네임스페이스>.svc.cluster.local:<포트>
prom = PrometheusConnect(
url=os.getenv(
"PROMETHEUS_URL",
"http://prometheus-server.monitoring.svc.cluster.local:9090"
)
)
# Grafana 시각화를 위한 커스텀 메트릭 노출 (포트 8001)
# Prometheus가 이 포트를 스크레이프하도록 ServiceMonitor를 추가하세요.
gate_p_value = Gauge("canary_gate_p_value", "Mann-Whitney p-value", ["canary", "namespace"])
gate_effect_ms = Gauge("canary_gate_effect_size_ms", "Effect size (ms)", ["canary", "namespace"])
start_http_server(8001)
class GateRequest(BaseModel):
name: str
namespace: str
metadata: dict = {}
def fetch_quantile_samples(
metric_base: str,
pod_selector: str,
duration: str = "5m",
) -> list[float]:
"""
Prometheus 히스토그램에서 여러 분위수를 수집하여 근사 분포 샘플로 반환.
주의: Prometheus는 개별 요청 레이턴시를 보관하지 않습니다.
p10~p99 분위수 7개를 근사 샘플로 사용합니다.
보다 정확한 분포 비교가 필요하다면 Loki 등 로그 기반 파이프라인에서
실제 요청 샘플을 수집하는 방식을 권장합니다.
"""
quantiles = [0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99]
samples = []
for q in quantiles:
query = (
f"histogram_quantile({q}, "
f"rate({metric_base}_bucket{{{pod_selector}}}[{duration}]))"
)
try:
result = prom.custom_query(query)
for r in result:
val = r["value"][1]
if val not in ("NaN", "+Inf", "-Inf"):
samples.append(float(val))
except Exception as e:
logger.warning("Prometheus 쿼리 실패 (q=%s): %s", q, e)
return samples
def run_mann_whitney(
canary: list[float],
baseline: list[float],
) -> tuple[float, float]:
"""Mann-Whitney U 검정을 수행하고 (p_value, effect_size_seconds)를 반환."""
stat, p_value = mannwhitneyu(
baseline,
canary,
alternative="less", # 단측 검정: "캐너리가 베이스라인보다 나쁜가"만 검정 → 검력 향상
)
effect_size = float(np.median(canary) - np.median(baseline))
return p_value, effect_size
@app.post("/check")
async def statistical_gate(req: GateRequest):
window = req.metadata.get("window", "5m")
alpha = float(req.metadata.get("alpha", "0.05"))
metric = req.metadata.get("metric", "http_request_duration_seconds")
canary_selector = f'pod=~"{req.name}-[0-9]+-.*",namespace="{req.namespace}"'
baseline_selector = f'pod=~"{req.name}-primary-[0-9]+-.*",namespace="{req.namespace}"'
try:
canary_samples = fetch_quantile_samples(metric, canary_selector, window)
baseline_samples = fetch_quantile_samples(metric, baseline_selector, window)
except Exception as e:
logger.error("메트릭 수집 중 오류 발생: %s", e)
# Prometheus 장애 시 기본 동작: 통과 처리 (fail-open).
# 운영 환경에서는 팀 정책에 따라 fail-closed(500 반환)로 변경 가능.
return {"result": "metrics_unavailable", "detail": str(e)}
if len(canary_samples) < 5 or len(baseline_samples) < 5:
# 표본 부족 → 아직 판단 불가, 데이터 수집을 더 기다림 (통과 처리)
return {
"result": "insufficient_data",
"canary_n": len(canary_samples),
"baseline_n": len(baseline_samples),
}
p_value, effect_size = run_mann_whitney(canary_samples, baseline_samples)
# Prometheus 커스텀 메트릭 업데이트 (Grafana 시각화용)
gate_p_value.labels(canary=req.name, namespace=req.namespace).set(p_value)
gate_effect_ms.labels(canary=req.name, namespace=req.namespace).set(effect_size * 1000)
if p_value < alpha:
raise HTTPException(
status_code=500,
detail={
"result": "degradation_detected",
"p_value": round(p_value, 4),
"effect_size_ms": round(effect_size * 1000, 2),
"alpha": alpha,
},
)
return {
"result": "pass",
"p_value": round(p_value, 4),
"effect_size_ms": round(effect_size * 1000, 2),
"canary_n": len(canary_samples),
"baseline_n": len(baseline_samples),
}주요 코드 포인트:
| 코드 위치 | 의도 |
|---|---|
alternative="less" |
단측 검정. "캐너리가 더 나쁜가"만 검정하여 동일 alpha로 검력을 두 배 높임 |
n < 5 조건 |
분위수 기반 샘플이 부족하면 판단 불가 → 통과 처리 |
try/except + fail-open |
Prometheus 장애 시 배포를 차단하지 않는 기본 정책 (팀 정책에 따라 조정) |
start_http_server(8001) |
p_value, effect_size를 Prometheus 메트릭으로 노출 → Grafana 대시보드 연동 |
raise HTTPException(500) |
Flagger가 인식하는 실패 신호 |
구현 옵션 B: Flagger Canary CRD 설정
통계 서비스를 rollout 타입 webhook으로 연결한다. 분석 사이클마다 통계 게이트를 통과해야 트래픽이 증가한다.
# canary.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: my-service
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: my-service
service:
port: 80
analysis:
interval: 1m # 60초마다 분석 사이클 실행
threshold: 3 # 3회 연속 실패 시 롤백
maxWeight: 50 # 최대 50% 트래픽까지 증가
stepWeight: 10 # 사이클마다 10%씩 증가
webhooks:
- name: statistical-gate
type: rollout # 매 분석 사이클마다 호출
url: http://stat-svc.monitoring.svc/check
timeout: 30s # Prometheus 쿼리 시간을 고려해 30s 이상 권장
metadata:
window: "5m"
alpha: "0.05"
metric: "http_request_duration_seconds"
- name: pre-load-test
type: pre-rollout # 최초 트래픽 이동 전 한 번만
url: http://flagger-k6-webhook.monitoring.svc/launch
timeout: 120s
metadata:
script: configmap/k6-script/test.js
threshold: 3의 의미: 통계 게이트가 3회 연속 HTTP 500을 반환해야 롤백이 트리거된다. 1~2회의 일시적 실패(네트워크 지연, Prometheus 과부하 등)로는 롤백되지 않는다.alpha수준과 함께 이 값도 민감도를 조절하는 핵심 파라미터다.
구현 옵션 C: 경계 구간 하이브리드 수동 승인 게이팅
p-value가 0.05~0.15 사이의 "경계 구간"일 때는 자동 판단 대신 팀에 알리고 수동 승인을 대기하는 패턴이다.
# stat_gate/main.py — confirm-rollout 엔드포인트 추가
import httpx
SLACK_WEBHOOK_URL = os.getenv("SLACK_WEBHOOK_URL", "")
# ⚠️ 경고: 아래 딕셔너리는 프로세스 재시작 시 초기화됩니다.
# 프로덕션에서는 반드시 Redis 또는 외부 스토어로 교체하세요.
APPROVAL_STORE: dict[str, bool] = {}
async def _run_check(req: GateRequest) -> float:
"""/check 엔드포인트와 동일한 로직으로 p-value를 반환하는 헬퍼."""
metric = req.metadata.get("metric", "http_request_duration_seconds")
window = req.metadata.get("window", "5m")
canary_selector = f'pod=~"{req.name}-[0-9]+-.*",namespace="{req.namespace}"'
baseline_selector = f'pod=~"{req.name}-primary-[0-9]+-.*",namespace="{req.namespace}"'
canary_samples = fetch_quantile_samples(metric, canary_selector, window)
baseline_samples = fetch_quantile_samples(metric, baseline_selector, window)
if len(canary_samples) < 5 or len(baseline_samples) < 5:
return 0.5 # 표본 부족 → 중립값 반환
p_value, _ = run_mann_whitney(canary_samples, baseline_samples)
return p_value
@app.post("/confirm")
async def confirm_gate(req: GateRequest):
"""confirm-rollout 타입 webhook 엔드포인트."""
key = f"{req.namespace}/{req.name}"
# 이미 수동 승인된 경우 즉시 통과
if APPROVAL_STORE.get(key):
del APPROVAL_STORE[key]
return {"result": "approved"}
try:
p_value = await _run_check(req)
except Exception as e:
logger.error("confirm 게이트 p-value 계산 실패: %s", e)
raise HTTPException(status_code=500, detail={"result": "check_failed"})
if p_value < 0.05:
return {"result": "auto_pass", "p_value": round(p_value, 4)}
elif p_value > 0.15:
raise HTTPException(
status_code=500,
detail={"result": "auto_fail", "p_value": round(p_value, 4)},
)
else:
# 경계 구간: Slack 알림 후 대기
await _notify_slack(key, p_value)
raise HTTPException(
status_code=500,
detail={"result": "pending_approval", "p_value": round(p_value, 4)},
)
@app.post("/approve/{namespace}/{name}")
async def approve(namespace: str, name: str):
"""Slack 봇 또는 관리자 UI에서 수동 승인 시 호출."""
APPROVAL_STORE[f"{namespace}/{name}"] = True
return {"result": "stored"}
async def _notify_slack(key: str, p_value: float):
if not SLACK_WEBHOOK_URL:
return
message = {
"text": (
f":warning: *{key}* 카나리 p-value={p_value:.3f} (경계 구간 0.05~0.15). "
f"수동 승인 필요: `POST /approve/{key}`"
)
}
async with httpx.AsyncClient() as client:
await client.post(SLACK_WEBHOOK_URL, json=message)# canary.yaml — confirm-rollout webhook 추가
webhooks:
- name: statistical-gate
type: rollout
url: http://stat-svc.monitoring.svc/check
timeout: 30s
metadata:
window: "5m"
alpha: "0.05"
- name: human-in-the-loop
type: confirm-rollout # 각 단계 진행 전 승인 확인
url: http://stat-svc.monitoring.svc/confirm
timeout: 1h # 최대 1시간 수동 승인 대기구현 옵션 D: Kayenta 스타일 이중 게이트 (p-value + 효과 크기)
p-value만으로는 부족하다. 표본이 매우 크면 실질적으로 무의미한 작은 차이도 통계적으로 유의하게 판정된다. Netflix Kayenta는 98% 신뢰 구간이 허용 오차 밴드를 벗어났을 때만 유의한 차이로 판정하는 이중 게이트를 사용한다.
# stat_gate/kayenta_style.py
from scipy.stats import mannwhitneyu, bootstrap # bootstrap: 비모수 신뢰 구간 계산
import numpy as np
def kayenta_style_check(
baseline: list[float],
canary: list[float],
alpha: float = 0.02, # Kayenta 기본값: 98% CI (alpha=0.02)
allowed_increase_ratio: float = 0.1 # 중앙값이 10% 이상 증가 시 유의
) -> tuple[bool, dict]:
"""
Kayenta의 핵심 통계 검정 방식과 동일한 이중 게이트:
1. Mann-Whitney U p-value < alpha
2. 효과 크기(중앙값 차이 비율) > allowed_increase_ratio
두 조건 모두 충족 시에만 실패 판정.
"""
stat, p_value = mannwhitneyu(baseline, canary, alternative="less")
baseline_median = np.median(baseline)
canary_median = np.median(canary)
effect_ratio = (canary_median - baseline_median) / (baseline_median + 1e-10)
# 95% 신뢰 구간 계산 (bootstrap)
# scipy.stats.bootstrap의 statistic 함수는 반드시 (*data, axis) 시그니처를 따라야 합니다.
# (b, c, axis) 형태로 작성하면 실행 시 TypeError가 발생합니다.
def diff_medians(*arrays, axis):
return np.median(arrays[1], axis=axis) - np.median(arrays[0], axis=axis)
data = (np.array(baseline), np.array(canary))
ci_result = bootstrap(
data,
diff_medians,
confidence_level=0.95,
n_resamples=1000,
random_state=42,
)
ci_low, ci_high = ci_result.confidence_interval
# 이중 게이트: 통계적으로 유의 AND 효과 크기도 임계값 초과
is_degradation = (p_value < alpha) and (effect_ratio > allowed_increase_ratio)
return not is_degradation, {
"p_value": round(p_value, 4),
"effect_ratio": round(effect_ratio, 4),
"ci_95": [round(ci_low, 4), round(ci_high, 4)],
"allowed_increase_ratio": allowed_increase_ratio,
}장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 노이즈 강건성 | 자연 변동에 의한 오탐을 통계적으로 제거하여 불필요한 롤백 방지 |
| 확장성 | Flagger의 기존 webhook 인터페이스를 그대로 사용. CRD 변경 없이 도입 가능 |
| 투명성 | p-value, 신뢰 구간을 JSON으로 반환하여 감사 추적(audit trail) 자동 구성 |
| 유연성 | 메트릭 종류(레이턴시, 에러율, CTR)별로 다른 검정 방법 적용 가능 |
| 독립성 | 통계 서비스가 별도 마이크로서비스이므로 Flagger 업그레이드와 무관하게 검정 로직 변경 가능 |
| ML 배포 적합성 | LLM·추천 모델의 비즈니스 메트릭(전환율, CTR)까지 동일 패턴으로 게이팅 가능 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 표본 크기 의존성 | 트래픽이 적으면 통계 검력 부족으로 False Negative 발생 | 표본 부족 시 통과 처리; CUPED로 필요 표본 수 절감 |
| 다중 비교 문제 | 여러 메트릭 동시 검정 시 Type I 오류 증가 | Bonferroni 또는 Holm-Bonferroni 보정 적용 |
| Sequential Testing 함정 | 반복 검정 시 alpha 인플레이션 발생 | Alpha Spending 함수 적용 필수 (다음 글에서 다룸) |
| Webhook 타임아웃 | 타임아웃이 짧으면 메트릭 수집 전에 실패 판정 | timeout: 30s 이상 설정; Prometheus 쿼리 최적화 |
| 운영 복잡도 | 통계 서비스 자체의 가용성·모니터링 추가 | HPA로 오토스케일링; /health 헬스체크 엔드포인트 구성 |
| Alpha 수준 선택 | 너무 낮으면 유효 배포 차단, 너무 높으면 나쁜 배포 통과 | 초기 0.05로 시작; 롤백 이력 기반으로 점진 조정 |
| APPROVAL_STORE 휘발성 | 인메모리 딕셔너리는 프로세스 재시작 시 승인 데이터 유실 | 프로덕션에서는 Redis로 반드시 교체 |
실무에서 가장 흔한 실수
- 표본 부족 상태에서 즉시 판단: 카나리 트래픽이 5% 미만일 때는 30분 동안 충분한 데이터가 쌓이지 않는다. 최소 표본 크기 가드 없이 검정하면 검력 부족으로 나쁜 배포가 통과된다. 반드시
n < threshold조건으로 통과 처리하는 가드를 구현할 것. - 양측 검정(two-tailed)으로 성능 저하 검정: "카나리가 베이스라인보다 나쁜가"를 검정할 때 양측 검정을 사용하면 동일한 alpha에서 검력이 절반이 된다.
alternative="less"단측 검정을 사용해야 한다. - Webhook timeout을 Prometheus 쿼리 시간보다 짧게 설정: Prometheus가 고부하 상태이면 집계 쿼리가 10~15초 걸릴 수 있다.
timeout: 5s로 설정하면 데이터 수집 전에 타임아웃이 발생하고 Flagger는 이를 실패로 간주한다. 최소timeout: 30s로 설정하라.
마치며
Flagger의 webhook 인터페이스에 통계적 유의성 검증 서비스를 연결하면, Kayenta의 핵심 통계 검정 방식과 동일한 수준의 엄밀성을 단 하나의 마이크로서비스와 YAML 수십 줄로 자체 인프라에 구현할 수 있다.
지금 바로 시작할 수 있는 3단계:
- 로컬에서 통계 서비스 검증:
pip install fastapi scipy prometheus-api-client uvicorn prometheus-client httpx로 환경 구성 후uvicorn main:app --reload로 실행. 아래 명령으로 응답 확인: curl -X POST localhost:8000/check \ -H "Content-Type: application/json" \ -d '{"name":"test","namespace":"default","metadata":{"window":"5m","alpha":"0.05","metric":"http_request_duration_seconds"}}'- 스테이징 클러스터에
rolloutwebhook 연결: 기존 Canary CRD에webhooks블록을 추가하고type: rollout,threshold: 3으로 설정. 처음에는alpha: "0.10"으로 느슨하게 시작해 롤백 빈도를 관찰하며 조정한다. - Grafana 대시보드로 검정 결과 시각화: 구현 옵션 A의 코드는 이미 포트 8001에
canary_gate_p_value,canary_gate_effect_size_ms메트릭을 노출한다. Prometheus가 이 포트를 스크레이프하도록 ServiceMonitor를 추가하면, Grafana에서 카나리 사이클별 p-value 추이를 즉시 시각화할 수 있다. 이 데이터가 쌓이면 alpha 수준 튜닝의 근거가 된다.
더 알고 싶다면
CUPED (Controlled-experiment Using Pre-Existing Data): 배포 전 기간의 메트릭 데이터를 공변량으로 사용해 분산을 줄이는 기법. 동일한 트래픽으로 더 빠르게 통계적 유의성에 도달할 수 있어, 트래픽이 적은 서비스에서 특히 유용하다. Optimizely, Kameleoon이 기본 통계 방법으로 채택했다.
Alpha Spending 함수: Sequential Testing에서 전체 실험 기간에 걸쳐 사용할 총 alpha 예산을 분할하는 방법. Flagger 게이팅 레이어에 적용하면 분석 사이클마다 반복 검정을 해도 전체 Type I 오류율을 목표 alpha 이하로 제어할 수 있다. 다음 글에서 구현까지 다룬다.
다음 편
Sequential Testing과 Alpha Spending 함수를 Flagger 게이팅 레이어에 적용해 조기 탈출(early stopping)을 구현하는 방법 — 더 빠른 롤백, 더 적은 사용자 피해
참고 자료
- Flagger Webhooks | 공식 문서
- Flagger Deployment Strategies (A/B Testing) | 공식 문서
- Flagger GitHub | fluxcd/flagger
- Automated Canary Analysis at Netflix with Kayenta | Netflix TechBlog
- How Canary Judgment Works (Mann-Whitney U) | Spinnaker
- Kayenta Canary Config 문서 | GitHub
- Grafana flagger-k6-webhook | GitHub
- Istio A/B Testing Tutorial | Flagger
- CUPED in A/B Testing | Optimizely
- Best Statistical Model for A/B Testing | AB Tasty
- Argo Rollouts Analysis & Progressive Delivery | 공식 문서
- A/B Testing with Linkerd and Flagger | InfraCloud