K8s PDB 가용성 보장 전략

PodDisruptionBudget이란?

Kubernetes 클러스터를 운영하면 노드 업그레이드, 스케일 다운, 커널 패치 등 자발적 중단(Voluntary Disruption)이 빈번하게 발생한다. 이때 Pod가 무분별하게 종료되면 서비스 가용성이 떨어진다. PodDisruptionBudget(PDB)은 동시에 중단될 수 있는 Pod 수를 제한하여 가용성을 보장하는 리소스다.

PDB가 없으면 kubectl drain 한 번에 모든 Pod가 한꺼번에 축출(evict)될 수 있다. PDB를 설정하면 Kubernetes API 서버가 축출 요청을 거부하여 최소 가용 Pod 수를 유지한다.

PDB 핵심 필드: minAvailable vs maxUnavailable

PDB는 두 가지 방식으로 가용성 기준을 설정한다. 둘 중 하나만 사용할 수 있다.

# 방식 1: minAvailable — 최소 유지 Pod 수
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-server-pdb
spec:
  minAvailable: 2          # 항상 최소 2개 Pod 유지
  selector:
    matchLabels:
      app: api-server

---
# 방식 2: maxUnavailable — 최대 중단 허용 수
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: worker-pdb
spec:
  maxUnavailable: 1         # 동시에 최대 1개만 중단 허용
  selector:
    matchLabels:
      app: worker
필드 의미 절대값 백분율
minAvailable 최소 유지해야 할 Pod 수 minAvailable: 3 minAvailable: "80%"
maxUnavailable 동시 중단 허용 Pod 수 maxUnavailable: 1 maxUnavailable: "25%"

프로덕션 권장: maxUnavailable이 더 유연하다. minAvailable은 스케일 업/다운 시 계산이 복잡해지고, replica 수 변경 시 PDB도 함께 수정해야 하는 번거로움이 있다.

자발적 중단 vs 비자발적 중단

PDB는 자발적 중단만 제어한다는 점이 중요하다. 노드 하드웨어 장애 같은 비자발적 중단에는 PDB가 적용되지 않는다.

자발적 중단 (PDB 적용) 비자발적 중단 (PDB 미적용)
kubectl drain 노드 하드웨어 장애
Cluster Autoscaler 스케일 다운 커널 패닉
노드 업그레이드 (cordon + drain) OOM Kill
Spot/Preemptible 인스턴스 회수 Pod 직접 삭제 (kubectl delete pod)

kubectl delete pod는 Eviction API를 거치지 않으므로 PDB를 우회한다. 프로덕션에서 Pod를 안전하게 제거하려면 kubectl drain을 사용해야 한다.

실전: Deployment + PDB 조합

3-replica Deployment에 PDB를 적용하는 전형적인 패턴이다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-api
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0       # PDB와 함께 사용 시 0 권장
  selector:
    matchLabels:
      app: payment-api
  template:
    metadata:
      labels:
        app: payment-api
    spec:
      topologySpreadConstraints:  # 노드 분산 필수
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: payment-api
      containers:
        - name: api
          image: payment-api:1.5.0
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: payment-api-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: payment-api

topologySpreadConstraints로 Pod를 여러 노드에 분산 배치하는 것이 PDB 효과를 극대화하는 핵심이다. 모든 Pod가 같은 노드에 있으면 해당 노드 drain 시 PDB가 축출을 막아 drain이 영원히 완료되지 않는 상황이 발생한다.

PDB 상태 확인과 모니터링

# PDB 상태 확인
$ kubectl get pdb
NAME               MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
payment-api-pdb    N/A             1                  1                     5d

# 상세 정보
$ kubectl describe pdb payment-api-pdb
Status:
    Current Healthy:   3
    Desired Healthy:   2
    Disruptions Allowed: 1
    Expected Pods:     3

핵심 지표는 Disruptions Allowed다. 이 값이 0이면 추가 축출이 불가능한 상태이며, kubectl drain이 대기(pending) 상태로 멈춘다.

# Prometheus 메트릭으로 모니터링
kube_poddisruptionbudget_status_current_healthy
kube_poddisruptionbudget_status_desired_healthy
kube_poddisruptionbudget_status_pod_disruptions_allowed

# AlertManager 알림 규칙
- alert: PDBDisruptionsAllowedZero
  expr: kube_poddisruptionbudget_status_pod_disruptions_allowed == 0
  for: 10m
  labels:
    severity: warning
  annotations:
    summary: "PDB {{ $labels.poddisruptionbudget }}의 허용 중단이 0입니다"

흔한 실수와 안티패턴

1. minAvailable = replicas (데드락)

# ❌ replicas와 minAvailable이 같으면 drain 불가
spec:
  replicas: 3
---
spec:
  minAvailable: 3    # 절대 축출 불가 → drain 영원히 대기

2. maxUnavailable: 0 (마찬가지로 데드락)

# ❌ 중단 허용이 0이면 아무 Pod도 축출 불가
spec:
  maxUnavailable: 0

3. selector 불일치

# ❌ PDB selector가 Deployment label과 불일치 → PDB 무효
# Deployment: app: payment-api
# PDB: app: payment     ← 오타!

PDB selector가 어떤 Pod도 매치하지 않으면 조용히 무시된다. kubectl get pdb에서 Expected Pods: 0이 보이면 selector를 점검해야 한다.

Cluster Autoscaler와의 상호작용

리소스 Right-Sizing과 함께 Cluster Autoscaler를 운영할 때 PDB는 스케일 다운 결정에 직접 영향을 미친다.

# Cluster Autoscaler의 PDB 관련 동작:
# 1. 노드 스케일 다운 후보 선정
# 2. 해당 노드의 모든 Pod에 대해 PDB 확인
# 3. PDB 위반 시 → 해당 노드 스케일 다운 건너뜀
# 4. PDB 충족 시 → Pod 축출 후 노드 제거

# 강제 스케일 다운이 필요하면 (비권장):
cluster-autoscaler --skip-nodes-with-system-pods=false
# 또는 annotation으로 특정 노드 제외:
cluster-autoscaler.kubernetes.io/safe-to-evict: "true"

K8s 1.27+: Unhealthy Pod Eviction Policy

Kubernetes 1.27부터 PDB에 unhealthyPodEvictionPolicy 필드가 추가되었다. 기본값은 IfHealthy로, 비정상 Pod가 있어도 PDB를 존중한다. AlwaysAllow로 설정하면 비정상 Pod는 PDB를 무시하고 즉시 축출할 수 있다.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  maxUnavailable: 1
  unhealthyPodEvictionPolicy: AlwaysAllow  # 비정상 Pod는 즉시 축출
  selector:
    matchLabels:
      app: api-server

이 설정은 CrashLoopBackOff 상태의 Pod가 PDB 예산을 소진하여 정상적인 운영을 방해하는 문제를 해결한다.

정리

PodDisruptionBudget은 프로덕션 K8s 클러스터에서 필수 리소스다. maxUnavailable: 1을 기본으로 설정하고, topologySpreadConstraints로 노드 분산을 보장하면 노드 유지보수 중에도 서비스 가용성을 유지할 수 있다. minAvailable=replicas 데드락, selector 불일치, Cluster Autoscaler 상호작용을 이해하고, K8s 1.27+에서는 unhealthyPodEvictionPolicy: AlwaysAllow를 활용하면 더욱 안정적인 클러스터를 운영할 수 있다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux