안녕하세요?
이번 글에서는 Kubernetes의 Scheduling과
Scheduling Plugin들인 Node Affinity, Taint and Toleration, Topology Spread Constraints들에 대해서 알아보겠습니다.
Kubernetes 공식문서를 보고 내용을 정리한 뒤 관련 주제를 테스트 하였습니다.
Kubernetes의 Scheduling
먼저 Kubernetes에서 Scheduling에 대해서 간단하게 살펴보고 가겠습니다.
현재 문서는 Kubernetes 1.32를 기준으로 합니다.
Kube-scheduler
Kube-scheduler는 Kubernetes의 Control-plane 컴포넌트 중 하나입니다.
Kube-scheduler는 각 노드에 있는 kubelet으로 하여금 Pod를 실행할 수 있도록 노드에 할당합니다.
이 때, Pod가 노드에 할당되지 않았는지 감시하고 스케줄링 원칙에 따라 최상의 노드를 찾게 됩니다.
Kubernetes cluster에서 Pod에 대한 스케줄링 요구사항을 충족하는 Node를 Feasible node라고 하며
이런 Feasible node가 없으면 Kube-scheduler가 배치할 수 있을 때까지 Pod는 Scheduling 되지 않습니다.
Scheduling Framework
Scheduling Framework | Kubernetes
Scheduling Framework는 Kube-scheduler를 위한 "Pluggable Architecture"입니다.
이 Framework는 plugin을 통해서 확장할 수 있는 몇 가지 지점을 정의하는데,
아래 두 단계를 통해서 Pod를 스케줄링하게 됩니다.
- Scheduling Cycle
- Binding Cycle
이 두 Cycle을 합쳐서 "Scheduling Context"라고 부릅니다.
아래와 같은 구조를 가집니다.
쿠버네티스 공식 문서의 그림을 이해를 위해 따라 그려봤습니다.
스케줄링은 다음의 Extention point 를 통해 일련의 단계로 진행됩니다.
- queueSort: 대기 중인 Pod의 순서를 정하는 정렬 기능을 제공하는 플러그인입니다. 한 번에 정확히 하나의 queueSort 플러그인만 활성화할 수 있습니다.
- preFilter: 필터링 이전 단계에서 Pod나 클러스터에 대한 정보를 미리 처리하거나 검사하는 플러그인입니다. 여기서 Pod를 스케줄 불가능(unschedulable) 상태로 표시할 수도 있습니다.
- filter: 기존 스케줄링 정책에서의 Predicate와 같은 역할을 하는 플러그인으로, 특정 Pod를 실행할 수 없는 노드를 필터링하여 제거합니다. 필터들은 설정된 순서대로 호출됩니다. 모든 필터를 통과한 노드가 없으면 Pod는 스케줄 불가능으로 표시됩니다.
- postFilter: Pod가 실행 가능한 노드를 찾지 못한 경우 호출되는 플러그인으로, 설정된 순서대로 실행됩니다. 만약 어떤 postFilter 플러그인이 Pod를 스케줄 가능으로 표시하면 나머지 플러그인은 호출되지 않습니다.
- preScore: 점수 부여(score) 이전 단계에서 필요한 사전 작업을 수행하기 위한 정보성 확장 지점입니다.
- score: 필터링 단계를 통과한 각 노드에 점수를 부여하는 플러그인입니다. 스케줄러는 이 점수의 가중치 합산이 가장 높은 노드를 선택하여 Pod를 배치합니다.
- reserve: 특정 Pod를 위해 자원이 예약되었음을 플러그인에게 알려주는 정보성 확장 지점입니다. 이 플러그인은 또한 Reserve 이후나 도중 실패가 발생했을 때 호출되는 Unreserve 기능도 구현합니다.
- permit: Pod의 바인딩을 지연시키거나 막을 수 있는 플러그인입니다.
- preBind: Pod가 노드에 바인딩되기 전에 필요한 작업을 수행하는 플러그인입니다.
- bind: Pod를 노드에 실제로 바인딩하는 플러그인입니다. bind 플러그인은 설정된 순서대로 호출되며, 한 플러그인이 바인딩을 성공적으로 완료하면 나머지 플러그인은 생략됩니다. 최소한 하나의 bind 플러그인이 필요합니다.
- postBind: Pod가 바인딩된 이후 호출되는 정보성 확장 지점입니다.
- multiPoint: 설정 전용 필드로, 여러 확장 지점에서 동시에 특정 플러그인을 활성화 또는 비활성화할 때 사용됩니다.
이 Extention point은 커스텀 할 수 있습니다.
각 Extention point마다 아래와 같이 기본 플러그인들을 비활성화하거나 사용자 정의 플러그인을 활성화할 수도 있습니다.
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- plugins:
score:
disabled:
- name: PodTopologySpread
enabled:
- name: MyCustomPluginA
weight: 2
- name: MyCustomPluginB
weight: 1
Filtering
위에서 얘기했던 과정 중 Filtering을 살펴보겠습니다.
Pod는 생성 후 Scheduling Queue에 들어갑니다.
Queue 는 위 과정 중 Sorting에 의해 Priority 순위대로 정렬됩니다.
Pod Priority and Preemption | Kubernetes
공식 문서에 있는 아래와 같은 예시 Priority Class 객체를 정의해서
Priority를 조절할 수 있습니다.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
Pod를 실행할 수 없는 node들은 이 단계에서 필터링됩니다.
예를 들어 후보 Node가 충분한 리소스를 갖고 있는지 확인하고, 이 Filtering을 통해 적합한 Node들이 포함됩니다. 만약 없으면 Pod는 Scheduling 되지 않습니다.
Scoring
필터링 이후에 스케줄러는 남은 node들에 대해 점수를 부여합니다.
점수가 가장 높은 node가 선택되고, 동일한 점수라면 임의로 선택합니다.
예를 들어 더 가용한 CPU가 더 많은 node가 선택됩니다.
Kubernetes Scheduling Plugins
Filtering과 Scoring에서 직접 제어 가능한 플러그인들
Scheduler Configuration | Kubernetes
위 문서를 보면 Schduling의 각 단계에 대해 적용 가능한 플러그인들이 나열되어 있습니다.
이 중 Filter, Score에서 직접 제어 가능한 플러그인들은 아래와 같습니다.
NodeAffinity, InterPodAffinity, PodTopologySpread, TaintToleration, NodeName(Filtering만), NodeUnscheulable(Filtering만)
Affinity와 Anti-affinity
Assigning Pods to Nodes | Kubernetes
Affinity와 Anti-affinity는 nodeSelector에 비해 더 확장된 기능을 사용할 수 있습니다.
nodeSelector는 해당 Label을 가진 Node만 선택할 수 있지만 Affinity는 꼭 그 Node만을 선택하지 않고 좀 더 선호한다는 개념으로 선택할 수도 있습니다.
그러니까 Soft 한 규칙이라고 표현할 수 있습니다.
Affinity 기능에는 2종류가 있습니다.
- nodeAffinity : nodeSelector와 유사하지만 좀 더 soft한 규칙
- Inter-pod affinity/anti-affinity : 다른 Pod의 Label을 이용해서 Pod를 제한
Node Affinity
Node Affinity에서는 다음 2개의 규칙을 사용할 수 있습니다.
- requiredDuringSchedulingIgnoredDuringExecution
- 규칙이 만족되지 않으면 스케줄러가 파드를 스케줄링할 수 없다.
- preferredDuringSchedulingIgnoredDuringExecution
- 스케줄러는 조건을 만족하는 노드를 찾으려고 노력한다. 해당되는 노드가 없더라도, 스케줄러는 여전히 파드를 스케줄링한다.
아래와 같은 예를 들어 볼 수 있습니다.
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- antarctica-east1
- antarctica-west1
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: another-node-label-key
operator: In
values:
- another-node-label-value
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0
위 예시에서 Pod는
requiredDuringSchedulingIgnoredDuringExecution에 따라 강제조건이 생깁니다.
Key가 topology.kubernetes.io/zone 이며 Label의 값은 antarctica-east1, antarctica-west1 여야 합니다.
그리고 preferredDuringSchedulingIgnoredDuringExecution 이건 선호조건입니다.
Key가 another-node-label-key 이고 값이 another-node-label-value 인 Node를 선호합니다.
Inter-pod affinity/anti-affinity
각 Node에 이미 실행 중인 다른 Pod의 Label을 기반으로 Pod가 Scheduling될 Node를 제한할 수 있습니다.
규칙은 다음과 같은 형태입니다.
"X가 규칙 Y를 충족하는 하나 이상의 파드를 이미 실행중인 경우 이 파드는 X에서 실행해야 한다/실행하면 안된다."
아래의 예를 들어보겠습니다.
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: topology.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: topology.kubernetes.io/zone
containers:
- name: with-pod-affinity
image: registry.k8s.io/pause:2.0
먼저 podAffinity 규칙에서
requiredDuringSchedulingIgnoredDuringExecution를,
그리고 podAntiAffinity 규칙에서
preferredDuringSchedulingIgnoredDuringExecution을 볼 수 있습니다.
podAffinity에서는 특정 Zone에 이미 security=S1
Label을 가진 다른 Pod가 있을 경우에만 예시 파드를 해당 Zone에 속한 Node에 배치할 수 있도록 허용하는 규칙입니다
예를 들어, "Zone V"라는 특정 Zone으로 구성된 클러스터가 있고, 이 Zone 내의 노드들이 topology.kubernetes.io/zone=V라는 Label을 갖고 있다고 가정합니다.
Zone V 내에 최소한 하나 이상의 Pod가 security=S1 Label을 가지고 있다면, 예시 Pod를 Zone V 내의 어느 Node에든 배치할 수 있습니다.
반대로 Zone V에 security=S1 Label이 붙은 Pod가 전혀 없다면, 해당 예시 Pod를 Zone V의 Node에 배치하지 않습니다.
podAntiAffinity에서는 이와 반대로 security=S2 을 가진 다른 Pod가 있을 경우, 가능한 한 예시 Pod를 해당 Zone 내의 Node에 배치하지 않습니다. 이는 Soft한 규칙입니다.
예를 들어, "Zone R"이라는 특정 Zone으로 구성된 클러스터가 있고, 이 Zone 내의 노드들이 topology.kubernetes.io/zone=R이라는 Label을 가지고 있다고 가정합니다.
이때 Zone R 내에 최소한 하나 이상의 파드가 security=S2 레이블을 가지고 있다면, 스케줄러는 가능한 한 예시 Pod를 Zone R 내의 Node에 배치하지 않으려고 합니다.
PodTopologySpread
Pod Topology Spread Constraints | Kubernetes
PodTopologySpread는 Kubernetes에서 Pod을 클러스터 내 node에 균등하게 배치하기 위한 기능입니다.
pod가 특정 node에 집중되지 않도록 하고 여러 AZ에 분산하여 HA를 달성하는 데 유용합니다.
Pod Topology Spread Constraints | Kubernetes
공식문서의 아래 예를 살펴보겠습니다.
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
# Configure a topology spread constraint
topologySpreadConstraints:
- maxSkew: <integer>
minDomains: <integer> # optional
topologyKey: <string>
whenUnsatisfiable: <string>
labelSelector: <object>
matchLabelKeys: <list> # optional; beta since v1.27
nodeAffinityPolicy: [Honor|Ignore] # optional; beta since v1.26
nodeTaintsPolicy: [Honor|Ignore] # optional; beta since v1.26
### other Pod fields go here
maxSkew : topology별 pod 개수 차이의 최대 허용 범위를 의미합니다.
예를 들면 maxSkew: 1인 경우, 특정 topology(특정 AZ라거나 )의 pod 갯수와 가장 적은 곳의 pod 갯수 차이가 최대 1이하가 되도록 보장합니다.
topologyKey: pod를 분산할 때, 기준이 되는 key입니다. 아래와 같은 값들이 사용될 수 있습니다.
- topology.kubernetes.io/zone: 가용 영역(AZ) 기준 분산
- kubernetes.io/hostname: 개별 노드 기준 분산
- topology.kubernetes.io/region: 리전 기준 분산
whenUnsatisfiable : PodTopologySpread 조건을 만족하지 못할 때의 동작을 설정합니다.
- DoNotSchedule: 조건을 만족하지 못하면 스케줄링하지 않습니다.
- ScheduleAnyway: 가능하면 균형을 맞추되, 조건을 완전히 만족할 수 없어도 일단 스케줄링합니다.
예를 들어,
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-app
이렇게 설정한다면, kubernetes.io/hostname을 기준으로 노드 간 분배를 합니다.
특정 노드의 pod 개수가 다른 노드보다 1개 이상 많아지지 않도록 합니다.
그렇지 못하면 DoNotSchedule이므로 스케줄링하지 않습니다.
TaintToleration
Taint와 Toleration은 특정 노드에 pod가 스케줄링되는 것을 제어합니다.
Taint는 오염이라는 뜻인데, 노드에 taint를 묻히면 아무 pod나 해당 노드에 스케줄링 되지 못합니다.
Toleration은 허용이라는 뜻이고, pod가 특정 taint를 허용하도록 설정하면, 해당 노드에서도 스케줄링될 수 있습니다.
이런 식으로 노드에 taint를 묻일 수 있습니다.
kubectl taint nodes <노드명> <key>=<value>:<effect>
- key: Taint의 이름
- value: 특정 값 (optional)
- effect: Taint가 Pod에 영향을 주는 방식
effect의 종류는 아래와 같습니다.
- NoSchedule : 이 Taint를 가지는 노드에는 해당 Taint에 대한 Toleration이 없는 Pod이 스케줄링되지 않습니다.
- PreferNoSchedule : 스케줄러가 가능하면 해당 노드에 Pod을 배치하지 않으려 하지만, 완전히 금지는 하지 않습니다.
- NoExecute : 기존 Pod도 Evict 시킵니다. 즉, Toleration이 없는 Pod은 배포될 수도 없고, 실행 중이던 Pod도 제거됩니다.
예를 들어 아래와 같이 해볼 수 있습니다.
kubectl taint nodes gpu-node dedicated=gpu:NoSchedule
이렇게 gpu-node에 dedicated=gpu라는 Taint를 묻히고 NoSchedule이기 때문에
이에 대한 Toleration이 없으면 이 노드에 스케줄링 될 수 없습니다.
이 노드에 스케줄링되기를 원한다면 pod spec에 아래와 같이 설정해야 합니다.
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
Kubernetes Scheduling Plugins 테스트
Affinity 테스트
해당 내용은 Kubernetes Karpenter + Keda로 특정 시간에 Autoscaling 걸기
글에서 테스트했던 내용으로 갈음하겠습니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 0
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 0
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: 1m
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: karpenter.sh/nodepool
operator: In
values:
- my-nodepool
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: "kubernetes.io/hostname"
위와 같이 설정하면
NodeAffinity가 적용되서 karpenter.sh/nodepool=my-nodepool 레이블이 있는 노드에먼 pod가 배포됩니다.
또한
PodAntiAffinity가 적용되어서 app=nginx 레이블이 있는 다른 pod가 있으면 같은 노드에 배포되지 않습니다.
그러니 Karpenter가 잘 작동하는지 보기적절한 예제입니다. nginx 파드를 5개로 늘린다면 한 노드에 하나씩 배치되어야 하기에 스케줄링이 필요해져서 노드가 늘어나게 됩니다.
Reference
- Scheduling Framework | Kubernetes*
- 스케줄링, 선점(Preemption), 축출(Eviction) | Kubernetes
- 쿠버네티스 스케줄러 | Kubernetes
- 노드 어피니티를 사용해 노드에 파드 할당 | Kubernetes
- 파드 토폴로지 분배 제약 조건 | Kubernetes
- 테인트(Taints)와 톨러레이션(Tolerations) | Kubernetes
- Pod Priority and Preemption | Kubernetes
- Kubernetes Scheduler, 제대로 이해하기
- Kubernetes Scheduling - Node Affinity
- Kubernetes Scheduling 소개 및 NodeSelector
'Kubernetes' 카테고리의 다른 글
Vault로 K8S Secret 관리하기 (0) | 2025.04.10 |
---|---|
EKS Blue/Green Migration (0) | 2025.04.05 |
Karpenter + Keda로 특정 시간에 Autoscaling 걸기 (0) | 2025.03.16 |
External Secret으로 AWS Secrets Manager와 EKS Secret 동기화하기 (0) | 2025.03.15 |
Datadog Agent로 Traefik pod의 로그를 Datadog으로 전송하기 (0) | 2025.02.26 |