Blue/Green 배포 (2): Kubernetes에서의 구현
순수 Kubernetes 리소스, Argo Rollouts, Istio를 활용한 Blue/Green 배포 구현과 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 환경
순수 Kubernetes로 구현
이 방식은 학습과 빠른 PoC에 가장 적합하다. 다만 전환 검증, 실패 감지, 롤백 트리거를 직접 스크립트로 관리해야 한다.
가장 기본적인 방법으로, Service의 selector를 변경하여 트래픽을 전환한다.
아키텍처
Blue Deployment
# 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: 20Green Deployment
# green-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-green
labels:
app: my-app
color: green
spec:
replicas: 3
selector:
matchLabels:
app: my-app
color: green
template:
metadata:
labels:
app: my-app
color: green
version: v1.1.0
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
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20Service (트래픽 라우터)
# 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전환 스크립트
#!/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 변경 시 오타 가능성
- 자동화 어려움: CI/CD 파이프라인에서 상태 관리 복잡
- 롤백 자동화 없음: 문제 발생 시 수동 개입 필요
- 모니터링 연동 없음: 자동 롤백 트리거 불가
Argo Rollouts로 자동화
선언형 리소스로 배포 상태를 관리하고, 승인 지점과 분석 단계를 내장할 수 있다. 수동 스위치 대비 휴먼 에러를 줄이고, 문제 발생 시 자동 롤백 정책을 구현하기 쉽다.
Argo Rollouts는 Kubernetes용 Progressive Delivery Controller로, Blue/Green과 Canary 배포를 선언적으로 관리한다.
설치
# 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-rolloutsRollout 리소스
# 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
# 자동 롤백 (Analysis 실패 시)
autoPromotionSeconds: 0 # 0 = 자동 승인 비활성화Services 구성
# 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배포 워크플로우
# 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대시보드에서 상태 확인
# Argo Rollouts 대시보드 실행
kubectl argo rollouts dashboard
# 브라우저에서 http://localhost:3100 접속Analysis를 통한 자동 검증
# 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]))# 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-activeIstio Service Mesh 연동
트래픽을 비율 단위로 제어하거나, 헤더 기반 분기와 미러링 테스트를 추가할 수 있다. 고급 라우팅이 필요한 대규모 환경에서 Blue/Green 운영 유연성이 크게 향상된다.
Istio를 사용하면 더 세밀한 트래픽 제어가 가능하다.
아키텍처
DestinationRule
# 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.0VirtualService (Blue/Green)
# 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 하이브리드)
# 단계별 전환 예시
# 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)
# 실제 트래픽을 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.0DB 스키마 마이그레이션 전략
Blue/Green에서 DB는 두 버전이 동시에 접근하므로 하위 호환이 핵심이다.
Expand -> Migrate -> Contract 순서를 지키면 안전하게 전환하고 롤백 가능성을 확보할 수 있다.
Blue/Green 배포에서 가장 어려운 부분은 DB 스키마 변경이다. 두 버전이 동시에 같은 DB를 사용하기 때문이다.
Expand & Contract 패턴
단계별 마이그레이션 예시
Phase 1: Expand (확장)
-- 새 컬럼 추가 (nullable)
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
-- 기본값 설정 (기존 데이터 호환)
UPDATE users SET full_name = user_name WHERE full_name IS NULL;# 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 (마이그레이션)
# 배치 마이그레이션 스크립트
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 (축소)
-- v1이 완전히 제거된 후에만 실행
-- 충분한 모니터링 기간 후 (예: 2주)
ALTER TABLE users DROP COLUMN user_name;마이그레이션 체크리스트
- 새 컬럼 추가: Blue(v1) 무시, Green(v2) 읽기/쓰기, 롤백 가능 ✅
- 데이터 복사: Blue(v1) 무시, Green(v2) 읽기/쓰기, 롤백 가능 ✅
- v2로 전환: Blue(v1) 비활성, Green(v2) 활성, 롤백 가능 ✅
- v1 제거: Blue(v1) 삭제, Green(v2) 활성, 롤백 가능 ⚠️
- 이전 컬럼 삭제: Blue(v1) 비활성, Green(v2) 활성, 롤백 가능 ❌
중요: Phase 5는 롤백이 불가능하므로, 충분한 모니터링 기간(최소 1-2주)을 거친 후 실행해야 한다.