NGINX 카나리아 배포
이 가이드에서는 NGINX 인그레스 컨트롤러와 Flagger를 사용해 카나리아 배포와 A/B 테스트를 자동화하는 방법을 보여드립니다. NGINX 카나리아 인그레스를 활용해 점진적으로 트래픽을 옮기고 검증하는 전체 과정을 살펴볼게요.
출처: 문서
본문
이 가이드에서는 NGINX 인그레스 컨트롤러와 Flagger를 사용해 카나리아 배포와 A/B 테스트를 자동화하는 방법을 보여드립니다.

사전 요구사항 (Prerequisites)
Flagger는 Kubernetes 클러스터 v1.19 이상과 NGINX ingress v1.0.2 이상이 필요합니다.
Helm v3로 NGINX 인그레스 컨트롤러를 설치합니다:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
kubectl create ns ingress-nginx
helm upgrade -i ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--set controller.metrics.enabled=true \
--set controller.podAnnotations."prometheus\.io/scrape"=true \
--set controller.podAnnotations."prometheus\.io/port"=10254
인그레스 컨트롤러와 같은 네임스페이스에 Flagger와 Prometheus 애드온을 설치합니다:
helm repo add flagger https://flagger.app
helm upgrade -i flagger flagger/flagger \
--namespace ingress-nginx \
--set prometheus.install=true \
--set meshProvider=nginx
부트스트랩 (Bootstrap)
Flagger는 Kubernetes deployment와 선택적으로 horizontal pod autoscaler(HPA)를 받아, 일련의 오브젝트(Kubernetes deployments, ClusterIP services, canary ingress)를 생성합니다. 이 오브젝트들은 클러스터 외부에서 애플리케이션을 노출하고 카나리아 분석과 승격을 구동합니다.
테스트 네임스페이스를 만듭니다:
kubectl create ns test
deployment와 horizontal pod autoscaler를 만듭니다:
kubectl apply -k https://github.com/fluxcd/flagger//kustomize/podinfo?ref=main
카나리아 분석 중 트래픽을 생성할 부하 테스트 서비스를 배포합니다:
helm upgrade -i flagger-loadtester flagger/loadtester \
--namespace=test
인그레스 정의를 만듭니다 (app.example.com을 자신의 도메인으로 바꾸세요):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: podinfo
namespace: test
labels:
app: podinfo
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: "app.example.com"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: podinfo
port:
number: 80
위 리소스를 podinfo-ingress.yaml로 저장한 뒤 적용합니다:
kubectl apply -f ./podinfo-ingress.yaml
canary 커스텀 리소스를 만듭니다 (app.example.com을 자신의 도메인으로 바꾸세요):
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
provider: nginx
# deployment reference
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
# ingress reference
ingressRef:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: podinfo
# HPA reference (optional)
autoscalerRef:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
name: podinfo
# the maximum time in seconds for the canary deployment
# to make progress before it is rollback (default 600s)
progressDeadlineSeconds: 60
service:
# ClusterIP port number
port: 80
# container port number or name
targetPort: 9898
analysis:
# schedule interval (default 60s)
interval: 10s
# max number of failed metric checks before rollback
threshold: 10
# max traffic percentage routed to canary
# percentage (0-100)
maxWeight: 50
# canary increment step
# percentage (0-100)
stepWeight: 5
# NGINX Prometheus checks
metrics:
- name: request-success-rate
# minimum req success rate (non 5xx responses)
# percentage (0-100)
thresholdRange:
min: 99
interval: 1m
# testing (optional)
webhooks:
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 30s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary/token | grep token"
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://app.example.com/"
위 리소스를 podinfo-canary.yaml로 저장한 뒤 적용합니다:
kubectl apply -f ./podinfo-canary.yaml
몇 초 후 Flagger가 canary 오브젝트를 생성합니다:
# applied
deployment.apps/podinfo
horizontalpodautoscaler.autoscaling/podinfo
ingresses.extensions/podinfo
canary.flagger.app/podinfo
# generated
deployment.apps/podinfo-primary
horizontalpodautoscaler.autoscaling/podinfo-primary
service/podinfo
service/podinfo-canary
service/podinfo-primary
ingresses.extensions/podinfo-canary
자동 카나리아 승격 (Automated canary promotion)
Flagger는 HTTP 요청 성공률, 요청 평균 지속 시간, 파드 상태 같은 핵심 성과 지표를 측정하면서 점진적으로 canary로 트래픽을 옮기는 제어 루프를 구현합니다. KPI 분석에 따라 canary는 승격되거나 중단되며, 분석 결과는 Slack 또는 MS Teams에 게시됩니다.

컨테이너 이미지를 업데이트해 카나리아 배포를 트리거합니다:
kubectl -n test set image deployment/podinfo \
podinfod=ghcr.io/stefanprodan/podinfo:6.0.1
Flagger는 배포 리비전이 변경되었음을 감지하고 새 롤아웃을 시작합니다:
kubectl -n test describe canary/podinfo
Status:
Canary Weight: 0
Failed Checks: 0
Phase: Succeeded
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Synced 3m flagger New revision detected podinfo.test
Normal Synced 3m flagger Scaling up podinfo.test
Warning Synced 3m flagger Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
Normal Synced 3m flagger Advance podinfo.test canary weight 5
Normal Synced 3m flagger Advance podinfo.test canary weight 10
Normal Synced 3m flagger Advance podinfo.test canary weight 15
Normal Synced 2m flagger Advance podinfo.test canary weight 20
Normal Synced 2m flagger Advance podinfo.test canary weight 25
Normal Synced 1m flagger Advance podinfo.test canary weight 30
Normal Synced 1m flagger Advance podinfo.test canary weight 35
Normal Synced 55s flagger Advance podinfo.test canary weight 40
Normal Synced 45s flagger Advance podinfo.test canary weight 45
Normal Synced 35s flagger Advance podinfo.test canary weight 50
Normal Synced 25s flagger Copying podinfo.test template spec to podinfo-primary.test
Warning Synced 15s flagger Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test
참고 카나리아 분석 중에 배포에 새 변경 사항을 적용하면 Flagger가 분석을 다시 시작합니다.
모든 canary는 다음과 같이 모니터링할 수 있습니다:
watch kubectl get canaries --all-namespaces
NAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME
test podinfo Progressing 15 2019-05-06T14:05:07Z
prod frontend Succeeded 0 2019-05-05T16:15:07Z
prod backend Failed 0 2019-05-04T17:05:07Z
자동 롤백 (Automated rollback)
카나리아 분석 중에 HTTP 500 오류를 생성해 Flagger가 결함 있는 버전을 일시 중지하고 롤백하는지 테스트할 수 있습니다.
또 다른 카나리아 배포를 트리거합니다:
kubectl -n test set image deployment/podinfo \
podinfod=ghcr.io/stefanprodan/podinfo:6.0.2
HTTP 500 오류를 생성합니다:
watch curl http://app.example.com/status/500
실패한 검사 횟수가 카나리아 분석 임계값에 도달하면 트래픽은 primary로 다시 라우팅되고, canary는 0으로 스케일되며 롤아웃은 실패로 표시됩니다.
kubectl -n test describe canary/podinfo
Status:
Canary Weight: 0
Failed Checks: 10
Phase: Failed
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Synced 3m flagger Starting canary deployment for podinfo.test
Normal Synced 3m flagger Advance podinfo.test canary weight 5
Normal Synced 3m flagger Advance podinfo.test canary weight 10
Normal Synced 3m flagger Advance podinfo.test canary weight 15
Normal Synced 3m flagger Halt podinfo.test advancement success rate 69.17% < 99%
Normal Synced 2m flagger Halt podinfo.test advancement success rate 61.39% < 99%
Normal Synced 2m flagger Halt podinfo.test advancement success rate 55.06% < 99%
Normal Synced 2m flagger Halt podinfo.test advancement success rate 47.00% < 99%
Normal Synced 2m flagger (combined from similar events): Halt podinfo.test advancement success rate 38.08% < 99%
Warning Synced 1m flagger Rolling back podinfo.test failed checks threshold reached 10
Warning Synced 1m flagger Canary failed! Scaling down podinfo.test
커스텀 메트릭 (Custom metrics)
카나리아 분석은 Prometheus 쿼리로 확장할 수 있습니다.
데모 앱은 Prometheus로 계측되어 있으므로, HTTP 요청 지속 시간 히스토그램을 사용해 canary를 검증하는 커스텀 검사를 만들 수 있습니다.
메트릭 템플릿을 만들고 클러스터에 적용합니다:
apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
name: latency
namespace: test
spec:
provider:
type: prometheus
address: http://flagger-prometheus.ingress-nginx:9090
query: |
histogram_quantile(0.99,
sum(
rate(
http_request_duration_seconds_bucket{
kubernetes_namespace="{{ namespace }}",
kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
}[1m]
)
) by (le)
)
카나리아 분석을 편집하고 latency 검사를 추가합니다:
analysis:
metrics:
- name: "latency"
templateRef:
name: latency
thresholdRange:
max: 0.5
interval: 1m
임계값은 500ms로 설정되므로, 지난 1분간의 평균 요청 지속 시간이 0.5초를 넘으면 분석이 실패하고 canary는 승격되지 않습니다.
컨테이너 이미지를 업데이트해 카나리아 배포를 트리거합니다:
kubectl -n test set image deployment/podinfo \
podinfod=ghcr.io/stefanprodan/podinfo:6.0.3
높은 응답 지연 시간을 생성합니다:
watch curl http://app.example.com/delay/2
Flagger 로그를 봅니다:
kubectl -n nginx-ingress logs deployment/flagger -f | jq .msg
Starting canary deployment for podinfo.test
Advance podinfo.test canary weight 5
Advance podinfo.test canary weight 10
Advance podinfo.test canary weight 15
Halt podinfo.test advancement latency 1.20 > 0.5
Halt podinfo.test advancement latency 1.45 > 0.5
Halt podinfo.test advancement latency 1.60 > 0.5
Halt podinfo.test advancement latency 1.69 > 0.5
Halt podinfo.test advancement latency 1.70 > 0.5
Rolling back podinfo.test failed checks threshold reached 5
Canary failed! Scaling down podinfo.test
알림이 구성되어 있다면 Flagger는 canary가 실패한 이유와 함께 알림을 보냅니다.
A/B 테스트
가중치 라우팅 외에도 Flagger는 HTTP 매치 조건을 기반으로 canary로 트래픽을 라우팅하도록 구성할 수 있습니다. A/B 테스트 시나리오에서는 HTTP 헤더나 쿠키를 사용해 특정 사용자 세그먼트를 대상으로 삼게 됩니다. 이는 세션 어피니티가 필요한 프론트엔드 애플리케이션에 특히 유용합니다.

카나리아 분석을 편집해 max/step weight를 제거하고 match 조건과 iterations를 추가합니다:
analysis:
interval: 1m
threshold: 10
iterations: 10
match:
# curl -H 'X-Canary: insider' http://app.example.com
- headers:
x-canary:
exact: "insider"
# curl -b 'canary=always' http://app.example.com
- headers:
cookie:
exact: "canary"
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
webhooks:
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 -H 'Cookie: canary=always' http://app.example.com/"
위 구성은 canary 쿠키가 always로 설정된 사용자나 X-Canary: insider 헤더로 서비스를 호출하는 사용자를 대상으로 10분 동안 분석을 실행합니다.
컨테이너 이미지를 업데이트해 카나리아 배포를 트리거합니다:
kubectl -n test set image deployment/podinfo \
podinfod=ghcr.io/stefanprodan/podinfo:6.0.4
Flagger는 배포 리비전이 변경되었음을 감지하고 A/B 테스트를 시작합니다:
kubectl -n test describe canary/podinfo
Status:
Failed Checks: 0
Phase: Succeeded
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Synced 3m flagger New revision detected podinfo.test
Normal Synced 3m flagger Scaling up podinfo.test
Warning Synced 3m flagger Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
Normal Synced 3m flagger Advance podinfo.test canary iteration 1/10
Normal Synced 3m flagger Advance podinfo.test canary iteration 2/10
Normal Synced 3m flagger Advance podinfo.test canary iteration 3/10
Normal Synced 2m flagger Advance podinfo.test canary iteration 4/10
Normal Synced 2m flagger Advance podinfo.test canary iteration 5/10
Normal Synced 1m flagger Advance podinfo.test canary iteration 6/10
Normal Synced 1m flagger Advance podinfo.test canary iteration 7/10
Normal Synced 55s flagger Advance podinfo.test canary iteration 8/10
Normal Synced 45s flagger Advance podinfo.test canary iteration 9/10
Normal Synced 35s flagger Advance podinfo.test canary iteration 10/10
Normal Synced 25s flagger Copying podinfo.test template spec to podinfo-primary.test
Warning Synced 15s flagger Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test
위 절차는 커스텀 메트릭 검사, 웹훅, 수동 승격 승인, Slack 또는 MS Teams 알림으로 확장할 수 있습니다.