PodDisruptionBudget란?
Kubernetes 운영 환경에서 노드 업그레이드, 클러스터 스케일 다운, 롤링 업데이트 시 Pod가 동시에 너무 많이 중단되는 것을 방지하는 메커니즘이 PodDisruptionBudget(PDB)이다. PDB 없이 kubectl drain을 실행하면 해당 노드의 모든 Pod가 한꺼번에 축출(evict)되어 서비스 다운타임이 발생할 수 있다.
PDB는 자발적 중단(Voluntary Disruption)에만 적용된다. 노드 장애 같은 비자발적 중단에는 영향을 주지 않는다. 자발적 중단의 예시로는 kubectl drain, 클러스터 오토스케일러의 노드 축소, Deployment 롤링 업데이트 등이 있다.
핵심 스펙: minAvailable vs maxUnavailable
PDB는 두 가지 방식으로 가용성을 보장한다:
| 필드 | 의미 | 예시 |
|---|---|---|
minAvailable |
최소 유지해야 할 Pod 수/비율 | minAvailable: 2 → 항상 2개 이상 실행 |
maxUnavailable |
최대 중단 허용 Pod 수/비율 | maxUnavailable: 1 → 동시에 1개만 중단 |
두 필드는 동시에 사용할 수 없다. 정수값 또는 퍼센트(%)를 지정할 수 있으며, 운영 환경에서는 maxUnavailable이 더 직관적으로 많이 사용된다.
기본 PDB 매니페스트
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-server-pdb
namespace: production
spec:
maxUnavailable: 1
selector:
matchLabels:
app: api-server
이 PDB는 app: api-server 레이블을 가진 Pod 중 동시에 최대 1개만 중단될 수 있도록 보장한다. 3개 레플리카가 있다면, drain 시 1개씩 순차적으로 축출된다.
퍼센트 기반 PDB 설정
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: worker-pdb
spec:
minAvailable: "75%"
selector:
matchLabels:
app: worker
워커 Pod가 8개라면 최소 6개(75%)가 항상 실행 상태를 유지해야 한다. 동시에 최대 2개까지만 중단 가능하다. 오토스케일링과 함께 사용할 때 퍼센트 기반이 유리하다 — Pod 수가 동적으로 변해도 비율은 자동으로 적용된다.
PDB 상태 확인과 모니터링
# PDB 목록 및 상태 확인
kubectl get pdb -n production
# 출력 예시:
# NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
# api-server-pdb N/A 1 1 5d
# worker-pdb 75% N/A 2 3d
# 상세 상태 확인
kubectl describe pdb api-server-pdb -n production
ALLOWED DISRUPTIONS 컬럼이 핵심이다. 이 값이 0이면 더 이상 Pod를 축출할 수 없다는 의미이며, kubectl drain이 블로킹된다.
drain 동작과 PDB 상호작용
# 노드 드레인 — PDB를 존중하며 순차 축출
kubectl drain node-3 --ignore-daemonsets --delete-emptydir-data
# PDB 무시 (비상 상황에서만!)
kubectl drain node-3 --ignore-daemonsets --disable-eviction
# 타임아웃 설정
kubectl drain node-3 --ignore-daemonsets --timeout=300s
--disable-eviction은 Eviction API 대신 직접 삭제를 사용하므로 PDB를 완전히 무시한다. 프로덕션에서는 절대 사용하지 않는 것이 원칙이다.
Unhealthy Pod Eviction Policy (v1.27+)
Kubernetes 1.27부터 unhealthyPodEvictionPolicy 필드가 추가되었다. 기존에는 비정상 Pod도 PDB 카운트에 포함되어 drain이 막히는 문제가 있었다.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb-v2
spec:
maxUnavailable: 1
unhealthyPodEvictionPolicy: AlwaysAllow
selector:
matchLabels:
app: api-server
| 정책 | 동작 |
|---|---|
IfHealthy (기본) |
정상 Pod만 PDB 보호, 비정상 Pod도 카운트에 포함 |
AlwaysAllow |
비정상 Pod는 PDB 무시하고 즉시 축출 가능 |
AlwaysAllow를 권장한다. CrashLoopBackOff 상태의 Pod가 PDB를 블로킹하여 노드 drain이 영원히 완료되지 않는 운영 장애 사례를 예방할 수 있다.
StatefulSet과 PDB 조합
# StatefulSet용 PDB — 데이터베이스 클러스터
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: mysql-pdb
spec:
maxUnavailable: 1
selector:
matchLabels:
app: mysql-cluster
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql-cluster
spec:
replicas: 3
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # K8s 1.24+
selector:
matchLabels:
app: mysql-cluster
template:
metadata:
labels:
app: mysql-cluster
spec:
containers:
- name: mysql
image: mysql:8.0
readinessProbe:
exec:
command: ["mysqladmin", "ping", "-h", "localhost"]
periodSeconds: 10
StatefulSet의 maxUnavailable(롤링 업데이트 속도)과 PDB의 maxUnavailable(축출 제한)은 별개의 설정이다. 둘 다 설정해야 완전한 보호가 된다.
운영 안티패턴과 베스트 프랙티스
❌ 피해야 할 패턴
# 안티패턴 1: 레플리카 수와 동일한 minAvailable
# replicas: 3이면서 minAvailable: 3 → drain 불가능!
spec:
minAvailable: 3 # ❌ 절대 축출 불가
# 안티패턴 2: maxUnavailable: 0
spec:
maxUnavailable: 0 # ❌ drain 영원히 블로킹
# 안티패턴 3: 단일 레플리카에 PDB 적용
# replicas: 1이면서 maxUnavailable: 0 → 데드락
✅ 권장 설정
# 프로덕션 권장 패턴
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: production-api-pdb
labels:
team: platform
tier: critical
spec:
maxUnavailable: 1
unhealthyPodEvictionPolicy: AlwaysAllow
selector:
matchLabels:
app: production-api
핵심 규칙:
- 레플리카 2개 이상인 워크로드에만 PDB 적용
maxUnavailable: 1이 대부분의 경우 최적unhealthyPodEvictionPolicy: AlwaysAllow항상 설정- PDB selector와 Deployment selector가 정확히 일치하는지 확인
Helm 차트에서 PDB 템플릿화
{{- if and .Values.pdb.enabled (gt (int .Values.replicaCount) 1) }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "app.fullname" . }}-pdb
labels:
{{- include "app.labels" . | nindent 4 }}
spec:
maxUnavailable: {{ .Values.pdb.maxUnavailable | default 1 }}
{{- if ge (int .Capabilities.KubeVersion.Minor) 27 }}
unhealthyPodEvictionPolicy: AlwaysAllow
{{- end }}
selector:
matchLabels:
{{- include "app.selectorLabels" . | nindent 6 }}
{{- end }}
replicaCount > 1일 때만 PDB를 생성하도록 조건을 걸면 단일 레플리카 데드락을 방지할 수 있다. Prometheus 모니터링과 함께 PDB 위반 알림을 설정하면 운영 안정성이 크게 향상된다.
클러스터 오토스케일러와 PDB
클러스터 오토스케일러가 노드를 축소할 때도 PDB를 존중한다. 그러나 주의할 점이 있다:
# 오토스케일러가 노드 축소를 시도할 때의 흐름:
# 1. 노드의 Pod들에 대한 PDB 확인
# 2. PDB 위반 없이 축출 가능한지 계산
# 3. 불가능하면 해당 노드는 축소 대상에서 제외
# 4. 10분 후 재시도 (기본 scan-interval)
# PDB가 너무 엄격하면 노드 축소가 안 되어 비용 낭비!
# 해결: maxUnavailable을 적절히 설정
spec:
maxUnavailable: "25%" # 오토스케일링 환경에서 유연한 설정
PDB 디버깅 체크리스트
# 1. PDB 상태 확인 — ALLOWED DISRUPTIONS가 0인지 체크
kubectl get pdb -A
# 2. PDB와 매칭되는 Pod 확인
kubectl get pods -l app=api-server -n production
# 3. drain이 막힌 이유 확인
kubectl get events --field-selector reason=EvictionFailed -A
# 4. Pod 상태 확인 — CrashLoopBackOff가 PDB를 블로킹하는지
kubectl get pods -l app=api-server -n production
-o custom-columns=NAME:.metadata.name,STATUS:.status.phase,READY:.status.conditions[?(@.type=="Ready")].status
# 5. 강제 해결 (최후 수단)
kubectl delete pdb api-server-pdb -n production # PDB 임시 삭제 후 재생성
PDB는 Kubernetes 프로덕션 운영의 안전벨트다. 없으면 언제든 서비스 장애가 발생할 수 있고, 너무 타이트하면 운영 자체가 불가능해진다. maxUnavailable: 1 + AlwaysAllow 조합을 기본으로 삼고, 워크로드 특성에 맞게 조정하는 것이 핵심이다.