스케줄링 프레임워크
스케줄링 프레임워크 (Scheduling Framework)
스케줄링 프레임워크는 Kubernetes 스케줄러를 위한 플러그인 구조(pluggable architecture)예요. 스케줄러에 직접 컴파일되는 "플러그인" API들의 집합으로, 이 API 덕분에 대부분의 스케줄링 기능을 플러그인으로 구현하면서도 스케줄링 "코어"는 가볍고 유지보수하기 쉽게 유지할 수 있어요. 프레임워크 설계에 대한 더 자세한 기술 정보는 스케줄링 프레임워크의 설계 제안을 참고하세요.
출처: 문서
본문
프레임워크 워크플로 (Framework workflow)
스케줄링 프레임워크는 몇 가지 확장 지점(extension point)을 정의해요. 스케줄러 플러그인은 하나 이상의 확장 지점에서 호출되도록 등록됩니다. 어떤 플러그인은 스케줄링 결정을 바꿀 수도 있고, 어떤 플러그인은 정보 제공용일 뿐이에요.
하나의 Pod를 스케줄하려는 각 시도는 두 단계, 즉 **스케줄링 사이클(scheduling cycle)**과 **바인딩 사이클(binding cycle)**로 나뉘어요.
스케줄링 사이클과 바인딩 사이클 (Scheduling cycle & binding cycle)
스케줄링 사이클은 Pod에 노드(node)를 선정하고, 바인딩 사이클은 그 결정을 클러스터에 적용해요. 둘을 합쳐 "스케줄링 컨텍스트(scheduling context)"라고 불러요.
스케줄링 사이클은 직렬(serially)로 실행되고, 바인딩 사이클은 동시에(concurrently) 실행될 수 있어요.
Pod가 스케줄 불가능(unschedulable)하다고 판단되거나 내부 오류가 발생하면 스케줄링 또는 바인딩 사이클이 중단(abort)될 수 있어요. 그러면 Pod는 큐로 돌아가 다시 시도됩니다.
인터페이스 (Interfaces)
아래 그림은 Pod의 스케줄링 컨텍스트와 스케줄링 프레임워크가 노출하는 인터페이스를 보여줘요.
한 플러그인이 여러 인터페이스를 구현해 더 복잡하거나 상태가 있는 작업을 수행할 수도 있어요.
일부 인터페이스는 Scheduler Configuration으로 설정할 수 있는 스케줄러 확장 지점과 일치해요.
스케줄링 프레임워크 확장 지점
PreEnqueue
이 플러그인들은 Pod가 스케줄링 준비 상태로 표시되는 내부 활성 큐(active queue)에 추가되기 전에 호출돼요.
모든 PreEnqueue 플러그인이 Success 를 반환해야만 Pod가 활성 큐에 들어갈 수 있어요. 그렇지 않으면 내부의 스케줄 불가능 Pod 목록에 배치되고, Unschedulable 컨디션은 부여되지 않아요.
내부 스케줄러 큐가 어떻게 동작하는지 더 자세히 보려면 kube-scheduler의 Scheduling queue 문서를 읽어보세요.
EnqueueExtension
EnqueueExtension은 플러그인이 클러스터의 변화에 따라 이 플러그인에 의해 거부된 Pod의 재스케줄링 여부를 제어할 수 있게 해주는 인터페이스예요. PreEnqueue, PreFilter, Filter, Reserve 또는 Permit을 구현하는 플러그인은 이 인터페이스를 구현해야 해요.
QueueingHint
이 기능은 Kubernetes에서 안정(stable) 기능이며 버전 1.34부터 적용됐어요. 처음 사용 가능해진 것은 v1.28 릴리스에서예요.
QueueingHint은 Pod가 활성 큐나 백오프 큐(backoff queue)에 다시 넣을 수 있는지 결정하는 콜백 함수예요. 특정 종류의 이벤트나 변화가 클러스터에서 발생할 때마다 실행됩니다. QueueingHint이 그 이벤트가 Pod를 스케줄 가능하게 만들 수 있다고 판단하면, Pod는 활성 큐나 백오프 큐에 들어가 스케줄러가 재시도하게 돼요.
QueueSort
이 플러그인들은 스케줄링 큐의 Pod를 정렬하는 데 사용돼요. 큐 정렬 플러그인은 기본적으로 Less(Pod1, Pod2) 함수를 제공해요. 한 번에 하나의 큐 정렬 플러그인만 활성화할 수 있어요.
PreFilter
이 플러그인들은 Pod에 대한 정보를 전처리하거나, 클러스터나 Pod가 충족해야 하는 특정 조건을 확인하는 데 사용돼요. PreFilter 플러그인이 오류를 반환하면 스케줄링 사이클이 중단됩니다.
Filter
이 플러그인들은 Pod를 실행할 수 없는 노드를 걸러내는 데 사용돼요. 스케줄러는 각 노드에 대해 설정된 순서대로 필터 플러그인을 호출해요. 어떤 필터 플러그인이라도 그 노드를 부적합(infeasible)으로 표시하면, 남은 플러그인은 그 노드에 대해 호출되지 않아요. 노드는 동시에 평가될 수 있어요.
PostFilter
이 플러그인들은 Filter 단계 이후에 호출되지만, Pod에 대해 적합한 노드를 찾지 못했을 때만 호출돼요. 설정된 순서대로 호출됩니다. 어떤 postFilter 플러그인이 노드를 Schedulable 로 표시하면 나머지 플러그인은 호출되지 않아요. 전형적인 PostFilter 구현은 선점(preemption)으로, 다른 Pod를 선점해 Pod를 스케줄 가능하게 만들려고 해요.
PreScore
이 플러그인들은 "사전 점수 계산(pre-scoring)" 작업을 수행하는 데 사용돼요. Score 플러그인이 사용할 공유 가능한 상태를 생성합니다. PreScore 플러그인이 오류를 반환하면 스케줄링 사이클이 중단됩니다.
Score
이 플러그인들은 필터링 단계를 통과한 노드에 점수를 매기는 데 사용돼요. 스케줄러는 각 노드에 대해 각 점수 계산 플러그인을 호출합니다. 최소/최대 점수를 나타내는 잘 정의된 정수 범위가 있어요. NormalizeScore 단계 이후 스케줄러는 설정된 플러그인 가중치에 따라 모든 플러그인의 노드 점수를 결합합니다.
용량 점수 계산 (Capacity scoring)
기능 게이트(feature gate) VolumeCapacityPriority는 v1.32에서 정적으로 프로비저닝되는 스토리지를 지원하는 데 사용됐어요. v1.33부터 새로운 기능 게이트 StorageCapacityScoring이 동적 프로비저닝 스토리지 지원을 더해 이전 VolumeCapacityPriority 게이트를 대체합니다. StorageCapacityScoring이 활성화되면 kube-scheduler의 VolumeBinding 플러그인이 확장되어 각 노드의 스토리지 용량에 따라 노드에 점수를 매겨요. 이 기능은 Storage Capacity를 지원하는 CSI 볼륨에 적용되며, CSI 드라이버가 뒷받침하는 로컬 스토리지도 포함돼요.
NormalizeScore
이 플러그인들은 스케줄러가 노드의 최종 순위를 계산하기 전에 점수를 수정하는 데 사용돼요. 이 확장 지점에 등록된 플러그인은 같은 플러그인의 Score 결과와 함께 호출됩니다. 스케줄링 사이클마다 플러그인별로 한 번씩 호출돼요.
예를 들어, BlinkingLightScorer 플러그인이 각 노드가 가진 깜빡이는 불빛(blinking lights)의 개수에 따라 노드에 순위를 매긴다고 가정해볼게요.
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 인터페이스를 구현하는 플러그인은 Reserve와 Unreserve라는 두 메서드를 가져요. 이 둘은 각각 Reserve와 Unreserve라는 두 개의 정보 제공용 스케줄링 단계를 뒷받침합니다. 런타임 상태를 유지하는 플러그인("상태 유지 플러그인")은 이 단계들을 사용해 주어진 Pod에 대해 노드의 리소스가 예약되고 해제될 때 스케줄러가 알려주도록 해요.
Reserve 단계는 스케줄러가 실제로 Pod를 지정된 노드에 바인딩하기 전에 발생합니다. 바인딩이 성공하기를 기다리는 동안 경쟁 조건(race condition)을 막기 위해 존재해요. 각 Reserve 플러그인의 Reserve 메서드는 성공하거나 실패할 수 있어요. 하나의 Reserve 메서드 호출이 실패하면 이후 플러그인은 실행되지 않고 Reserve 단계는 실패한 것으로 간주됩니다. 모든 플러그인의 Reserve 메서드가 성공하면 Reserve 단계는 성공한 것으로 간주되고 나머지 스케줄링 사이클과 바인딩 사이클이 실행됩니다.
Unreserve 단계는 Reserve 단계나 이후 단계가 실패하면 촉발됩니다. 이때 모든 Reserve 플러그인의 Unreserve 메서드가 Reserve 메서드 호출의 역순으로 실행됩니다. 이 단계는 예약된 Pod와 관련된 상태를 정리하기 위해 존재해요.
주의: Reserve 플러그인의 Unreserve 메서드 구현은 멱등(idempotent)이어야 하고 실패해서는 안 됩니다.
Permit
Permit 플러그인은 각 Pod의 스케줄링 사이클이 끝날 때 호출되어 후보 노드로의 바인딩을 막거나 지연시켜요. permit 플러그인은 다음 세 가지 중 하나를 할 수 있어요.
- approve 모든 Permit 플러그인이 Pod를 승인하면 바인딩을 위해 보내집니다.
- deny 어떤 Permit 플러그인이라도 Pod를 거부하면 스케줄링 큐로 돌아갑니다. 이는 Reserve 플러그인의 Unreserve 단계를 촉발합니다.
- wait (타임아웃 포함) Permit 플러그인이 "wait"을 반환하면 Pod는 내부 "대기 중(waiting)" Pod 목록에 유지되고, 이 Pod의 바인딩 사이클은 시작되지만 승인될 때까지 직접 차단됩니다. 타임아웃이 발생하면 wait은 deny가 되고 Pod는 스케줄링 큐로 돌아가면서 Reserve 플러그인의 Unreserve 단계를 촉발합니다.
참고: 어떤 플러그인이든 "대기 중" Pod 목록에 접근해 승인할 수 있지만(FrameworkHandle 참고), "대기 중" 상태인 예약된 Pod의 바인딩 승인은 permit 플러그인만 해야 한다고 기대합니다. Pod가 승인되면 PreBind 단계로 보내집니다.
PreBind
이 플러그인들은 Pod가 바인딩되기 전에 필요한 작업을 수행하는 데 사용돼요. 예를 들어 pre-bind 플러그인은 Pod가 그곳에서 실행되도록 허용하기 전에 네트워크 볼륨을 프로비저닝하고 대상 노드에 마운트할 수 있어요.
어떤 PreBind 플러그인이든 오류를 반환하면 Pod는 거부되고 스케줄링 큐로 돌아갑니다.
Bind
이 플러그인들은 Pod를 노드에 바인딩하는 데 사용돼요. 모든 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(context.Context, *framework.CycleState, *v1.pod) error
}
// ...
플러그인 구성 (Plugin configuration)
스케줄러 구성에서 플러그인을 활성화하거나 비활성화할 수 있어요. Kubernetes v1.18 이상을 사용한다면 대부분의 스케줄링 플러그인이 이미 기본적으로 활성화되어 있어요.
기본 플러그인 외에도 자신만의 스케줄링 플러그인을 구현해 기본 플러그인과 함께 설정할 수 있어요. 자세한 내용은 scheduler-plugins를 방문하세요.
Kubernetes v1.18 이상을 사용한다면 플러그인 집합을 스케줄러 프로필(scheduler profile)로 구성하고, 다양한 종류의 워크로드에 맞게 여러 프로필을 정의할 수 있어요. 여러 프로필(multiple profiles)에서 자세히 알아보세요.