Scheduling Framework
Kubernetes Scheduling Framework는 kube-scheduler의 플러그인 기반 확장 메커니즘이다.
기존의 커스텀 스케줄링 방식들이 가진 한계를 극복하고, 유연하면서도 안정적인 확장성을 제공하기 위해 설계되었다.
Kubernetes에서 커스텀 스케줄링을 적용하기 위한 여러 시행착오가 있었다.
Scheduler Extender
Section titled “Scheduler Extender”매 스케줄링마다 HTTP webhook을 호출하여 커스텀 결과를 받아오는 방식이다.
- 외부 서비스와의 HTTP 통신으로 인한 지연 발생
- 네트워크 장애 시 스케줄링 실패 가능성
Custom Scheduler
Section titled “Custom Scheduler”완전히 별도의 스케줄러를 띄우는 방식이다.
- 여러 스케줄러가 동시에 동작하면 race condition 발생 가능
- 따라서 Pod에
schedulerName을 딱 하나씩 명시하는 방식으로만 사용한다.
Scheduling Framework
Section titled “Scheduling Framework”분리 없이 kube-scheduler에서 모든 것을 처리하고, 플러그인으로 기능을 확장하는 형태이다.
- 단일 스케줄러에서 플러그인 방식으로 확장
- race condition 방지
- 기본 스케줄러의 최적화를 그대로 활용 가능
스케줄링 주기 구조
Section titled “스케줄링 주기 구조”스케줄링은 두 단계로 나뉜다.
- Scheduling Cycle: 노드 선택 (직렬 실행)
- Binding Cycle: API Server에 바인딩 통보 (병렬 실행 가능)
Scheduling Cycle이 직렬인 이유는 두 Pod가 동시에 같은 리소스를 선점하는 race condition을 막기 위함이다. Binding은 네트워크 I/O라 느리므로, 기다리는 동안 다음 Pod 스케줄링을 병렬로 수행한다.
Scheduling Cycle
Section titled “Scheduling Cycle”Queue Sort
Section titled “Queue Sort”스케줄링 큐에서 Pod의 정렬 순서를 결정한다.
스케줄러의 우선순위 큐는 힙(Heap)을 사용하여 구현된다. 힙에 대한 비교 함수는 하나만 존재할 수 있으므로, 기본적으로 Pod의 우선순위(Priority)를 기준으로 정렬하고 우선순위가 같으면 타임스탬프를 비교한다.
PreFilter
Section titled “PreFilter”스케줄링 주기가 시작될 때 호출된다.
- 모든 PreFilter 플러그인이 성공을 반환해야 다음 단계로 진행됨
- 실패하면 Pod가 거부되어 스케줄링 프로세스가 실패함
- 스케줄링 프로세스 시작 전에 Pod 정보를 사전 처리할 수 있음
- 클러스터가 충족해야 하는 전제 조건 및 Pod 요구 사항을 확인함
기본 플러그인
NodeResourcesFit: Pod가 요청한 리소스를 노드가 제공할 수 있는지 사전 계산함PodTopologySpread: 토폴로지 분산 제약 조건을 사전 계산함InterPodAffinity: Pod 간 친화성/반친화성 규칙을 사전 계산함VolumeBinding: Pod가 요청한 PVC를 바인딩할 수 있는지 확인함
Filter
Section titled “Filter”Pod를 스케줄링할 수 없는 노드를 필터링하는 데 사용된다.
- Filter 플러그인의 실행 순서를 구성할 수 있음
- 다수의 노드를 필터링할 수 있는 정책의 우선 순위를 높게 지정하면 효율적임 (예: NodeSelector)
- 노드는 필터링 정책을 동시에(Concurrently) 실행함
- 스케줄링 주기 동안 Filter 플러그인이 여러 번 호출됨
기본 플러그인
NodeUnschedulable:node.spec.unschedulable이 true인 노드를 필터링함NodeName: Pod의spec.nodeName과 일치하지 않는 노드를 필터링함TaintToleration: Pod의 toleration과 노드의 taint를 비교하여 필터링함PodTopologySpread: 토폴로지 분산 제약을 만족하지 않는 노드를 필터링함NodePorts: Pod가 요청한 포트가 이미 사용 중인 노드를 필터링함NodeResourcesFit: 리소스가 부족한 노드를 필터링함 (requests/limits 확인)NodeAffinity: Pod의 nodeAffinity 규칙과 일치하지 않는 노드를 필터링함InterPodAffinity: Pod 간 친화성/반친화성 규칙을 만족하지 않는 노드를 필터링함VolumeBinding: PV/PVC 바인딩이 불가능한 노드를 필터링함VolumeZone: 볼륨의 zone 제약을 만족하지 않는 노드를 필터링함
PostFilter
Section titled “PostFilter”Kubernetes 1.19에서 도입되었다.
Filter 단계에서 Pod가 실패한 후 수행되는 작업을 처리한다.
- 선점(Preemption) 처리
- 자동 스케일링(Auto Scaling) 트리거
기본 플러그인
DefaultPreemption: Filter 단계에서 모든 노드가 실패했을 때 선점을 수행함. 우선순위가 낮은 Pod를 축출하여 새 Pod가 스케줄링될 수 있도록 함
선점 예시
# 높은 우선순위 PriorityClassapiVersion: scheduling.k8s.io/v1kind: PriorityClassmetadata: name: high-priorityvalue: 1000000preemptionPolicy: PreemptLowerPriority---# Pod에 적용apiVersion: v1kind: Podspec: priorityClassName: high-priorityPreFilter, Filter, PostFilter가 분리된 이유
Section titled “PreFilter, Filter, PostFilter가 분리된 이유”이 세 단계가 분리된 이유는 각 단계의 목적과 실행 시점이 다르기 때문이다.
-
PreFilter는 Pod당 한 번만 실행되고, Filter는 노드마다 실행된다. PreFilter에서 비용이 큰 계산을 한 번만 수행하고 Filter에서 재사용할 수 있다. 만약 합쳐져 있다면 노드마다 같은 계산을 반복해야 한다. 예를 들어
NodeResourcesFit플러그인은 PreFilter에서 Pod의 리소스 요청량을 미리 계산해두고, Filter에서는 각 노드의 가용 리소스와 비교만 수행한다. -
PreFilter에서 실패하면 수백 개 노드를 검사할 필요 없이 바로 스케줄링 실패로 처리할 수 있다. 클러스터 전체 조건이나 Pod 자체의 문제를 먼저 확인하여 불필요한 연산을 줄인다.
-
관심사 분리
- PreFilter: 이 Pod가 스케줄링 가능한 상태인가?
- Filter: 이 특정 노드가 적합한가?
- PostFilter: 아무 노드도 안 되면 어떻게 복구할까?
PostFilter는 Filter에서 모든 노드가 실패했을 때만 호출되므로, 정상적인 스케줄링 경로에서는 실행되지 않는다. 선점(Preemption)이나 Auto Scaling 트리거 같은 복구 로직을 분리하여 관리할 수 있다.
상태 공유를 통한 플러그인 재사용
하나의 플러그인이 여러 인터페이스(PreFilterPlugin, FilterPlugin 등)를 구현하여 여러 단계에 등록될 수 있다. 이를 통해 PreFilter에서 계산한 결과를 CycleState에 저장하고 Filter에서 재사용할 수 있다. 만약 별도 플러그인이었다면 같은 계산을 반복해야 한다.
PreScore
Section titled “PreScore”Score 플러그인에서 사용되는 정보를 생성하는 데 사용된다.
- Filter 단계를 통과한 노드 목록을 얻음
- 일부 정보를 사전 처리하거나 로그/모니터링 정보를 생성할 수 있음
기본 플러그인
InterPodAffinity: Score 단계를 위해 친화성 관련 정보를 준비함PodTopologySpread: Score 단계를 위해 토폴로지 분산 정보를 준비함TaintToleration: Score 단계를 위해 taint/toleration 정보를 준비함
Filter 후 남은 노드 중에서 최적의 노드를 선택한다.
두 단계로 나뉜다:
- Scoring: 구성된 채점 정책을 호출하여 남은 노드의 점수를 매김
- Normalization: 점수를 0에서 100 사이로 정규화함
기본 플러그인
NodeResourcesBalancedAllocation: 리소스 사용량이 균형 잡힌 노드에 높은 점수를 줌NodeResourcesFit: 리소스 여유가 있는 노드에 점수를 줌NodeAffinity: Pod의 nodeAffinitypreferredDuringScheduling규칙에 따라 점수를 줌InterPodAffinity: Pod 간 친화성 규칙에 따라 점수를 줌TaintToleration: toleration이 더 잘 맞는 노드에 높은 점수를 줌PodTopologySpread: 토폴로지 분산이 잘 되는 노드에 높은 점수를 줌ImageLocality: Pod가 사용할 이미지가 이미 있는 노드에 높은 점수를 줌
NodeResourcesFit Score 전략
LeastAllocated: 리소스 사용량이 적은 노드 선호 (기본값)MostAllocated: 리소스 사용량이 많은 노드 선호 (bin packing)RequestedToCapacityRatio: 지정된 비율에 가까운 노드 선호
Reserve
Section titled “Reserve”스케줄러는 이 단계에서 스케줄링 결과를 캐시한다.
후속 단계에서 오류나 실패가 발생하면 UnReserve 단계로 들어가 데이터를 롤백한다.
기본 플러그인
VolumeBinding: PV/PVC 바인딩을 예약함
Permit
Section titled “Permit”Scheduler Framework V2에 도입된 기능이다.
Reserve 단계 후, Bind 작업 전에 Pod를 가로채는 정책을 정의할 수 있다. 조건에 따라 다음 작업을 수행할 수 있다:
- Approve: Pod가 Permit 단계를 통과할 수 있음
- Deny: Pod가 거부되고 Permit 단계를 통과하지 못함 (스케줄링 실패)
- Wait: Pod가 대기 상태에 있음 (타임아웃 기간 설정 가능)
활용 예시
- Gang Scheduling: 여러 Pod가 동시에 스케줄링되어야 할 때 Wait으로 대기시킴
- 승인 프로세스: 외부 시스템의 승인을 기다림
Binding Cycle
Section titled “Binding Cycle”Kube-apiserver에서 제공하는 API 작업 호출을 포함하므로 시간이 많이 걸린다. 스케줄링 효율성을 높이기 위해 비동기적으로 실행된다. 이 단계는 스레드에 안전하지 않다.
PreBind
Section titled “PreBind”Bind 작업 전에 실행된다. 데이터 정보를 얻고 업데이트할 수 있다.
기본 플러그인
VolumeBinding: PV/PVC를 실제로 바인딩함
Kube-apiserver에서 제공하는 API 작업을 호출하여 Pod를 해당 노드에 바인딩한다.
기본 플러그인
DefaultBinder: kube-apiserver에 Pod-Node 바인딩을 요청함
PostBind
Section titled “PostBind”Bind 작업 후에 실행된다. 바인딩 완료 후 정리 작업이나 알림을 수행할 수 있다.
UnReserve
Section titled “UnReserve”Reserve 단계에서 생성된 캐시를 지우고 데이터를 초기 상태로 롤백하는 데 사용된다. 후속 단계(Permit, PreBind, Bind)에서 실패가 발생했을 때 호출된다.
참고