왜 드레인이 위험한가

노드 업그레이드나 스케일 다운을 위해 kubectl drain을 실행하면, 해당 노드의 파드들이 한꺼번에 evict된다. 문제는 이 축출(eviction)이 롤링 업데이트와 달리 "정상 파드 수"를 기준으로 삼지 않는다는 점이다. 3개 레플리카가 모두 같은 노드에 스케줄링되어 있었다면, 드레인 한 번에 서비스 가용 인스턴스가 0이 될 수 있다. 재스케줄링 후 새 파드가 Ready 되기까지 수십 초, 이미지 풀이 늦으면 분 단위로 다운타임이 발생한다.

롤링 배포는 우리가 통제하지만, 노드 유지보수는 클라우드 사업자의 자동 업그레이드나 스팟 인스턴스 회수처럼 예고 없이 발생하기도 한다. 이때 애플리케이션의 최소 가용성을 강제하는 장치가 PodDisruptionBudget(PDB)이다.

PDB가 막는 것과 못 막는 것

PDB는 자발적 중단(voluntary disruption)에만 개입한다. 즉 drain, `kubectl` 기반 축출, 클러스터 오토스케일러의 노드 축소가 대상이다. 노드가 갑자기 죽거나(하드웨어 장애), 파드가 OOMKilled 되는 비자발적 중단은 PDB가 막지 못한다. PDB는 "동시에 몇 개까지 자발적으로 내려도 되는지"의 계약일 뿐이다.

중단 유형예시PDB 적용
자발적drain, 오토스케일러 축소적용됨
비자발적노드 장애, OOMKill, 커널 패닉미적용

minAvailable vs maxUnavailable

두 필드는 동시에 쓸 수 없다. 상황에 맞게 하나만 고른다.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  minAvailable: 2          # 항상 최소 2개는 살아있어야 함
  selector:
    matchLabels:
      app: api
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: worker-pdb
spec:
  maxUnavailable: 1        # 한 번에 1개까지만 내릴 수 있음
  selector:
    matchLabels:
      app: worker

퍼센트도 가능하다(minAvailable: 50%). 다만 레플리카 수가 적을 때 반올림 규칙에 주의해야 한다. 레플리카 3개에 minAvailable: 50%는 올림하여 2개가 되므로, 한 번에 1개만 축출된다.

동작 확인

PDB가 실제로 몇 개의 여유를 허용하는지는 status로 확인한다. ALLOWED DISRUPTIONS가 0이면 드레인이 그 파드에서 블록된다.

# PDB 상태 확인
kubectl get pdb api-pdb
# NAME      MIN AVAILABLE   ALLOWED DISRUPTIONS   AGE
# api-pdb   2               1                     3d

# 노드 드레인 (PDB를 존중하며 진행)
kubectl drain node-01 \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --timeout=300s

드레인은 PDB를 위반하는 축출을 거부하고, 여유가 생길 때까지 재시도한다. 다른 파드가 새 노드에서 Ready가 되어 ALLOWED DISRUPTIONS가 1 이상으로 회복되면 다음 파드가 내려간다.

흔한 실수: PDB가 드레인을 영원히 막는 경우

가장 자주 겪는 사고는 드레인이 무한 대기하는 상황이다. 원인은 대개 셋 중 하나다. 첫째, minAvailable이 레플리카 수와 같으면(예: 레플리카 3, minAvailable 3) 단 하나도 축출할 수 없다. 둘째, 새 파드가 스케줄될 여유 노드가 없어 가용 파드가 회복되지 못한다. 셋째, 파드가 CrashLoopBackOff라 애초에 Ready 개수를 채우지 못한다.

이런 경우 드레인은 정상적으로 블록된 것이지 버그가 아니다. kubectl get events에서 Cannot evict pod as it would violate the pod's disruption budget 메시지를 확인하고, PDB 값이나 클러스터 용량을 조정해야 한다.

운영에서의 권장 설정

실무에서는 절대값 minAvailable보다 maxUnavailable: 1을 기본으로 쓰는 편이 안전하다. 레플리카 수가 오토스케일링으로 바뀌어도 "한 번에 하나씩"이라는 의미가 유지되기 때문이다. 단, 배포 replicas가 1인 워크로드에 maxUnavailable: 0이나 minAvailable: 1을 걸면 그 파드는 드레인으로 절대 내려가지 않아 노드 유지보수 자체가 막힌다. 무중단이 필요하면 replicas를 최소 2로 올리고 topologySpreadConstraints로 노드 분산을 강제하는 것이 근본 해법이다.

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: kubernetes.io/hostname
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: api

PDB는 가용성 하한을 강제하고, 스프레드 제약은 애초에 파드가 한 노드에 몰리지 않게 한다. 두 장치를 함께 쓸 때 비로소 드레인이 예측 가능하고 안전해진다.