kube-scheduler Preemption 내부 동작, 왜 내 PriorityClass는 예상대로 안 미나
작년쯤부터 팀 클러스터에 PriorityClass를 슬금슬금 붙이기 시작했다. 배치 잡이 야간 스케줄러를 물고 늘어져서 온라인 워크로드가 스케줄이 안 되는 일이 종종 생겼고, 그때마다 "그냥 우선순위 붙이면 되지 않나?" 하는 이야기가 나왔다. 문서 그대로 priority: 1000000 짜리 클래스를 만들고 붙였는데, 실제로 클러스터가 밀릴 만한 상황이 왔을 때 예상대로 안 밀린 경우가 몇 번 있었다. 그래서 kube-scheduler의 preemption 코드를 찬찬히 뜯어봤고, 그 과정에서 "아, 이래서 이게 안 됐구나" 싶은 지점이 몇 개 나왔다. 오늘은 그 내용을 정리해보려고 한다.
이 글은 사용법이 아니라 안쪽 이야기다. Preemption이 어떤 순서로, 어떤 기준으로 pod들을 죽일 후보로 뽑는지, 그리고 왜 이게 종종 우리 기대와 어긋나는지에 초점을 맞춘다. 2026년 2월 기준 upstream 문서와 pkg/scheduler/framework/preemption 패키지를 참고했다.
언제 preemption이 시작되는가
우선 오해부터 하나 풀고 가자. Preemption은 "높은 priority를 가진 pod가 새로 뜨면 자동으로 낮은 priority pod를 죽인다"가 아니다. 스케줄러가 Filter 단계에서 "이 pod 앉힐 노드가 하나도 없다"고 판정해야 그때서야 발동한다. 즉, Pending 큐에 들어와서 정상 스케줄링이 실패했을 때만 후속 절차로 preemption이 돌아간다. 리소스 여유가 있으면 낮은 priority pod가 앉아 있어도 밀지 않는다. 이게 노드 압박 상황에서의 kubelet eviction과 헷갈리기 쉬운 부분이다. QoS 기반 eviction은 노드가 memory pressure 같은 걸 감지해서 kubelet이 직접 죽이는 거고, preemption은 어디까지나 스케줄러가 "새 pod 자리를 만들기 위해" 죽이는 거다. 두 개는 별개 경로다.
Victim 선정, 실제 어떻게 도는지
Preemption 로직은 대략 이 순서로 돈다.
- 실패한 filter reason을 보고, 애초에 preemption으로 해결 가능한 노드만 후보로 남긴다. Node affinity 같은 걸로 튕긴 노드는 아무리 밀어봤자 소용없으니 제외된다.
- 후보 노드마다, 그 노드에 있는 pod 중 preemptor보다 낮은 priority를 가진 놈들을 전부 뽑는다. 이걸 potential victims라고 부른다.
- 이 potential victims를 전부 "가상으로" 지운 상태를 만들고, Filter를 다시 돌린다. 그래도 안 앉으면 이 노드는 탈락.
- Filter를 통과하면, 이제 반대로 victims를 하나씩 다시 노드에 "가상으로" 복구하면서, preemptor가 여전히 앉을 수 있는지 확인한다. 복구했더니 preemptor가 못 앉게 되는 순간의 victim은 진짜로 죽여야 하는 애다.
- 이 과정을 우선순위 낮은 pod부터 시도한다. 즉 되도록 낮은 놈부터 살려두고, 필요한 만큼만 죽인다.
이 4번 단계가 핵심이다. 흔히 오해하는 것과 달리, "낮은 priority pod 다 죽인다"가 아니다. 최소한의 victim 세트를 찾는다. 그래서 낮은 priority pod가 여러 개 있어도, preemptor 하나 앉히는 데 딱 필요한 만큼만 evict된다.
후보 노드가 여러 개면?
여러 노드가 preemption 후보로 나오면 그중 하나를 골라야 한다. 스케줄러는 이 우선순위로 고른다.
- PDB를 덜 침해하는 노드
- 가장 높은 priority의 victim이 가장 낮은 노드
- victim들의 priority 총합이 가장 낮은 노드
- victim 수가 가장 적은 노드
- 가장 최근에 생성된 victim이 가장 오래된(즉 상대적으로 젊은 victim 위주가 아닌) 노드
정리하면 "덜 아프게 밀 수 있는 노드"를 고른다. 여기서 재밌는 게 PDB다.
PDB는 best-effort다
Preemption은 PDB를 존중하려고 노력은 한다. 하지만 문서에 아주 명시적으로 적혀 있는 문장이 있다. PDB를 안 깨고 victim을 찾을 수 없으면, 스케줄러는 PDB를 그냥 깨고 preemption을 강행한다. 이건 몰랐다가 "왜 minAvailable 걸어놨는데 이렇게 다 밀렸지?" 하는 상황이 벌어진다. 특히 critical한 서비스일수록 PriorityClass를 명시적으로 올려두는 게 PDB보다 훨씬 확실한 방어책이다. PDB는 preemption에 대한 마지막 방어선이 아니라 노드 drain에 대한 방어선에 가깝다.
NominatedNodeName의 함정
Preemption이 성공하면 preemptor pod의 status에 nominatedNodeName이 찍힌다. "너 이 노드로 갈 거야, 자리 만들고 있으니까 기다려" 하는 표시다. 스케줄러는 이후 스케줄 사이클마다 이 노드를 먼저 시도한다.
그런데 여기서 조금 미묘한 게, victim들의 graceful termination을 기다리는 동안 스케줄러는 계속 돌고, 만약 다른 노드가 그사이 비면 그 노드로 preemptor를 앉힐 수도 있다. 즉 nominatedNodeName과 실제 nodeName이 다를 수 있다. 이걸 monitoring 지표로 삼아서 alert을 걸어놨다가 잘못된 판단을 내리는 사람을 본 적이 있다. NominatedNodeName은 "여기로 갈 예정"일 뿐 "여기로 갔다"가 아니다.
또 하나, preemption 후보를 시뮬레이션할 때 스케줄러는 다른 pod의 nominatedNodeName도 고려한다. 다만 그 pod의 priority가 지금 preemptor보다 같거나 높을 때만 "이미 앉은 걸로 친다". 낮으면 무시한다. 그래서 아주 드물게 여러 preemptor가 같은 노드를 두고 경쟁하는 상황이 생기고, 결과적으로 하나가 취소되기도 한다.
실전에서 부딪히는 지점들
이 정도만 알아도 "왜 내 PriorityClass가 예상대로 동작 안 하는가"에 대한 답이 대충 나온다. 우리 팀에서 실제로 겪었던 케이스들이다.
첫째, globalDefault: true 짜리 PriorityClass가 하나도 없어서 대부분 pod가 priority 0이었다. 이러면 정말 아무 pod나 다 밀린다. CoreDNS 같은 system pod도 예외가 아니다. 시스템 컴포넌트에는 system-cluster-critical, system-node-critical이 이미 예약되어 있으니 그건 그대로 두고, 우리 서비스 워크로드용 기본값을 하나 만들어 두는 게 낫다.
둘째, priority 인플레이션. 팀마다 "우리 것도 critical"이라고 붙이기 시작하면 preemption은 순식간에 무의미해진다. 우리는 결국 admission policy에서 특정 priority 값 이상은 특정 namespace에서만 허용하도록 막았다. Kyverno든 VAP든 뭐든 상관없다. 이건 기술 문제라기보단 거버넌스 문제에 가깝다.
셋째, Node affinity와 preemption의 상호작용. Preemption이 해결할 수 없는 filter 실패라면 후보 자체에서 빠진다. 특정 nodegroup에만 뜨게 해놓은 워크로드가 nodegroup 부족으로 pending이면, preemption은 도움이 안 된다. 이때는 Karpenter 같은 노드 오토스케일러에 기대야지, PriorityClass 올려도 안 뜬다. 처음에는 이걸 몰라서 "왜 우선순위 최상위인데 안 앉지?" 하고 한참 삽질했다.
열린 질문 하나
문서에는 "cross-node preemption에 대한 요구가 충분하면 검토할 수 있다"는 문구가 아직 남아 있다. 즉, 여러 노드에 걸쳐 낮은 priority pod들을 밀어서 자리를 만드는 형태는 지금도 없다. 우리 팀은 그게 있으면 좋았을 상황을 몇 번 겪었지만, 반대로 그게 있으면 preemption의 영향 반경이 훨씬 커질 거라 조심스러워 보이기도 한다. 아직 검증 중인 문제고, 다음에 이 부분이 어떻게 논의되는지 KEP를 좀 더 팔로우해보려고 한다.
혹시 팀에서 preemption 관련해서 재밌게 겪은 케이스가 있으면 댓글로 남겨주시면 좋겠다. 특히 PDB가 무시되는 상황을 대비해서 어떻게 방어하는지가 궁금하다.