4.8. 배포 전략
어떨 때 읽는 장인가
- 새 버전을 배포하는데 서비스가 끊기면 안 될 때
- 문제가 생기면 즉시 되돌려야 할 때
- 새 버전을 일부 사용자에게 먼저 보여 주고 싶을 때
- 어느 방식을 쓸지 정해야 할 때
앞의 4.2~4.7 장이 무엇으로 배포하는가(Console·Jenkins·Bastion·CI 도구·ArgoCD)를 다뤘다면, 이 장은 어떤 순서로 바꾸는가 를 다룹니다. 어느 경로로 배포하든 여기서 고른 방식을 그대로 쓸 수 있습니다.
네 가지 비교
롤링 업데이트 와 Recreate 는 Deployment 설정으로 끝나지만, 블루그린 과 카나리 는 Deployment 설정이 아닙니다. Service 를 어떻게 두느냐로 만드는 방식 입니다.
| 전략 | 무엇으로 하는가 | 서비스 중단 | 되돌리는 속도 | 필요한 자원 |
|---|---|---|---|---|
| 롤링 업데이트 | Deployment 설정 | 없음 | 롤백에 시간이 걸림 | 조금 더 |
| Recreate | Deployment 설정 | 있음 | 다시 배포해야 함 | 그대로 |
| 블루그린 | Deployment 2개 + Service 선택 조건 전환 | 없음 | 즉시 | 두 배 |
| 카나리 | Deployment 2개 + 복제본 비율 | 없음 | 빠름 | 조금 더 |
Kubernetes 가 직접 지원하는 것은 앞의 둘뿐입니다. 뒤의 둘은 이미 있는 기능(Service 의 레이블 선택)을 조합해 만듭니다.
롤링 업데이트 — 하나씩 바꾸기
기본값입니다. 새 파드를 하나 띄우고 준비되면 옛 파드를 하나 내리는 식으로 교체합니다.
설정은 Deployment 의 strategy 한 곳에서 끝납니다(3.2 장).
| 좋은 점 | 아쉬운 점 |
|---|---|
| 설정이 간단합니다 | 교체 중에 두 버전이 동시에 돕니다 |
| 자원을 조금만 더 씁니다 | 되돌리려면 다시 롤링으로 바꿔야 해 시간이 걸립니다 |
| 서비스가 끊기지 않습니다 | 문제를 늦게 발견하면 이미 대부분 교체된 뒤입니다 |
대부분의 경우 이것으로 충분합니다. 아래 방식은 이것으로 안 되는 사정이 있을 때 씁니다.
Recreate — 다 내리고 다시 띄우기
옛 파드를 전부 내린 뒤 새 파드를 띄웁니다. 그 사이 서비스가 멈춥니다.
두 버전이 동시에 돌면 안 될 때만 씁니다.
| 쓰는 경우 | 이유 |
|---|---|
| 데이터베이스 스키마가 바뀜 | 옛 버전이 새 스키마를 읽지 못합니다 |
같은 파일을 쓰는데 ReadWriteOnce | 두 파드가 동시에 붙을 수 없습니다(6.1 장) |
| 배타적 잠금을 쓰는 프로그램 | 둘이 동시에 돌면 충돌합니다 |
블루그린 — 다 띄운 뒤 한 번에 옮기기
같은 애플리케이션을 Deployment 두 개로 나눠 두고, Service 가 어느 쪽을 볼지 바꾸는 방식입니다. Service 는 레이블로 파드를 고르므로(5.1 장) 그 조건만 바꾸면 트래픽이 통째로 옮겨 갑니다.
COP 는 이 구성을 샘플 애플리케이션에 미리 만들어 둡니다. egov 네임스페이스를 보면
Deployment 가 넷 있습니다.
| Deployment | 레이블 | 용도 |
|---|---|---|
egov | application: egov | 기본 배포 |
egov-blue | application: egov-blue | 블루그린의 한쪽 |
egov-green | application: egov-green | 블루그린의 다른 쪽 |
egov-hpa | application: egov-hpa | 자동 확장 시연용(3.5 장) |
아래 설명은 이 실제 구성을 그대로 따릅니다.
1. Deployment 를 두 벌 만듭니다. 레이블 하나로 구분합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: egov-blue
namespace: egov
spec:
replicas: 2
selector:
matchLabels:
application: egov-blue # 이 값으로 Service 가 고릅니다
template:
metadata:
labels:
application: egov-blue
spec:
containers:
- name: egov-blue
image: registry.example.com/apps/egov:1.0.0
egov-green 은 위와 같고 이름과 레이블만 egov-green, 이미지는 새 버전으로 둡니다.
COP 는 두 벌을 각각 빌드·배포할 수 있게 스크립트 디렉터리도 나눠 둡니다. Bastion 의
workspaces/apps/egov/ 아래에 egov-blue·egov-green 디렉터리가 따로 있고, 각각의
env.sh 에 자기 Deployment 이름이 들어 있습니다(4.5 장). 그래서 한쪽만 새
버전으로 배포할 수 있습니다.
2. Service 는 한쪽만 가리킵니다.
apiVersion: v1
kind: Service
metadata:
name: egov-blue-green
namespace: egov
labels:
application: egov-blue-green
spec:
type: ClusterIP
selector:
application: egov-blue # 지금은 blue 로 보냅니다
ports:
- protocol: TCP
port: 8080
targetPort: 8080
Ingress 는 이 Service 하나만 가리키므로 외부 주소는 전환해도 바뀌지 않습니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: egov-blue-green
namespace: egov
spec:
ingressClassName: default
rules:
- host: egov-blue-green.apps.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: egov-blue-green # Service 는 그대로, 뒤만 바뀝니다
port:
number: 8080
3. 새 버전을 먼저 띄워 확인합니다. egov-green 은 Service 가 가리키지 않으므로 사용자
트래픽을 받지 않습니다. 확인용 Service 를 하나 더 두면 미리 시험할 수 있습니다.
apiVersion: v1
kind: Service
metadata:
name: egov-preview # 확인 전용
namespace: egov
spec:
selector:
application: egov-green
ports:
- port: 8080
targetPort: 8080
4. 전환합니다. Service 의 선택 조건을 egov-green 으로 바꿉니다. Console 의 Service
상세에서 편집하거나 YAML 편집기로 고칩니다(1.2 장).
spec:
selector:
application: egov-green # egov-blue → egov-green
되돌릴 때는 이 값을 egov-blue 로 돌리면 끝입니다. 옛 버전 파드가 그대로 떠 있으므로 몇
초 안에 원래대로 돌아갑니다. 이것이 블루그린의 가장 큰 이점입니다.
Jenkins 작업이 이 전환을 대신합니다(4.4 장). 전환 대상을 고르면 위 Service 선택 조건 변경을 수행합니다. 실제로 하는 일은 이 한 가지뿐입니다.
| 좋은 점 | 아쉬운 점 |
|---|---|
| 되돌리기가 즉시 됩니다 | 두 버전이 동시에 떠 있어 자원이 두 배 필요합니다 |
| 새 버전을 미리 확인할 수 있습니다 | 데이터베이스를 공유하면 두 버전이 함께 쓸 수 있어야 합니다 |
| 두 버전이 섞여 돌지 않습니다 | 관리할 자원이 늘어납니다 |
전환 뒤 옛 버전을 바로 지우지 마십시오. 문제가 늦게 드러날 수 있으므로 하루 이틀 남겨 두었다가 정리하는 것이 안전합니다. 자원이 아까우면 복제본을 0 으로 줄여 둡니다.
카나리 — 일부에게만 먼저
새 버전을 소수의 파드로만 띄워 일부 사용자에게 먼저 보여 주고, 문제가 없으면 비율을 늘려 갑니다. 광부가 유해 가스를 알아채려고 데려간 새에서 온 이름입니다.
COP 가 미리 만들어 주지는 않습니다. 블루그린과 달리 준비된 구성이 없어 직접 만들어야 합니다. 다만 자원 구성은 블루그린과 거의 같고 Service 가 양쪽을 모두 가리킨다 는 점만 다릅니다.
1. 두 Deployment 에 공통 레이블을 하나 둡니다.
# 안정 버전
apiVersion: apps/v1
kind: Deployment
metadata:
name: egov-stable
namespace: egov
spec:
replicas: 9
selector:
matchLabels:
application: egov-canary # 공통 — Service 가 이것을 봅니다
track: stable # 구분용
template:
metadata:
labels:
application: egov-canary
track: stable
spec:
containers:
- name: egov
image: registry.example.com/apps/egov:1.0.0
# 새 버전 — 처음에는 1개만
apiVersion: apps/v1
kind: Deployment
metadata:
name: egov-canary
namespace: egov
spec:
replicas: 1
selector:
matchLabels:
application: egov-canary # 같은 값
track: canary # 여기만 다릅니다
template:
metadata:
labels:
application: egov-canary
track: canary
spec:
containers:
- name: egov
image: registry.example.com/apps/egov:1.1.0
2. Service 는 공통 레이블만 봅니다.
apiVersion: v1
kind: Service
metadata:
name: egov-canary
namespace: egov
spec:
selector:
application: egov-canary # track 은 넣지 않습니다
ports:
- port: 8080
targetPort: 8080
track 을 빼는 것이 핵심입니다. 넣으면 한쪽만 대상이 되어 블루그린과 같아집니다.
3. 복제본 비율로 단계를 올립니다.
| 단계 | egov-stable | egov-canary | 새 버전이 받는 비율 |
|---|---|---|---|
| 시작 | 9 | 1 | 10% |
| 1차 확대 | 7 | 3 | 30% |
| 2차 확대 | 5 | 5 | 50% |
| 완료 | 0 | 10 | 100% |
Console 의 복제본 수 조절로 단계를 올립니다(위 "복제본 수 조절"). 문제가 보이면 카나리 쪽을 0 으로 내리면 즉시 멈춥니다.
| 좋은 점 | 아쉬운 점 |
|---|---|
| 문제가 있어도 일부 사용자만 겪습니다 | 비율이 파드 수로만 정해집니다 |
| 실제 트래픽으로 확인할 수 있습니다 | 두 버전이 동시에 돌므로 호환되어야 합니다 |
| 자원을 조금만 더 씁니다 | 단계를 사람이 올려야 합니다 |
비율이 정확 하지 않습니다. 이 방식의 트래픽 비율은 파드 개수 비율입니다. "5% 에게만" 처럼 세밀하게 나누려면 파드가 20개는 있어야 합니다.
카나리 — 요청 비율로 나누기 (HAProxy)
앞의 방식은 파드 개수로만 비율이 정해집니다. 요청 수를 기준으로 정확히 나누려면 COP 가
쓰는 HAProxy Ingress Controller 의 route-acl 기능을 씁니다(5.1 장).
Service 에 조건을 하나 붙이면 그 조건에 맞는 요청만 그 Service 로 갑니다. 파드가 몇 개든 상관없습니다.
구성
1. Service 두 개를 따로 둡니다. 앞 절과 달리 각자 자기 파드만 가리킵니다.
apiVersion: v1
kind: Service
metadata:
name: egov-stable
namespace: egov
spec:
selector:
application: egov
track: stable
ports:
- port: 8080
targetPort: 8080
apiVersion: v1
kind: Service
metadata:
name: egov-canary
namespace: egov
annotations:
haproxy.org/route-acl: rand(100) lt 20 # 요청의 20% 만 이리로
spec:
selector:
application: egov
track: canary
ports:
- port: 8080
targetPort: 8080
rand(100) lt 20 은 요청마다 0~99 중 하나를 뽑아 20 미만이면 참 이라는 뜻입니다. 숫자를
바꾸면 비율이 바뀝니다 — lt 5 는 5%, lt 50 은 50% 입니다.
2. Ingress 를 두 개 만듭니다. 같은 주소·같은 경로로 둡니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: egov-canary
namespace: egov
spec:
ingressClassName: default
rules:
- host: egov.apps.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: egov-canary # 조건이 붙은 쪽
port:
number: 8080
안정 버전 Ingress 는 같은 내용에 egov-stable 을 가리키면 됩니다.
route-acl 을 붙여도 Ingress 는 반드시 있어야 합니다. 주석만으로는 아무 일도 일어나지
않습니다.
이렇게 동작합니다
컨트롤러가 만드는 규칙은 이런 형태이고, 조건이 붙은 규칙이 일반 규칙보다 앞에 놓입니다.
use_backend egov_svc_egov-canary_8080 if { 주소 일치 } { 경로 일치 } { rand(100) lt 20 }
use_backend <일반 라우팅>
요청이 오면 위에서부터 확인합니다. 20% 는 첫 줄에 걸려 카나리로 가고, 나머지 80% 는 아래로 내려가 안정 버전으로 갑니다.
비율 올리기
Service 의 주석 값만 바꿉니다. 파드 수는 건드리지 않습니다.
| 단계 | 주석 값 | 카나리가 받는 비율 |
|---|---|---|
| 시작 | rand(100) lt 5 | 5% |
| 1차 | rand(100) lt 20 | 20% |
| 2차 | rand(100) lt 50 | 50% |
| 완료 | 카나리 Ingress 를 지우고 안정 버전을 새 이미지로 배포 | — |
멈출 때는 카나리 Ingress 를 지우면 됩니다. 조건이 사라져 모든 요청이 안정 버전으로 갑니다.
비율 말고 다른 조건도 됩니다
route-acl 에는 HAProxy 의 조건식을 그대로 씁니다. 무작위 비율 대신 특정 대상에게만 새
버전을 보여 줄 수도 있습니다.
| 목적 | 조건식 |
|---|---|
| 요청의 N% | rand(100) lt 20 |
| 특정 쿠키를 가진 사용자 | cookie(canary) -m found |
| 특정 헤더를 보낸 요청 | req.hdr(X-Canary) -m str yes |
| 사내 주소 대역에서만 | src 10.0.0.0/8 |
내부 검증에는 쿠키나 헤더 조건이 더 낫습니다. 무작위 비율은 같은 사용자가 요청마다 다른 버전을 받아 화면이 오락가락할 수 있지만, 쿠키 조건은 그 사용자만 계속 새 버전을 봅니다.
두 방식 비교
| 복제본 비율 | route-acl | |
|---|---|---|
| 비율 단위 | 파드 개수로만 정해집니다 | 요청 단위로 자유롭게 정합니다 |
| 5% 로 맞추려면 | 파드가 20개 필요 | 숫자만 lt 5 로 |
| 조정 방법 | 복제본 수 변경 | 주석 값 변경 |
| 같은 사용자 | 요청마다 달라질 수 있음 | 조건에 따라 고정 가능(쿠키·헤더) |
| 필요한 자원 | 비율만큼 파드 필요 | 카나리 파드 1개면 충분 |
| 적용 범위 | 어느 환경에서나 | HAProxy Ingress Controller 를 쓰는 환경 |
한 Ingress 에 경로를 여럿 두지 마십시오. 여러 경로가 같은 요청에 맞을 수 있는 Ingress 에서는 이 기능이 의도대로 동작하지 않습니다. 카나리용 Ingress 는 경로 하나만 둡니다.
비율은 무작위입니다. 요청 수가 적으면 실제 분배가 지정한 값과 차이가 납니다. 100건 정도로는 20% 가 정확히 20건이 되지 않습니다.
무엇을 고를 것인가
| 상황 | 권장 |
|---|---|
| 특별한 사정이 없다 | 롤링 업데이트 |
| 두 버전이 동시에 돌면 안 된다 | Recreate |
| 문제가 생기면 즉시 되돌려야 한다 | 블루그린 |
| 자원 여유가 두 배 있다 | 블루그린 |
| 실제 트래픽으로 먼저 확인하고 싶다 | 카나리 |
| 자원 여유가 적다 | 롤링 업데이트 또는 카나리 |
어느 방식이든 준비 상태 검사가 있어야 합니다(3.1 장). 새 파드가 준비되었는지 판단하지 못하면 롤링은 멈추고, 블루그린·카나리는 아직 뜨지 않은 파드로 트래픽을 보냅니다.
블루그린과 카나리는 옛 버전과 새 버전이 같은 데이터를 다룰 수 있어야 합니다. 스키마가 바뀌는 배포에는 쓸 수 없습니다 — 그때는 Recreate 를 쓰거나, 두 버전이 함께 쓸 수 있도록 스키마를 단계적으로 바꿉니다.