Pod 우선순위와 선점
Pod 우선순위와 선점 (Pod Priority and Preemption)
기능 상태: Kubernetes v1.14 [stable]
Pod에는 우선순위(priority) 가 있을 수 있어요. 우선순위는 다른 Pod들과 비교했을 때 Pod의 중요성을 나타내요. Pod를 스케줄링할 수 없다면, 스케줄러는 더 낮은 우선순위의 Pod를 선점(축출)해서 pending 상태의 Pod 스케줄링을 가능하게 하려고 해요.
경고:
모든 사용자가 신뢰할 수 없는 클러스터에서 악의적인 사용자가 가능한 가장 높은 우선순위로 Pod를 만들어 다른 Pod가 축출되거나 스케줄링되지 않게 할 수 있어요. 관리자는 ResourceQuota를 사용해 사용자가 높은 우선순위로 pod를 만드는 것을 막을 수 있어요.
자세한 내용은 기본적으로 우선순위 클래스 소비 제한을 참고하세요.
우선순위와 선점을 사용하는 방법
우선순위와 선점을 사용하려면:
- PriorityClass를 하나 이상 추가해요.
- 추가한 PriorityClass 중 하나로
priorityClassName이 설정된 Pod를 만들어요. 물론 Pod를 직접 만들 필요는 없어요. 보통 Deployment 같은 컬렉션 객체의 Pod 템플릿에priorityClassName을 추가하면 돼요.
더 자세한 내용은 계속 읽어보세요.
참고:
Kubernetes는 이미 두 개의 PriorityClass(
system-cluster-critical과system-node-critical)를 제공해요. 이들은 공통 클래스로, 중요한 컴포넌트가 항상 먼저 스케줄링되도록 보장하는 데 사용돼요. Kubernetes v1.36에서 그 우선순위 값은system-cluster-critical이 2000000000,system-node-critical이 2000001000이에요.
PriorityClass
PriorityClass는 우선순위 클래스 이름을 우선순위의 정수 값에 매핑하는 것을 정의하는 네임스페이스가 없는 객체예요. 이름은 PriorityClass 객체의 메타데이터 name 필드에 지정돼요. 값은 필수 value 필드에 지정돼요. 값이 높을수록 우선순위가 높아요. PriorityClass 객체의 이름은 유효한 DNS 하위 도메인 이름이어야 하고 system- 접두사로 시작할 수 없어요.
PriorityClass 객체는 10억 이하의 모든 32비트 정수 값을 가질 수 있어요. 즉 PriorityClass 객체의 값 범위는 -2147483648부터 1000000000까지(포함)예요. 더 큰 숫자는 중요한 시스템 Pod를 나타내는 내장 PriorityClass를 위해 예약되어 있어요. 클러스터 관리자는 원하는 각 매핑에 대해 PriorityClass 객체 하나를 만들어야 해요.
참고:
Kubernetes v1.36에서
system-cluster-critical의 우선순위 값은 2000000000,system-node-critical은 2000001000이에요.
PriorityClass에는 globalDefault와 description이라는 두 가지 선택 필드도 있어요. globalDefault 필드는 이 PriorityClass의 값이 priorityClassName이 없는 Pod에 사용되어야 함을 나타내요. globalDefault가 true로 설정된 PriorityClass는 시스템에 하나만 존재할 수 있어요. globalDefault가 설정된 PriorityClass가 없으면 priorityClassName이 없는 Pod의 우선순위는 0이에요.
description 필드는 임의의 문자열이에요. 클러스터 사용자에게 언제 이 PriorityClass를 사용해야 하는지 알려주기 위한 것이에요.
PodPriority와 기존 클러스터에 대한 참고 사항
- 이 기능이 없는 기존 클러스터를 업그레이드하면 기존 Pod의 우선순위는 사실상 0이 돼요.
globalDefault가true로 설정된 PriorityClass를 추가해도 기존 Pod의 우선순위는 바뀌지 않아요. 그런 PriorityClass의 값은 PriorityClass가 추가된 후 생성된 Pod에만 사용돼요.- PriorityClass를 삭제하면 삭제된 PriorityClass의 이름을 사용하는 기존 Pod는 그대로 유지되지만, 삭제된 PriorityClass의 이름을 사용하는 더 많은 Pod를 만들 수는 없어요.
PriorityClass 예시
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
선점하지 않는 PriorityClass (Non-preempting PriorityClass)
기능 상태: Kubernetes v1.24 [stable]
preemptionPolicy: Never인 Pod는 더 낮은 우선순위 pod보다 앞서 스케줄링 큐에 배치되지만, 다른 pod를 선점할 수는 없어요. 스케줄링을 기다리는 선점하지 않는 pod는 충분한 리소스가 비워져서 스케줄링될 수 있을 때까지 스케줄링 큐에 남아 있어요. 선점하지 않는 pod는 다른 pod와 마찬가지로 스케줄러 백오프(back-off)의 적용을 받아요. 즉, 스케줄러가 이 pod들을 시도했지만 스케줄링할 수 없으면 더 낮은 빈도로 재시도해서, 더 낮은 우선순위의 다른 pod가 그들보다 먼저 스케줄링되도록 해요.
선점하지 않는 pod도 여전히 다른 높은 우선순위 pod에 의해 선점될 수 있어요.
preemptionPolicy의 기본값은 PreemptLowerPriority인데, 그 PriorityClass의 pod가 더 낮은 우선순위 pod를 선점할 수 있게 해줘요(기존 기본 동작). preemptionPolicy가 Never로 설정되면 그 PriorityClass의 pod는 선점하지 않게 돼요.
사용 사례의 예시는 데이터 과학 워크로드예요. 사용자는 다른 워크로드보다 우선순위가 높길 원하지만 실행 중인 pod를 선점해 기존 작업을 폐기하고 싶지 않은 job을 제출할 수 있어요. preemptionPolicy: Never인 높은 우선순위 job은 충분한 클러스터 리소스가 "자연스럽게" 비워지는 즉시, 다른 대기 중인 pod보다 앞서 스케줄링돼요.
선점하지 않는 PriorityClass 예시
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority-nonpreempting
value: 1000000
preemptionPolicy: Never
globalDefault: false
description: "This priority class will not cause other pods to be preempted."
Pod 우선순위
PriorityClass를 하나 이상 가진 뒤에는 그 PriorityClass 이름 중 하나를 스펙에 지정한 Pod를 만들 수 있어요. 우선순위 승인 컨트롤러는 priorityClassName 필드를 사용해 우선순위의 정수 값을 채워요. 우선순위 클래스를 찾을 수 없으면 Pod는 거부돼요.
다음 YAML은 앞선 예시에서 만든 PriorityClass를 사용하는 Pod 구성의 예시예요. 우선순위 승인 컨트롤러가 스펙을 확인하고 Pod의 우선순위를 1000000으로 해석해요.
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
Pod 우선순위가 스케줄링 순서에 미치는 영향
Pod 우선순위가 활성화되면 스케줄러는 pending Pod를 우선순위별로 정렬하고, pending Pod는 스케줄링 큐에서 더 낮은 우선순위의 다른 pending Pod보다 앞에 배치돼요. 그 결과 더 높은 우선순위 Pod는 스케줄링 요구사항이 충족되면 더 낮은 우선순위 Pod보다 더 빨리 스케줄링될 수 있어요. 그런 Pod를 스케줄링할 수 없으면, 스케줄러는 계속해서 다른 더 낮은 우선순위 Pod를 스케줄링하려고 해요.
선점 (Preemption)
Pod가 생성되면 큐에 들어가 스케줄링되기를 기다려요. 스케줄러는 큐에서 Pod를 고르고 그것을 노드에 스케줄링하려고 해요. Pod의 모든 지정된 요구사항을 충족하는 노드가 없으면 pending Pod에 대해 선점 로직이 촉발돼요. pending Pod를 P라고 부를게요. 선점 로직은 P보다 낮은 우선순위의 Pod 하나 이상을 제거하면 P를 그 노드에 스케줄링할 수 있는 노드를 찾으려고 해요. 그런 노드를 찾으면 하나 이상의 더 낮은 우선순위 Pod가 노드에서 축출돼요. Pod들이 사라진 후 P를 그 노드에 스케줄링할 수 있어요.
사용자에게 노출되는 정보
Pod P가 노드 N에서 하나 이상의 Pod를 선점하면, Pod P의 status의 nominatedNodeName 필드가 노드 N의 이름으로 설정돼요. 이 필드는 스케줄러가 Pod P를 위해 예약된 리소스를 추적하는 데 도움을 주고 사용자에게 클러스터의 선점에 대한 정보를 제공해요.
Pod P가 반드시 "지명된 노드(nominated Node)"에 스케줄링되는 것은 아니라는 점을 유의하세요. 스케줄러는 다른 노드를 반복하기 전에 항상 "지명된 노드"를 먼저 시도해요. 희생자(victim) Pod가 선점된 후에는 그들의 유예 종료 기간(graceful termination period)을 받아요. 스케줄러가 희생자 Pod의 종료를 기다리는 동안 다른 노드가 사용 가능해지면, 스케줄러는 다른 노드를 사용해 Pod P를 스케줄링할 수 있어요. 그 결과 Pod 스펙의 nominatedNodeName과 nodeName이 항상 같지는 않아요. 또한 스케줄러가 노드 N에서 Pod들을 선점했는데 Pod P보다 높은 우선순위의 Pod가 도착하면, 스케줄러는 노드 N을 새 더 높은 우선순위 Pod에게 줄 수 있어요. 그런 경우 스케줄러는 Pod P의 nominatedNodeName을 지워요. 이렇게 함으로써 스케줄러는 Pod P가 다른 노드에서 Pod를 선점할 수 있게 해요.
선점의 제한 사항 (Limitations of preemption)
선점 희생자의 유예 종료
Pod가 선점되면 희생자는 그 유예 종료 기간을 받아요. 그들은 그만큼의 시간 동안 작업을 끝내고 종료할 수 있어요. 그렇지 않으면 종료돼요. 이 유예 종료 기간은 스케줄러가 Pod를 선점한 시점과 pending Pod(P)를 노드(N)에 스케줄링할 수 있는 시점 사이에 시간 간격을 만든다. 그동안 스케줄러는 다른 pending Pod를 계속 스케줄링해요. 희생자가 종료되거나 종료되면 스케줄러는 pending 큐의 Pod를 스케줄링하려고 해요. 따라서 스케줄러가 희생자를 선점한 시점과 Pod P가 스케줄링되는 시점 사이에 보통 시간 간격이 있어요. 이 간격을 최소화하려면 더 낮은 우선순위 Pod의 유예 종료 기간을 0이나 작은 숫자로 설정할 수 있어요.
PodDisruptionBudget은 지원되지만 보장되지는 않음
PodDisruptionBudget(PDB)은 애플리케이션 소유자가 자발적 중단(voluntary disruptions)으로 인해 복제된 애플리케이션의 동시에 중단되는 Pod 수를 제한할 수 있게 해줘요. Kubernetes는 Pod를 선점할 때 PDB를 지원하지만, PDB를 존중하는 것은 최선 노력(best effort)이에요. 스케줄러는 선점으로 PDB가 위반되지 않는 희생자를 찾으려고 하지만, 그런 희생자를 찾지 못하면 선점은 여전히 발생하고, 더 낮은 우선순위 Pod는 그 PDB가 위반되더라도 제거돼요.
더 낮은 우선순위 Pod에 대한 pod 간 친화성
노드는 "pending Pod보다 낮은 우선순위의 모든 Pod를 노드에서 제거하면, pending Pod를 그 노드에 스케줄링할 수 있는가?"라는 질문에 예라고 답할 때만 선점이 고려돼요.
참고:
선점이 모든 더 낮은 우선순위 Pod를 반드시 제거하는 것은 아니에요. 모든 더 낮은 우선순위 Pod보다 적게 제거해도 pending Pod를 스케줄링할 수 있다면, 더 낮은 우선순위 Pod의 일부만 제거돼요. 그래도 앞선 질문에 대한 답은 예여야 해요. 답이 아니요이면 그 노드는 선점에 고려되지 않아요.
pending Pod가 노드의 더 낮은 우선순위 Pod 하나 이상에 pod 간 친화성을 가지면, 그 더 낮은 우선순위 Pod들이 없으면 pod 간 친화성 규칙이 충족될 수 없어요. 이 경우 스케줄러는 그 노드에서 어떤 Pod도 선점하지 않아요. 대신 다른 노드를 찾아요. 스케줄러가 적합한 노드를 찾을 수도 있고 못 찾을 수도 있어요. pending Pod가 스케줄링될 수 있다는 보장은 없어요.
이 문제에 대한 권장 해결책은 같거나 더 높은 우선순위 Pod에 대해서만 pod 간 친화성을 만드는 거예요.
노드 간 선점 (Cross node preemption)
pending Pod P를 노드 N에 스케줄링할 수 있도록 노드 N이 선점을 위해 고려되고 있다고 가정해보세요. P는 다른 노드의 Pod가 선점될 때만 N에서 실행 가능해질 수 있어요. 예시는 다음과 같아요.
- Pod P가 노드 N을 위해 고려 중이에요.
- Pod Q가 노드 N과 같은 Zone의 다른 노드에서 실행 중이에요.
- Pod P는 Pod Q와 Zone 전체 anti-affinity(
topologyKey: topology.kubernetes.io/zone)를 가져요. - Zone에서 Pod P와 다른 Pod 사이의 다른 anti-affinity 사례는 없어요.
- Pod P를 노드 N에 스케줄링하려면 Pod Q를 선점할 수 있지만, 스케줄러는 노드 간 선점을 수행하지 않아요. 그래서 Pod P는 노드 N에서 스케줄링 불가능한 것으로 간주돼요.
Pod Q가 그 노드에서 제거되면 Pod anti-affinity 위반이 사라지고 Pod P가 노드 N에 스케줄링될 수 있을지도 몰라요.
충분한 수요가 있고 합리적인 성능의 알고리즘을 찾으면 미래 버전에서 노드 간 선점을 추가하는 것을 고려할 수 있어요.
문제해결 (Troubleshooting)
Pod 우선순위와 선점은 원하지 않는 부작용을 가질 수 있어요. 잠재적 문제와 대처 방법의 예시는 다음과 같아요.
Pod가 불필요하게 선점됨
선점은 더 높은 우선순위 pending Pod를 위한 공간을 만들기 위해 리소스 압박이 있는 상태에서 클러스터에서 기존 Pod를 제거해요. 실수로 특정 Pod에 높은 우선순위를 부여하면, 의도치 않게 높은 우선순위 Pod가 클러스터에서 선점을 일으킬 수 있어요. Pod 우선순위는 Pod 스펙의 priorityClassName 필드를 설정해 지정해요. 우선순위의 정수 값은 그런 다음 해석되어 podSpec의 priority 필드에 채워져요.
이 문제를 해결하려면 그러한 Pod의 priorityClassName을 더 낮은 우선순위 클래스를 사용하도록 바꾸거나, 그 필드를 비워 둘 수 있어요. 빈 priorityClassName은 기본적으로 0으로 해석돼요.
Pod가 선점되면 선점된 Pod에 대해 이벤트가 기록돼요. 선점은 클러스터에 Pod를 위한 충분한 리소스가 없을 때만 일어나야 해요. 그런 경우 선점은 pending Pod(선점자)의 우선순위가 희생자 Pod보다 높을 때만 일어나요. pending Pod가 없거나 pending Pod의 우선순위가 희생자보다 같거나 낮을 때는 선점이 일어나지 않아야 해요. 그런 시나리오에서 선점이 일어나면 이슈를 제기해 주세요.
Pod가 선점됐지만 선점자가 스케줄링되지 않음
pod가 선점되면 요청된 유예 종료 기간을 받는데, 기본은 30초예요. 희생자 Pod가 이 기간 안에 종료되지 않으면 강제로 종료돼요. 모든 희생자가 사라지면 선점자 Pod를 스케줄링할 수 있어요.
선점자 Pod가 희생자들이 사라지기를 기다리는 동안, 같은 노드에 맞는 더 높은 우선순위 Pod가 생성될 수 있어요. 이 경우 스케줄러는 선점자 대신 더 높은 우선순위 Pod를 스케줄링해요.
이것은 예상된 동작이에요: 더 높은 우선순위의 Pod가 더 낮은 우선순위의 Pod의 자리를 차지해야 해요.
더 높은 우선순위 Pod가 더 낮은 우선순위 pod보다 먼저 선점됨
스케줄러는 pending Pod를 실행할 수 있는 노드를 찾으려고 해요. 노드를 찾지 못하면 pending pod를 위한 공간을 만들기 위해 임의의 노드에서 더 낮은 우선순위의 Pod를 제거하려고 해요. 낮은 우선순위 Pod가 있는 노드가 pending Pod를 실행하는 데 적합하지 않으면, 스케줄러는 (다른 노드의 Pod와 비교해) 더 높은 우선순위 Pod가 있는 다른 노드를 선점에 선택할 수 있어요. 희생자는 여전히 선점자 Pod보다 낮은 우선순위여야 해요.
선점에 사용할 수 있는 노드가 여러 개일 때, 스케줄러는 가장 낮은 우선순위의 Pod 집합이 있는 노드를 선택하려고 해요. 하지만 그런 Pod들에, 선점하면 위반되는 PodDisruptionBudget이 있다면 스케줄러는 더 높은 우선순위 Pod가 있는 다른 노드를 선택할 수 있어요.
선점에 여러 노드가 있고 위 시나리오 중 어느 것도 적용되지 않으면, 스케줄러는 가장 낮은 우선순위의 노드를 선택해요.
Pod 우선순위와 서비스 품질의 상호작용
Pod 우선순위와 QoS 클래스는 상호작용이 거의 없는 두 개의 직교 기능이며, QoS 클래스에 따라 Pod의 우선순위를 설정하는 것에 대한 기본 제한은 없어요. 스케줄러의 선점 로직은 선점 대상을 선택할 때 QoS를 고려하지 않아요. 선점은 Pod 우선순위를 고려하고 가장 낮은 우선순위의 대상 집합을 선택하려고 해요. 더 높은 우선순위 Pod는, 가장 낮은 우선순위 Pod의 제거가 스케줄러가 선점자 Pod를 스케줄링하기에 충분하지 않을 때나, 가장 낮은 우선순위 Pod가 PodDisruptionBudget으로 보호될 때만 선점에 고려돼요.
kubelet은 노드 압력 축출(node-pressure eviction)의 pod 순서를 결정하기 위해 Priority를 사용해요. QoS 클래스를 사용해 pod가 가장 축출될 가능성이 높은 순서를 추정할 수 있어요. kubelet은 다음 요소에 기반해 축출할 pod 순위를 매겨요.
- 부족한(resource starved) 리소스 사용량이 requests를 초과하는지
- Pod 우선순위
- requests에 상대적인 리소스 사용량
자세한 내용은 kubelet 축출을 위한 pod 선택을 참고하세요.
kubelet 노드 압력 축출은 사용량이 requests를 초과하지 않으면 Pod를 축출하지 않아요. 더 낮은 우선순위의 Pod가 requests를 초과하지 않으면 축출되지 않아요. requests를 초과하는 더 높은 우선순위의 다른 Pod는 축출될 수 있어요.
다음으로 볼 것 (What's next)
- ResourceQuota를 PriorityClass와 함께 사용하는 방법 읽기: 기본적으로 우선순위 클래스 소비 제한
- Pod 중단(Pod Disruption)에 대해 배우기
- API 기반 축출에 대해 배우기
- 노드 압력 축출에 대해 배우기