ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 이코에코(Eco²) Deployment Strategy: Canary Deployments
    이코에코(Eco²) 2025. 12. 30. 22:10

    2024-12-30 | 리팩토링을 앞두고 안전한 배포 전략 수립

    TL;DR

    • 문제: 대규모 Clean Architecture 리팩토링을 안전하게 배포해야 함
    • 선택지: Rolling Update, Blue-Green, Canary
    • 결정: Canary 배포 (헤더 기반)
    • 이유: 기존 Istio 인프라 활용, 비용 효율성, 점진적 검증 가능

    1. 배경

    1.1 현재 상황

    도메인 서비스들의 Clean Architecture 리팩토링을 앞두고 있다. Auth 서비스를 시작으로 모든 도메인 서비스에 다음 변경이 예정되어 있다:

    • 디렉토리 구조 전면 개편 (models/domain/entities/, services/application/commands/ 등)
    • CQRS 패턴 도입 (Command/Query 분리)
    • Repository 인터페이스 추가 (DIP 적용)
    • 의존성 주입 개선

    이는 단순한 버그 픽스가 아니라 코드베이스의 근본적인 변경이다. 프로덕션에 직접 배포하기엔 위험이 크다.

    1.2 요구사항

    점진적 검증전체 트래픽에 영향 없이 새 버전 테스트🔴 필수
    빠른 롤백문제 발생 시 즉시 복구 (< 1분)🔴 필수
    비용 효율성추가 인프라 비용 최소화🟡 중요
    개발자 친화성쉬운 테스트 환경🟢 권장
    자동화 가능성CI/CD 파이프라인 통합🟢 권장

    1.3 현재 인프라 구성

    ┌─────────────────────────────────────────────────────────────────┐
    │                        AWS EKS Cluster                           │
    ├─────────────────────────────────────────────────────────────────┤
    │                                                                  │
    │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐           │
    │  │  auth node   │  │character node│  │  scan node   │  ...      │
    │  │  (2 CPU,4GB) │  │  (2 CPU,4GB) │  │  (2 CPU,4GB) │           │
    │  │              │  │              │  │              │           │
    │  │ - auth-api   │  │-character-api│  │ - scan-api   │           │
    │  │ - ext-authz  │  │-character-   │  │ - scan-worker│           │
    │  │ - auth-relay │  │  worker      │  │              │           │
    │  └──────────────┘  └──────────────┘  └──────────────┘           │
    │                                                                  │
    │  ┌────────────────────────────────────────────────────────────┐ │
    │  │                    Istio Service Mesh                       │ │
    │  │  - Gateway (외부 트래픽 진입점)                              │ │
    │  │  - VirtualService (라우팅 규칙)                              │ │
    │  │  - DestinationRule (트래픽 정책)                             │ │
    │  └────────────────────────────────────────────────────────────┘ │
    │                                                                  │
    │  ┌────────────────────────────────────────────────────────────┐ │
    │  │                    ArgoCD (GitOps)                          │ │
    │  │  - ApplicationSet으로 도메인별 자동 배포                      │ │
    │  │  - Kustomize 오버레이 (dev/prod)                             │ │
    │  └────────────────────────────────────────────────────────────┘ │
    │                                                                  │
    └─────────────────────────────────────────────────────────────────┘

    핵심 제약사항:

    • 각 노드는 2 CPU, ~4GB 메모리로 제한
    • 도메인별 전용 노드로 Taint/Toleration 설정
    • 추가 노드 프로비저닝 비용 부담

    2. 배포 전략 비교

    2.1 Rolling Update (롤링 업데이트)

    Kubernetes의 기본 배포 전략이다. Pod를 하나씩 순차적으로 교체한다.

    ┌─────────────────────────────────────────────────────────────────┐
    │                       Rolling Update                             │
    ├─────────────────────────────────────────────────────────────────┤
    │                                                                  │
    │  시간 →                                                          │
    │                                                                  │
    │  T0:  ┌───┐ ┌───┐ ┌───┐ ┌───┐                                   │
    │       │v1 │ │v1 │ │v1 │ │v1 │  ← 모두 v1                        │
    │       └───┘ └───┘ └───┘ └───┘                                   │
    │         │                                                        │
    │  T1:  ┌───┐ ┌───┐ ┌───┐ ┌───┐                                   │
    │       │v2 │ │v1 │ │v1 │ │v1 │  ← 첫 번째 교체 (v1+v2 혼재)      │
    │       └─┬─┘ └───┘ └───┘ └───┘                                   │
    │         │                                                        │
    │  T2:  ┌───┐ ┌───┐ ┌───┐ ┌───┐                                   │
    │       │v2 │ │v2 │ │v1 │ │v1 │  ← 두 번째 교체                   │
    │       └───┘ └─┬─┘ └───┘ └───┘                                   │
    │               │                                                  │
    │  ...          ▼                                                  │
    │                                                                  │
    │  Tn:  ┌───┐ ┌───┐ ┌───┐ ┌───┐                                   │
    │       │v2 │ │v2 │ │v2 │ │v2 │  ← 모두 v2                        │
    │       └───┘ └───┘ └───┘ └───┘                                   │
    │                                                                  │
    └─────────────────────────────────────────────────────────────────┘

    동작 방식

    # Kubernetes Deployment 기본 설정
    spec:
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxSurge: 1        # 목표 replica 대비 초과 허용 Pod 수
          maxUnavailable: 0  # 최소 가용 Pod 보장

    장점

    장점 설명
    Zero Config Kubernetes 기본 전략, 별도 설정 불필요
    리소스 효율 추가 리소스 최소화 (maxSurge: 1이면 1개만 추가)
    자동화 kubectl rollout 명령으로 상태 확인/롤백

    단점

    버전 혼재🔴배포 중 v1/v2가 동시에 서비스 → API 호환성 문제
    영향 범위🔴첫 Pod 교체 시점부터 실제 사용자 영향
    검증 불가🟡특정 버전만 선택적 테스트 불가능
    느린 롤백🟡롤백도 롤링 방식 (전체 복구에 시간 소요)

    리팩토링에 부적합한 이유

    Clean Architecture 리팩토링은 내부 구조가 완전히 바뀌지만 API는 동일해야 한다. 만약 리팩토링 과정에서 의도치 않은 동작 변경이 발생하면:

    1. Rolling Update 시작 → 첫 v2 Pod 생성
    2. v2 Pod가 트래픽 수신 시작 (이미 일부 사용자 영향)
    3. 에러 발견 → 롤백 시작
    4. 롤백도 롤링 방식으로 진행 → 복구에 수 분 소요

    결론: 리팩토링 같은 대규모 변경에는 부적합


    2.2 Blue-Green Deployment (블루-그린 배포)

    두 개의 동일한 환경을 유지하고, 라우터 전환으로 즉시 배포한다.

    ┌─────────────────────────────────────────────────────────────────┐
    │                     Blue-Green Deployment                        │
    ├─────────────────────────────────────────────────────────────────┤
    │                                                                  │
    │           ┌─────────────────────────────────────┐               │
    │           │            Load Balancer            │               │
    │           └─────────────────┬───────────────────┘               │
    │                             │                                    │
    │              ┌──────────────┴──────────────┐                    │
    │              │                             │                     │
    │              ▼                             ▼                     │
    │  ┌─────────────────────┐     ┌─────────────────────┐            │
    │  │    Blue (Active)    │     │   Green (Standby)   │            │
    │  │                     │     │                     │            │
    │  │  ┌───┐ ┌───┐ ┌───┐ │     │  ┌───┐ ┌───┐ ┌───┐ │            │
    │  │  │v1 │ │v1 │ │v1 │ │     │  │v2 │ │v2 │ │v2 │ │            │
    │  │  └───┘ └───┘ └───┘ │     │  └───┘ └───┘ └───┘ │            │
    │  │                     │     │                     │            │
    │  │  ← 100% 트래픽      │     │  ← 0% (대기)        │            │
    │  └─────────────────────┘     └─────────────────────┘            │
    │                                                                  │
    │  ═══════════════════════════════════════════════════════════    │
    │                     전환 후 (Switch)                             │
    │  ═══════════════════════════════════════════════════════════    │
    │                                                                  │
    │  ┌─────────────────────┐     ┌─────────────────────┐            │
    │  │   Blue (Standby)    │     │   Green (Active)    │            │
    │  │                     │     │                     │            │
    │  │  ┌───┐ ┌───┐ ┌───┐ │     │  ┌───┐ ┌───┐ ┌───┐ │            │
    │  │  │v1 │ │v1 │ │v1 │ │     │  │v2 │ │v2 │ │v2 │ │            │
    │  │  └───┘ └───┘ └───┘ │     │  └───┘ └───┘ └───┘ │            │
    │  │                     │     │                     │            │
    │  │  ← 0% (롤백 대기)   │     │  ← 100% 트래픽      │            │
    │  └─────────────────────┘     └─────────────────────┘            │
    │                                                                  │
    └─────────────────────────────────────────────────────────────────┘

    동작 방식

    # Service를 selector 변경으로 전환
    # 전환 전
    spec:
      selector:
        app: my-app
        version: blue  # v1
    
    # 전환 후
    spec:
      selector:
        app: my-app
        version: green  # v2

    또는 Istio VirtualService weight 조정:

    http:
    - route:
      - destination:
          host: my-app
          subset: blue
        weight: 0     # 전환 후
      - destination:
          host: my-app
          subset: green
        weight: 100   # 전환 후

    장점

    즉시 전환라우터 변경 한 번으로 완료 (< 1초)
    즉시 롤백문제 시 다시 Blue로 전환 (< 1초)
    완전한 격리테스트 환경이 프로덕션과 동일
    일관성항상 단일 버전만 서비스

    단점

    2배 리소스🔴Blue + Green 모두 프로비저닝 필요
    유휴 비용🔴Standby 환경이 항상 대기 상태
    DB 스키마🟡양쪽 환경이 동일 DB 사용 시 스키마 변경 복잡
    상태 동기화🟡세션, 캐시 등 상태 관리 필요

    비용 분석

    현재 인프라 기준 Blue-Green 적용 시:

    현재 비용 (Stable만):
      - auth 노드: $X/월
      - character 노드: $X/월
      - scan 노드: $X/월
      - ... (총 N개 노드)
      = $NX/월
    
    Blue-Green 적용 시:
      - Blue 환경: $NX/월
      - Green 환경: $NX/월
      = $2NX/월 (100% 증가)

    우리 환경에서는 비용이 2배가 되므로 현실적으로 불가능.


    2.3 Canary Deployment (카나리 배포)

    소수의 카나리 Pod로 새 버전을 먼저 검증하고, 문제 없으면 점진적으로 확대한다.

    이름의 유래: 광산에서 유독 가스 감지를 위해 카나리아 새를 사용한 것에서 유래

    ┌─────────────────────────────────────────────────────────────────┐
    │                      Canary Deployment                           │
    ├─────────────────────────────────────────────────────────────────┤
    │                                                                  │
    │           ┌─────────────────────────────────────┐               │
    │           │         Istio VirtualService        │               │
    │           │   (Header-based / Weight-based)     │               │
    │           └─────────────────┬───────────────────┘               │
    │                             │                                    │
    │            ┌────────────────┴────────────────┐                  │
    │            │                                 │                   │
    │            ▼                                 ▼                   │
    │  ┌───────────────────────┐     ┌───────────────────────┐        │
    │  │    Stable (v1)        │     │    Canary (v2)        │        │
    │  │                       │     │                       │        │
    │  │  ┌───┐ ┌───┐ ┌───┐   │     │       ┌───┐          │        │
    │  │  │v1 │ │v1 │ │v1 │   │     │       │v2 │          │        │
    │  │  └───┘ └───┘ └───┘   │     │       └───┘          │        │
    │  │                       │     │                       │        │
    │  │  일반 트래픽 (95%)    │     │  테스트 트래픽 (5%)   │        │
    │  │  또는                 │     │  또는                 │        │
    │  │  X-Canary 헤더 없음   │     │  X-Canary: true       │        │
    │  └───────────────────────┘     └───────────────────────┘        │
    │                                                                  │
    │  ═══════════════════════════════════════════════════════════    │
    │                    점진적 확대 단계                               │
    │  ═══════════════════════════════════════════════════════════    │
    │                                                                  │
    │  Phase 1: 헤더 기반 (개발자만)     0% weight, 헤더로 접근        │
    │      ↓                                                           │
    │  Phase 2: 5% 트래픽               일반 사용자 5% 노출            │
    │      ↓                                                           │
    │  Phase 3: 20% 트래픽              에러율/레이턴시 모니터링       │
    │      ↓                                                           │
    │  Phase 4: 50% 트래픽              대규모 검증                    │
    │      ↓                                                           │
    │  Phase 5: 100% 트래픽             Canary → Stable 전환           │
    │                                                                  │
    └─────────────────────────────────────────────────────────────────┘

    라우팅 방식

    방식 1: 헤더 기반 (Header-based)

    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    spec:
      http:
      # X-Canary: true 헤더가 있으면 Canary로
      - match:
        - headers:
            x-canary:
              exact: 'true'
        route:
        - destination:
            subset: canary
    
      # 그 외는 Stable로
      - route:
        - destination:
            subset: stable

    방식 2: 가중치 기반 (Weight-based)

    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    spec:
      http:
      - route:
        - destination:
            subset: stable
          weight: 95
        - destination:
            subset: canary
          weight: 5

    방식 3: 하이브리드 (우리가 선택한 방식)

    http:
    # 1순위: 헤더가 있으면 무조건 Canary
    - match:
      - headers:
          x-canary:
            exact: 'true'
      route:
      - destination:
          subset: canary
    
    # 2순위: 나머지는 Stable (나중에 weight 추가 가능)
    - route:
      - destination:
          subset: stable

    장점

    최소 리소스Canary Pod 1-2개만 추가 (+10~15% 비용)
    점진적 검증단계별 트래픽 확대로 위험 최소화
    선택적 테스트헤더 기반으로 개발자만 테스트 가능
    빠른 롤백Canary Pod 제거 또는 weight 0으로 즉시 복구
    실제 트래픽프로덕션 환경에서 실제 사용자 패턴으로 검증

    단점

    설정 복잡도🟡VirtualService, DestinationRule 이해 필요
    모니터링 필요🟡버전별 메트릭 분리 및 비교 필요
    디버깅 복잡🟢두 버전 동시 운영 시 로그 추적 복잡

    비용 분석

    현재 비용 (Stable만):
      - N개 노드, 각 노드에 Pod 2-3개
      = 기준 비용 $B/월
    
    Canary 적용 시:
      - 기존 Stable Pod 유지
      - 서비스당 Canary Pod 1개 추가
      - 추가 리소스: CPU 0.2코어, 메모리 256MB (노드당)
      = $B × 1.1~1.15/월 (10~15% 증가)

    3. 최종 선택: Canary (헤더 기반)

    3.1 결정 매트릭스

    점진적 검증30%125Canary만 단계별 검증 가능
    빠른 롤백25%255BG/Canary 모두 즉시 가능
    비용 효율성25%514BG는 2배 비용
    설정 용이성10%532Rolling이 가장 간단
    기존 인프라 활용10%325Istio 이미 구축됨
    총점100%2.82.44.5 

    3.2 Istio 활용 이점

    우리 클러스터에는 이미 Istio Service Mesh가 구축되어 있다:

    # 이미 존재하는 리소스들
    - Gateway: istio-system/eco2-gateway
    - VirtualService: 각 도메인별 라우팅 규칙
    - DestinationRule: 일부 서비스에 존재 (character, my, ext-authz)

    Canary 배포를 위해 추가로 필요한 것:

    # 추가/수정이 필요한 리소스
    1. DestinationRule: stable/canary subset 정의 (신규 또는 수정)
    2. VirtualService: X-Canary 헤더 매칭 규칙 추가 (수정)
    3. Deployment: Canary 버전 (version: v2 라벨) 추가 (신규)

    핵심: 새로운 인프라 구성 요소 없이 기존 Istio로 구현 가능

    3.3 구현 결정사항

    라우팅 전략: 헤더 기반 우선

    처음에는 가중치 기반이 아닌 헤더 기반으로 시작:

    # Phase 1: 개발자 테스트 (헤더 기반)
    http:
    - match:
      - headers:
          x-canary:
            exact: 'true'
      route:
      - destination:
          subset: canary
    - route:
      - destination:
          subset: stable

    이유:

    1. 리팩토링 초기에는 개발자(본인, Opus)만 테스트
    2. 안정성 확인 후 가중치 기반으로 전환
    3. 가중치 전환은 YAML 수정만으로 가능

    버전 라벨링 전략

    # Stable Deployment
    metadata:
      labels:
        app: auth-api
        version: v1    # ← subset 매칭에 사용
    
    # Canary Deployment  
    metadata:
      labels:
        app: auth-api
        version: v2    # ← subset 매칭에 사용
        release: canary

    이미지 태그 전략

    Stable: mng990/eco2:{service}-dev-latest
    Canary: mng990/eco2:{service}-dev-canary

    CI/CD에서 -canary 태그로 빌드하면 자동으로 Canary Deployment에 적용.


    4. 구현 상세

    4.1 적용 범위

    API 서비스 (9개)

    auth-apiauth✅ (신규)
    character-apicharacter✅ (수정)
    chat-apichat✅ (신규)
    image-apiimage✅ (신규)
    location-apilocation✅ (신규)
    my-apimy✅ (수정)
    scan-apiscan✅ (신규)
    sse-gatewaysse-consumer✅ (신규)
    ext-authzauth- (gRPC)✅ (수정)

    Worker 서비스 (4개)

    HTTP 라우팅이 불필요하므로 Canary Deployment만 추가:

    auth-relayauth
    character-workercharacter
    scan-workerscan
    my-workermy

    4.2 파일 구조

    workloads/
    ├── domains/{service}/base/
    │   ├── deployment.yaml           # Stable (version: v1)
    │   ├── deployment-canary.yaml    # Canary (version: v2) ← 신규
    │   ├── destination-rule.yaml     # stable/canary subset ← 신규/수정
    │   ├── service.yaml
    │   ├── configmap.yaml
    │   └── kustomization.yaml        # canary 리소스 포함 ← 수정
    │
    └── routing/{service}/base/
        └── virtual-service.yaml      # X-Canary 헤더 라우팅 ← 수정

    4.3 배포 워크플로우

    ┌─────────────────────────────────────────────────────────────────┐
    │                    Canary 배포 워크플로우                         │
    ├─────────────────────────────────────────────────────────────────┤
    │                                                                  │
    │  1. 코드 변경                                                    │
    │     └─→ feature/xxx 브랜치에서 작업                              │
    │                                                                  │
    │  2. Canary 이미지 빌드                                           │
    │     └─→ docker build -t mng990/eco2:{service}-dev-canary        │
    │     └─→ docker push mng990/eco2:{service}-dev-canary            │
    │                                                                  │
    │  3. ArgoCD 자동 배포                                             │
    │     └─→ Canary Deployment가 새 이미지로 업데이트                 │
    │     └─→ Canary Pod 생성 (version: v2)                           │
    │                                                                  │
    │  4. 개발자 테스트 (헤더 기반)                                     │
    │     └─→ curl -H "X-Canary: true" https://api.growbin.app/...    │
    │     └─→ 로그/메트릭 확인                                         │
    │                                                                  │
    │  5. 문제 발견 시                                                 │
    │     └─→ 코드 수정 후 2번부터 반복                                │
    │     └─→ 또는 Canary Pod 스케일 0으로 비활성화                    │
    │                                                                  │
    │  6. 테스트 통과                                                  │
    │     └─→ 옵션 A: 가중치 기반으로 전환 (5% → 20% → 50% → 100%)    │
    │     └─→ 옵션 B: Stable 이미지를 Canary로 교체                    │
    │                                                                  │
    │  7. 완전 전환                                                    │
    │     └─→ Canary 이미지를 latest 태그로 재태깅                     │
    │     └─→ Stable Deployment 업데이트                               │
    │     └─→ Canary Pod 스케일 다운                                   │
    │                                                                  │
    └─────────────────────────────────────────────────────────────────┘

    5. 사용 가이드

    5.1 Canary 버전 테스트

    # cURL
    curl -H "X-Canary: true" https://api.growbin.app/api/v1/auth/me
    
    # HTTPie
    http https://api.growbin.app/api/v1/auth/me X-Canary:true
    
    # 브라우저: ModHeader 확장 프로그램 사용

    5.2 로그 확인

    # Canary Pod 로그
    kubectl logs -n auth -l version=v2 --tail=100 -f
    
    # Stable Pod 로그
    kubectl logs -n auth -l version=v1 --tail=100 -f

    5.3 메트릭 구분

    Jaeger/Grafana에서 서비스명으로 구분:

    • Stable: auth-api
    • Canary: auth-api-canary

    5.4 롤백

    # 즉시 롤백: Canary Pod 제거
    kubectl scale deployment auth-api-canary --replicas=0 -n auth
    
    # 또는 VirtualService에서 canary route 제거

    6. 결론

    선택 요약

    ubuntu@k8s-master:~$ echo '=== Check Canary Deployments ==='
    echo 'auth namespace:'
    kubectl get deploy -n auth | grep -E 'canary|NAME'
    echo ''
    echo 'character namespace:'
    kubectl get deploy -n character | grep -E 'canary|NAME'
    echo ''
    echo 'scan namespace:'
    kubectl get deploy -n scan | grep -E 'canary|NAME'
    === Check Canary Deployments ===
    auth namespace:
    NAME                READY   UP-TO-DATE   AVAILABLE   AGE
    auth-api-canary     1/1     1            1           19m
    auth-relay-canary   0/1     1            0           19m
    ext-authz-canary    0/1     1            0           19m
    
    character namespace:
    NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
    character-api-canary      1/1     1            1           19m
    character-worker-canary   1/1     1            1           20m
    
    scan namespace:
    NAME                 READY   UP-TO-DATE   AVAILABLE   AGE
    scan-api-canary      1/1     1            1           19m
    scan-worker-canary   1/1     1            1           19m
    배포 전략Canary Deployment
    라우팅 방식헤더 기반 (X-Canary: true)
    구현 기술Istio VirtualService + DestinationRule
    적용 범위API 서비스 9개 + Worker 4개
    비용 증가~10-15% (Pod 1개/서비스 추가)

    기대 효과

    1. 안전한 리팩토링: 실제 클러스터 배포 환경에서 검증 후 전환
    2. 빠른 피드백: 문제 발견 시 즉시 롤백 가능
    3. 개발자 경험 향상: 헤더 하나로 새 버전 테스트
    4. 비용 효율성: Blue-Green 대비 85% 비용 절감

    관련 커밋

    • 4efee1e2 - feat(canary): add header-based canary deployment for auth service
    • d3a67f61 - feat(canary): add header-based canary deployment for all workloads

    참고 자료

    댓글

ABOUT ME

🎓 부산대학교 정보컴퓨터공학과 학사: 2017.03 - 2023.08
☁️ Rakuten Symphony Jr. Cloud Engineer: 2024.12.09 - 2025.08.31
🏆 2025 AI 새싹톤 우수상 수상: 2025.10.30 - 2025.12.02
🌏 이코에코(Eco²) 백엔드/인프라 고도화 중: 2025.12 - Present

Designed by Mango