Certified Kubernetes Administrator (CKA) #14 Scheduling 2: Taints/tolerations, Priority/PriorityClass, preemption
#13 Scheduling 1에서는 nodeSelector와 affinity로 Pod가 노드를 고르는 메커니즘을 다뤘습니다. 이번 글은 방향이 반대입니다. 노드가 Pod를 밀어내는 taint, 그리고 자원이 부족할 때 누구를 먼저 살릴지 정하는 PriorityClass와 preemption을 다룹니다.
affinity가 Pod의 끌어당김이라면 taint는 노드의 밀어냄입니다. 둘은 정반대 방향의 도구라 함께 두면 노드 배치를 양쪽에서 통제할 수 있습니다. 여기에 우선순위가 더해지면, 노드가 가득 찼을 때 스케줄러가 낮은 우선순위 Pod를 쫓아내고 높은 우선순위 Pod를 들이는 preemption까지 일어납니다. 운영자 관점에서 YAML과 kubectl로 차례로 손에 익히겠습니다.
Taint와 Toleration: 노드가 거부하고 Pod가 받아들인다 #
taint와 toleration은 한 쌍으로 동작합니다. taint는 노드에 거는 거부 표시이고, toleration은 그 거부를 견디겠다는 Pod의 선언입니다. 노드에 taint가 걸려 있으면, 그 taint를 견디는 toleration을 가진 Pod만 그 노드에 들어갈 수 있습니다.
노드: "나는 key=value:NoSchedule이라는 거부 표시를 걸었다"
Pod A: toleration 없음 → 이 노드에 못 들어감
Pod B: 같은 taint를 견딤 → 이 노드에 들어갈 수 있음여기서 핵심은 toleration이 허가가 아니라 면제라는 점입니다. toleration을 가진 Pod가 반드시 그 노드로 가는 것이 아니라, 그 노드의 거부에서 면제될 뿐입니다. 실제로 그 노드에 Pod를 보내려면 #13의 nodeAffinity나 nodeSelector를 함께 써야 합니다. 이 차이가 시험에서 자주 헷갈리는 지점입니다.
taint와 affinity의 역할은 다음처럼 갈립니다.
| 도구 | 방향 | 주체 | 효과 |
|---|---|---|---|
| nodeAffinity / nodeSelector | 끌어당김 | Pod | Pod가 특정 노드를 고른다 |
| taint / toleration | 밀어냄 | 노드 | 노드가 Pod를 거부하고, toleration이 그 거부를 면제한다 |
Taint를 거는 법 #
taint는 kubectl taint node로 겁니다. 형식은 key=value:effect입니다.
# 노드에 taint를 건다 (key=value:effect)
k taint node node01 gpu=true:NoSchedule
# taint 제거 (끝에 - 를 붙인다)
k taint node node01 gpu=true:NoSchedule-
# 노드에 걸린 taint 확인
k describe node node01 | grep -i taint
# Taints: gpu=true:NoSchedulevalue는 생략할 수 있고, 그때는 key와 effect만으로 taint가 성립합니다.
# value 없는 taint
k taint node node01 dedicated:NoScheduleEffect 세 가지: NoSchedule / PreferNoSchedule / NoExecute #
taint의 강도는 effect가 결정합니다. 세 가지가 있고, 무엇을 막는지가 서로 다릅니다.
| effect | 새 Pod 스케줄 | 이미 떠 있는 Pod |
|---|---|---|
NoSchedule | toleration 없으면 배치 거부 | 건드리지 않음 |
PreferNoSchedule | 가능하면 피하지만 강제는 아님 | 건드리지 않음 |
NoExecute | toleration 없으면 배치 거부 | toleration 없으면 즉시 축출(evict) |
NoSchedule은 가장 흔한 effect로, 앞으로 들어올 Pod만 막습니다. PreferNoSchedule은 약한 버전이라 다른 노드가 없으면 결국 배치됩니다. NoExecute는 가장 강합니다. 새 Pod를 막는 데 더해, 이미 그 노드에서 돌고 있던 toleration 없는 Pod까지 쫓아냅니다. 노드를 비워야 할 때 강하게 쓰는 effect입니다.
# NoExecute: 이미 떠 있는 toleration 없는 Pod까지 축출
k taint node node01 maintenance=true:NoExecuteToleration을 다는 법 #
Pod가 taint를 견디려면 spec.tolerations에 toleration을 적습니다. taint의 key, value, effect와 맞아야 합니다.
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
tolerations:
- key: "gpu"
operator: "Equal" # key=value가 같은 taint를 견딤
value: "true"
effect: "NoSchedule"
containers:
- name: app
image: nginxoperator는 두 가지입니다.
| operator | 의미 |
|---|---|
Equal | key, value, effect가 모두 일치하는 taint를 견딘다 (value 필요) |
Exists | key(와 effect)만 일치하면 value와 무관하게 견딘다 (value 생략) |
Exists는 value를 보지 않으므로, 특정 key의 모든 taint를 한 번에 견디게 할 때 편합니다. key까지 생략하면 모든 taint를 견디는 toleration이 되는데, 보통 DaemonSet처럼 어디든 떠야 하는 워크로드에서 씁니다.
# 모든 taint를 견딤 (key 생략 + Exists)
tolerations:
- operator: "Exists"NoExecute와 tolerationSeconds #
NoExecute taint에는 tolerationSeconds를 함께 쓸 수 있습니다. 이 값을 주면 toleration을 가진 Pod라도 무한정 남지 않고, 지정한 초만큼만 머문 뒤 축출됩니다.
tolerations:
- key: "maintenance"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 300 # 300초 동안만 견디고 그 뒤 축출이 동작은 노드 장애 대응에서 그대로 보입니다. 노드가 NotReady가 되면 control plane이 node.kubernetes.io/not-ready:NoExecute taint를 자동으로 답니다. 모든 Pod에는 기본적으로 이 taint에 대한 tolerationSeconds: 300 toleration이 자동으로 주입되어 있어서, 노드가 잠깐 끊겼다 돌아오면 Pod가 살아남고, 5분을 넘기면 다른 노드로 옮겨집니다. tolerationSeconds가 장애 감지와 재배치 사이의 유예 시간인 셈입니다.
Control plane 노드의 기본 taint #
kubeadm으로 세운 클러스터의 control plane 노드에는 기본 taint가 걸려 있습니다. 그래서 일반 워크로드 Pod가 control plane에 올라가지 않습니다.
k describe node controlplane | grep -i taint
# Taints: node-role.kubernetes.io/control-plane:NoSchedule이 taint 덕분에 control plane 컴포넌트가 사용자 워크로드와 자원을 다투지 않습니다. 단일 노드 클러스터처럼 control plane에도 Pod를 띄워야 하는 상황이면, 이 taint를 제거하면 됩니다.
# control plane 노드의 기본 taint 제거 (끝에 - )
k taint node controlplane node-role.kubernetes.io/control-plane:NoSchedule-반대로 kubeadm이 제어 평면 컴포넌트(kube-apiserver 등) Pod를 control plane에 띄울 수 있는 이유도 taint입니다. 그 static Pod들은 이 taint를 견디는 toleration을 가지고 있습니다.
Priority와 PriorityClass #
지금까지는 어디에 배치할지를 다뤘습니다. PriorityClass는 다른 차원입니다. 자원이 부족해 모두를 띄울 수 없을 때 누구를 먼저 살릴지를 정합니다.
PriorityClass는 클러스터 범위(non-namespaced) 리소스로, 정수 우선순위 값을 정의합니다. Pod는 spec.priorityClassName으로 이 클래스를 참조하고, 그 값이 Pod의 우선순위가 됩니다. 값이 클수록 우선순위가 높습니다.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "결제 등 중요 워크로드용 우선순위"| 필드 | 의미 |
|---|---|
value | 우선순위 정수. 클수록 높음. 사용자 정의는 보통 10억 미만 |
globalDefault | true면 priorityClassName이 없는 Pod의 기본 우선순위. 클러스터에 하나만 둘 수 있음 |
preemptionPolicy | PreemptLowerPriority(기본)면 preemption 수행. Never면 줄 앞에 서되 축출은 안 함 |
Pod 쪽에서는 이름으로 참조합니다.
apiVersion: v1
kind: Pod
metadata:
name: payment
spec:
priorityClassName: high-priority
containers:
- name: app
image: nginx# PriorityClass 목록과 값 확인
k get priorityclass
# NAME VALUE GLOBAL-DEFAULT
# high-priority 1000000 false
# system-cluster-critical 2000000000 false
# system-node-critical 2000001000 falsesystem-node-critical과 system-cluster-critical은 쿠버네티스가 미리 만들어 두는 시스템 PriorityClass입니다. kube-proxy나 CNI 같은 노드 필수 컴포넌트가 이 값을 써서, 자원이 부족해도 가장 마지막까지 살아남습니다.
Preemption: 낮은 우선순위 Pod를 축출한다 #
우선순위가 실제로 힘을 발휘하는 순간이 preemption입니다. 높은 우선순위 Pod가 Pending인데 클러스터에 빈자리가 없으면, 스케줄러는 더 낮은 우선순위 Pod를 축출(evict)해 자리를 만들고 그 자리에 높은 우선순위 Pod를 넣습니다.
1. high-priority Pod가 Pending. 어느 노드에도 자리가 없음
2. 스케줄러: "이 노드의 low-priority Pod를 비우면 자리가 난다"
3. low-priority Pod가 축출됨 (graceful termination)
4. high-priority Pod가 그 자리에 배치됨축출된 낮은 우선순위 Pod는 다른 노드에 자리가 있으면 거기로 옮겨가고, 없으면 Pending으로 남습니다. 즉 preemption은 자원을 두고 경쟁할 때 우선순위 순서를 강제하는 장치입니다.
preemptionPolicy를 Never로 두면 동작이 달라집니다. 이 Pod는 스케줄링 줄에서는 높은 우선순위로 앞에 서지만, 다른 Pod를 축출하지는 않습니다. 자리가 날 때까지 기다리되 남을 쫓아내지는 않아야 하는, 배치 작업 같은 워크로드에 맞습니다.
# 줄 앞에는 서되 축출은 하지 않음
preemptionPolicy: Neverpreemption과 PodDisruptionBudget #
preemption은 graceful하게 진행되어, 축출 대상 Pod에 terminationGracePeriod를 줍니다. 다만 PodDisruptionBudget(PDB)은 preemption을 완전히 막지는 못합니다. 스케줄러는 PDB를 가능한 한 존중하려 시도하지만, 높은 우선순위 Pod를 띄울 다른 방법이 없으면 PDB를 위반해서라도 축출할 수 있습니다. PDB는 #15 이후 리소스 관리 흐름에서 다시 만나겠습니다.
Affinity와 무엇이 다른가 #
#13의 affinity와 이번 글의 도구는 자주 한자리에서 비교됩니다. 셋을 한 단락으로 정리하면 이렇습니다. affinity는 Pod가 노드를 끌어당기는 선호이고, taint/toleration은 노드가 Pod를 밀어내는 거부이며, PriorityClass/preemption은 자원이 부족할 때의 생존 순서입니다. 앞의 둘은 “어느 노드에 갈 수 있는가"라는 배치의 문제이고, 마지막 하나는 “자리가 모자랄 때 누가 남는가"라는 경쟁의 문제입니다. 실무에서는 셋을 함께 씁니다. taint로 GPU 노드를 일반 Pod로부터 보호하고, nodeAffinity로 GPU 워크로드를 그 노드로 유도하며, PriorityClass로 그중에서도 중요한 작업을 먼저 살리는 식입니다.
시험 포인트 #
- toleration은 허가가 아니라 면제다. toleration이 있어도 그 노드로 간다는 보장은 없고, 노드로 보내려면 affinity나 nodeSelector를 함께 쓴다.
- effect 세 가지를 구분하라.
NoSchedule(새 Pod만),PreferNoSchedule(약한 회피),NoExecute(이미 떠 있는 Pod까지 축출). - taint는
k taint node <노드> key=value:effect, 제거는 끝에-를 붙인다. - control plane 기본 taint는
node-role.kubernetes.io/control-plane:NoSchedule이다. 단일 노드면 이 taint를 제거해야 워크로드가 올라간다. tolerationSeconds는NoExecute에서만 의미가 있고, not-ready/unreachable 자동 taint의 기본 유예가 300초다.- PriorityClass
value는 클수록 높고,globalDefault: true는 클러스터에 하나만 둔다. - preemption은 높은 우선순위 Pod가
Pending일 때 낮은 우선순위 Pod를 축출한다.preemptionPolicy: Never면 줄 앞에 서되 축출은 안 한다.
정리 #
이번 글에서 잡은 것:
- taint는 노드의 거부, toleration은 그 거부의 면제라는 한 쌍의 동작, 그리고 toleration이 배치를 보장하지 않는다는 핵심
- effect 세 가지(
NoSchedule/PreferNoSchedule/NoExecute)와NoExecute의 축출,tolerationSeconds의 유예 의미 - control plane 노드의 기본 taint와 그 제거, 그리고 not-ready 자동 taint의 동작
- PriorityClass(value/globalDefault/preemptionPolicy)와 preemption으로 자원 경쟁의 생존 순서를 강제하는 법
- affinity(끌어당김),taint(밀어냄),priority(생존 순서)의 역할 차이
scheduling을 마쳤으니, 이제 그 배치의 전제가 되는 자원 자체를 살펴보겠습니다.
다음: 리소스 관리 #
Pod를 어느 노드에 둘지 정했다면, 그 노드의 자원을 어떻게 나눠 쓸지가 다음 문제입니다. 자원을 적게 잡으면 노드가 과밀해지고, 많이 잡으면 노드가 비어도 다른 Pod가 못 들어옵니다.
#15 리소스 관리: requests/limits, QoS, LimitRange, ResourceQuota에서는 컨테이너가 요청하고 제한하는 CPU와 메모리(requests/limits), 그에 따라 정해지는 QoS class(Guaranteed/Burstable/BestEffort), 네임스페이스 단위로 기본값과 한도를 강제하는 LimitRange와 ResourceQuota를 다루겠습니다. 자원 설정이 스케줄링과 축출 양쪽에 어떻게 연결되는지 함께 정리하겠습니다.