오늘 글에서는 스케줄링 과정에서 Predicates 와 Priorities 의 스케줄링 전략이 주로 적용되는 단계를 소개합니다.
Predicates
먼저 Predicates에 대해 살펴보겠습니다. Predicates는 스케줄링 과정에서 Filter 역할을 합니다. 즉, 현재 클러스터의 모든 노드 중에서 스케줄링 정책에 따라 조건을 만족하는 일련의 노드를 "필터링"하는 것입니다. 이 노드들은 모두 스케줄링을 기다리는 Pod의 잠재적 호스트입니다. Kubernetes에는 네 가지 기본 스케줄링 정책이 있습니다.
첫 번째 유형은 GeneralPredicates입니다.
이름에서 알 수 있듯이 이 필터 규칙 집합은 가장 기본적인 스케줄링 정책을 담당합니다. 예를 들어 PodFitsResources는 호스트의 CPU 및 메모리 리소스가 충분한지 계산합니다. 물론 앞서 언급했듯이 PodFitsResources는 Pod의 requests 필드만 확인합니다. Kubernetes 스케줄러는 GPU와 같은 하드웨어 리소스에 대해 특정 리소스 유형을 정의하지 않고 Extended Resource 라는 Key-Value 형식을 사용하여 설명합니다. 예를 들어:
apiVersion: v1
kind: Pod
metadata:
name: extended-resource-demo
spec:
containers:
- name: extended-resource-demo-ctr
image: nginx
resources:
requests:
alpha.kubernetes.io/nvidia-gpu: "2"
limits:
alpha.kubernetes.io/nvidia-gpu: "2"
위에서 볼 수 있듯이 Pod는 alpha.kubernetes.io/nvidia-gpu=2 정의 방법을 사용하여 두 개의 NVIDIA 유형 GPU 사용을 선언합니다. PodFitsResources에서 스케줄러는 이 필드 Key의 의미가 GPU라는 것을 실제로 알지 못하고, 후속 Value를 직접 사용하여 계산합니다. 물론 Node의 Capacity 필드에도 이 호스트의 총 GPU 수를 추가해야 합니다(예: alpha.kubernetes.io/nvidia-gpu=4). 이러한 과정은 나중에 Device Plugin을 설명할 때 자세히 소개하겠습니다.
PodFitsHost는 호스트 이름이 Pod의 spec.nodeName과 일치하는지 확인합니다. PodFitsHostPorts는 Pod가 요청한 호스트 포트(spec.nodePort)가 이미 사용 중인 포트와 충돌하는지 확인합니다. PodMatchNodeSelector는 Pod의 nodeSelector 또는 nodeAffinity가 지정한 노드가 검사 중인 노드와 일치하는지 확인합니다. 이처럼 GeneralPredicates 집합은 Kubernetes가 Pod가 Node에서 실행될 수 있는지 검사하는 가장 기본적인 필터링 조건입니다. 따라서 GeneralPredicates는 다른 구성 요소(예: kubelet)에서도 직접 호출됩니다.
두 번째 유형은 Volume 관련 필터 규칙입니다.
이 필터 규칙 집합은 컨테이너의 Persistent Volume과 관련된 스케줄링 정책을 담당합니다. 그중 NoDiskConflict는 여러 Pod가 선언한 Persistent Volume 간에 충돌이 있는지 확인합니다. 예를 들어 AWS EBS 유형 Volume은 두 Pod가 동시에 사용할 수 없습니다. 따라서 A라는 이름의 EBS Volume이 특정 노드에 이미 마운트된 경우, 이 A Volume 사용을 선언하는 다른 Pod는 해당 노드로 스케줄링될 수 없습니다. MaxPDVolumeCountPredicate는 노드의 특정 유형 Persistent Volume 수가 일정 수를 초과했는지 확인합니다. 초과했다면 이 유형의 Persistent Volume 사용을 선언하는 Pod는 해당 노드로 더 이상 스케줄링될 수 없습니다. VolumeZonePredicate는 Persistent Volume의 Zone(고가용성 도메인) 레이블이 검사 중인 노드의 Zone 레이블과 일치하는지 확인합니다.
또한 VolumeBindingPredicate라는 규칙이 있습니다. 이는 Pod에 해당하는 PV의 nodeAffinity 필드가 특정 노드의 레이블과 일치하는지 확인합니다. Local Persistent Volume은 nodeAffinity를 사용하여 특정 노드에 바인딩되어야 합니다. 즉, Predicates 단계에서 Kubernetes는 Pod의 Volume 속성을 기반으로 스케줄링할 수 있어야 합니다. 또한 Pod의 PVC가 아직 특정 PV에 바인딩되지 않은 경우, 스케줄러는 바인딩할 모든 PV를 확인해야 합니다. 사용 가능한 PV가 있고 이 PV의 nodeAffinity가 검사 중인 노드와 일치하면 이 규칙은 "성공"을 반환합니다. 예를 들어:
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-local-pv
spec:
capacity:
storage: 500Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/disks/vol1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- my-node
위에서 볼 수 있듯이 이 PV에 해당하는 영구 디렉터리는 my-node라는 호스트에만 나타납니다. 따라서 PVC를 통해 이 PV를 사용하는 모든 Pod는 정상 작동을 위해 my-node로 스케줄링되어야 합니다. VolumeBindingPredicate가 바로 스케줄러가 이 결정을 내리는 곳입니다.
세 번째 유형은 호스트 관련 필터 규칙입니다.
이 규칙 집합은 주로 스케줄링될 Pod가 Node 자체의 특정 조건을 충족하는지 검사합니다. 예를 들어 PodToleratesNodeTaints는 우리가 자주 사용하는 Node의 “taint” 메커니즘을 확인합니다. Pod의 Toleration 필드가 Node의 Taint 필드와 일치하는 경우에만 Pod를 해당 Node로 스케줄링할 수 있습니다. NodeMemoryPressurePredicate는 현재 Node의 메모리가 더 이상 충분하지 않은지 확인합니다. 그렇다면 스케줄링 대기 중인 Pod는 이 Node로 스케줄링될 수 없습니다.
네 번째 유형은 Pod 관련 필터 규칙입니다.
이 규칙 집합은 대부분의 GeneralPredicates와 중복됩니다. 특별한 것은 PodAffinityPredicate입니다. 이 규칙은 스케줄링될 Pod와 Node의 기존 Pod 간의 어피니티(affinity) 및 안티-어피니티(anti-affinity) 관계를 확인합니다. 예를 들어:
apiVersion: v1
kind: Pod
metadata:
name: with-pod-antiaffinity
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: kubernetes.io/hostname
containers:
- name: with-pod-affinity
image: docker.io/ocpqe/hello-pod
이 예에서 podAntiAffinity 규칙은 이 Pod가 security=S2 레이블을 가진 Pod와 동일한 Node에 있지 않도록 지정합니다. PodAffinityPredicate에는 범위가 있으며, 위 규칙은 kubernetes.io/hostname Key 레이블을 가진 Node에만 유효합니다. 이것이 topologyKey 키워드의 역할입니다.
이 네 가지 유형의 Predicates는 스케줄러가 Node에서 스케줄링될 Pod를 실행할 수 있는지 결정하는 기본 전략을 구성합니다.
실행 시, Pod가 스케줄링될 때 Kubernetes 스케줄러는 16개의 Goroutine을 동시에 시작하여 클러스터의 모든 Node에 대해 Predicates를 병렬로 계산하고, 마지막으로 이 Pod를 실행할 수 있는 호스트 목록을 반환합니다. 각 Node에 대해 Predicates를 실행할 때 스케줄러는 고정된 순서로 확인합니다. 이 순서는 Predicates 자체의 의미에 따라 결정됩니다. 예를 들어, 호스트 관련 Predicates는 더 먼저 확인됩니다. 그렇지 않으면 리소스가 심각하게 부족한 호스트에서 PodAffinityPredicate를 계산하는 것은 의미가 없습니다.
Priorities
Predicates 단계의 노드 “필터링” 후, Priorities 단계의 작업은 이 노드들에 점수를 매기는 것입니다. 점수 범위는 0-10점이며, 가장 높은 점수를 받은 노드가 Pod가 최종적으로 바인딩되는 최상의 노드입니다.
Priorities에서 가장 일반적으로 사용되는 점수 규칙은 LeastRequestedPriority입니다. 이 알고리즘은 실제로 가장 많은 유휴 리소스(CPU 및 Memory)를 가진 호스트를 선택합니다.
또한 다른 세 가지 Priorities가 있습니다: NodeAffinityPriority, TaintTolerationPriority, InterPodAffinityPriority. 이름에서 알 수 있듯이 의미와 계산 방법은 이전의 세 가지 Predicates(PodMatchNodeSelector, PodToleratesNodeTaints, PodAffinityPredicate)와 유사합니다. 그러나 Priority로서 Node가 위 규칙을 더 많이 충족할수록 점수가 높아집니다.
기본 Priorities에는 ImageLocalityPriority라는 전략도 있습니다. 이는 Kubernetes v1.12에서 활성화된 새로운 스케줄링 규칙입니다. 즉, 스케줄링될 Pod에 필요한 이미지가 크고 이미 일부 Node에 존재하는 경우 해당 Node의 점수가 더 높아집니다. 물론 알고리즘으로 인한 스케줄링 집중을 방지하기 위해 스케줄러는 점수 계산 시 이미지 분포에 따라 최적화합니다. 즉, 큰 이미지가 분포된 Node 수가 매우 적으면 해당 Node의 가중치가 감소하여 스케줄링 집중 위험을 "상쇄"합니다.
요약하면, 이것이 Kubernetes 스케줄러의 기본 스케줄링 규칙의 주요 작동 원리입니다.
실제 실행 과정에서 스케줄러의 클러스터 및 Pod 정보는 캐시되므로 이러한 알고리즘의 실행 속도는 비교적 빠릅니다. 또한 PodAffinityPredicate와 같은 더 복잡한 스케줄링 알고리즘의 경우, 계산 시 스케줄링될 Pod와 검사 중인 Node뿐만 아니라 전체 클러스터의 정보(예: 모든 노드 순회 및 해당 Label 읽기)에도 주의해야 합니다. 이때 Kubernetes 스케줄러는 각 Pod에 대한 스케줄링 알고리즘을 실행하기 전에 알고리즘에 필요한 클러스터 정보를 미리 계산한 후 캐시합니다. 이렇게 하면 실제 알고리즘 실행 시 스케줄러는 캐시된 정보만 읽어 계산하면 되므로, Predicates 계산 시 각 Node에 대해 전체 클러스터 정보를 반복적으로 검색하고 계산할 필요가 없습니다.
요약
요약하자면, 이 글에서는 Kubernetes 기본 스케줄러의 주요 스케줄링 알고리즘에 대해 논의했습니다. 이 글에서 다룬 규칙 외에도 Kubernetes 스케줄러에는 기본적으로 활성화되지 않은 몇 가지 전략이 더 있습니다. kube-scheduler에 대한 구성 파일을 지정하거나 ConfigMap을 생성하여 활성화하거나 비활성화할 규칙을 구성할 수 있습니다. 또한 Priorities에 가중치를 설정하여 스케줄러의 동작을 제어할 수 있습니다.
이것으로 Kubernetes 스케줄러의 기본 스케줄링 메커니즘에 대한 설명을 마칩니다. 스케줄러는 클러스터 및 Pod 정보의 캐싱 덕분에 빠르게 작동하지만, 더 복잡한 알고리즘의 경우 스케줄러는 해당 Pod와 Node뿐만 아니라 전체 클러스터 상태를 고려해야 합니다. 여기에는 실제 스케줄링 알고리즘을 실행하기 전에 필요한 클러스터 정보를 미리 계산하고 캐시하는 작업이 포함되어 스케줄링 프로세스의 효율성과 정확성을 보장합니다.
Novita AI 는 AI 야망을 실현하는 올인원 클라우드 플랫폼입니다. 원활하게 통합된 API, 서버리스 컴퓨팅 및 GPU 가속을 통해 AI 기반 비즈니스를 신속하게 구축하고 확장하는 데 필요한 비용 효율적인 도구를 제공합니다. 인프라 문제를 없애고 무료로 시작하세요 – Novita AI가 AI 꿈을 현실로 만들어 드립니다.
추천 읽을거리:
