Taint와 Toleration이란?
Kubernetes의 Taint와 Toleration은 특정 노드에 Pod가 스케줄링되는 것을 제어하는 메커니즘이다. Taint는 노드에 “오염”을 표시하여 일반 Pod를 거부하고, Toleration은 Pod에 “내성”을 부여하여 오염된 노드에서도 실행할 수 있게 한다. Node Affinity가 “이 노드에 가고 싶다”라면, Taint/Toleration은 “이 노드에 오지 마라”의 역방향 제어다.
이 글에서는 Taint/Toleration의 핵심 동작부터 GPU 노드 전용화, 스팟 인스턴스 관리, 노드 유지보수, 멀티테넌트 격리까지 실전 운영 패턴을 다룬다.
Taint 기본 문법과 Effect
# Taint 추가
kubectl taint nodes worker-01 dedicated=gpu:NoSchedule
# Taint 확인
kubectl describe node worker-01 | grep Taints
# Taint 제거 (키 뒤에 - 추가)
kubectl taint nodes worker-01 dedicated=gpu:NoSchedule-
Taint는 key=value:effect 형식이며, effect는 세 가지가 있다:
| Effect | 동작 | 사용 시점 |
|---|---|---|
NoSchedule |
새 Pod 스케줄링 거부 (기존 Pod 유지) | 전용 노드 격리 |
PreferNoSchedule |
가능하면 스케줄링 피함 (soft 제약) | 선호도 기반 분산 |
NoExecute |
새 Pod 거부 + 기존 Pod도 퇴거 | 노드 유지보수, 장애 격리 |
Toleration 설정
Pod가 Taint된 노드에서 실행되려면 매칭되는 Toleration을 선언해야 한다.
apiVersion: v1
kind: Pod
metadata:
name: gpu-training-job
spec:
tolerations:
# 정확한 매칭 (Equal 연산자)
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
# 키만 매칭 (Exists 연산자 — 값 무관)
- key: "node-role"
operator: "Exists"
effect: "NoSchedule"
# NoExecute + tolerationSeconds: 300초 후 퇴거
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 300
containers:
- name: trainer
image: pytorch/pytorch:2.0-cuda11.8
resources:
limits:
nvidia.com/gpu: 1
실전 패턴 1: GPU 노드 전용화
GPU 노드는 비싸므로 GPU 워크로드만 실행해야 한다. Taint로 일반 Pod를 차단하고, GPU Pod에만 Toleration을 부여한다.
# GPU 노드에 Taint 설정
kubectl taint nodes gpu-node-01 nvidia.com/gpu=present:NoSchedule
kubectl taint nodes gpu-node-02 nvidia.com/gpu=present:NoSchedule
# GPU 노드에 라벨도 추가 (Affinity와 함께 사용)
kubectl label nodes gpu-node-01 gpu-type=a100
kubectl label nodes gpu-node-02 gpu-type=a100
apiVersion: apps/v1
kind: Deployment
metadata:
name: ml-inference
spec:
replicas: 3
selector:
matchLabels:
app: ml-inference
template:
metadata:
labels:
app: ml-inference
spec:
# Taint 허용
tolerations:
- key: "nvidia.com/gpu"
operator: "Equal"
value: "present"
effect: "NoSchedule"
# + Affinity로 GPU 노드에만 배치
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu-type
operator: In
values: ["a100", "v100"]
containers:
- name: inference
image: registry.example.com/ml-inference:v2
resources:
limits:
nvidia.com/gpu: 1
requests:
cpu: "2"
memory: 8Gi
핵심: Toleration만으로는 해당 노드에 “반드시” 가는 것이 아니다. Toleration은 “갈 수 있다”는 허용이며, “반드시 가라”는 Node Affinity로 보완해야 한다.
실전 패턴 2: 스팟 인스턴스 관리
스팟(Spot/Preemptible) 노드는 언제든 회수될 수 있으므로, 중요하지 않은 워크로드만 배치한다.
# 스팟 노드에 Taint (클라우드 프로바이더가 자동 추가하기도 함)
kubectl taint nodes spot-node-01 cloud.google.com/gke-spot=true:NoSchedule
---
# 스팟 허용 + 회수 대비 graceful shutdown
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
spec:
replicas: 10
template:
spec:
tolerations:
- key: "cloud.google.com/gke-spot"
operator: "Equal"
value: "true"
effect: "NoSchedule"
# 스팟 회수 시 120초 유예
- key: "cloud.google.com/gke-spot"
operator: "Equal"
value: "true"
effect: "NoExecute"
tolerationSeconds: 120
terminationGracePeriodSeconds: 120
containers:
- name: processor
image: registry.example.com/batch:v1
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "kill -SIGTERM 1 && sleep 100"]
실전 패턴 3: 노드 유지보수 (Cordon + Drain)
노드 유지보수 시 kubectl drain은 내부적으로 NoExecute Taint를 설정하여 Pod를 퇴거시킨다.
# Step 1: Cordon — 새 스케줄링 차단 (NoSchedule Taint 추가)
kubectl cordon worker-03
# Step 2: Drain — 기존 Pod 퇴거
kubectl drain worker-03
--ignore-daemonsets
--delete-emptydir-data
--grace-period=60
--timeout=300s
# Step 3: 유지보수 완료 후 Uncordon
kubectl uncordon worker-03
중요: DaemonSet Pod는 drain 시 퇴거되지 않는다 (--ignore-daemonsets). 모니터링 에이전트 등 DaemonSet은 노드에 남아야 하기 때문이다.
실전 패턴 4: 멀티테넌트 노드 격리
# 테넌트 A 전용 노드
kubectl taint nodes pool-a-01 tenant=team-a:NoSchedule
kubectl taint nodes pool-a-02 tenant=team-a:NoSchedule
# 테넌트 B 전용 노드
kubectl taint nodes pool-b-01 tenant=team-b:NoSchedule
---
# 테넌트 A의 워크로드
apiVersion: apps/v1
kind: Deployment
metadata:
name: team-a-api
namespace: team-a
spec:
template:
spec:
tolerations:
- key: "tenant"
operator: "Equal"
value: "team-a"
effect: "NoSchedule"
nodeSelector:
tenant-pool: "team-a"
containers:
- name: api
image: registry.example.com/team-a-api:v1
시스템 Taint: K8s 내장 Taint
Kubernetes는 노드 상태에 따라 자동으로 Taint를 추가한다.
| Taint Key | 조건 | Effect |
|---|---|---|
node.kubernetes.io/not-ready |
노드 NotReady 상태 | NoExecute |
node.kubernetes.io/unreachable |
노드와 통신 불가 | NoExecute |
node.kubernetes.io/memory-pressure |
메모리 압박 | NoSchedule |
node.kubernetes.io/disk-pressure |
디스크 압박 | NoSchedule |
node.kubernetes.io/pid-pressure |
PID 고갈 | NoSchedule |
node.kubernetes.io/unschedulable |
kubectl cordon 실행됨 | NoSchedule |
기본적으로 Pod는 not-ready와 unreachable에 대해 tolerationSeconds: 300(5분)의 내장 Toleration을 가진다. 이 시간을 줄이면 장애 노드에서 더 빠르게 Pod가 재배치된다.
# 빠른 페일오버: 30초 후 퇴거
tolerations:
- key: "node.kubernetes.io/not-ready"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 30
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 30
Taint + PodDisruptionBudget 조합
NoExecute Taint로 Pod를 퇴거할 때 PDB(PodDisruptionBudget)와 함께 사용하면 가용성을 보장하면서 안전하게 노드를 비울 수 있다.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
spec:
minAvailable: 2 # 최소 2개 Pod 유지
selector:
matchLabels:
app: api-server
안티패턴과 주의점
| 안티패턴 | 문제점 | 해결책 |
|---|---|---|
| 모든 Pod에 와일드카드 Toleration | Taint 무력화, 격리 무의미 | 필요한 Pod에만 명시적 Toleration |
| Toleration만 사용 (Affinity 없이) | 다른 노드에도 스케줄링됨 | Toleration + Node Affinity 조합 |
| tolerationSeconds 미설정 | NoExecute에서 영원히 실행 | 적절한 유예 시간 설정 |
| Drain 시 PDB 미설정 | 모든 Pod 동시 퇴거, 서비스 중단 | PDB로 최소 가용 Pod 보장 |
마무리
Taint와 Toleration은 Kubernetes 스케줄링의 방어적 제어 메커니즘이다. Node Affinity가 Pod를 끌어당기는 “당김”이라면, Taint/Toleration은 Pod를 밀어내는 “밀침”이다. GPU 전용화, 스팟 관리, 노드 유지보수, 멀티테넌트 격리 등 실전 시나리오에서 Affinity 스케줄링과 함께 사용하면 클러스터 리소스를 정밀하게 제어할 수 있다.