본문으로 건너뛰기
목록으로 돌아가기
기술
16

PDB는 Pod를 지켜 주는 방패가 아니다: Eviction과 node drain의 상태 머신

Replica 3개와 minAvailable 50%를 예로 퍼센트 올림, disruptionsAllowed, Eviction API의 200·429, Pending 대체 Pod로 drain이 멈추는 이유를 단계별로 검증합니다.

박효열 (Hyoyoul Park)
#Kubernetes#PodDisruptionBudget#Eviction#SRE#고가용성#시각화

이 글의 문제 발견 출처는 Threads의 PDB 질문입니다. 원문 캡션·이미지·영상은 사용하지 않았고 링크만 provenance로 남겼습니다. 동작 설명은 Kubernetes 공식 문서의 PDB 설정, Disruption 개념, API-initiated Eviction, policy/v1 API를 기준으로 교차 검증했습니다.

먼저 결론

PodDisruptionBudget(PDB)는 Pod를 항상 살려 두는 장치가 아니다. Eviction API를 사용하는 자발적 중단이 한꺼번에 너무 많은 Ready Pod를 없애지 못하게 하는 admission 정책이다.

Replica가 3개이고 minAvailable: "50%"라면 Kubernetes는 1.5를 아래로 버리지 않고 올림해 desiredHealthy=2로 계산한다. Ready Pod가 3개일 때 허용 가능한 중단은 1개다. 첫 eviction 뒤 Ready가 2개가 되면 budget은 0이 되고, 대체 Pod가 Ready가 되기 전 두 번째 eviction 요청은 PDB 때문에 429 Too Many Requests를 받을 수 있다.

보드에서 대체 Pod가 Ready가 되는 경로와 Pending에 머무는 경로를 비교할 수 있다. 같은 PDB라도 스케줄링 용량이 회복되느냐에 따라 drain 완료와 정지가 갈린다.

세 숫자로 읽는 PDB

PDB status의 핵심은 다음 값이다.

의미
currentHealthyselector에 잡힌 Pod 중 현재 Ready인 수
desiredHealthyeviction 뒤에도 유지해야 하는 최소 건강 Pod 수
disruptionsAllowed현재 시점에 추가로 허용할 수 있는 자발적 중단 수

단순한 정상 상태에서는 다음처럼 생각할 수 있다.

desiredHealthy = ceil(3 × 50%) = 2
disruptionsAllowed ≈ max(0, currentHealthy - desiredHealthy)
                   = max(0, 3 - 2) = 1

실제 controller는 삭제가 시작됐지만 아직 관측이 완전히 수렴하지 않은 Pod를 disruptedPods로 추적하고, owning controller의 기대 replica와 generation도 본다. 따라서 위 식은 mental model이지 API 구현 전체를 대체하는 공식은 아니다. 운영에서는 계산을 재현하려고 하기보다 kubectl get pdb -o yamlobservedGeneration, currentHealthy, desiredHealthy, disruptionsAllowed를 함께 확인한다.

퍼센트는 왜 올림되는가

minAvailablemaxUnavailable은 정수나 퍼센트로 쓸 수 있다. 퍼센트가 정확한 Pod 수로 떨어지지 않으면 Kubernetes는 올림한다. 3개의 50%는 2개, 7개의 50%는 4개다.

다만 두 필드는 의미가 다르다.

  • minAvailable: "50%": 최소 건강 수를 올림한다. 3개 중 2개를 유지한다.
  • maxUnavailable: "50%": 허용 불가용 수를 올림한다. 작은 replica에서는 의도보다 실제 비율이 커질 수 있다.
  • 한 PDB에 두 필드를 동시에 쓸 수 없다.
  • maxUnavailable은 선택한 Pod가 같은 controller에 의해 관리되는 일반적인 workload에서 scaling 의도와 맞추기 쉽다.

Replica 하나에 maxUnavailable: "30%"를 두면 올림 결과가 1이어서 유효 불가용률이 100%가 될 수 있다. 퍼센트가 항상 더 안전한 표현은 아니다. 작은 replica에서는 표로 실제 숫자를 먼저 계산해야 한다.

drain은 삭제가 아니라 정책을 거친 요청이다

kubectl drain은 일반적인 Pod에 대해 Eviction subresource를 호출한다. API server의 주요 응답은 다음과 같다.

  • 200 OK: PDB와 admission 조건을 만족해 eviction이 허용됐다.
  • 429 Too Many Requests: 지금은 PDB budget이 부족하다. controller가 대체 Pod를 Ready로 만들면 재시도할 수 있다.
  • 500 Internal Server Error: 여러 PDB가 같은 Pod를 덮는 등 잘못된 설정일 수 있다.

허용된 뒤에는 Pod에 deletion timestamp가 붙고 kubelet이 graceful shutdown을 시작하며, control plane은 EndpointSlice에서 해당 Pod를 제거한다. 이 순서 때문에 terminationGracePeriodSeconds, readiness 전환, connection draining도 가용성 시간에 영향을 준다.

직접 DELETE Pod를 보내거나 kubectl drain --disable-eviction처럼 eviction을 우회하면 PDB 보호를 받지 못한다. “drain이 안 되니 강제 옵션을 쓴다”는 해결이 아니라, 보호 규칙을 무시하겠다는 운영 결정이다.

PDB가 있어도 drain이 멈추는 진짜 이유

첫 Pod eviction 뒤 ReplicaSet은 대체 Pod를 만들려고 한다. 그러나 다음 조건 때문에 새 Pod가 Pending일 수 있다.

  • 다른 node에 requests를 수용할 CPU·memory가 없다.
  • node affinity, topology spread, taint/toleration이 후보를 없앤다.
  • PersistentVolume의 zone 또는 attach 제한을 만족하지 못한다.
  • image pull, admission webhook, quota가 시작을 막는다.
  • Pod는 Running이지만 readiness probe를 통과하지 못한다.

이때 PDB는 capacity를 만들지 않는다. Ready가 2인 상태에서 다음 eviction을 막을 뿐이다. 따라서 진단 순서는 “PDB를 지울까?”가 아니라 다음이어야 한다.

  1. kubectl get pdb -o yaml로 budget과 generation을 확인한다.
  2. Pending Pod의 describe event에서 scheduler 원인을 확인한다.
  3. Ready가 아닌 Pod의 probe, EndpointSlice, termination 시간을 본다.
  4. Autoscaler가 새 capacity를 만들 수 있는지와 scale-up 상한을 확인한다.
  5. maintenance 시간 안에 회복할 수 없다면 위험을 기록하고 강제 중단 여부를 결정한다.

자발적 중단과 비자발적 장애를 분리한다

PDB가 다루는 대표적인 자발적 중단은 node drain, cluster autoscaler의 node 제거, 관리자가 Eviction API로 요청한 종료다. 반면 node 전원 장애, kernel panic, network partition, application crash, OOM kill은 PDB가 사전에 막을 수 없는 비자발적 장애다.

그래서 minAvailable: 2는 “항상 두 Pod가 살아 있다”는 SLA가 아니다. 이미 한 node가 고장 난 순간 currentHealthy가 최소치 아래로 갈 수 있다. PDB는 다음 관리 작업이 상황을 더 악화하지 않게 할 뿐이다. 진짜 고가용성에는 replica 분산, anti-affinity 또는 topology spread, 충분한 spare capacity, readiness 정확성, 빠른 복구가 함께 필요하다.

unhealthy Pod 정책도 의도적으로 고른다

unhealthyPodEvictionPolicy의 기본 의미인 IfHealthyBudget에서는 Running이지만 Ready가 아닌 Pod도 애플리케이션이 이미 충분히 건강한 경우에만 evict할 수 있다. AlwaysAllow는 이런 unhealthy Pod의 eviction을 허용해 깨진 node를 drain하기 쉽게 만들지만, 회복 중인 Pod가 다시 Ready가 될 기회를 줄일 수 있다.

정답은 하나가 아니다. stateless workload와 quorum system의 위험이 다르다. 정책 변경 전 다음 실패를 게임데이에서 확인한다.

  • 하나의 Pod가 영구적으로 NotReady일 때 drain이 끝나는가?
  • 두 zone 중 하나가 사라진 상태에서 maintenance를 시작하면 어떻게 되는가?
  • 대체 Pod가 10분간 Pending이면 자동화가 timeout 후 무엇을 하는가?
  • 겹치는 selector와 빈 selector가 의도한 Pod만 선택하는가?

배포 전 검증 체크리스트

  • selector가 workload label과 정확히 맞고 다른 PDB와 겹치지 않는가?
  • 최소 replica에서 퍼센트 반올림 결과를 숫자로 계산했는가?
  • PDB와 Deployment의 maxUnavailable을 서로 다른 계층의 규칙으로 이해했는가?
  • node 한 대를 비워도 대체 Pod를 둘 capacity가 있는가?
  • readiness가 실제 트래픽 처리 가능 상태를 나타내는가?
  • drain timeout, alert, 승인된 강제 우회 절차가 있는가?

PDB의 가치는 “Pod를 삭제하지 못하게 한다”가 아니라 회복을 기다릴 시간을 control plane에 부여한다는 데 있다. 그 시간이 실제 회복으로 이어지려면 scheduler와 workload가 대체 가용성을 만들 수 있어야 한다.