본문 바로가기
사이드 프로젝트

[CI/CD 프로젝트] ArgoRollout로 canary배포하기

by 간장공장공차장 2024. 11. 20.
반응형

Canary 배포

이전 광부들이 광산에 들어갈 때 이 canary 새를 풀어봐 광산이 안전한지 위험도 테스트를 진행했다.

유래와 같이 Canary 배포는 새로운 애플리케이션 버전을 점진적으로 배포하여, 작은 사용자 그룹에게 먼저 제공한 뒤 안정성이 확인되면 전체로 확대하는 배포 방식이다. 이 방식은 배포 시 위험을 줄이고, 문제가 발생했을 때 신속히 롤백할 수 있다는 장점이 있다.

Canary 배포

  1. Canary 릴리즈: 새로운 버전의 애플리케이션을 소수의 Pod 또는 인스턴스에 배포합니다. 초기에는 전체 트래픽 중 일부만 새로운 버전에 전달한다.( EX. 회사 내부 인력, 베타 테스트 등..) header, cookie로 트래픽을 조절할 수 있어 베타테스트 등에 유용하다.
  2. 모니터링 및 검증: 새로운 버전이 소수의 사용자에게 제공되는 동안 시스템의 성능, 오류율, 메트릭 등을 모니터링하여 문제가 없는지 검증한다.
  3. 점진적 확대: 검증이 완료되고 안정성이 확인되면 Canary 버전으로 전환되는 트래픽 비율을 점진적으로 높입니다. 예를 들어, 처음에는 10%로 시작하여 점차 25%, 50%, 100%로 증가시킬 수 있습니다.
  4. 완료 또는 롤백: 모든 트래픽이 새로운 버전으로 전환되면 기존 버전은 종료된다.

이 또한 구버전/새버전에 대한 인프라를 모두 운용해야하여 비용적인 단점이 있다.

실습

  • Rollout.yaml

기존 helm chart에서 strategy를 canary로 변경한다.

처음엔 20%만 트래픽을 보내다, 30초동안 대기 후 전체 트래픽을 신규로 보내도록 설정한다.

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: {{ include "hellospring.name" . }}-rollout
  labels:
    app: {{ .Values.labels.app }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Values.labels.app }}
  template:
    metadata:
      labels:
        app: {{ .Values.labels.app }}
    spec:
      containers:
        - name: {{ include "hellospring.name" . }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
          imagePullPolicy: {{ .Values.image.pullPolicy }}
  strategy:
    canary:
      steps:
        - setWeight: 20  # 첫 번째 배포 시 20%의 트래픽을 새 버전으로 전환
        - pause: { duration: 30 }  # 30초 동안 대기
        - setWeight: 100  # 나머지 80%의 트래픽을 새 버전으로 전환
      activeService: {{ include "hellospring.name" . }}-blue
      previewService: {{ include "hellospring.name" . }}-green
      autoPromotionEnabled: false
      scaleDownDelaySeconds: 30
  • Service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "hellospring.name" . }}-active
  labels:
    app: {{ .Values.labels.app }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: 3000
      targetPort: 8080
  selector:
    app: {{ .Values.labels.app }}

---

# preview Service
apiVersion: v1
kind: Service
metadata:
  name: {{ include "hellospring.name" . }}-canary
  labels:
    app: {{ .Values.labels.app }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: 3000
      targetPort: 8080
  selector:
    app: {{ .Values.labels.app }}
  • Ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "hellospring.name" . }}-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - http:
        paths:
          - path: {{ .Values.ingress.path }}
            pathType: Prefix
            backend:
              service:
                name: {{ include "hellospring.name" . }}-active
                port:
                  number: {{ .Values.service.port }}
  • canary배포에서도 배포 전에는 모두 같은 endpoint를 가지고 있는 것을 볼 수 있다.

  • 신기한 것은, 내가 canary에 대한 ingress를 설정하지 않아도, canary ingress가 생성된다. 아직은 배포를 하지 않아서 연결된 service의 endpoint가 동일한 것을 알 수 있다.

  • helm values에서 version을 변경하여 배포하면, canary pod가 점점 생기는 것을 볼 수 있다. service의 endpoint가 1개 생성되었다. ( 처음엔, 20%로 30s 동안 유지하는 것이니, replica가 5개 이므로 1개의 endpoint가 생긴 것을 볼 수 있다. )

  • 브라우저를 무한 새로고침하면, v2.0와 v3.0(canary) 가 동시에 보인다.

  • 먼저 생긴 pod가 생성된지 30s 가 지나면 canary pod 나머지 4개가 동시에 생긴다. 30s 이후 100% 트래픽을 canary로 보내기 위해서이다. 옆 ingress에 할당된 service에서, canary에 대한 endpoint가 늘어난 것을 볼 수 있다.

  • 이후 나머지 active pod들은 terminate된다. 이때 active ingress의 endpoint는 canary endpoint를 바라보게 된다.

반응형