jongkwan.dev
개발 · Essay №037

Blue/Green 배포 (2): Kubernetes에서의 구현

순수 Kubernetes 리소스, Argo Rollouts, Istio를 활용한 Blue/Green 배포 구현과 DB 마이그레이션 전략

이종관2026년 1월 30일15 min read
Contents

Kubernetes에서 Blue/Green 배포를 Service selector, Argo Rollouts, Istio 세 단계로 구현하고 DB 마이그레이션까지 다룬다.

TL;DR

  • Kubernetes에서 Blue/Green은 Service selector만으로도 가능하지만 운영 자동화가 어렵다.
  • 프로덕션에서는 Argo Rollouts로 전환/검증/롤백 자동화를 우선 고려하는 것이 안전하다.
  • Istio를 결합하면 점진 전환, 트래픽 미러링, 헤더 기반 라우팅까지 확장할 수 있다.

Kubernetes에서의 Blue/Green 개요

핵심은 "트래픽 라우팅 제어 방식"을 먼저 선택하는 것이다. 운영 성숙도에 따라 Service selector -> Argo Rollouts -> Istio 순으로 고도화하는 접근이 현실적이다.

Kubernetes는 선언적 배포 관리를 제공하지만, 기본적으로는 Rolling Update 전략을 사용한다. Blue/Green 배포를 구현하려면 추가적인 설정이 필요하다.

구현 방법 비교

  • Service Selector: 복잡도 낮음, 자동화/롤백 수동, 추천 상황은 학습·소규모
  • Argo Rollouts: 복잡도 중간, 자동화/롤백 자동, 추천 상황은 프로덕션
  • Istio: 복잡도 높음, 자동화/롤백 자동, 추천 상황은 대규모·복잡 라우팅
  • Flagger: 복잡도 중간, 자동화/롤백 자동, 추천 상황은 GitOps(Git 저장소를 배포 상태의 단일 소스로 두는 운영 방식) 환경

순수 Kubernetes로 구현

이 방식은 학습과 빠른 Proof of Concept(PoC, 개념 검증)에 가장 적합하다. 다만 전환 검증, 실패 감지, 롤백 트리거를 직접 스크립트로 관리해야 한다.

가장 기본적인 방법으로, Service의 selector를 변경하여 트래픽을 전환한다.

아키텍처

Blue Deployment

yaml
# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-blue
  labels:
    app: my-app
    color: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      color: blue
  template:
    metadata:
      labels:
        app: my-app
        color: blue
        version: v1.0.0
    spec:
      containers:
      - name: app
        image: my-app:1.0.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20

Green Deployment

Green 매니페스트는 Blue와 구조가 같다. 바뀌는 건 식별용 이름·라벨(color)과 버전·이미지 5개 라인뿐이고, replicas·resources·probe·port는 동일하다.

diff
# green-deployment.yaml — blue-deployment.yaml과 아래 라인만 다르다
 metadata:
-  name: my-app-blue
+  name: my-app-green
   labels:
     app: my-app
-    color: blue
+    color: green
 spec:
   replicas: 3
   selector:
     matchLabels:
       app: my-app
-      color: blue
+      color: green
   template:
     metadata:
       labels:
         app: my-app
-        color: blue
-        version: v1.0.0
+        color: green
+        version: v1.1.0
     spec:
       containers:
       - name: app
-        image: my-app:1.0.0
+        image: my-app:1.1.0
         # ports·resources·readinessProbe·livenessProbe는 Blue와 동일

color 라벨이 Service의 selector와 맞물려 트래픽 라우팅을 결정하므로, 두 Deployment에서 이 값만 정확히 구분되면 된다.

Service (트래픽 라우터)

yaml
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: ClusterIP
  selector:
    app: my-app
    color: blue  # 이 값을 green으로 변경하여 전환
  ports:
    - name: http
      port: 80
      targetPort: 8080

전환 스크립트

bash
#!/bin/bash
# switch-traffic.sh
 
NAMESPACE=${NAMESPACE:-default}
SERVICE_NAME=${SERVICE_NAME:-my-app}
TARGET_COLOR=${1:-green}
 
echo "Switching traffic to $TARGET_COLOR..."
 
# 현재 상태 확인
CURRENT_COLOR=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE \
  -o jsonpath='{.spec.selector.color}')
echo "Current: $CURRENT_COLOR -> Target: $TARGET_COLOR"
 
# Green Deployment가 Ready인지 확인
READY_REPLICAS=$(kubectl get deployment my-app-$TARGET_COLOR -n $NAMESPACE \
  -o jsonpath='{.status.readyReplicas}')
DESIRED_REPLICAS=$(kubectl get deployment my-app-$TARGET_COLOR -n $NAMESPACE \
  -o jsonpath='{.spec.replicas}')
 
if [ "$READY_REPLICAS" != "$DESIRED_REPLICAS" ]; then
  echo "Error: Target deployment not ready ($READY_REPLICAS/$DESIRED_REPLICAS)"
  exit 1
fi
 
# 트래픽 전환
kubectl patch svc $SERVICE_NAME -n $NAMESPACE \
  -p "{\"spec\":{\"selector\":{\"color\":\"$TARGET_COLOR\"}}}"
 
echo "Traffic switched to $TARGET_COLOR successfully!"
 
# 전환 후 상태 확인
kubectl get svc $SERVICE_NAME -n $NAMESPACE -o wide
kubectl get endpoints $SERVICE_NAME -n $NAMESPACE

수동 구현의 한계

selector 전환을 사람이 직접 다루면 다음 문제가 누적된다.

  • 휴먼 에러: selector 변경 시 오타 가능성
  • 자동화 어려움: 지속적 통합/지속적 배포(CI/CD) 파이프라인에서 상태 관리 복잡
  • 롤백 자동화 없음: 문제 발생 시 수동 개입 필요
  • 모니터링 연동 없음: 자동 롤백 트리거 불가

Argo Rollouts로 자동화

선언형 리소스로 배포 상태를 관리하고, 승인 지점과 분석 단계를 내장할 수 있다. 수동 스위치 대비 휴먼 에러를 줄이고, 문제 발생 시 자동 롤백 정책을 구현하기 쉽다.

Argo Rollouts는 Kubernetes용 Progressive Delivery(점진적 배포, 트래픽을 단계적으로 새 버전에 넘기며 검증하는 방식) 컨트롤러로, Blue/Green과 Canary 배포를 선언적으로 관리한다.

설치

bash
# Argo Rollouts 설치
kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts \
  -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml
 
# kubectl 플러그인 설치 (선택)
brew install argoproj/tap/kubectl-argo-rollouts  # macOS
# 또는
curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
chmod +x kubectl-argo-rollouts-linux-amd64
sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts

Rollout 리소스

yaml
# rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  replicas: 4
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: my-app:1.1.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
  strategy:
    blueGreen:
      # 현재 운영 트래픽을 받는 서비스
      activeService: my-app-active
      # 새 버전 미리보기용 서비스
      previewService: my-app-preview
      # 자동 승인 비활성화 (수동 승인 필요)
      autoPromotionEnabled: false
      # 전환 후 이전 ReplicaSet 유지 시간
      scaleDownDelaySeconds: 30
      # Preview ReplicaSet 복제본 수
      previewReplicaCount: 2

Services 구성

yaml
# services.yaml
---
# Active Service (현재 운영)
apiVersion: v1
kind: Service
metadata:
  name: my-app-active
spec:
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080
---
# Preview Service (새 버전 테스트용)
apiVersion: v1
kind: Service
metadata:
  name: my-app-preview
spec:
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080

배포 워크플로우

bash
# 1. 현재 상태 확인
kubectl argo rollouts get rollout my-app -w
 
# 2. 새 버전 배포 (이미지 변경)
kubectl argo rollouts set image my-app app=my-app:1.2.0
 
# 3. Preview 환경 테스트
# my-app-preview 서비스로 내부 테스트 수행
 
# 4. 트래픽 전환 승인
kubectl argo rollouts promote my-app
 
# 5. 문제 발생 시 롤백
kubectl argo rollouts abort my-app
# 또는 이전 버전으로
kubectl argo rollouts undo my-app

대시보드에서 상태 확인

bash
# Argo Rollouts 대시보드 실행
kubectl argo rollouts dashboard
 
# 브라우저에서 http://localhost:3100 접속

Analysis를 통한 자동 검증

AnalysisTemplate은 전환 전후로 측정할 지표를 정의한다. successCondition은 통과 기준(여기서는 2xx 응답 비율 95% 이상), failureLimit은 허용하는 실패 횟수, interval·count는 측정 주기와 반복 횟수다.

yaml
# analysis-template.yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  args:
  - name: service-name
  metrics:
  - name: success-rate
    interval: 30s
    count: 5
    successCondition: result[0] >= 0.95
    failureLimit: 3
    provider:
      prometheus:
        address: http://prometheus.monitoring:9090
        query: |
          sum(rate(http_requests_total{
            service="{{args.service-name}}",
            status=~"2.."
          }[5m])) /
          sum(rate(http_requests_total{
            service="{{args.service-name}}"
          }[5m]))
yaml
# rollout-with-analysis.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  # ... (이전 설정 동일)
  strategy:
    blueGreen:
      activeService: my-app-active
      previewService: my-app-preview
      autoPromotionEnabled: false
      # 전환 전 자동 분석
      prePromotionAnalysis:
        templates:
        - templateName: success-rate
        args:
        - name: service-name
          value: my-app-preview
      # 전환 후 자동 분석 (롤백 트리거)
      postPromotionAnalysis:
        templates:
        - templateName: success-rate
        args:
        - name: service-name
          value: my-app-active

Istio Service Mesh 연동

트래픽을 비율 단위로 제어하거나, 헤더 기반 분기와 미러링 테스트를 추가할 수 있다. 고급 라우팅이 필요한 대규모 환경에서 Blue/Green 운영 유연성이 크게 향상된다.

Istio를 사용하면 더 세밀한 트래픽 제어가 가능하다.

아키텍처

DestinationRule

yaml
# destination-rule.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: my-app
spec:
  host: my-app
  subsets:
  - name: blue
    labels:
      version: v1.0.0
  - name: green
    labels:
      version: v1.1.0

VirtualService (Blue/Green)

yaml
# virtual-service.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: my-app
spec:
  hosts:
  - my-app
  - my-app.example.com
  gateways:
  - my-app-gateway
  http:
  # 헤더 기반 라우팅 (테스트용)
  - match:
    - headers:
        x-canary:
          exact: "true"
    route:
    - destination:
        host: my-app
        subset: green
        port:
          number: 80
  # 기본 트래픽 (Blue로)
  - route:
    - destination:
        host: my-app
        subset: blue
        port:
          number: 80
      weight: 100
    - destination:
        host: my-app
        subset: green
        port:
          number: 80
      weight: 0

점진적 전환 (Istio + Blue/Green 하이브리드)

yaml
# 단계별 전환 예시
# Step 1: 0% Green
- destination:
    host: my-app
    subset: blue
  weight: 100
- destination:
    host: my-app
    subset: green
  weight: 0
 
# Step 2: 10% Green (테스트)
- destination:
    host: my-app
    subset: blue
  weight: 90
- destination:
    host: my-app
    subset: green
  weight: 10
 
# Step 3: 100% Green (전환 완료)
- destination:
    host: my-app
    subset: blue
  weight: 0
- destination:
    host: my-app
    subset: green
  weight: 100

트래픽 미러링 (Shadow Testing)

yaml
# 실제 트래픽을 Green에 복제하여 테스트 (응답은 무시)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: my-app
spec:
  hosts:
  - my-app
  http:
  - route:
    - destination:
        host: my-app
        subset: blue
      weight: 100
    mirror:
      host: my-app
      subset: green
    mirrorPercentage:
      value: 100.0

DB 스키마 마이그레이션 전략

Blue/Green에서 DB는 두 버전이 동시에 접근하므로 하위 호환이 핵심이다. Expand -> Migrate -> Contract 순서를 지키면 안전하게 전환하고 롤백 가능성을 확보할 수 있다.

Blue/Green 배포에서 가장 어려운 부분은 DB 스키마 변경이다. 두 버전이 동시에 같은 DB를 사용하기 때문이다.

Expand & Contract 패턴

단계별 마이그레이션 예시

Phase 1: Expand (확장)

sql
-- 새 컬럼 추가 (nullable)
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
 
-- 기본값 설정 (기존 데이터 호환)
UPDATE users SET full_name = user_name WHERE full_name IS NULL;
python
# v2 애플리케이션: 두 컬럼 모두 지원
class User:
    def get_display_name(self):
        # 새 컬럼 우선, 없으면 기존 컬럼 사용
        return self.full_name or self.user_name
 
    def save(self):
        # 두 컬럼 모두 업데이트 (하위 호환)
        self.full_name = self.display_name
        self.user_name = self.display_name  # v1 호환

Phase 2: Migrate (마이그레이션)

python
# 배치 마이그레이션 스크립트
def migrate_user_names():
    users = User.objects.filter(full_name__isnull=True)
    for batch in chunked(users, 1000):
        for user in batch:
            user.full_name = user.user_name
        User.objects.bulk_update(batch, ['full_name'])

Phase 3: Contract (축소)

sql
-- v1이 완전히 제거된 후에만 실행
-- 충분한 모니터링 기간 후 (예: 2주)
ALTER TABLE users DROP COLUMN user_name;

마이그레이션 체크리스트

단계Blue(v1)Green(v2)롤백
1. 새 컬럼 추가무시읽기/쓰기가능
2. 데이터 복사무시읽기/쓰기가능
3. v2로 전환비활성활성가능
4. v1 제거삭제활성주의
5. 이전 컬럼 삭제비활성활성불가

중요: Phase 5는 롤백이 불가능하므로, 충분한 모니터링 기간(최소 1-2주)을 거친 후 실행해야 한다.

정리

Kubernetes에서 Blue/Green을 구현하는 방법은 운영 성숙도에 따라 갈린다. 학습·소규모는 Service selector 전환으로 충분하고, 프로덕션은 Argo Rollouts로 승인·분석·자동 롤백을 선언적으로 관리하는 편이 안전하다. 비율 단위 제어나 헤더 기반 분기, 트래픽 미러링이 필요한 대규모 환경이면 Istio를 결합한다. DB 스키마는 두 버전이 같은 DB를 동시에 쓰므로 Expand -> Migrate -> Contract 순서로 하위 호환을 유지해야 롤백 가능성이 남는다. 다음 글에서는 클라우드 환경과 CI/CD 파이프라인에 Blue/Green을 통합하는 방법을 다룬다.