Certified Kubernetes Application Developer (CKAD) #8 Deployment 전략: Blue-green, canary
새 버전을 배포하면서도 서비스가 끊기지 않게 하는 것은 운영의 기본기입니다. 클라우드의 매니지드 배포 도구나 서비스 메시는 이 작업을 화려하게 자동화하지만, CKAD는 그런 도구가 없는 바닐라 쿠버네티스에서 똑같은 결과를 만들어 낼 수 있는지를 묻습니다. 즉 Deployment와 Service, 그리고 label만으로 blue-green과 canary를 직접 구현하는 능력입니다.
핵심은 단 하나입니다. Service는 selector(label)로 Pod를 고른다는 사실입니다. 이 selector를 어떻게 바꾸느냐, 어떤 Pod 들이 그 selector에 동시에 걸리느냐를 조절하면 트래픽의 흐름을 손으로 제어할 수 있습니다. 이번 글에서는 Deployment의 rolling update를 복습한 뒤, label 전환만으로 blue-green과 canary를 구성하겠습니다.
출발점: rolling update와 recreate 복습 #
배포 전략을 직접 구현하기 전에, Deployment가 기본으로 제공하는 두 전략을 짚고 가겠습니다. Deployment의 spec.strategy.type에는 RollingUpdate(기본값)와 Recreate 두 가지가 있습니다.
RollingUpdate는 기존 Pod를 한꺼번에 내리지 않고 새 Pod를 조금씩 띄우며 교체합니다. 교체 속도는 maxSurge(임시로 더 띄울 수 있는 Pod 수)와 maxUnavailable(동시에 내릴 수 있는 Pod 수)로 조절합니다. 이미지를 바꾸면 같은 Service 뒤에서 구버전과 신버전 Pod가 잠깐 섞여 돌며 무중단으로 넘어갑니다. 자세한 동작은 #5와 K8s 실무 #4에서 다뤘습니다.
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0Recreate는 기존 Pod를 모두 내린 뒤 새 Pod를 띄웁니다. 그사이 다운타임이 생기지만, 구버전과 신버전이 동시에 떠 있으면 안 되는 경우(예: 단일 쓰기 데이터베이스 마이그레이션)에 씁니다.
spec:
strategy:
type: Recreaterolling update는 강력하지만 한계가 있습니다. 구버전과 신버전이 교체 도중 섞여서 트래픽을 받는다는 점, 그리고 트래픽 비율을 정밀하게 제어하기 어렵다는 점입니다. 이 한계를 보완하려고 blue-green과 canary가 등장합니다. 두 전략 모두 쿠버네티스가 별도 리소스로 제공하지 않으며, Deployment와 Service, label을 조합해 직접 만듭니다.
Blue-green: selector 전환으로 즉시 컷오버 #
blue-green은 현재 버전(blue)과 새 버전(green)을 동시에 띄워 두고, Service의 selector를 한 번에 green으로 돌려 트래픽을 통째로 옮기는 방식입니다. 교체 도중 두 버전이 섞이지 않으며, 문제가 생기면 selector를 blue로 되돌려 즉시 롤백합니다.
1) blue Deployment와 Service #
먼저 blue를 띄우고, 그 Pod만 가리키는 Service를 만듭니다. 핵심은 selector에 version: blue를 넣는 것입니다.
# blue Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-blue
spec:
replicas: 3
selector:
matchLabels:
app: web
version: blue
template:
metadata:
labels:
app: web
version: blue
spec:
containers:
- name: web
image: nginx:1.25
---
# 두 label을 모두 selector로 거는 Service
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
version: blue
ports:
- port: 80
targetPort: 80이 시점에서 web Service는 app: web이면서 version: blue 인 Pod만 고르므로, 트래픽은 전부 blue로 갑니다.
2) green Deployment를 별도로 띄운다 #
새 버전은 label만 version: green으로 다르게 하여 별도 Deployment 로 띄웁니다. 아직 Service의 selector는 blue이므로 green은 트래픽을 받지 않습니다. 이 상태에서 green을 충분히 검증합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-green
spec:
replicas: 3
selector:
matchLabels:
app: web
version: green
template:
metadata:
labels:
app: web
version: green
spec:
containers:
- name: web
image: nginx:1.27green에 직접 접근해 검증하고 싶다면 임시 테스트용 Service를 따로 만들거나 kubectl port-forward deploy/web-green 8080:80으로 들여다봅니다.
3) 컷오버: Service selector를 green으로 #
검증이 끝나면 Service의 selector를 green으로 바꿉니다. 이 한 번의 변경으로 모든 트래픽이 green으로 즉시 넘어갑니다. 명령형으로 빠르게 처리하는 방법은 두 가지입니다.
# 방법 1: kubectl set selector
k set selector svc web 'app=web,version=green'
# 방법 2: kubectl patch
k patch svc web -p '{"spec":{"selector":{"app":"web","version":"green"}}}'k get endpoints web로 Service 뒤의 Pod IP가 green Pod로 바뀌었는지 확인합니다.
4) 롤백: selector를 blue로 되돌린다 #
green에서 문제가 발견되면 selector를 다시 blue로 돌리기만 하면 됩니다. blue Deployment를 그대로 남겨 두었기 때문에 롤백이 즉시 끝납니다.
k patch svc web -p '{"spec":{"selector":{"app":"web","version":"blue"}}}'green이 안정화되었다고 판단되면 blue Deployment를 삭제해 자원을 회수합니다. blue-green의 비용은 컷오버 전까지 두 버전을 동시에 띄워 자원을 두 배로 쓴다는 점입니다. 대신 컷오버와 롤백이 selector 변경 한 번으로 끝나 가장 빠릅니다.
Canary: 공통 label과 replicas 비율로 트래픽 분배 #
canary는 새 버전을 전체에 한꺼번에 노출하지 않고 소수의 트래픽에만 먼저 흘려보내 위험을 줄이는 방식입니다. blue-green이 selector를 통째로 바꿔 즉시 전환했다면, canary는 stable과 canary의 Pod를 하나의 Service가 함께 선택하게 만들어 트래픽을 나눕니다.
핵심 아이디어: 공통 label로 동시 선택 #
Service의 selector를 두 Deployment가 공유하는 label(예: app: web)로만 잡습니다. 그러면 stable Pod와 canary Pod가 모두 같은 Service 뒤에 묶입니다. Service는 자신이 고른 Pod 들에 대략 균등하게 트래픽을 분산하므로, replicas 개수의 비율이 곧 트래픽 비율의 근사치가 됩니다.
1) stable Deployment와 공통 selector Service #
# stable: replicas 9
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-stable
spec:
replicas: 9
selector:
matchLabels:
app: web
track: stable
template:
metadata:
labels:
app: web
track: stable
spec:
containers:
- name: web
image: nginx:1.25
---
# Service는 공통 label인 app: web만 selector로 잡는다
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80Service의 selector에 track이 없다는 점이 핵심입니다. app: web만 보므로 stable이든 canary 든 이 label만 있으면 모두 트래픽을 받습니다.
2) canary Deployment를 소수로 추가 #
새 버전을 track: canary label로, 그러나 공통 label 인 app: web은 그대로 단 채로 적은 replicas로 띄웁니다. stable 9 개와 canary 1 개면 약 9:1, 즉 전체 트래픽의 약 10%만 canary로 흘러갑니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-canary
spec:
replicas: 1
selector:
matchLabels:
app: web
track: canary
template:
metadata:
labels:
app: web
track: canary
spec:
containers:
- name: web
image: nginx:1.27이 상태에서 k get endpoints web을 보면 stable 9 개와 canary 1 개의 Pod IP가 함께 들어 있습니다. 트래픽 비율을 더 높이고 싶으면 canary의 replicas를 늘립니다.
# canary 비중을 약 30%로 (stable 7 : canary 3)
k scale deploy web-canary --replicas=3
k scale deploy web-stable --replicas=73) 승격 또는 폐기 #
canary의 지표(에러율, 지연)가 정상이면 stable을 새 버전으로 교체하고 canary를 거둡니다. stable의 이미지를 올린 뒤 canary를 0으로 줄이거나 삭제합니다.
# stable을 새 버전으로 교체하고 원래 replicas로 복구
k set image deploy/web-stable web=nginx:1.27
k scale deploy web-stable --replicas=9
# canary 정리
k delete deploy web-canary문제가 발견되면 canary를 삭제하는 것으로 즉시 노출을 중단합니다. stable은 손대지 않았으므로 사용자 영향이 적습니다.
canary의 한계도 분명합니다. 트래픽 분배가 replicas 개수에 비례하는 근사치일 뿐, 헤더나 사용자 기준의 정밀한 라우팅은 불가능합니다. 정밀 제어가 필요하면 Ingress의 canary 어노테이션이나 서비스 메시를 쓰지만, CKAD 범위는 여기까지의 replicas 기반 구현입니다.
세 전략 비교 #
| 항목 | rolling update | blue-green | canary |
|---|---|---|---|
| 구현 | Deployment 기본 strategy | 두 Deployment + selector 전환 | 두 Deployment + 공통 label |
| 추가 자원 | 거의 없음(maxSurge만큼) | 두 배(두 버전 동시 유지) | 약간(canary replicas만큼) |
| 롤백 속도 | 보통(k rollout undo) | 가장 빠름(selector 되돌리기) | 빠름(canary 삭제) |
| 트래픽 제어 | 불가(점진 교체) | 전부 또는 전무 | replicas 비율로 근사 |
| 버전 혼재 | 교체 도중 섞임 | 섞이지 않음 | 의도적으로 공존 |
| 검증 기회 | 적음 | 컷오버 전 충분히 | 소수 트래픽으로 점진 |
세 전략은 우열이 아니라 상황에 따른 선택입니다. 자원 여유가 적고 무난한 교체면 rolling update, 빠른 컷오버와 즉시 롤백이 중요하면 blue-green, 위험을 점진적으로 검증하고 싶으면 canary 입니다.
시험 포인트 #
CKAD에서 이 주제는 selector 전환이 전부라고 해도 지나치지 않습니다.
- blue-green 컷오버는 Service의 selector 변경입니다.
k set selector svc <name> 'app=web,version=green'또는k patch svc를 손에 익혀 두면 몇 초 만에 끝납니다. - 롤백은 selector를 되돌리는 것입니다. blue Deployment를 미리 지우지 않는 것이 롤백의 전제입니다.
- canary는 Service selector를 공통 label로만 잡고, 두 Deployment가 그 label을 공유하게 만드는 것이 핵심입니다. selector에
track같은 구분 label을 넣어 버리면 분배가 되지 않습니다. - 트래픽 비율은
k scale deploy ... --replicas=N으로 조정합니다. 9:1이면 약 10%라는 점만 기억하면 됩니다. - 확인은 항상
k get endpoints <svc>와k get pods --show-labels로 합니다. 어떤 Pod가 Service 뒤에 묶였는지를 눈으로 검증하는 습관이 오답을 막습니다.
label과 selector의 동작이 헷갈린다면 K8s 실무 #5에서 Service가 어떻게 Pod를 찾는지를 다시 확인하면 좋습니다.
정리 #
이번 글에서 잡은 것:
- 배포 전략은 별도 리소스가 아니라 Deployment + Service + label의 조합입니다. CKAD는 매니지드 도구 없이 직접 구현하는 능력을 묻습니다.
- rolling update(기본, 점진 교체)와 recreate(전체 내림 후 띄움)는 Deployment의 strategy로 제공됩니다.
- blue-green은 두 버전을 동시에 띄우고 Service selector를 한 번에 전환해 즉시 컷오버합니다. 롤백은 selector 되돌리기로 가장 빠릅니다.
- canary는 Service가 공통 label로 stable과 canary를 함께 선택하게 하고, replicas 비율로 트래픽을 근사 분배합니다.
- 모든 전략의 검증은
k get endpoints와--show-labels로 어떤 Pod가 묶였는지 확인하는 것에서 시작합니다.
다음: Helm #
지금까지는 매니페스트를 손으로 만들고 명령형으로 조작했습니다. 그러나 실무에서는 같은 앱을 환경마다 조금씩 다르게 반복 배포해야 합니다. #9 Helm: install, upgrade, rollback, values에서는 매니페스트를 템플릿으로 묶어 values로 환경 차이를 주입하고, install과 upgrade, rollback을 패키지 단위로 다루는 Helm을 정리하겠습니다.