노드 패치를 위해 kubectl drain을 실행했더니 결제 파드가 동시에 내려가 서비스가 끊겼습니다. 계획된 유지보수라도 최소 가용 파드 수를 지키지 않으면 장애와 다르지 않습니다. PDB는 운영 작업 중에도 서비스 가용성을 지키도록 Eviction을 제어합니다.
PodDisruptionBudget — 무중단 노드 유지보수
새벽 2시, 클러스터 노드 패치를 위해 kubectl drain node-3을 실행했습니다. 잠시 후 슬랙 알림이 울립니다. "결제 서비스 다운, 주문 처리 불가." 나중에 확인해보니 결제 파드가 node-3에 2개, 다른 노드에 1개 있었는데 드레인으로 node-3의 파드 2개가 동시에 제거되면서 남은 파드 1개가 트래픽을 감당 못하고 OOM으로 죽은 것이었습니다. PodDisruptionBudget(PDB)는 이런 상황을 막기 위한 안전장치입니다. "이 서비스는 최소 2개의 파드가 항상 실행 중이어야 한다"고 클러스터에 선언하면, 노드 드레인이나 클러스터 업그레이드 같은 계획된 중단(voluntary disruption) 시 K8s가 이 조건을 지키면서 파드를 이동시킵니다.
- 1Voluntary Disruption vs Involuntary Disruption 차이
- 2minAvailable과 maxUnavailable 설정 방식
- 3PDB가 kubectl drain에 미치는 영향
- 4퍼센트(%) 표기로 동적 보호 설정
- 5PDB 상태 모니터링과 드레인 진행 추적
- 6무중단 클러스터 업그레이드 체크리스트
kubectl get pdb -Akubectl create namespace pdb-demokubectl create deployment web --image=nginx --replicas=3 -n pdb-demokubectl get nodesVoluntary vs Involuntary Disruption
PDB는 **계획된 중단(Voluntary Disruption)**에만 적용됩니다. 노드 장애로 파드가 죽는 것(Involuntary)은 PDB가 막을 수 없습니다.
| 구분 | 예시 | PDB 적용 |
|---|---|---|
| Voluntary (계획됨) | kubectl drain, 클러스터 업그레이드, kubectl delete pod | 적용됨 |
| Involuntary (비계획) | 노드 OOM, 하드웨어 장애, 커널 패닉 | 적용 안 됨 |
minAvailable vs maxUnavailable 설정
결제 서비스 파드가 3개 실행 중인데 노드 드레인 중 2개가 동시에 evict되면 남은 1개가 모든 트래픽을 받다 OOM으로 죽을 수 있습니다. PDB는 "이 서비스는 항상 최소 N개가 살아있어야 한다"는 제약을 클러스터에 선언하는 리소스입니다. 드레인이나 업그레이드 같은 계획된 중단 상황에서 Kubernetes가 이 조건을 지키면서 파드를 이동시킵니다. minAvailable과 maxUnavailable 두 가지 방식으로 보호 수준을 표현할 수 있으며, 서비스 특성에 따라 적합한 쪽을 선택합니다.
두 설정 모두 절대값과 퍼센트(%) 표기를 지원합니다.

# minAvailable: 절대값 — 항상 최소 2개 실행 보장
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb-min
namespace: pdb-demo
spec:
minAvailable: 2 # 절대값: 최소 2개
selector:
matchLabels:
app: web
---
# minAvailable: 퍼센트 — 전체의 66% 이상 실행 보장
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb-percent
namespace: pdb-demo
spec:
minAvailable: "66%" # 3개 중 2개 (66.6% → 올림)
selector:
matchLabels:
app: web
---
# maxUnavailable: 한 번에 최대 1개만 중단 허용
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb-max
namespace: pdb-demo
spec:
maxUnavailable: 1 # 동시에 최대 1개까지 중단 가능
selector:
matchLabels:
app: web
kubectl apply -f web-pdb-min.yaml
# PDB 상태 확인
kubectl get pdb -n pdb-demo
# NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
# web-pdb-min 2 N/A 1 30s
# ↑ 현재 3개 중 1개까지 내릴 수 있음
kubectl describe pdb web-pdb-min -n pdb-demo
# Status:
# Observed Generation: 1
# Disruptions Allowed: 1 ← 현재 Evict 가능한 파드 수
# Current Healthy: 3
# Desired Healthy: 2 (= minAvailable)
# Expected Pods: 3
- NAME—조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
- STATUS/READY—Running, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
- RESTARTS/EVENTS—재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.
PDB와 kubectl drain의 상호작용
노드 유지보수를 위해 kubectl drain을 실행했는데 파드가 예상보다 빠르게 모두 내려가면서 서비스가 중단된 경험이 있다면, PDB 설정이 없었기 때문일 가능성이 높습니다. PDB가 설정된 환경에서 drain은 단순히 파드를 삭제하지 않고 Eviction API를 통해 PDB 조건을 확인하면서 순서대로 파드를 옮깁니다. 이 동작을 이해하면 drain이 멈추는 이유, 언제 다음 파드를 내릴 수 있는지를 정확히 예측할 수 있습니다.
kubectl drain은 내부적으로 Eviction API를 호출합니다. PDB 위반 시 429 Too Many Requests를 반환하고, drain은 이를 받아 대기합니다.
노드 드레인
안전한 실행 조건: PDB, 복제본 수, 다른 노드의 여유 리소스를 확인한 유지보수 상황에서만 실행하세요.
실행 전 반드시 확인
- 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
- 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
- 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl drain node-3위 항목을 모두 확인한 후 복사할 수 있습니다
# PDB가 있는 상태에서 노드 드레인
kubectl drain node-3 \
--ignore-daemonsets \
--delete-emptydir-data \
--timeout=5m
# 드레인 진행 출력 예시:
# evicting pod pdb-demo/web-6d8b4b5c9-abcde
# evicting pod pdb-demo/web-6d8b4b5c9-fghij
# error when evicting pods/"web-6d8b4b5c9-fghij" -n "pdb-demo"
# (will retry after 5s): Cannot evict pod as it would violate
# the pod's disruption budget.
# pod/web-6d8b4b5c9-abcde evicted ← 첫 번째 파드 성공적으로 evict
# waiting for eviction of pod/web-6d8b4b5c9-fghij... ← PDB 대기 중
# 다른 터미널에서 파드 재배치 확인
kubectl get pods -n pdb-demo -w
# web-6d8b4b5c9-abcde 0/1 Terminating → node-1로 재배치
# web-6d8b4b5c9-xyzab 0/1 Pending → Running 후 두 번째 drain 진행
# PDB 상태 실시간 확인 (드레인 진행 중)
watch kubectl get pdb -n pdb-demo
# NAME MIN AVAILABLE ALLOWED DISRUPTIONS AGE
# web-pdb-min 2 0 2m ← 0이면 현재 drainable 없음
# web-pdb-min 2 1 2m ← 재배치 완료 후 1로 늘어남
실습: PDB 없을 때와 있을 때 비교
노드 드레인
안전한 실행 조건: PDB, 복제본 수, 다른 노드의 여유 리소스를 확인한 유지보수 상황에서만 실행하세요.
실행 전 반드시 확인
- 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
- 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
- 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl drain node-2위 항목을 모두 확인한 후 복사할 수 있습니다
# 1. PDB 없이 드레인 시뮬레이션
kubectl create deployment payment \
--image=nginx --replicas=2 -n pdb-demo
# 파드 2개가 같은 노드에 있는 경우를 가정
kubectl get pods -n pdb-demo -o wide | grep payment
# payment-xxx Running node-2
# payment-yyy Running node-2
# PDB 없이 node-2 드레인 → 파드 2개 동시 제거 → 서비스 다운
kubectl drain node-2 --ignore-daemonsets --delete-emptydir-data --dry-run
# (실제로는 실행 않고 dry-run만)
# 2. PDB 추가 후 동일한 상황
cat <<EOF | kubectl apply -f -
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payment-pdb
namespace: pdb-demo
spec:
minAvailable: 1
selector:
matchLabels:
app: payment
EOF
# 이제 drain 시도 → 1개 Evict 후 재배치 완료될 때까지 대기
kubectl drain node-2 \
--ignore-daemonsets \
--delete-emptydir-data \
--timeout=3m
# 드레인 완료 후 파드 분산 확인
kubectl get pods -n pdb-demo -o wide | grep payment
# payment-xxx Running node-1 ← 재배치됨
# payment-yyy Running node-3 ← 재배치됨
# 3. PDB Disruptions Allowed 변화 모니터링
kubectl get pdb payment-pdb -n pdb-demo -w
# NAME MIN AVAILABLE ALLOWED DISRUPTIONS
# payment-pdb 1 1 ← 드레인 전
# payment-pdb 1 0 ← 첫 파드 Evict 중
# payment-pdb 1 1 ← 재배치 완료 후 복구
클러스터 보안 패치를 위해 노드를 순서대로 드레인했습니다. node-2를 드레인하는 순간 API 에러율이 급상승하고 결제 서비스가 15초간 다운되었습니다.
# 사후 분석
kubectl get events -n production --sort-by='.lastTimestamp' | tail -20
# Warning Killing payment-pod-abc Stopping container payment
# Warning Killing payment-pod-def Stopping container payment
# ← 두 파드가 거의 동시에 삭제됨
# PDB 확인
kubectl get pdb -n production
# (출력 없음 — PDB 미설정)
# 파드 배치 분산 여부 확인
kubectl get pods -n production -o wide | grep payment
# payment-abc node-2 ← 두 파드가 같은 노드에 있었음
# payment-def node-2
# 대응 1: PDB 즉시 추가 (다음 유지보수 전에)
cat <<EOF | kubectl apply -f -
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payment-pdb
namespace: production
spec:
minAvailable: 1
selector:
matchLabels:
app: payment
EOF
# 대응 2: 파드 반분산 (PodAntiAffinity) 추가
# Deployment spec.template.spec에 추가:
# affinity:
# podAntiAffinity:
# preferredDuringSchedulingIgnoredDuringExecution:
# - weight: 100
# podAffinityTerm:
# labelSelector:
# matchLabels:
# app: payment
# topologyKey: kubernetes.io/hostname
# 대응 3: 드레인 전 PDB 상태 확인을 SOP에 추가
kubectl get pdb -A | grep -v "N/A" # ALLOWED DISRUPTIONS가 0인 항목 확인
예방 체크리스트:
- 모든 프로덕션 Deployment에 PDB 설정 확인:
kubectl get pdb -n production - PDB의
ALLOWED DISRUPTIONS가 0이면 드레인 전에 조건 확인 --timeout옵션으로 드레인 대기 시간 제한 설정- 드레인 후 파드 재배치 완료 확인 후 다음 노드로 진행
시나리오: 클러스터 버전 업그레이드 전 PDB 점검
EKS를 1.28에서 1.29로 업그레이드하기 전, SRE 온콜이 점검 항목 리스트를 줬습니다. "PDB 없는 프로덕션 서비스 목록 뽑아서 팀에 공유해줘."
# 프로덕션 네임스페이스의 Deployment 목록
kubectl get deployments -n production -o json | \
jq '.items[].metadata.name' -r | sort > /tmp/deployments.txt
# PDB의 selector와 매칭되는 Deployment 추출
kubectl get pdb -n production -o json | \
jq '.items[].spec.selector.matchLabels.app' -r | sort > /tmp/pdb-covered.txt
# PDB 없는 Deployment 식별
comm -23 /tmp/deployments.txt /tmp/pdb-covered.txt
# catalog-service ← PDB 없음
# notification-svc ← PDB 없음
# 일괄 PDB 생성 스크립트
for svc in catalog-service notification-svc; do
cat <<EOF | kubectl apply -f -
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: ${svc}-pdb
namespace: production
spec:
maxUnavailable: 1
selector:
matchLabels:
app: ${svc}
EOF
done
# 업그레이드 중 PDB 상태 모니터링
watch -n 5 "kubectl get pdb -n production"
실무 포인트: 클러스터 업그레이드 전 PDB 점검은 "가드레일 설치"입니다. 업그레이드 툴(eksctl, kubeadm)은 PDB를 존중하며 노드를 드레인하므로, PDB만 제대로 있으면 업그레이드 중 서비스 중단 없이 노드를 순서대로 교체할 수 있습니다.
핵심 요약
| 개념 | 명령/설정 | 실무 사용 빈도 |
|---|---|---|
| PDB 생성 | kubectl create pdb <name> --selector=app=<app> --min-available=1 | 서비스 배포 시 |
| PDB 상태 확인 | kubectl get pdb -n <ns> | 유지보수 전 |
| Disruptions Allowed | kubectl describe pdb <name> | 드레인 전 확인 |
| 노드 드레인 | kubectl drain <node> --ignore-daemonsets --delete-emptydir-data | 노드 유지보수 |
| 드레인 타임아웃 | --timeout=5m | 장시간 대기 방지 |
| 강제 드레인 (PDB 무시) | --disable-eviction (비권장) | 긴급 상황만 |
| PDB 커버리지 확인 | kubectl get pdb -A + Deployment 매칭 | 업그레이드 전 |