카나리 배포 vs 블루-그린 vs 롤링: 배포 전략 비교
배포는 항상 긴장된다. 코드를 밀어 넣는 그 순간, 뭔가 잘못될 수 있다. 그래서 "어떻게 배포하느냐"는 "무엇을 배포하느냐" 만큼 중요하다.
제로 다운타임 배포 전략은 크게 세 가지다. 롤링 업데이트, 블루-그린, 카나리. 각각 쓸 상황이 다르고 트레이드오프가 다르다. 뭘 언제 써야 하는지 명확하게 알아두자.
왜 제로 다운타임이 중요한가
예전엔 "점검 시간" 공지 띄우고 새벽에 배포하는 게 당연했다. 지금은 다르다.
- 글로벌 서비스는 어느 시간대나 누군가 쓰고 있다
- SLA 99.9% = 연간 허용 다운타임 8.7시간
- 잦은 배포(CI/CD)가 필수인 팀은 점검 창을 낼 수 없다
다운타임 없이 배포하는 방법을 익혀두는 건 이제 선택이 아니라 기본기다.
1. 롤링 업데이트 (Rolling Update)
어떻게 동작하나
인스턴스를 한 번에 하나씩(또는 배치 단위로) 교체한다. 새 버전이 올라오면 이전 버전을 내리는 식이다.
Before: [v1] [v1] [v1] [v1]
Step 1: [v2] [v1] [v1] [v1]
Step 2: [v2] [v2] [v1] [v1]
Step 3: [v2] [v2] [v2] [v1]
After: [v2] [v2] [v2] [v2]
롤링 중에는 v1과 v2가 동시에 살아 있다. 그래서 두 버전이 하위 호환성을 갖춰야 한다. DB 스키마 변경이나 API 응답 형식이 바뀌면 문제가 생긴다.
Kubernetes 기본 설정
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 동시에 추가로 뜰 수 있는 Pod 수
maxUnavailable: 0 # 동시에 내려갈 수 있는 Pod 수 (0 = 항상 4개 유지)
selector:
matchLabels:
app: api-service
template:
metadata:
labels:
app: api-service
spec:
containers:
- name: api-service
image: my-registry/api-service:v2
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
maxUnavailable: 0으로 설정하면 항상 최소 4개의 Pod가 살아 있어서 다운타임이 없다. readinessProbe는 새 Pod가 준비됐을 때만 트래픽을 보내도록 보장한다.
롤백
# 이전 버전으로 즉시 롤백
kubectl rollout undo deployment/api-service
# 특정 리비전으로
kubectl rollout undo deployment/api-service --to-revision=3
# 롤아웃 상태 확인
kubectl rollout status deployment/api-service
장단점
장점
- 추가 인프라 필요 없음 (비용 효율적)
- Kubernetes 기본 지원, 설정 간단
- 점진적 배포라 문제 일찍 발견 가능
단점
- 배포 중 v1/v2 혼재 → 하위 호환 필수
- 롤백이 즉각적이지 않음 (다시 롤링해야 함)
- 복잡한 DB 마이그레이션과 함께 쓰기 까다로움
2. 블루-그린 배포 (Blue-Green Deployment)
어떻게 동작하나
두 개의 동일한 프로덕션 환경을 유지한다. "블루(Blue)"가 현재 라이브, "그린(Green)"이 새 버전. 새 버전이 준비되면 트래픽을 한 번에 전환한다.
Phase 1 (배포 전):
트래픽 → [Blue: v1] [Blue: v1] [Blue: v1] [Blue: v1]
[Green: v2] [Green: v2] [Green: v2] [Green: v2] (대기 중)
Phase 2 (전환):
트래픽 → [Green: v2] [Green: v2] [Green: v2] [Green: v2]
[Blue: v1] [Blue: v1] [Blue: v1] [Blue: v1] (대기 중, 롤백용)
Phase 3 (안정화 후):
Blue 환경 해제 또는 다음 배포의 "Blue"로 재사용
Kubernetes 구현
# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service-blue
spec:
replicas: 4
selector:
matchLabels:
app: api-service
version: blue
template:
metadata:
labels:
app: api-service
version: blue
spec:
containers:
- name: api-service
image: my-registry/api-service:v1
---
# green-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service-green
spec:
replicas: 4
selector:
matchLabels:
app: api-service
version: green
template:
metadata:
labels:
app: api-service
version: green
spec:
containers:
- name: api-service
image: my-registry/api-service:v2
---
# service.yaml - 여기서 트래픽 전환
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
selector:
app: api-service
version: blue # ← 이 라벨만 바꾸면 전환 완료
ports:
- port: 80
targetPort: 3000
트래픽 전환은 Service의 selector를 blue에서 green으로 바꾸는 것 하나다.
# 그린으로 전환
kubectl patch service api-service \
-p '{"spec":{"selector":{"version":"green"}}}'
# 문제 생기면 즉시 블루로 롤백 (1~2초)
kubectl patch service api-service \
-p '{"spec":{"selector":{"version":"blue"}}}'
장단점
장점
- 롤백이 즉각적 (트래픽 전환만)
- 두 버전이 동시에 서비스 중이 아님 → 호환성 걱정 덜함
- 그린 환경에서 충분히 테스트 후 전환 가능
단점
- 인프라 비용 2배 (전환 기간 동안)
- DB 스키마 변경이 있으면 여전히 복잡함 (둘 다 같은 DB를 씀)
- 스테이트풀 서비스에 적용하기 까다로움
3. 카나리 배포 (Canary Deployment)
어떻게 동작하나
이름의 유래: 광부들이 탄광에서 독가스 감지용으로 카나리아 새를 먼저 들여보낸 데서 왔다. 새 버전을 먼저 소수 트래픽에게만 노출시켜서 안전한지 확인한다.
트래픽 분배 (점진적 증가):
90% → [v1] [v1] [v1]
10% → [v2] ← 카나리
확인 후:
70% → [v1] [v1] [v1]
30% → [v2] [v2]
완전 전환:
0% → (v1 종료)
100% → [v2] [v2] [v2] [v2]
Kubernetes + Nginx Ingress로 구현
# canary-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-service-canary
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10" # 10% 트래픽
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service-canary
port:
number: 80
# stable-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-service-stable
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service-stable
port:
number: 80
카나리 비중 조절은 annotation 값만 바꾸면 된다.
# 카나리 비중 30%로 늘리기
kubectl annotate ingress api-service-canary \
nginx.ingress.kubernetes.io/canary-weight="30" --overwrite
# 이상 없으면 50%
kubectl annotate ingress api-service-canary \
nginx.ingress.kubernetes.io/canary-weight="50" --overwrite
# 완전 전환 후 stable 업데이트
kubectl set image deployment/api-service-stable api-service=my-registry/api-service:v2
kubectl delete ingress api-service-canary
헤더 기반 카나리
특정 사용자(내부 직원, 베타 테스터)에게만 새 버전 보내기:
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
nginx.ingress.kubernetes.io/canary-by-header-value: "true"
X-Canary: true 헤더를 보낸 요청만 새 버전으로 라우팅된다.
카나리 중 모니터링해야 할 지표
// 카나리 배포 중 자동으로 체크할 지표들
const CANARY_METRICS = {
errorRate: {
threshold: 0.01, // 에러율 1% 이하
query: 'rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m])'
},
p99Latency: {
threshold: 500, // P99 레이턴시 500ms 이하
query: 'histogram_quantile(0.99, rate(http_request_duration_ms_bucket[5m]))'
},
successRate: {
threshold: 0.99, // 성공률 99% 이상
query: 'rate(http_requests_total{status=~"2.."}[5m]) / rate(http_requests_total[5m])'
}
};
장단점
장점
- 실제 사용자 트래픽으로 테스트 (스테이징 환경과 다른 실전 데이터)
- 문제 발생 시 영향 범위 최소화 (10%만 영향)
- 지표 기반 자동 롤백 가능
단점
- 롤링과 마찬가지로 두 버전 동시 서비스 → 호환성 필요
- 트래픽 분배 인프라 필요 (Ingress, 서비스 메시 등)
- 설정 복잡도 높음
전략 비교표
| 항목 | 롤링 업데이트 | 블루-그린 | 카나리 |
|---|---|---|---|
| 다운타임 | 없음 | 없음 | 없음 |
| 롤백 속도 | 느림 (재롤링) | 매우 빠름 (초 단위) | 빠름 |
| 비용 | 낮음 | 높음 (2배 인프라) | 중간 |
| 복잡도 | 낮음 | 중간 | 높음 |
| 영향 범위 | 점진적 | 전체 (전환 즉시) | 일부 (10~30%) |
| 버전 혼재 | 있음 | 없음 | 있음 |
| DB 마이그레이션 | 까다로움 | 까다로움 | 까다로움 |
| 적합한 팀 | 소/중규모 | 중/대규모 | 대규모, 고가용성 |
어떤 전략을 선택해야 할까
롤링 업데이트가 맞는 상황
- 인프라 비용이 중요한 스타트업이나 소규모 서비스
- API가 하위 호환성을 잘 지키는 팀
- Kubernetes를 처음 도입하고 복잡도를 낮게 유지하고 싶을 때
블루-그린이 맞는 상황
- 즉각적인 롤백이 비즈니스적으로 매우 중요할 때
- 새 버전을 충분히 테스트한 뒤 전환하고 싶을 때
- 마이크로서비스 간 호환성 관리가 복잡한 경우
카나리가 맞는 상황
- 트래픽이 많아서 소수 실사용자로 먼저 검증이 필요할 때
- A/B 테스트와 함께 기능 검증을 하고 싶을 때
- SRE 팀이 있고 지표 기반 자동화 파이프라인을 갖출 수 있을 때
현실적으로 많은 팀이 평소엔 롤링, 중요 릴리스는 블루-그린, 대규모 기능 출시는 카나리 식으로 혼합해서 쓴다.
DB 마이그레이션과의 궁합
어떤 전략을 쓰든 DB 스키마 변경은 별도로 관리해야 한다. 팁 몇 가지:
-- 1단계: 새 컬럼 추가 (하위 호환, 배포 전)
ALTER TABLE users ADD COLUMN display_name VARCHAR(100);
-- 2단계: 새 코드 배포 (display_name 쓰기 시작)
-- 기존 코드도 아직 살아 있으므로 nullable 이어야 함
-- 3단계: 데이터 백필
UPDATE users SET display_name = username WHERE display_name IS NULL;
-- 4단계: NOT NULL 제약 추가 (구 버전 완전히 사라진 후)
ALTER TABLE users ALTER COLUMN display_name SET NOT NULL;
-- 5단계: 구 컬럼 제거 (몇 배포 후)
ALTER TABLE users DROP COLUMN username;
이렇게 단계별로 나눠서 마이그레이션하면 어떤 배포 전략과도 안전하게 조합할 수 있다.
Argo Rollouts로 자동화하기
Kubernetes에서 카나리/블루-그린을 더 쉽게 관리하는 도구가 Argo Rollouts다.
# rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: api-service
spec:
replicas: 4
strategy:
canary:
steps:
- setWeight: 10 # 10% 카나리
- pause: {} # 수동 확인 대기
- setWeight: 30 # 30%로 증가
- pause:
duration: 10m # 10분 자동 대기
- setWeight: 60
- pause:
duration: 10m
- setWeight: 100 # 완전 전환
# 자동 분석
analysis:
templates:
- templateName: success-rate
startingStep: 2
args:
- name: service-name
value: api-service-canary
template:
# ... Pod 스펙
에러율이 임계값을 넘으면 자동으로 롤백한다. 이런 게 "Progressive Delivery"다.
마무리
배포 전략 선택은 결국 세 가지 트레이드오프 사이의 균형이다.
- 비용 vs 빠른 롤백 (블루-그린)
- 단순함 vs 안전성 (롤링 vs 카나리)
- 속도 vs 영향 범위 제어 (롤링 vs 카나리)
팀 규모, 서비스 중요도, 배포 빈도에 따라 맞는 전략이 다르다. 중요한 건 "배포가 무서운 이벤트"가 아니라 "일상적인 프로세스"가 되는 것이다. 올바른 배포 전략은 그 여정을 훨씬 편하게 만들어준다.