Blue/Green 배포 (2): Kubernetes에서의 구현
순수 Kubernetes 리소스, Argo Rollouts, Istio를 활용한 Blue/Green 배포 구현과 DB 마이그레이션 전략
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
# 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 매니페스트는 Blue와 구조가 같다. 바뀌는 건 식별용 이름·라벨(color)과 버전·이미지 5개 라인뿐이고, replicas·resources·probe·port는 동일하다.
# 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 (트래픽 라우터)
# 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 전환을 사람이 직접 다루면 다음 문제가 누적된다.
- 휴먼 에러: selector 변경 시 오타 가능성
- 자동화 어려움: 지속적 통합/지속적 배포(CI/CD) 파이프라인에서 상태 관리 복잡
- 롤백 자동화 없음: 문제 발생 시 수동 개입 필요
- 모니터링 연동 없음: 자동 롤백 트리거 불가
Argo Rollouts로 자동화
선언형 리소스로 배포 상태를 관리하고, 승인 지점과 분석 단계를 내장할 수 있다. 수동 스위치 대비 휴먼 에러를 줄이고, 문제 발생 시 자동 롤백 정책을 구현하기 쉽다.
Argo Rollouts는 Kubernetes용 Progressive Delivery(점진적 배포, 트래픽을 단계적으로 새 버전에 넘기며 검증하는 방식) 컨트롤러로, 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: 2Services 구성
# 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를 통한 자동 검증
AnalysisTemplate은 전환 전후로 측정할 지표를 정의한다. successCondition은 통과 기준(여기서는 2xx 응답 비율 95% 이상), failureLimit은 허용하는 실패 횟수, interval·count는 측정 주기와 반복 횟수다.
# 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) | 롤백 |
|---|---|---|---|
| 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을 통합하는 방법을 다룬다.