현대의 마이크로서비스 아키텍처에서는 응용 프로그램의 업데이트와 배포가 매우 빈번하고 중요한 작업입니다. 사용자 경험에 영향을 주지 않으면서 안전하고 원활하게 새로운 버전의 애플리케이션을 프로덕션 환경으로 이동시키는 것은 모든 개발자와 운영 팀이 해결해야 할 과제입니다. 단계적 배포(그레이스풀 리릴리즈)는 출시 위험을 줄이는 데 효과적인 점진적인 배포 전략이며, Kubernetes의 Ingress 애노테이션 기능은 이를 간단하고 강력하게 구현하는 방법을 제공합니다.
이 문서에서는 Kubernetes에서 Ingress 애노테이션을 사용하여 단계적 배포를 어떻게 수행할 수 있는지 자세히 설명하며, 트래픽 분배 및 가중치 제어와 같은 핵심 기술을 단계적으로 익힐 수 있습니다. Kubernetes 초보자나 숙련된 사용자 모두에게 유용한 지식과 실행 가이드를 제공합니다.
단계적 배포란?
단계적 배포 또는 캔러리 배포(Canary Release)는 점진적인 애플리케이션 배포 전략입니다. 이 방식의 핵심 아이디어는 다음과 같습니다: 새로운 버전의 애플리케이션을 소수의 사용자에게만 단계적으로 배포하여 그 상태를 관찰하고 문제가 없다면 점차 범위를 확장하여 완전한 배포를 완료합니다.
전체 배포에 비해 단계적 배포는 다음과 같은 이점이 있습니다:
- 위험 감소: 전체 시스템 고장을 방지하기 위해 작은 범위에서 새 버전을 검증합니다.
- 빠른 롤백: 새 버전에 문제가 발생하면 기존 버전으로 신속하게 복귀할 수 있습니다.
- 사용자 경험이 개선됨: 점진적인 배포로 인해 사용자에게 미치는 영향을 최소화합니다.
Kubernetes에서 단계적 배포 구현 방법
Kubernetes에서는 다양한 방법으로 단계적 배포를 구현할 수 있습니다:
- Deployment + Service: 트래픽 스위칭을 수동으로 제어합니다.
- Istio: 서비스 메시를 통해 고급 트래픽 관리를 수행합니다.
- Ingress 애노테이션: Nginx Ingress 컨트롤러의 애노테이션 기능을 사용하여 트래픽을 분배합니다.
본 문서에서는 간단하고 추가 구성 요소 없이도 사용 가능한 Ingress 애노테이션 방법에 중점을 두겠습니다.
Ingress 애노테이션을 통한 단계적 배포
Nginx Ingress 컨트롤러는 단계적 배포를 쉽게 구현할 수 있도록 다양한 애노테이션(Annotations)을 제공합니다. 아래는 구체적인 단계입니다:
- 기존 및 새 버전 애플리케이션 배포
먼저 두 가지 버전의 애플리케이션을 배포해야 합니다:
- 기존 버전(v1): 현재 프로덕션에서 실행 중인 버전.
- 새 버전(v2): 배포될 예정인 새 버전.
1.1 v1 버전 Deployment 및 Service 생성
# v1 버전 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-old-version
spec:
replicas: 3
template:
metadata:
labels:
app: my-app
version: old
spec:
containers:
- name: app-container
image: my-app:old
ports:
- containerPort: 80
# v1 버전 Service
apiVersion: v1
kind: Service
metadata:
name: service-old
spec:
selector:
app: my-app
version: old
ports:
- port: 80
targetPort: 80
1.2 v2 버전 Deployment 및 Service 생성
# v2 버전 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-new-version
spec:
replicas: 3
template:
metadata:
labels:
app: my-app
version: new
spec:
containers:
- name: app-container
image: my-app:new
ports:
- containerPort: 80
# v2 버전 Service
apiVersion: v1
kind: Service
metadata:
name: service-new
spec:
selector:
app: my-app
version: new
ports:
- port: 80
targetPort: 80
- Ingress 설정을 통한 단계적 배포
Nginx Ingress Controller의 canary 애노테이션을 사용하여 트래픽 분배를 쉽게 구현할 수 있습니다.
2.1 Ingress 리소스 생성
다음은 예제 구성입니다:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: progressive-deployment
annotations:
nginx.ingress.kubernetes.io/canary: "true" # 단계적 배포 활성화
nginx.ingress.kubernetes.io/canary-weight: "20" # 새 버전으로 20% 트래픽 전송
spec:
rules:
- host: example.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-new
port:
number: 80
- host: example.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-old
port:
number: 80
2.2 주요 애노테이션 설명
nginx.ingress.kubernetes.io/canary: "true": 단계적 배포 기능 활성화.nginx.ingress.kubernetes.io/canary-weight: "20": 새 버전(v2)으로 20%의 트래픽을 전달하고 나머지 80%는 기존 버전(v1)으로 유지.
- 트래픽 가중치 점진적 조정
단계적 배포 과정에서 새 버전으로 전달되는 트래픽 비율을 점진적으로 증가시킬 수 있습니다:
- 초기 단계: v2로 20% 트래픽 전달.
- 확인 후: 가중치를 70%로 조정.
- 최종 단계: 가중치를 100%로 조정하여 전체 배포 완료.
canary-weight 애노테이션 값을 수정하여 조정 가능:
nginx.ingress.kubernetes.io/canary-weight: "70" # 새 버전으로 70% 트래픽 전달
- 모니터링 및 롤백
단계적 배포 과정에서 새 버전의 상태를 꾸준히 모니터링해야 합니다:
- 애플리케이션 로그: 오류나 예외가 있는지 확인.
- 성능 지표: 응답 시간, 오류율 등.
- 사용자 피드백: 사용자 경험 수집.
문제가 발견되면 canary-weight 애노테이션을 조정하여 트래픽을 기존 버전으로 되돌릴 수 있습니다:
nginx.ingress.kubernetes.io/canary-weight: "0" # 모든 트래픽을 기존 버전으로 이동
고급 단계적 배포 방법
가중치 기반 트래픽 분배 외에도 Nginx Ingress Controller는 다음 단계적 배포 전략을 지원합니다:
- 요청 헤더 기반 트래픽 분배
nginx.ingress.kubernetes.io/canary-by-header 애노테이션을 사용하여 특정 요청 헤더의 트래픽을 새 버전으로 라우팅합니다.
예제 구성
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: header-based-routing
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "Custom-Header"
nginx.ingress.kubernetes.io/canary-by-header-value: "active"
spec:
rules:
- host: example.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-new
port:
number: 80
- host: example.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-old
port:
number: 80
설명
- 요청 헤더에
Custom-Header: active가 포함되어 있으면 새 버전(v2)으로 라우팅됩니다. - 다른 요청은 기존 버전(v1)을 계속 사용합니다.
- 쿠키 기반 트래픽 분배
nginx.ingress.kubernetes.io/canary-by-cookie 애노테이션을 사용하여 특정 쿠키의 트래픽을 새 버전으로 라우팅합니다.
예제 구성
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cookie-based-routing
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-cookie: "testCookie"
spec:
rules:
- host: example.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-new
port:
number: 80
- host: example.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-old
port:
number: 80
설명
- 요청에
testCookie=true쿠키가 포함되어 있으면 새 버전(v2)으로 라우팅됩니다. - 다른 요청은 기존 버전(v1)을 계속 사용합니다.
- 조합 사용
가중치, 요청 헤더 및 쿠키를 동시에 사용하여 더 복잡한 단계적 배포 전략을 구현할 수 있습니다.
예제 구성
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: combined-strategy
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "15"
nginx.ingress.kubernetes.io/canary-by-header: "Special-Header"
nginx.ingress.kubernetes.io/canary-by-header-value: "enable"
nginx.ingress.kubernetes.io/canary-by-cookie: "customCookie"
spec:
rules:
- host: example.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-new
port:
number: 80
- host: example.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-old
port:
number: 80
설명
- 15%의 트래픽은 새 버전(v2)으로 전달됩니다.
- 요청 헤더에
Special-Header: enable또는 쿠키에customCookie=true가 포함된 경우에도 새 버전으로 라우팅됩니다.