스케줄링 프레임워크

스케줄링 프레임워크 (Scheduling Framework)

기능 상태: Kubernetes v1.19 [stable]

스케줄링 프레임워크는 쿠버네티스 스케줄러를 위한 플러그형(pluggable) 아키텍처예요. 스케줄러에 직접 컴파일되는 "플러그인" API 집합으로 구성됩니다. 이 API들은 스케줄링 "코어"를 가볍고 유지보수 가능하게 유지하면서, 대부분의 스케줄링 기능을 플러그인으로 구현할 수 있게 해줘요. 프레임워크 설계에 대한 더 기술적인 정보는 스케줄링 프레임워크 설계 제안을 참고하세요.

프레임워크 워크플로 (Framework workflow)

스케줄링 프레임워크는 몇 가지 확장 지점(extension points)을 정의합니다. 스케줄러 플러그인은 하나 이상의 확장 지점에서 호출되도록 등록합니다. 일부 플러그인은 스케줄링 결정을 바꿀 수 있고, 일부는 정보 제공용일 뿐이에요.

하나의 Pod를 스케줄하려는 각 시도는 **스케줄링 사이클(scheduling cycle)**과 바인딩 사이클(binding cycle) 두 단계로 나뉩니다.

스케줄링 사이클과 바인딩 사이클 (Scheduling cycle & binding cycle)

스케줄링 사이클은 Pod에 대한 노드를 선택하고, 바인딩 사이클은 그 결정을 클러스터에 적용해요. 함께 스케줄링 사이클과 바인딩 사이클을 "스케줄링 컨텍스트(scheduling context)"라고 부릅니다.

스케줄링 사이클은 직렬로 실행되는 반면, 바인딩 사이클은 동시에 실행될 수 있어요.

Pod를 스케줄할 수 없다고 판단되거나 내부 오류가 있으면 스케줄링 또는 바인딩 사이클이 중단될 수 있습니다. Pod는 큐로 돌아가 재시도됩니다.

인터페이스 (Interfaces)

다음 그림은 Pod의 스케줄링 컨텍스트와 스케줄링 프레임워크가 노출하는 인터페이스를 보여줍니다.

하나의 플러그인은 더 복잡하거나 상태를 가진 작업을 수행하기 위해 여러 인터페이스를 구현할 수 있어요.

일부 인터페이스는 [Scheduler Configuration]을 통해 구성할 수 있는 스케줄러 확장 지점과 일치합니다.

PreEnqueue

이 플러그인들은 Pod를 내부 활성 큐(active queue)에 추가하기 전에 호출돼요. 이 큐에서 Pod는 스케줄링 준비가 된 것으로 표시됩니다.

모든 PreEnqueue 플러그인이 Success를 반환해야만 Pod가 활성 큐에 들어갈 수 있어요. 그렇지 않으면 내부의 unschedulable Pod 목록에 배치되며 Unschedulable 조건을 받지 않습니다.

내부 스케줄러 큐가 어떻게 동작하는지에 대한 자세한 내용은 kube-scheduler의 스케줄링 큐를 읽어보세요.

EnqueueExtension

EnqueueExtension은 플러그인이 클러스터의 변경을 기반으로, 이 플러그인에 의해 거부된 Pod의 스케줄링 재시도 여부를 제어할 수 있는 인터페이스예요. PreEnqueue, PreFilter, Filter, Reserve 또는 Permit을 구현하는 플러그인은 이 인터페이스를 구현해야 합니다.

QueueingHint

기능 상태: Kubernetes v1.34 [stable] (기본 활성화)

QueueingHint는 Pod가 활성 큐나 백오프 큐에 재등록될 수 있는지 결정하는 콜백 함수예요. 클러스터에서 특정 종류의 이벤트나 변경이 발생할 때마다 실행됩니다. QueueingHint가 그 이벤트가 Pod를 스케줄 가능하게 만들 수 있다고 판단하면, Pod는 활성 큐나 백오프 큐에 배치되어 스케줄러가 Pod의 스케줄링을 재시도합니다.

QueueSort

이 플러그인들은 스케줄링 큐의 Pod를 정렬하는 데 사용됩니다. 큐 정렬 플러그인은 기본적으로 Less(Pod1, Pod2) 함수를 제공해요. 한 번에 하나의 큐 정렬 플러그인만 활성화할 수 있습니다.

PreFilter

이 플러그인들은 Pod에 대한 정보를 전처리하거나, 클러스터나 Pod가 충족해야 하는 특정 조건을 확인하는 데 사용됩니다. PreFilter 플러그인이 오류를 반환하면 스케줄링 사이클이 중단됩니다.

Filter

이 플러그인들은 Pod를 실행할 수 없는 노드를 걸러내는 데 사용됩니다. 각 노드에 대해 스케줄러는 구성된 순서로 필터 플러그인을 호출해요. 어떤 필터 플러그인이 노드를 실행 불가능으로 표시하면, 나머지 플러그인은 그 노드에 대해 호출되지 않습니다. 노드는 동시에 평가될 수 있어요.

PostFilter

이 플러그인들은 Filter 단계 후에 호출되지만, Pod에 대해 실행 가능한 노드를 찾지 못했을 때만 호출됩니다. 플러그인은 구성된 순서로 호출돼요. 어떤 postFilter 플러그인이 노드를 Schedulable로 표시하면, 나머지 플러그인은 호출되지 않습니다. 전형적인 PostFilter 구현은 선점(preemption)으로, 다른 Pod를 선점해 Pod를 스케줄 가능하게 만들려고 시도합니다.

PreScore

이 플러그인들은 Score 플러그인이 사용할 공유 가능한 상태를 생성하는 "사전 점수 매기기(pre-scoring)" 작업을 수행하는 데 사용됩니다. PreScore 플러그인이 오류를 반환하면 스케줄링 사이클이 중단됩니다.

Score

이 플러그인들은 필터링 단계를 통과한 노드에 순위를 매기는 데 사용됩니다. 스케줄러는 각 노드에 대해 각 점수 플러그인을 호출해요. 최소 및 최대 점수를 나타내는 잘 정의된 정수 범위가 있을 거예요. NormalizeScore 단계 후, 스케줄러는 구성된 플러그인 가중치에 따라 모든 플러그인의 노드 점수를 결합합니다.

용량 점수 매기기 (Capacity scoring)

기능 상태: Kubernetes v1.33 [alpha] (기본 비활성)

VolumeCapacityPriority 기능 게이트는 v1.32에서 정적으로 프로비저닝된 저장소를 지원하는 데 사용되었습니다. v1.33부터 새로운 기능 게이트 StorageCapacityScoring이 동적으로 프로비저닝된 저장소 지원을 추가하여 이전 VolumeCapacityPriority 게이트를 대체합니다. StorageCapacityScoring이 활성화되면 kube-scheduler의 VolumeBinding 플러그인이 각 노드의 저장소 용량을 기반으로 노드에 점수를 매기도록 확장됩니다. 이 기능은 [Storage Capacity]를 지원하는 CSI 볼륨에 적용되며, CSI 드라이버로 뒷받침되는 로컬 저장소도 포함합니다.

NormalizeScore

이 플러그인들은 스케줄러가 노드의 최종 순위를 계산하기 전에 점수를 수정하는 데 사용됩니다. 이 확장 지점에 등록하는 플러그인은 같은 플러그인의 Score 결과로 호출됩니다. 이는 스케줄링 사이클당 플러그인별로 한 번 호출됩니다.

예를 들어 플러그인 BlinkingLightScorer가 노드가 가진 깜빡이는 불빛의 수에 따라 노드 순위를 매긴다고 가정해 볼게요.

func ScoreNode(_ *v1.Pod, n *v1.Node) (int, error) {
	return getBlinkingLightCount(n)
}

그러나 깜빡이는 불빛의 최대 개수는 NodeScoreMax에 비해 작을 수 있어요. 이를 고치려면 BlinkingLightScorer도 이 확장 지점에 등록해야 합니다.

func NormalizeScores(scores map[string]int) {
	highest := 0
	for _, score := range scores {
		highest = max(highest, score)
	}
	for node, score := range scores {
		scores[node] = score * NodeScoreMax / highest
	}
}

어떤 NormalizeScore 플러그인이 오류를 반환하면 스케줄링 사이클이 중단됩니다.

참고: "사전 예약(pre-reserve)" 작업을 수행하려는 플러그인은 NormalizeScore 확장 지점을 사용해야 합니다.

Reserve

Reserve 인터페이스를 구현하는 플러그인은 ReserveUnreserve 두 메서드를 가져요. 이들은 각각 Reserve와 Unreserve라는 두 정보 제공 스케줄링 단계를 뒷받침합니다. 런타임 상태를 유지하는(일명 "stateful") 플러그인은 이 단계들을 사용해 주어진 Pod에 대해 노드의 리소스가 예약되고 예약 해제될 때 스케줄러로부터 통지를 받아야 합니다.

Reserve 단계는 스케줄러가 Pod를 지정된 노드에 실제로 바인딩하기 전에 발생해요. 바인딩이 성공하기를 기다리는 동안 경쟁 상태(race condition)를 방지하기 위해 존재합니다. 각 Reserve 플러그인의 Reserve 메서드는 성공하거나 실패할 수 있어요. 하나의 Reserve 메서드 호출이 실패하면 이후 플러그인은 실행되지 않고 Reserve 단계가 실패한 것으로 간주됩니다. 모든 플러그인의 Reserve 메서드가 성공하면 Reserve 단계는 성공한 것으로 간주되고 나머지 스케줄링 사이클과 바인딩 사이클이 실행됩니다.

Reserve 단계나 이후 단계가 실패하면 Unreserve 단계가 트리거됩니다. 이때 모든 Reserve 플러그인의 Unreserve 메서드가 Reserve 메서드 호출의 역순으로 실행됩니다. 이 단계는 예약된 Pod와 관련된 상태를 정리하기 위해 존재합니다.

주의: Reserve 플러그인의 Unreserve 메서드 구현은 멱등(idempotent)해야 하며 실패해서는 안 됩니다.

Permit

Permit 플러그인은 각 Pod에 대해 스케줄링 사이클 끝에 호출되어, 후보 노드에 대한 바인딩을 방지하거나 지연시킵니다. permit 플러그인은 세 가지 중 하나를 할 수 있어요.

  1. approve (승인) 모든 Permit 플러그인이 Pod를 승인하면, Pod는 바인딩을 위해 보내집니다.
  2. deny (거부) 어떤 Permit 플러그인이 Pod를 거부하면, Pod는 스케줄링 큐로 돌아갑니다. 이는 Reserve 플러그인의 Unreserve 단계를 트리거합니다.
  3. wait (대기, 타임아웃 포함) Permit 플러그인이 "wait"를 반환하면 Pod는 내부 "waiting" Pod 목록에 유지되고, 이 Pod의 바인딩 사이클은 시작되지만 승인될 때까지 직접 차단됩니다. 타임아웃이 발생하면 waitdeny가 되고 Pod는 스케줄링 큐로 돌아가며, Reserve 플러그인의 Unreserve 단계를 트리거합니다.

참고: 어떤 플러그인이든 "waiting" Pod 목록에 접근해 승인할 수 있지만(FrameworkHandle 참고), "waiting" 상태인 예약된 Pod의 바인딩을 승인하는 것은 permit 플러그인만이 하도록 기대해요. Pod가 승인되면 PreBind 단계로 보내집니다.

PreBind

이 플러그인들은 Pod가 바인딩되기 전에 필요한 작업을 수행하는 데 사용됩니다. 예를 들어 pre-bind 플러그인은 Pod가 그곳에서 실행되도록 허용하기 전에 네트워크 볼륨을 프로비저닝하고 대상 노드에 마운트할 수 있어요.

어떤 PreBind 플러그인이 오류를 반환하면 Pod는 거부되고 스케줄링 큐로 돌아갑니다.

Bind

이 플러그인들은 Pod를 Node에 바인딩하는 데 사용됩니다. 모든 PreBind 플러그인이 완료될 때까지 Bind 플러그인은 호출되지 않습니다. 각 bind 플러그인은 구성된 순서로 호출됩니다. bind 플러그인은 주어진 Pod를 처리할지 여부를 선택할 수 있어요. bind 플러그인이 Pod를 처리하기로 선택하면 나머지 bind 플러그인은 건너뜁니다.

PostBind

이것은 정보 제공용 인터페이스예요. post-bind 플러그인은 Pod가 성공적으로 바인딩된 후 호출됩니다. 이것은 바인딩 사이클의 끝이며, 관련 리소스를 정리하는 데 사용될 수 있어요.

플러그인 API (Plugin API)

플러그인 API에는 두 단계가 있습니다. 먼저 플러그인은 등록되고 구성되어야 하며, 그 다음 확장 지점 인터페이스를 사용합니다. 확장 지점 인터페이스는 다음 형태를 가져요.

type Plugin interface {
	Name() string
}

type QueueSortPlugin interface {
	Plugin
	Less(*v1.Pod, *v1.Pod) bool
}

type PreFilterPlugin interface {
	Plugin
	PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) error
}
// ...

플러그인 구성 (Plugin configuration)

스케줄러 구성에서 플러그인을 활성화하거나 비활성화할 수 있어요. Kubernetes v1.18 이상을 사용한다면 대부분의 스케줄링 [플러그인]이 사용 중이며 기본으로 활성화되어 있습니다.

기본 플러그인 외에도 자신만의 스케줄링 플러그인을 구현하고 기본 플러그인과 함께 구성할 수 있습니다. 자세한 내용은 scheduler-plugins를 방문하세요.

Kubernetes v1.18 이상을 사용한다면 플러그인 집합을 스케줄러 프로필로 구성하고, 다양한 종류의 워크로드에 맞게 여러 프로필을 정의할 수 있어요. 더 알아보려면 [multiple profiles]을 참고하세요.