
카나리 배포 vs 블루-그린 vs 롤링: 배포 전략 비교
서비스 다운 없이 새 버전을 배포하는 방법은 여러 가지다. 롤링 업데이트, 블루-그린, 카나리 배포의 동작 원리부터 트레이드오프, Kubernetes 구현까지 한 번에 정리한다.

서비스 다운 없이 새 버전을 배포하는 방법은 여러 가지다. 롤링 업데이트, 블루-그린, 카나리 배포의 동작 원리부터 트레이드오프, Kubernetes 구현까지 한 번에 정리한다.
AWS 콘솔 클릭질로 만든 서버가 왜 문제인지, Terraform으로 인프라를 코드로 선언하면 무엇이 달라지는지 실전 예제와 함께 정리했다.

새벽엔 낭비하고 점심엔 터지는 서버 문제 해결기. '택시 배차'와 '피자 배달' 비유로 알아보는 오토 스케일링과 서버리스의 차이, 그리고 Spot Instance를 활용한 비용 절감 꿀팁.

서버를 끄지 않고 배포하는 법. 롤링, 카나리, 블루-그린의 차이점과 실제 구축 전략. DB 마이그레이션의 난제(팽창-수축 패턴)와 AWS CodeDeploy 활용법까지 심층 분석합니다.

내 서버가 해킹당하지 않는 이유. 포트와 IP를 검사하는 '패킷 필터링'부터 AWS Security Group까지, 방화벽의 진화 과정.

배포는 항상 긴장된다. 코드를 밀어 넣는 그 순간, 뭔가 잘못될 수 있다. 그래서 "어떻게 배포하느냐"는 "무엇을 배포하느냐" 만큼 중요하다.
제로 다운타임 배포 전략은 크게 세 가지다. 롤링 업데이트, 블루-그린, 카나리. 각각 쓸 상황이 다르고 트레이드오프가 다르다. 뭘 언제 써야 하는지 명확하게 알아두자.
예전엔 "점검 시간" 공지 띄우고 새벽에 배포하는 게 당연했다. 지금은 다르다.
다운타임 없이 배포하는 방법을 익혀두는 건 이제 선택이 아니라 기본기다.
인스턴스를 한 번에 하나씩(또는 배치 단위로) 교체한다. 새 버전이 올라오면 이전 버전을 내리는 식이다.
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 응답 형식이 바뀌면 문제가 생긴다.
# 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
두 개의 동일한 프로덕션 환경을 유지한다. "블루(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"로 재사용
# 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"}}}'
이름의 유래: 광부들이 탄광에서 독가스 감지용으로 카나리아 새를 먼저 들여보낸 데서 왔다. 새 버전을 먼저 소수 트래픽에게만 노출시켜서 안전한지 확인한다.
트래픽 분배 (점진적 증가):
90% → [v1] [v1] [v1]
10% → [v2] ← 카나리
확인 후:
70% → [v1] [v1] [v1]
30% → [v2] [v2]
완전 전환:
0% → (v1 종료)
100% → [v2] [v2] [v2] [v2]
# 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])'
}
};
| 항목 | 롤링 업데이트 | 블루-그린 | 카나리 |
|---|---|---|---|
| 다운타임 | 없음 | 없음 | 없음 |
| 롤백 속도 | 느림 (재롤링) | 매우 빠름 (초 단위) | 빠름 |
| 비용 | 낮음 | 높음 (2배 인프라) | 중간 |
| 복잡도 | 낮음 | 중간 | 높음 |
| 영향 범위 | 점진적 | 전체 (전환 즉시) | 일부 (10~30%) |
| 버전 혼재 | 있음 | 없음 | 있음 |
| 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;
이렇게 단계별로 나눠서 마이그레이션하면 어떤 배포 전략과도 안전하게 조합할 수 있다.
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"다.
배포 전략 선택은 결국 세 가지 트레이드오프 사이의 균형이다.
팀 규모, 서비스 중요도, 배포 빈도에 따라 맞는 전략이 다르다. 중요한 건 "배포가 무서운 이벤트"가 아니라 "일상적인 프로세스"가 되는 것이다. 올바른 배포 전략은 그 여정을 훨씬 편하게 만들어준다.