파드 우선순위(priority)와 선점(preemption)
Kubernetes v1.14 [stable]
파드는 우선순위 를 가질 수 있다. 우선순위는 다른 파드에 대한 상대적인 파드의 중요성을 나타낸다. 파드를 스케줄링할 수 없는 경우, 스케줄러는 우선순위가 낮은 파드를 선점(축출)하여 보류 중인 파드를 스케줄링할 수 있게 한다.
모든 사용자를 신뢰할 수 없는 클러스터에서, 악의적인 사용자가 우선순위가 가장 높은 파드를 생성하여 다른 파드가 축출되거나 스케줄링되지 않을 수 있다. 관리자는 리소스쿼터를 사용하여 사용자가 우선순위가 높은 파드를 생성하지 못하게 할 수 있다.
자세한 내용은 기본적으로 프라이어리티클래스(Priority Class) 소비 제한을 참고한다.
우선순위와 선점을 사용하는 방법
우선순위와 선점을 사용하려면 다음을 참고한다.
-
하나 이상의 프라이어리티클래스를 추가한다.
-
추가된 프라이어리티클래스 중 하나에
priorityClassName
이 설정된 파드를 생성한다. 물론 파드를 직접 생성할 필요는 없다. 일반적으로 디플로이먼트와 같은 컬렉션 오브젝트의 파드 템플릿에priorityClassName
을 추가한다.
이 단계에 대한 자세한 내용은 계속 읽어보자.
system-cluster-critical
과 system-node-critical
,
두 개의 프라이어리티클래스를 제공한다.
이들은 일반적인 클래스이며 중요한(critical) 컴포넌트가 항상 먼저 스케줄링이 되도록 하는 데 사용된다.
프라이어리티클래스
프라이어리티클래스는 프라이어리티클래스 이름에서 우선순위의 정수 값으로의 매핑을
정의하는 네임스페이스가 아닌(non-namespaced) 오브젝트이다. 이름은
프라이어리티클래스 오브젝트의 메타데이터의 name
필드에 지정된다. 값은
필수 value
필드에 지정되어 있다. 값이 클수록, 우선순위가
높다.
프라이어리티클래스 오브젝트의 이름은 유효한
DNS 서브 도메인 이름이어야 하며,
system-
접두사를 붙일 수 없다.
프라이어리티클래스 오브젝트는 10억 이하의 32비트 정수 값을 가질 수 있다. 일반적으로 선점하거나 축출해서는 안되는 중요한 시스템 파드에는 더 큰 숫자가 예약되어 있다. 클러스터 관리자는 원하는 각 매핑에 대해 프라이어리티클래스 오브젝트를 하나씩 생성해야 한다.
프라이어리티클래스에는 globalDefault
와 description
두 개의 선택적인 필드도 있다.
globalDefault
필드는 이 프라이어리티클래스의 값을 priorityClassName
이 없는
파드에 사용해야 함을 나타낸다. 시스템에서 globalDefault
가 true
로 설정된
프라이어리티클래스는 하나만 존재할 수 있다. globalDefault
가 설정된
프라이어리티클래스가 없을 경우, priorityClassName
이 없는 파드의
우선순위는 0이다.
description
필드는 임의의 문자열이다. 이 필드는 이 프라이어리티클래스를 언제
사용해야 하는지를 클러스터 사용자에게 알려주기 위한 것이다.
PodPriority와 기존 클러스터에 대한 참고 사항
-
이 기능없이 기존 클러스터를 업그레이드 하는 경우, 기존 파드의 우선순위는 사실상 0이다.
-
globalDefault
가true
로 설정된 프라이어리티클래스를 추가해도 기존 파드의 우선순위는 변경되지 않는다. 이러한 프라이어리티클래스의 값은 프라이어리티클래스를 추가한 후 생성된 파드에만 사용된다. -
프라이어리티클래스를 삭제하면, 삭제된 프라이어리티클래스의 이름을 사용하는 기존 파드는 변경되지 않고 남아있지만, 삭제된 프라이어리티클래스의 이름을 사용하는 파드는 더 이상 생성할 수 없다.
프라이어리티클래스 예제
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "이 프라이어리티클래스는 XYZ 서비스 파드에만 사용해야 한다."
비-선점 프라이어리티클래스
Kubernetes v1.24 [stable]
preemptionPolicy: Never
를 가진 파드는 낮은 우선순위 파드의 스케줄링 대기열의
앞쪽에 배치되지만,
그 파드는 다른 파드를 축출할 수 없다.
스케줄링 대기 중인 비-선점 파드는 충분한 리소스가 확보되고
스케줄링될 수 있을 때까지
스케줄링 대기열에 대기한다.
다른 파드와 마찬가지로,
비-선점 파드는
스케줄러 백오프(back-off)에 종속된다.
이는 스케줄러가 이러한 파드를 스케줄링하려고 시도하고 스케줄링할 수 없는 경우,
더 적은 횟수로 재시도하여,
우선순위가 낮은 다른 파드를 미리 스케줄링할 수 있음을 의미한다.
비-선점 파드는 다른 우선순위가 높은 파드에 의해 축출될 수 있다.
preemptionPolicy
는 기본값으로 PreemptLowerPriority
로 설정되어,
해당 프라이어리티클래스의 파드가 우선순위가 낮은 파드를 축출할 수
있다(기존의 기본 동작과 동일).
preemptionPolicy
가 Never
로 설정된 경우,
해당 프라이어리티클래스의 파드는 비-선점될 것이다.
예제 유스케이스는 데이터 과학 관련 워크로드이다.
사용자는 다른 워크로드보다 우선순위가 높은 잡(job)을 제출할 수 있지만,
실행 중인 파드를 축출하여 기존의 작업을 삭제하지는 않을 것이다.
클러스터 리소스가 "자연스럽게" 충분히 사용할 수 있게 되면,
preemptionPolicy: Never
의 우선순위가 높은 잡이
다른 대기 중인 파드보다 먼저 스케줄링된다.
비-선점 프라이어리티클래스 예제
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority-nonpreempting
value: 1000000
preemptionPolicy: Never
globalDefault: false
description: "이 프라이어리티클래스는 다른 파드를 축출하지 않는다."
파드 우선순위
프라이어리티클래스가 하나 이상 있으면, 그것의 명세에서 이들 프라이어리티클래스 이름 중 하나를
지정하는 파드를 생성할 수 있다. 우선순위 어드미션
컨트롤러는 priorityClassName
필드를 사용하고 우선순위의 정수 값을
채운다. 프라이어리티클래스를 찾을 수 없으면, 파드가 거부된다.
다음의 YAML은 이전 예제에서 생성된 프라이어리티클래스를 사용하는 파드 구성의 예이다. 우선순위 어드미션 컨트롤러는 명세를 확인하고 파드의 우선순위를 1000000으로 해석한다.
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
스케줄링 순서에 대한 파드 우선순위의 영향
파드 우선순위가 활성화되면, 스케줄러가 우선순위에 따라 보류 중인 파드를 주문하고 보류 중인 파드는 스케줄링 대기열에서 우선순위가 낮은 다른 보류 중인 파드보다 우선한다. 결과적으로, 스케줄링 요구 사항이 충족되는 경우 우선순위가 더 낮은 파드보다 우선순위가 높은 파드가 더 빨리 스케줄링될 수 있다. 이러한 파드를 스케줄링할 수 없는 경우, 스케줄러는 계속 진행하고 우선순위가 낮은 다른 파드를 스케줄링하려고 한다.
선점
파드가 생성되면, 대기열로 이동하여 스케줄링을 기다린다. 스케줄러가 대기열에서 파드를 선택하여 노드에 스케줄링하려고 한다. 파드의 지정된 모든 요구 사항을 충족하는 노드가 없으면, 보류 중인 파드에 대해 선점 로직이 트리거된다. 보류 중인 파드를 P라 하자. 선점 로직은 P보다 우선순위가 낮은 하나 이상의 파드를 제거하면 해당 노드에서 P를 스케줄링할 수 있는 노드를 찾는다. 이러한 노드가 발견되면, 하나 이상의 우선순위가 낮은 파드가 노드에서 축출된다. 파드가 축출된 후, P는 노드에 스케줄링될 수 있다.
사용자 노출 정보
파드 P가 노드 N에서 하나 이상의 파드를 축출할 경우, 파드 P의 상태 nominatedNodeName
필드는 노드 N의 이름으로 설정된다. 이 필드는 스케줄러가 파드 P에
예약된 리소스를 추적하는 데 도움이 되고 사용자에게 클러스터의 선점에 대한
정보를 제공한다.
파드 P는 반드시 "지정된 노드"로 스케줄링되지는 않는다.
The scheduler always tries the "nominated Node" before iterating over any other nodes.
스케줄러는 다른 노드에 스케줄링을 시도하기 전에 항상 "지정된 노드"부터 시도한다.
피해자 파드가 축출된 후, 그것은 정상적(graceful)으로 종료되는 기간을 갖는다.
스케줄러가 종료될 피해자 파드를 기다리는 동안 다른 노드를 사용할 수
있게 되면, 스케줄러는 파드 P를 스케줄링하기 위해 다른 노드를 사용할 수 있다. 그 결과,
파드 스펙의 nominatedNodeName
과 nodeName
은 항상 동일하지 않다. 또한,
스케줄러가 노드 N에서 파드를 축출했지만, 파드 P보다 우선순위가 높은 파드가
도착하면, 스케줄러가 노드 N에 새로운 우선순위가 높은 파드를 제공할 수 있다. 이러한
경우, 스케줄러는 파드 P의 nominatedNodeName
을 지운다. 이렇게하면, 스케줄러는
파드 P가 다른 노드에서 파드를 축출할 수 있도록 한다.
선점의 한계
선점 피해자의 정상적인 종료
파드가 축출되면, 축출된 피해자 파드는 정상적인 종료 기간을 가진다. 피해자 파드는 작업을 종료하고 빠져나가는 데(exit) 많은 시간을 가진다. 그렇지 않으면, 파드는 강제종료(kill) 된다. 이 정상적인 종료 기간은 스케줄러가 파드를 축출하는 지점과 보류 중인 파드(P)를 노드(N)에서 스케줄링할 수 있는 시점 사이의 시간 간격을 만든다. 그 동안, 스케줄러는 보류 중인 다른 파드를 계속 스케줄링한다. 피해자 파드가 빠져나가거나 종료되면, 스케줄러는 보류 대기열에서 파드를 스케줄하려고 한다. 따라서, 일반적으로 스케줄러가 피해자 파드를 축출하는 시점과 파드 P가 스케줄링된 시점 사이에 시간 간격이 있다. 이러한 차이를 최소화하기 위해, 우선순위가 낮은 파드의 정상적인 종료 기간을 0 또는 작은 수로 설정할 수 있다.
PodDisruptionBudget을 지원하지만, 보장하지 않음
Pod Disruption Budget(PDB)은 애플리케이션 소유자가 자발적 중단에서 동시에 다운된 복제된 애플리케이션의 파드 수를 제한할 수 있다. 쿠버네티스는 파드를 선점할 때 PDB를 지원하지만, PDB를 따르는 것이 최선의 노력이다. 스케줄러는 선점에 의해 PDB를 위반하지 않은 피해자 파드를 찾으려고 하지만, 그러한 피해자 파드가 발견되지 않으면, 선점은 여전히 발생하며, PDB를 위반하더라도 우선순위가 낮은 파드는 제거된다.
우선순위가 낮은 파드에 대한 파드-간 어피니티
이 질문에 대한 답변이 '예'인 경우에만 노드가 선점 대상으로 간주된다. "대기 중인 파드보다 우선순위가 낮은 모든 파드가 노드에서 제거되면, 보류 중인 파드를 노드에 스케줄링할 수 있습니까?"
보류 중인 파드가 노드에 있는 하나 이상의 우선순위가 낮은 파드에 대한 파드-간 어피니티를 가진 경우에, 우선순위가 낮은 파드가 없을 때 파드-간 어피니티 규칙을 충족할 수 없다. 이 경우, 스케줄러는 노드의 파드를 축출하지 않는다. 대신, 다른 노드를 찾는다. 스케줄러가 적합한 노드를 찾거나 찾지 못할 수 있다. 보류 중인 파드를 스케줄링할 수 있다는 보장은 없다.
이 문제에 대한 권장 솔루션은 우선순위가 같거나 높은 파드에 대해서만 파드-간 어피니티를 생성하는 것이다.
교차 노드 선점
보류 중인 파드 P가 노드 N에 스케줄링될 수 있도록 노드 N이 선점 대상으로 고려되고 있다고 가정한다. 다른 노드의 파드가 축출된 경우에만 P가 N에서 실행 가능해질 수 있다. 예를 들면 다음과 같다.
- 파드 P는 노드 N에 대해 고려된다.
- 파드 Q는 노드 N과 동일한 영역의 다른 노드에서 실행 중이다.
- 파드 P는 파드 Q(
topologyKey: topology.kubernetes.io/zone
)와 영역(zone) 전체의 안티-어피니티를 갖는다. - 영역에서 파드 P와 다른 파드 간의 안티-어피니티에 대한 다른 경우는 없다.
- 노드 N에서 파드 P를 스케줄링하기 위해, 파드 Q를 축출할 수 있지만, 스케줄러는 교차-노드 선점을 수행하지 않는다. 따라서, 파드 P가 노드 N에서 스케줄링할 수 없는 것으로 간주된다.
파드 Q가 노드에서 제거되면, 파드 안티-어피니티 위반이 사라지고, 파드 P는 노드 N에서 스케줄링될 수 있다.
수요가 충분하고 합리적인 성능의 알고리즘을 찾을 경우 향후 버전에서 교차 노드 선점의 추가를 고려할 수 있다.
문제 해결
파드 우선순위와 선점은 원하지 않는 부작용을 가질 수 있다. 다음은 잠재적 문제의 예시와 이를 해결하는 방법이다.
파드가 불필요하게 선점(축출)됨
선점은 우선순위가 높은 보류 중인 파드를 위한 공간을 만들기 위해 리소스 압박을 받고 있는
클러스터에서 기존 파드를 제거한다. 실수로 특정 파드에 높은 우선순위를
부여하면, 의도하지 않은 높은 우선순위 파드가 클러스터에서
선점을 유발할 수 있다. 파드 우선순위는 파드 명세에서
priorityClassName
필드를 설정하여 지정한다. 그런 다음
우선순위의 정수 값이 분석되어 podSpec
의 priority
필드에 채워진다.
문제를 해결하기 위해, 해당 파드가 우선순위가 낮은 클래스를 사용하도록 priorityClassName
을
변경하거나, 해당 필드를 비워둘 수 있다. 빈
priorityClassName
은 기본값이 0으로 해석된다.
파드가 축출되면, 축출된 파드에 대한 이벤트가 기록된다. 선점은 클러스터가 파드에 대한 리소스를 충분히 가지지 못한 경우에만 발생한다. 이러한 경우, 선점은 보류 중인 파드(선점자)의 우선순위가 피해자 파드보다 높은 경우에만 발생한다. 보류 중인 파드가 없거나, 보류 중인 파드의 우선순위가 피해자 파드와 같거나 낮은 경우 선점이 발생하지 않아야 한다. 그러한 시나리오에서 선점이 발생하면, 이슈를 올리기 바란다.
파드가 축출되었지만, 선점자는 스케줄링되지 않음
파드가 축출되면, 요청된 정상적인 종료 기간(기본적으로 30초)이 주어진다. 이 기간 내에 대상 파드가 종료되지 않으면, 강제 종료된다. 모든 피해자 파드가 사라지면, 선점자 파드를 스케줄링할 수 있다.
선점자 파드가 피해자 파드가 없어지기를 기다리는 동안, 동일한 노드에 적합한 우선순위가 높은 파드가 생성될 수 있다. 이 경우, 스케줄러는 선점자 대신 우선순위가 높은 파드를 스케줄링한다.
이것은 예상된 동작이다. 우선순위가 높은 파드는 우선순위가 낮은 파드를 대체해야 한다.
우선순위가 높은 파드는 우선순위가 낮은 파드보다 우선함
스케줄러가 보류 중인 파드를 실행할 수 있는 노드를 찾으려고 한다. 노드를 찾지 못하면, 스케줄러는 임의의 노드에서 우선순위가 낮은 파드를 제거하여 보류 중인 파드를 위한 공간을 만든다. 우선순위가 낮은 파드가 있는 노드가 보류 중인 파드를 실행할 수 없는 경우, 스케줄러는 선점을 위해 우선순위가 높은 다른 노드(다른 노드의 파드와 비교)를 선택할 수 있다. 피해자 파드는 여전히 선점자 파드보다 우선순위가 낮아야 한다.
선점할 수 있는 여러 노드가 있는 경우, 스케줄러는 우선순위가 가장 낮은 파드 세트를 가진 노드를 선택하려고 한다. 그러나, 이러한 파드가 위반될 PodDisruptionBudget을 가지고 있고 축출된 경우 스케줄러는 우선순위가 높은 파드를 가진 다른 노드를 선택할 수 있다.
선점을 위해 여러 개의 노드가 존재하고 위의 시나리오 중 어느 것도 적용되지 않는 경우, 스케줄러는 우선순위가 가장 낮은 노드를 선택한다.
파드 우선순위와 서비스 품질 간의 상호 작용
파드 우선순위와 QoS 클래스는
상호 작용이 거의 없고 QoS 클래스를 기반으로 파드 우선순위를 설정하는 데 대한
기본 제한이 없는 두 개의 직교(orthogonal) 기능이다. 스케줄러의
선점 로직은 선점 대상을 선택할 때 QoS를 고려하지 않는다.
선점은 파드 우선순위를 고려하고 우선순위가 가장 낮은 대상 세트를
선택하려고 한다. 우선순위가 가장 높은 파드는 스케줄러가
선점자 파드를 스케줄링할 수 없거나 우선순위가 가장 낮은 파드가
PodDisruptionBudget
으로 보호되는 경우에만, 우선순위가 가장 낮은 파드를
축출 대상으로 고려한다.
kubelet은 우선순위를 사용하여 파드의 노드-압박(node-pressure) 축출 순서를 결정한다. 사용자는 QoS 클래스를 사용하여 어떤 파드가 축출될 것인지 예상할 수 있다. kubelet은 다음의 요소들을 통해서 파드의 축출 순위를 매긴다.
- 기아(starved) 리소스 사용량이 요청을 초과하는지 여부
- 파드 우선순위
- 요청 대비 리소스 사용량
더 자세한 내용은 kubelet 축출을 위한 파드 선택을 참조한다.
kubelet 노드-압박 축출은 사용량이 요청을 초과하지 않는 경우 파드를 축출하지 않는다. 우선순위가 낮은 파드가 요청을 초과하지 않으면, 축출되지 않는다. 요청을 초과하는 우선순위가 더 높은 다른 파드가 축출될 수 있다.
다음 내용
- 프라이어리티클래스와 함께 리소스쿼터 사용에 대해 읽기: 기본으로 프라이어리티 클래스 소비 제한
- 파드 중단(disruption)에 대해 학습한다.
- API를 이용한 축출에 대해 학습한다.
- 노드-압박(node-pressure) 축출에 대해 학습한다.