지난주 화요일 새벽이었다. 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_healthy 와 kube_poddisruptionbudget_status_desired_healthy 를 비교해서, desired - current == 0 (즉 여유가 없는 상태) 가 15분 이상 지속되면 warning. 두 번째는 노드가 SchedulingDisabled 상태로 20분 이상 남아있으면 warning. 이 두 개만 있어도 새벽에 깨는 대신 저녁에 조용히 처리할 수 있었을 거다.
혹시 다른 팀은 PDB를 어떻게 관리하는지, 특히 HPA와 PDB의 min 값이 어긋나지 않게 하는 자동화가 있는지 궁금하다. 우리는 아직 매니페스트 리뷰 때 눈으로 잡는 수준이라 완벽하지 않다.
'IT > SRE' 카테고리의 다른 글
| SLO burn rate 알림 튜닝하다가 새벽에 두 번 깨진 이야기 (0) | 2026.08.24 |
|---|---|
| burn rate alert 튜닝하다 3주 삽질한 이야기 (0) | 2026.08.14 |
| 에러버짓 정책, 이렇게 굴리면 실제로 돌아간다 (0) | 2026.08.01 |
| 다중 윈도우 번-레이트 알림, 사실 내부적으로는 이렇게 돈다 (0) | 2026.07.28 |
| SLO burn rate 알람, fast burn 하나만 두지 마세요 (0) | 2026.07.11 |