K8s PDB 운영 가이드

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 조합을 기본으로 삼고, 워크로드 특성에 맞게 조정하는 것이 핵심이다.

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