Linkerd 카나리 배포와 A/B 테스트 자동화

이 가이드에서는 Linkerd와 Flagger를 활용하여 카나리 배포와 A/B 테스트를 자동화하는 방법을 설명합니다.

사전 요구사항

Flagger는 Kubernetes 클러스터 v1.16 이상과 Linkerd 2.10 이상을 필요로 합니다.

Linkerd와 Prometheus(Linkerd Viz 구성요소)를 설치합니다:

linkerd install | kubectl apply -f -
linkerd viz install | kubectl apply -f -

linkerd 네임스페이스에 Flagger를 설치합니다:

kubectl apply -k github.com/fluxcd/flagger//kustomize/linkerd

초기 설정

Flagger는 Kubernetes 디플로이먼트와 선택적 Horizontal Pod Autoscaler(HPA)를 기반으로 하여 일련의 객체(Kubernetes 디플로이먼트, ClusterIP 서비스, SMI 트래픽 스플릿)를 생성합니다. 이러한 객체들은 애플리케이션을 메시 네트워크 내부에 노출시키고 카나리 분석 및 프로모션을 관리합니다.

test 네임스페이스를 생성하고 Linkerd 프록시 주입을 활성화합니다:

kubectl create ns test
kubectl annotate namespace test linkerd.io/inject=enabled

카나리 분석 중 트래픽을 생성하기 위한 로드 테스트 서비스를 설치합니다:

kubectl apply -k https://github.com/fluxcd/flagger//kustomize/tester?ref=main

디플로이먼트와 Horizontal Pod Autoscaler를 생성합니다:

kubectl apply -k https://github.com/fluxcd/flagger//kustomize/podinfo?ref=main

podinfo 디플로이먼트에 대한 Canary 커스텀 리소스를 생성합니다:

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: podinfo
  namespace: test
spec:
  # 디플로이먼트 참조
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: podinfo
  # HPA 참조 (선택사항)
  autoscalerRef:
    apiVersion: autoscaling/v2beta2
    kind: HorizontalPodAutoscaler
    name: podinfo
  # 카나리 디플로이먼트가 롤백되기 전에 진행해야 하는 최대 시간(초)
  # 기본값: 600초
  progressDeadlineSeconds: 60
  service:
    # ClusterIP 포트 번호
    port: 9898
    # 컨테이너 포트 번호 또는 이름 (선택사항)
    targetPort: 9898
  analysis:
    # 스케줄 간격 (기본값: 60초)
    interval: 30s
    # 롤백 전 최대 실패 메트릭 체크 횟수
    threshold: 5
    # 카나리로 라우팅되는 최대 트래픽 백분율 (0-100)
    maxWeight: 50
    # 카나리 증가 단계 백분율 (0-100)
    stepWeight: 5
    # Linkerd Prometheus 체크
    metrics:
    - name: request-success-rate
      # 최소 요청 성공률 (5xx가 아닌 응답)
      # 백분율 (0-100)
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      # 최대 요청 지연 시간 P99 (밀리초)
      thresholdRange:
        max: 500
      interval: 30s
    # 테스트 (선택사항)
    webhooks:
      - name: acceptance-test
        type: pre-rollout
        url: http://flagger-loadtester.test/
        timeout: 30s
        metadata:
          type: bash
          cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
      - name: load-test
        type: rollout
        url: http://flagger-loadtester.test/
        metadata:
          cmd: "hey -z 2m -q 10 -c 2 http://podinfo-canary.test:9898/"

위를 podinfo-canary.yaml로 저장한 후 적용합니다:

kubectl apply -f ./podinfo-canary.yaml

카나리 분석이 시작되면 Flagger는 트래픽을 카나리로 라우팅하기 전에 pre-rollout 웹후크를 호출합니다. 카나리 분석은 5분간 실행되며, 매 30초마다 HTTP 메트릭과 롤아웃 훅을 검증합니다.

몇 초 후 Flagger가 카나리 객체를 생성합니다:

# 적용됨
deployment.apps/podinfo
horizontalpodautoscaler.autoscaling/podinfo
ingresses.extensions/podinfo
canary.flagger.app/podinfo

# 생성됨
deployment.apps/podinfo-primary
horizontalpodautoscaler.autoscaling/podinfo-primary
service/podinfo
service/podinfo-canary
service/podinfo-primary
trafficsplits.split.smi-spec.io/podinfo

초기화 후, podinfo 디플로이먼트는 0으로 스케일링되고, podinfo.test로의 트래픽은 기본 Pod로 라우팅됩니다. 카나리 분석 기간 중에는 podinfo-canary.test 주소를 사용하여 카나리 Pod에 직접 접근할 수 있습니다.

자동 카나리 프로모션

Flagger는 HTTP 요청成功率, 요청 평균 지속 시간, Pod 건강 상태 등 핵심 성능 지표를 측정하면서 점진적으로 트래픽을 카나리로 이전하는 제어 루프를 구현합니다. KPI 분석에 따라 카나리를 승격하거나 중단하고, 분석 결과를 Slack에 게시합니다.

카나리 디플로이먼트를 트리거하려면 컨테이너 이미지를 업데이트합니다:

kubectl -n test set image deployment/podinfo \
podinfod=stefanprodan/podinfo:3.1.1

Flagger가 디플로이먼트 리비전 변경을 감지하고 새로운 배포를 시작합니다:

kubectl -n test describe canary/podinfo

Status:
  Canary Weight:         0
  Failed Checks:         0
  Phase:                 Succeeded
Events:
 New revision detected! Scaling up podinfo.test
 Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
 Pre-rollout check acceptance-test passed
 Advance podinfo.test canary weight 5
 Advance podinfo.test canary weight 10
 Advance podinfo.test canary weight 15
 Advance podinfo.test canary weight 20
 Advance podinfo.test canary weight 25
 Waiting for podinfo.test rollout to finish: 1 of 2 updated replicas are available
 Advance podinfo.test canary weight 30
 Advance podinfo.test canary weight 35
 Advance podinfo.test canary weight 40
 Advance podinfo.test canary weight 45
 Advance podinfo.test canary weight 50
 Copying podinfo.test template spec to podinfo-primary.test
 Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
 Promotion completed! Scaling down podinfo.test

카나리 분석 기간 중 디플로이먼트에 새 변경을 적용하면 Flagger가 분석을 다시 시작합니다.

카나리 배포는 다음 객체의 변경으로 트리거됩니다:

  • 디플로이먼트 PodSpec (컨테이너 이미지, 커맨드, 포트, 환경변수, 리소스 등)
  • 볼륨으로 마운트되거나 환경변수로 매핑된 ConfigMaps
  • 볼륨으로 마운트되거나 환경변수로 매핑된 Secrets

모든 카나리를 모니터링하려면:

watch kubectl get canaries --all-namespaces

NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
test        podinfo   Progressing   15       2019-06-30T14:05:07Z
prod        frontend  Succeeded     0        2019-06-30T16:15:07Z
prod        backend   Failed        0        2019-06-30T17:05:07Z

자동 롤백

카나리 분석 기간中にHTTP 500 오류와 높은 지연 시간을 발생시켜 Flagger가 장애 버전을 일시 중지하고 롤백하는지 테스트할 수 있습니다.

別のカ.na리 デプロイメントをトリガーします:

kubectl -n test set image deployment/podinfo \
podinfod=stefanprodan/podinfo:3.1.2

로드테스터 Pod에서 다음 명령어를 실행합니다:

kubectl -n test exec -it flagger-loadtester-xx-xx sh

HTTP 500 오류를 생성합니다:

watch -n 1 curl http://podinfo-canary.test:9898/status/500

지연 시간을 생성합니다:

watch -n 1 curl http://podinfo-canary.test:9898/delay/1

실패한 체크 횟수가 카나리 분석 임계값에 도달하면 트래픽은 기본 서버로 라우팅되고, 카나리는 0으로 스케일링되며, 롤아웃은 실패로 표시됩니다.

kubectl -n test describe canary/podinfo

Status:
  Canary Weight:         0
  Failed Checks:         10
  Phase:                 Failed
Events:
 Starting canary analysis for podinfo.test
 Pre-rollout check acceptance-test passed
 Advance podinfo.test canary weight 5
 Advance podinfo.test canary weight 10
 Advance podinfo.test canary weight 15
 Halt podinfo.test advancement success rate 69.17% < 99%
 Halt podinfo.test advancement success rate 61.39% < 99%
 Halt podinfo.test advancement success rate 55.06% < 99%
 Halt podinfo.test advancement request duration 1.20s > 0.5s
 Halt podinfo.test advancement request duration 1.45s > 0.5s
 Rolling back podinfo.test failed checks threshold reached 5
 Canary failed! Scaling down podinfo.test

사용자 정의 메트릭

카나리 분석은 Prometheus 쿼리로 확장할 수 있습니다.

404 에러 검사를 정의해 보겠습니다. 카나리 분석을 편집하여 다음 메트릭을 추가합니다:

  analysis:
    metrics:
    - name: "404s percentage"
      threshold: 3
      query: |
        100 - sum(
            rate(
                response_total{
                    namespace="test",
                    deployment="podinfo",
                    status_code!="404",
                    direction="inbound"
                }[1m]
            )
        )
        /
        sum(
            rate(
                response_total{
                    namespace="test",
                    deployment="podinfo",
                    direction="inbound"
                }[1m]
            )
        )
        * 100

위 구성은 HTTP 404 요청/초 비율이 총 트래픽의 3% 미만인지 확인하여 카나리 버전을 검증합니다. 404s율이 3% 임계값에 도달하면 분석이 중단되고 카나리는 실패로 표시됩니다.

카나리 디플로이먼트를 트리거하려면 컨테이너 이미지를 업데이트합니다:

kubectl -n test set image deployment/podinfo \
podinfod=stefanprodan/podinfo:3.1.3

404를 생성합니다:

watch -n 1 curl http://podinfo-canary:9898/status/404

Flagger 로그를 모니터링합니다:

kubectl -n linkerd logs deployment/flagger -f | jq .msg

Starting canary deployment for podinfo.test
Pre-rollout check acceptance-test passed
Advance podinfo.test canary weight 5
Halt podinfo.test advancement 404s percentage 6.20 > 3
Halt podinfo.test advancement 404s percentage 6.45 > 3
Halt podinfo.test advancement 404s percentage 7.22 > 3
Halt podinfo.test advancement 404s percentage 6.50 > 3
Halt podinfo.test advancement 404s percentage 6.34 > 3
Rolling back podinfo.test failed checks threshold reached 5
Canary failed! Scaling down podinfo.test

Slack이 구성되어 있으면 Flagger가 카나리 실패 이유를 설명하는 알림을 보냅니다.

Linkerd 인그레스

Flagger와 Linkerd 호환 가능한 인그레스 컨트롤러는 NGINX와 Gloo 두 가지가 있습니다.

NGINX를 설치합니다:

helm upgrade -i nginx-ingress stable/nginx-ingress \
--namespace ingress-nginx

podinfo에 대한 인그레스를 생성하고, 수신 헤더를 내부 서비스 이름으로 재작성합니다(Linkerd 필요):

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: podinfo
  namespace: test
  labels:
    app: podinfo
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header l5d-dst-override $service_name.$namespace.svc.cluster.local:9898;
      proxy_hide_header l5d-remote-ip;
      proxy_hide_header l5d-server-id;
spec:
  rules:
    - host: app.example.com
      http:
        paths:
          - backend:
              serviceName: podinfo
              servicePort: 9898

인그레스 컨트롤러를 사용할 때, NGINX가 메시 외부에서 실행되므로 Linkerd 트래픽 스플릿이 수신 트래픽에 적용되지 않습니다. 프론트엔드 애플리케이션에 대한 카나리 분석을 실행하기 위해 Flagger는 shadow 인gress를 생성하고 NGINX 전용 어노테이션을 설정합니다.

A/B 테스트

가중 라우팅 외에도 Flagger는 HTTP 매칭 조건에 따라 트래픽을 카나리로 라우팅하도록 구성할 수 있습니다. A/B 테스트 시나리오에서는 HTTP 헤더 또는 쿠키를 사용하여 특정 사용자 그룹을 타겟팅합니다. 이는 세션 affinity가 필요한 프론트엔드 애플리케이션에 특히 유용합니다.

podinfo 카나리 분석을 편집하여 공급자를 nginx로 설정하고, 인그레스 참조를 추가하고, max/step 가중치를 제거하고 매칭 조건과 반복 횟수를 추가합니다:

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: podinfo
  namespace: test
spec:
  # 인그레스 참조
  provider: nginx
  ingressRef:
    apiVersion: extensions/v1beta1
    kind: Ingress
    name: podinfo
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: podinfo
  autoscalerRef:
    apiVersion: autoscaling/v2beta2
    kind: HorizontalPodAutoscaler
    name: podinfo
  service:
    # 컨테이너 포트
    port: 9898
  analysis:
    interval: 1m
    threshold: 10
    iterations: 10
    match:
      # curl -H 'X-Canary: always' http://app.example.com
      - headers:
          x-canary:
            exact: "always"
      # curl -b 'canary=always' http://app.example.com
      - headers:
          cookie:
            exact: "canary"
    # Linkerd Prometheus 체크
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500
      interval: 30s
    webhooks:
      - name: acceptance-test
        type: pre-rollout
        url: http://flagger-loadtester.test/
        timeout: 30s
        metadata:
          type: bash
          cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
      - name: load-test
        type: rollout
        url: http://flagger-loadtester.test/
        metadata:
          cmd: "hey -z 2m -q 10 -c 2 -H 'Cookie: canary=always' http://app.example.com"

위 구성은 10분간 분석을 실행하며, 대상 사용자는 canary 쿠키가 always로 설정되거나 X-Canary: always 헤더로 서비스를 호출하는 사용자입니다.

로드 테스트가 이제 외부 주소를 대상으로 하고 canary 쿠키를 사용하는 것을 주의하세요.

카나리 디플로이먼트를 트리거하려면 컨테이너 이미지를 업데이트합니다:

kubectl -n test set image deployment/podinfo \
podinfod=stefanprodan/podinfo:3.1.4

Flagger가 디플로이먼트 리비전 변경을 감지하고 A/B 테스트를 시작합니다:

kubectl -n test describe canary/podinfo

Events:
 Starting canary deployment for podinfo.test
 Pre-rollout check acceptance-test passed
 Advance podinfo.test canary iteration 1/10
 Advance podinfo.test canary iteration 2/10
 Advance podinfo.test canary iteration 3/10
 Advance podinfo.test canary iteration 4/10
 Advance podinfo.test canary iteration 5/10
 Advance podinfo.test canary iteration 6/10
 Advance podinfo.test canary iteration 7/10
 Advance podinfo.test canary iteration 8/10
 Advance podinfo.test canary iteration 9/10
 Advance podinfo.test canary iteration 10/10
 Copying podinfo.test template spec to podinfo-primary.test
 Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
 Promotion completed! Scaling down podinfo.test

태그: Linkerd flagger kubernetes canary-deployment service-mesh

7월 23일 19:43에 게시됨