Ver3.5 클러스터 롤링 업데이트 설계

클러스터 롤링 업데이트 설계
야 솔직히 말해서... 서비스 배포하다가 "잠깐 서비스 점검 중입니다" 뜨는 거 보면 진짜 식은땀 나지 않음? 😅
특히 트래픽 많은 시간대에 배포 잘못 건드렸다가 장애 터지면... 그날 야근은 기본이고 슬랙에 알림 폭탄 맞는 거 상상만 해도 아찔하잖아 ㅋㅋㅋ
그래서 오늘은 클러스터 롤링 업데이트(Rolling Update)에 대해 진짜 깊게 파볼 거야.
단순히 "이런 게 있어요~" 수준이 아니라, 실제로 설계하고 운영할 때 어떤 포인트를 잡아야 하는지까지 다 털어놓을게. 🔥
🎯 롤링 업데이트가 뭔지 제대로 알고 가자
롤링 업데이트(Rolling Update)는 클러스터 내의 여러 노드(서버 인스턴스)를 한 번에 다 내리지 않고, 순차적으로 조금씩 교체하면서 업데이트하는 배포 전략이야.
쉽게 비유하면... 🏗️ 건물 리모델링할 때 전체 층을 한 번에 공사하면 아무도 못 들어가잖아?
근데 1층 공사하는 동안 2~5층은 정상 운영, 1층 완료되면 2층 공사... 이런 식으로 하면 건물 전체가 멈추지 않고 계속 돌아가는 거랑 같은 원리야.
위 그림처럼 파드(Pod)를 하나씩 순차적으로 교체하면서 전체 서비스는 계속 살아있는 상태를 유지하는 게 핵심이야.
이게 바로 무중단 배포(Zero-Downtime Deployment)의 기본 중 기본! 🎉
📊 배포 전략 비교 — 롤링이 왜 핵심인가
배포 전략이 롤링 업데이트만 있는 건 아니야. 다른 전략들이랑 비교해보면 왜 롤링이 그렇게 많이 쓰이는지 바로 이해됨 ㅋㅋ
| 전략 | 방식 | 다운타임 | 리소스 | 롤백 속도 |
|---|---|---|---|---|
| Recreate | 전체 종료 후 새 버전 배포 | ❌ 있음 | 적음 | 느림 |
| Rolling Update | 순차적 교체 | ✅ 없음 | 보통 | 보통 |
| Blue/Green | 두 환경 동시 운영 후 스위칭 | ✅ 없음 | 2배 필요 | 매우 빠름 |
| Canary | 일부 트래픽만 신버전으로 | ✅ 없음 | 보통 | 빠름 |
Kubernetes에서 Deployment의 기본 배포 전략이 바로
RollingUpdate야.추가 리소스 없이도 무중단 배포가 가능하고, 대부분의 상황에서 충분한 안정성을 제공하기 때문이지.
Blue/Green은 리소스가 2배 필요하고, Canary는 설정이 복잡해서 롤링이 가성비 최고임 ㅋㅋ
⚙️ Kubernetes 롤링 업데이트 핵심 파라미터
쿠버네티스에서 롤링 업데이트를 제대로 설계하려면 두 가지 핵심 파라미터를 완전히 이해해야 해.
이거 모르면 설계 자체가 불가능하다고 봐야 함 😤
예를 들어 파드가 10개인데
maxUnavailable: 2라면,업데이트 중에도 최소 8개의 파드는 항상 정상 운영 상태여야 한다는 뜻이지.
절대값으로 설정할 수도 있고 (
2),퍼센트로 설정할 수도 있어 (
25%).
파드가 10개인데
maxSurge: 2라면,업데이트 중에 최대 12개의 파드가 동시에 존재할 수 있다는 뜻.
이 값이 클수록 업데이트 속도가 빨라지지만, 그만큼 리소스를 더 쓰게 돼.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2 # 최대 2개까지 동시에 내릴 수 있음
maxSurge: 2 # 최대 2개까지 추가 생성 가능
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:v2.0
readinessProbe: # 이거 없으면 롤링 업데이트 의미 없음!
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe 없이 롤링 업데이트 하면 반쪽짜리야.새 파드가 실제로 트래픽 받을 준비가 됐는지 확인하는 게 readinessProbe인데,
이게 없으면 파드가 뜨자마자 트래픽 받다가 에러 터질 수 있거든. 꼭 설정해줘! 🙏
🏗️ 실전 롤링 업데이트 설계 전략
이론은 알겠고, 이제 진짜 실전에서 어떻게 설계해야 하는지 파보자.
서비스 규모나 특성에 따라 전략이 달라지거든. 이걸 모르면 교과서 지식만 있는 거야 ㅋㅋ
이런 경우엔 maxUnavailable을 0으로 설정하는 게 핵심이야.
maxUnavailable: 0으로 하면 기존 파드를 하나도 내리지 않고,새 파드가 완전히 Ready 상태가 된 다음에야 구 파드를 내리게 돼.
대신
maxSurge를 적절히 설정해서 배포 속도를 조절하면 됨.
# 고가용성 우선 설정
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # 절대 기존 파드 먼저 내리지 않음
maxSurge: 1 # 한 번에 1개씩 추가 생성 후 교체
# 이 설정의 동작 순서:
# 1. 새 파드 1개 생성 (총 replicas+1개)
# 2. 새 파드 Ready 확인
# 3. 구 파드 1개 종료
# 4. 반복...
이런 경우엔
maxUnavailable과 maxSurge 둘 다 높게 설정하면 돼.
# 빠른 배포 우선 설정 (replicas: 20 기준)
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 5 # 25% 동시 교체
maxSurge: 5 # 25% 추가 생성 허용
# 이 설정으로 20개 파드를 5개씩 묶어서 배포
# 총 4번의 배치로 완료 → 훨씬 빠름!
maxUnavailable: 25%, maxSurge: 25%가 기본값인데,이게 가용성과 배포 속도 사이의 균형점이라고 보면 돼.
# 균형 잡힌 기본 설정 (replicas: 8 기준)
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25% # = 2개 (소수점 내림)
maxSurge: 25% # = 2개 (소수점 올림)
# 동작: 2개씩 교체, 최대 10개까지 파드 존재 가능
# 총 4번의 배치로 완료
🔍 Probe 설계 — 롤링 업데이트의 진짜 핵심
롤링 업데이트 설계에서 파라미터 설정만큼 중요한 게 바로 Probe 설계야.
Probe를 잘못 설계하면 롤링 업데이트가 있으나 마나야. 진짜로 ㅋㅋ 😅
containers:
- name: my-app
image: my-app:v2.0
# 시작 프로브: 앱이 완전히 뜰 때까지 기다림
startupProbe:
httpGet:
path: /health/startup
port: 8080
failureThreshold: 30 # 30번 실패 허용
periodSeconds: 10 # 10초마다 체크 → 최대 300초 대기
# 준비 프로브: 트래픽 받을 준비 됐는지 (롤링 업데이트 핵심!)
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5 # 시작 후 5초 뒤부터 체크
periodSeconds: 5 # 5초마다 체크
successThreshold: 1 # 1번 성공하면 Ready
failureThreshold: 3 # 3번 연속 실패하면 Not Ready
timeoutSeconds: 3 # 3초 안에 응답 없으면 실패
# 생존 프로브: 데드락 등 감지
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
단순히 200 반환하는 건 반쪽짜리야. 진짜 Readiness 체크는 이런 걸 확인해야 해:
1. DB 연결 상태 — DB에 실제로 쿼리 날려보기
2. 캐시 연결 상태 — Redis/Memcached 연결 확인
3. 외부 API 의존성 — 필수 외부 서비스 연결 확인
4. 워밍업 완료 여부 — JVM 워밍업, 모델 로딩 완료 등
이 중 하나라도 실패하면 503 반환 → 트래픽 차단 → 안전하게 업데이트 진행!
🔄 Graceful Shutdown — 진행 중인 요청 보호하기
롤링 업데이트 중에 구버전 파드를 내릴 때, 진행 중인 요청을 어떻게 처리하느냐가 진짜 중요해.
이걸 제대로 안 하면 롤링 업데이트 중에 클라이언트가 에러를 받게 되거든. 😱
1단계: 파드를 로드밸런서에서 제거 (새 요청 차단)
2단계: 컨테이너에 SIGTERM 시그널 전송
3단계:
terminationGracePeriodSeconds 동안 대기 (기본 30초)4단계: 시간 초과 시 SIGKILL로 강제 종료
spec:
# 파드 종료 대기 시간 설정 (기본 30초)
terminationGracePeriodSeconds: 60 # 60초로 늘림
containers:
- name: my-app
lifecycle:
preStop:
exec:
# SIGTERM 받기 전에 먼저 실행되는 훅
# 로드밸런서 업데이트 지연을 위한 sleep
command: ["/bin/sh", "-c", "sleep 5"]
쿠버네티스가 파드를 엔드포인트에서 제거하는 것과 SIGTERM을 보내는 게 동시에 일어나는데,
로드밸런서가 업데이트되는 데 약간의 시간이 걸려.
그 사이에 새 요청이 들어올 수 있어서 5초 정도 sleep 줘서 안전하게 처리하는 거야.
이거 모르면 롤링 업데이트 중에 간헐적 에러 발생함! 🔥
# Python FastAPI 예시
import signal
import asyncio
from fastapi import FastAPI
app = FastAPI()
is_shutting_down = False
@app.on_event("shutdown")
async def shutdown_event():
global is_shutting_down
is_shutting_down = True
# 진행 중인 요청 완료 대기
await asyncio.sleep(5)
@app.get("/health/ready")
async def readiness():
if is_shutting_down:
# 종료 중이면 503 반환 → 로드밸런서에서 제거
return Response(status_code=503)
return {"status": "ready"}
# Node.js Express 예시
process.on('SIGTERM', () => {
console.log('SIGTERM received, starting graceful shutdown');
server.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
// 새 연결 거부, 기존 연결 완료 대기
setTimeout(() => process.exit(1), 30000);
});
🚨 롤백 전략 — 문제 생겼을 때 빠르게 되돌리기
롤링 업데이트 설계에서 배포만큼 중요한 게 롤백 전략이야.
배포 잘 됐는데 운영 중에 문제 터지면? 그때 빠르게 롤백할 수 있어야 진짜 설계가 완성되는 거거든 ㅋㅋ
# 현재 배포 상태 확인
kubectl rollout status deployment/my-app
# 배포 히스토리 확인
kubectl rollout history deployment/my-app
# REVISION CHANGE-CAUSE
# 1 kubectl apply --image=my-app:v1.0
# 2 kubectl apply --image=my-app:v2.0
# 3 kubectl apply --image=my-app:v3.0
# 즉시 이전 버전으로 롤백 (가장 많이 씀!)
kubectl rollout undo deployment/my-app
# 특정 리비전으로 롤백
kubectl rollout undo deployment/my-app --to-revision=1
# 롤백 진행 상황 모니터링
kubectl rollout status deployment/my-app --watch
쿠버네티스는 이전 ReplicaSet을 삭제하지 않고 보관해 둬.
그래서 롤백 시 새 이미지를 다운받을 필요 없이 기존 ReplicaSet을 활성화하는 방식으로 동작해서 매우 빠름!
기본적으로 10개의 리비전을 보관하는데,
revisionHistoryLimit으로 조절 가능해.
spec:
revisionHistoryLimit: 5 # 최근 5개 리비전만 보관 (기본값 10)
# 너무 많이 보관하면 etcd 용량 차지하니까 적절히 설정
수동 롤백은 사람이 모니터링하다가 문제 발견하고 명령어 치는 거잖아.
근데 새벽 3시에 배포하다가 문제 터지면? 😴 그래서 자동 롤백 설계가 필요해!
# Argo Rollouts를 활용한 자동 롤백 설계 예시
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10 # 10% 트래픽만 신버전으로
- pause: {duration: 5m} # 5분 관찰
- setWeight: 50 # 50%로 증가
- pause: {duration: 5m}
- setWeight: 100 # 전체 전환
# 자동 분석 설정
analysis:
templates:
- templateName: success-rate
startingStep: 1
args:
- name: service-name
value: my-app
---
# 분석 템플릿 (에러율 5% 초과 시 자동 롤백)
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.95 # 성공률 95% 이상
failureLimit: 3
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{
job="my-app",status!~"5.."}[5m]))
/
sum(rate(http_requests_total{
job="my-app"}[5m]))
📈 모니터링 설계 — 롤링 업데이트 중 뭘 봐야 하나
배포 중에 눈 감고 있으면 안 되지 ㅋㅋ 어떤 메트릭을 봐야 하는지 정리해줄게.
🛡️ PodDisruptionBudget — 클러스터 레벨 안전장치
롤링 업데이트 설계에서 자주 놓치는 게 바로 PodDisruptionBudget(PDB)이야.
노드 유지보수나 클러스터 업그레이드 때도 서비스 가용성을 보장해주는 중요한 설정이거든.
클러스터 노드 유지보수 시
kubectl drain을 실행하면 해당 노드의 파드들이 다른 노드로 이동해.이때 PDB가 없으면 여러 파드가 동시에 내려가서 서비스 장애가 날 수 있어.
PDB는 "동시에 내릴 수 있는 파드 수를 제한"해서 이런 상황을 방지해줌!
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
# 방법 1: 최소 가용 파드 수 지정
minAvailable: 3 # 항상 최소 3개는 살아있어야 함
# 방법 2: 최대 불가용 파드 수 지정 (둘 중 하나만 사용)
# maxUnavailable: 1 # 동시에 최대 1개만 내릴 수 있음
selector:
matchLabels:
app: my-app
# 퍼센트로도 설정 가능
# minAvailable: "70%" # 항상 70% 이상 유지
# maxUnavailable: "30%" # 최대 30%까지 동시 중단 허용
minAvailable을 replicas의 50% 이상으로 설정하는 게 일반적이야.단,
minAvailable: replicas로 설정하면 노드 드레인 자체가 불가능해지니까 주의!그리고 싱글 레플리카 파드에 PDB 설정하면 드레인이 블로킹될 수 있어서 조심해야 해.
🔧 실전 트러블슈팅 — 자주 만나는 문제들
이론 다 알아도 실전에서 막히는 순간이 있잖아 ㅋㅋ 자주 만나는 문제들 정리해줄게.
kubectl rollout status 해보면 계속 Waiting 상태원인 1: 새 파드가 Readiness Probe를 통과 못 하고 있음
→
kubectl describe pod [파드명]으로 이벤트 확인원인 2: 리소스 부족으로 새 파드가 Pending 상태
→
kubectl get events에서 "Insufficient cpu/memory" 확인원인 3: ImagePullBackOff — 이미지 못 가져옴
→ 이미지 태그, 레지스트리 접근 권한 확인
# 디버깅 명령어 모음
# 파드 상태 상세 확인
kubectl describe pod [파드명] -n [네임스페이스]
# 파드 로그 확인
kubectl logs [파드명] --previous # 이전 컨테이너 로그
# 롤아웃 이벤트 확인
kubectl describe deployment [디플로이먼트명]
# 리소스 사용량 확인
kubectl top nodes
kubectl top pods
# 롤아웃 일시정지 (문제 분석 시간 벌기)
kubectl rollout pause deployment/my-app
# 분석 후 재개
kubectl rollout resume deployment/my-app
원인: 파드 종료 시 진행 중인 요청이 끊김 (Graceful Shutdown 미설정)
해결책:
1.
preStop 훅에 sleep 추가2.
terminationGracePeriodSeconds 늘리기3. 애플리케이션 코드에서 SIGTERM 처리 구현
4. 로드밸런서 헬스체크 간격 확인
원인: DB 스키마 변경이 이미 적용됨 (하위 호환성 없는 마이그레이션)
해결책:
1. DB 마이그레이션은 항상 하위 호환성 유지 (Expand-Contract 패턴)
2. 새 컬럼 추가 시 NOT NULL 제약 없이 먼저 추가
3. 구버전이 새 스키마에서도 동작하는지 확인 후 배포
Expand-Contract 패턴:
Phase 1 (Expand): 새 컬럼 추가 (구버전 호환)
Phase 2: 새 버전 배포 (새 컬럼 사용)
Phase 3 (Contract): 구 컬럼 제거
🏢 대규모 클러스터에서의 롤링 업데이트 설계
파드 수백 개, 노드 수십 개 규모에서는 추가로 고려해야 할 게 있어.
재능넷 같은 플랫폼에서도 트래픽이 늘어나면 이런 설계가 필요해지거든 😊
예를 들어 10개 파드가 모두 같은 노드에 있으면, 그 노드에 문제 생기면 전체 서비스 장애!
topologySpreadConstraints로 파드를 균등하게 분산시켜야 해.
spec:
template:
spec:
# 파드를 가용 영역(AZ)에 균등 분산
topologySpreadConstraints:
- maxSkew: 1 # AZ 간 파드 수 차이 최대 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-app
# 노드 간에도 균등 분산
- maxSkew: 2
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway # 불가능하면 그냥 배치
labelSelector:
matchLabels:
app: my-app
containers:
- name: my-app
resources:
requests:
cpu: "500m" # 스케줄링 기준 (이만큼 보장)
memory: "512Mi"
limits:
cpu: "1000m" # 최대 사용 제한
memory: "1Gi"
# requests가 없으면 스케줄러가 노드 선택을 못 해서
# 롤링 업데이트 중 파드가 Pending 상태로 멈출 수 있음!
# 반드시 requests 설정할 것!
1. 배포 시간대 선택: 트래픽이 낮은 시간대에 배포 (새벽 2~4시)
2. 배포 속도 조절: maxUnavailable을 낮게 설정해서 천천히 배포
3. 카나리 먼저: 전체 배포 전에 1~2개 파드만 먼저 신버전으로 테스트
4. 피처 플래그: 코드는 배포하되 기능은 플래그로 제어해서 리스크 분리
📋 롤링 업데이트 설계 체크리스트
자, 이제 정리해보자. 롤링 업데이트 설계할 때 이 체크리스트 다 확인하면 거의 완벽해 ㅋㅋ 💪
🎓 마무리 — 롤링 업데이트 설계의 핵심 정리
자, 여기까지 왔으면 이제 롤링 업데이트에 대해 진짜 깊게 이해한 거야 👏
마지막으로 핵심만 딱 정리해줄게.
롤링 업데이트는 단순히 쿠버네티스 명령어 몇 개 아는 게 아니야.
애플리케이션 코드, 인프라 설정, 모니터링, 팀 프로세스가 모두 맞물려야 진짜 무중단 배포가 되는 거거든.
이런 DevOps 관련 지식이나 클라우드 인프라 설계 도움이 필요하다면,
재능넷(jaenung.net)에서 관련 전문가를 찾아볼 수도 있어.
다양한 DevOps 엔지니어들이 실전 경험을 나눠주고 있으니까 참고해봐! 😊
오늘 배운 내용 바로 적용해보고, 배포 때 식은땀 흘리는 일 없어지길 바랄게! 🚀
관련 키워드
댓글 작성
이 글에 대한 여러분의 생각을 들려주세요
로그인이 필요합니다
댓글을 작성하려면 먼저 로그인해주세요.
댓글 0
지식인의 숲 - 지적 재산권 보호 고지
지적 재산권 보호 고지
- 저작권 및 소유권: 본 컨텐츠는 재능넷의 독점 AI 기술로 생성되었으며, 대한민국 저작권법 및 국제 저작권 협약에 의해 보호됩니다.
- AI 생성 컨텐츠의 법적 지위: 본 AI 생성 컨텐츠는 재능넷의 지적 창작물로 인정되며, 관련 법규에 따라 저작권 보호를 받습니다.
- 사용 제한: 재능넷의 명시적 서면 동의 없이 본 컨텐츠를 복제, 수정, 배포, 또는 상업적으로 활용하는 행위는 엄격히 금지됩니다.
- 데이터 수집 금지: 본 컨텐츠에 대한 무단 스크래핑, 크롤링, 및 자동화된 데이터 수집은 법적 제재의 대상이 됩니다.
- AI 학습 제한: 재능넷의 AI 생성 컨텐츠를 타 AI 모델 학습에 무단 사용하는 행위는 금지되며, 이는 지적 재산권 침해로 간주됩니다.
아직 댓글이 없습니다.