IT/SRE

PDB 잘못 걸어놓고 새벽 3시에 노드 드레인이 멈춘 이야기

gfrog 2026. 8. 16. 15:15

지난주 화요일 새벽이었다. EKS 컨트롤 플레인 마이너 버전 업그레이드에 맞춰 노드 그룹 rolling 교체를 걸어놨는데, 새벽 3시쯤 슬랙에 "노드 3대가 30분째 SchedulingDisabled 상태로 안 빠진다"는 알림이 떴다. 새벽에 눈이 번쩍 떠졌다.

원인은 결국 우리가 반년 전쯤 붙여둔 PodDisruptionBudget 이었다. 그런데 이게 왜 하필 이번 업그레이드에서 터졌는지, 어디서 계산이 어긋났는지를 정리해두려고 이 글을 쓴다.

뭐가 문제였나

우리 팀은 결제 관련 워크로드 몇 개에 minAvailable: 2 로 PDB를 걸어뒀다. 처음 붙일 때 replica가 4~5개였고, "최소 2개는 살아있어야 한다"는 서비스 팀 요구를 반영한 값이었다.

문제는 그 사이에 트래픽 특성이 바뀌면서 몇몇 서비스의 replica 수가 조용히 2로 줄었다는 거다. HPA min을 낮췄고, 새벽 시간대엔 실제로 2개만 떠 있었다. 이 상태에서 minAvailable: 2 는 곧 "아무도 못 죽인다" 와 같은 뜻이다. kubectl get pdb로 보면 ALLOWED DISRUPTIONS 컬럼이 그냥 0이었다.

Karpenter 가 노드 하나를 뽑으려고 파드에 eviction 요청을 보내면, API 서버는 이렇게 답한다.

Cannot evict pod as it would violate the pod's disruption budget.

이걸 Karpenter 는 계속 재시도하고, 노드는 계속 SchedulingDisabled 상태로 남아있고, 결과적으로 노드 rolling 이 그냥 멈춰버린다. 재밌는 건 kubectl drain 처럼 대놓고 에러가 뜨는 게 아니라 그냥 "느리다"로 보인다는 점이다. 그래서 처음엔 IMDS 문제인가, 이미지 pull 문제인가 한참 헛다리를 짚었다.

새벽에 뭘 했나

일단 무너지지 않게 시간부터 벌었다. 사내에서 쓰는 대응 순서는 대충 이렇다.

먼저 어떤 파드가 걸려있는지 확인.

kubectl get pdb -A -o wide | awk '$5==0 {print}'

ALLOWED DISRUPTIONS 가 0인 PDB를 뽑아 봤더니 결제 팀 서비스 두 개, 그리고 별로 안 중요한 사이드카 워크로드 하나가 걸려 있었다. 사이드카 쪽은 그냥 PDB를 임시로 지웠고, 결제 쪽은 판단이 필요했다.

새벽에 결제 팀 온콜을 깨우기가 좀 그래서, 일단 HPA min을 손으로 3으로 올렸다. 파드 하나가 더 뜨자마자 PDB ALLOWED DISRUPTIONS 가 1이 됐고, 그 순간부터 노드 드레인이 슬금슬금 진행됐다.

이게 완전한 해결은 아니었지만, 그날 밤은 이걸로 넘겼다. 아침에 결제 팀과 얘기해서 PDB를 다시 설계하기로 했다.

다음 날 어떻게 고쳤나

우리가 PDB를 다시 설계할 때 지킨 규칙 두 가지가 있다.

첫 번째는 minAvailable 대신 maxUnavailable 을 쓴다. minAvailable: 2 는 replica가 2로 떨어지는 순간 자멸한다. maxUnavailable: 1 이나 maxUnavailable: 25% 는 replica 수와 무관하게 최소 한 개는 항상 뽑아낼 수 있다는 걸 보장한다. 예전엔 "N개는 살아있어야 한다"는 표현이 더 직관적이라 minAvailable 을 썼는데, 운영해보니 파드 수가 시간에 따라 변한다는 걸 자꾸 잊는다.

두 번째는 unhealthyPodEvictionPolicy: AlwaysAllow. Kubernetes 1.27부터 정식으로 들어온 옵션인데, 이걸 안 걸어두면 CrashLoopBackOff 상태의 파드도 PDB 계산에 "healthy"가 아니라는 이유로 eviction이 안 될 수 있다. 진짜 어처구니 없는 상황이지만, 옛날 우리 클러스터에서 이걸로 발목 잡힌 적이 있다.

수정한 매니페스트는 대충 이렇게 생겼다.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: payments-api
spec:
  maxUnavailable: 25%
  unhealthyPodEvictionPolicy: AlwaysAllow
  selector:
    matchLabels:
      app: payments-api

25% 는 replica 4개 기준이면 1개, 8개면 2개까지 허용된다. HPA min이 어떻게 바뀌어도 최소 한 개는 evict 되니까 노드 드레인이 무한히 멈추는 상황은 사라진다.

이걸 어떻게 미리 잡을 수 있을까

돌이켜보면 이건 예방 가능한 사고였다. 우리가 놓친 건 "PDB의 ALLOWED DISRUPTIONS가 0인 상태" 자체를 알림으로 걸어두지 않았다는 거다. 그래서 어제부터 다음 두 개의 Prometheus 알럿을 추가했다.

첫 번째는 kube_poddisruptionbudget_status_current_healthykube_poddisruptionbudget_status_desired_healthy 를 비교해서, desired - current == 0 (즉 여유가 없는 상태) 가 15분 이상 지속되면 warning. 두 번째는 노드가 SchedulingDisabled 상태로 20분 이상 남아있으면 warning. 이 두 개만 있어도 새벽에 깨는 대신 저녁에 조용히 처리할 수 있었을 거다.

혹시 다른 팀은 PDB를 어떻게 관리하는지, 특히 HPA와 PDB의 min 값이 어긋나지 않게 하는 자동화가 있는지 궁금하다. 우리는 아직 매니페스트 리뷰 때 눈으로 잡는 수준이라 완벽하지 않다.