Part 12: Taint, Toleration, Affinity - 스케줄링 제어
Part 12: 스케줄링
스케줄링 개요
Kubernetes Scheduler는 새로 생성된 Pod를 가장 적합한 Node에 배치하는 역할을 한다. Scheduler는 두 단계로 동작한다:
1. Filtering (필터링)
조건을 만족하지 않는 노드를 제외한다:
- 리소스 요구사항을 만족하는가?
- Node Selector, Node Affinity 조건을 만족하는가?
- Taint를 Tolerate할 수 있는가?
- 볼륨을 마운트할 수 있는가?
2. Scoring (점수 부여)
남은 노드들에 점수를 부여하여 가장 적합한 노드를 선택한다:
- 리소스 가용량
- Pod 분산 정도
- Data locality
- Inter-pod affinity
Manual Scheduling
Scheduler를 우회하고 Pod를 특정 노드에 직접 배치할 수 있다.
방법 1: nodeName 사용
1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: manual-pod
spec:
nodeName: node01 # 직접 노드 지정
containers:
- name: nginx
image: nginx
nodeName 필드를 설정하면 Scheduler를 거치지 않고 즉시 해당 노드에 바인딩된다. 단, 해당 노드가 존재하지 않거나 리소스가 부족하면 Pod는 Pending 상태가 된다.
방법 2: Binding 객체 생성
이미 생성된 Pod를 특정 노드에 바인딩:
1
2
3
4
5
6
7
8
apiVersion: v1
kind: Binding
metadata:
name: existing-pod
target:
apiVersion: v1
kind: Node
name: node01
1
2
3
curl -X POST https://kube-apiserver:6443/api/v1/namespaces/default/pods/existing-pod/binding \
-H "Content-Type: application/json" \
-d @binding.json
Node Selector
가장 간단한 노드 선택 방법이다. Node의 Label을 기반으로 Pod를 특정 노드 그룹에만 스케줄링한다.
노드에 Label 추가:
1
2
3
4
kubectl label nodes node01 disktype=ssd
kubectl label nodes node01 size=large
kubectl label nodes node02 disktype=hdd
kubectl label nodes node02 size=medium
Pod에서 Node Selector 사용:
1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
nodeSelector:
disktype: ssd # disktype=ssd 라벨이 있는 노드에만 배치
size: large # AND 조건
containers:
- name: nginx
image: nginx
제약사항:
- OR 조건 불가
- NOT 조건 불가
- 복잡한 표현식 불가
이러한 제약을 극복하기 위해 Node Affinity를 사용한다.
Node Affinity
Node Selector보다 더 유연하고 강력한 노드 선택 메커니즘이다.
종류:
1. requiredDuringSchedulingIgnoredDuringExecution
Hard requirement. 조건을 만족하는 노드가 없으면 Pod가 스케줄링되지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: size
operator: In
values:
- Large
- Medium
containers:
- name: nginx
image: nginx
2. preferredDuringSchedulingIgnoredDuringExecution
Soft requirement. 선호하지만 필수는 아니다. 조건을 만족하는 노드가 없어도 다른 노드에 스케줄링된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity-preferred
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100 # 가중치 (1-100)
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
- weight: 50
preference:
matchExpressions:
- key: zone
operator: In
values:
- us-west-1a
containers:
- name: nginx
image: nginx
연산자 (Operators):
In: 값이 목록에 포함NotIn: 값이 목록에 미포함Exists: 키가 존재 (값 무관)DoesNotExist: 키가 존재하지 않음Gt: 값이 더 큼 (숫자)Lt: 값이 더 작음 (숫자)
복합 조건:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions: # OR 관계
- key: size
operator: In
values:
- Large
- key: zone
operator: NotIn
values:
- us-east-1
- matchExpressions: # 또는 이 조건
- key: disktype
operator: In
values:
- ssd
Taints and Tolerations
Taint는 노드에 “오염”을 표시하여 특정 Pod만 스케줄링되도록 제한한다. Toleration은 Pod가 특정 Taint를 “용인”할 수 있음을 나타낸다.
개념:
Taint/Toleration은 Node Affinity와 반대 개념이다:
- Node Affinity: Pod가 특정 노드를 선택
- Taint/Toleration: 노드가 특정 Pod만 허용
Taint 문법:
1
kubectl taint nodes <NODE_NAME> <KEY>=<VALUE>:<EFFECT>
- KEY: Taint 식별자
- VALUE: Taint 값 (선택사항)
- EFFECT: NoSchedule, PreferNoSchedule, NoExecute 중 하나
Taint 추가:
1
2
3
4
5
6
7
8
9
10
11
# 기본 형식
kubectl taint nodes node01 key=value:NoSchedule
# GPU 노드 전용화
kubectl taint nodes node01 gpu=true:NoSchedule
# 특정 팀 전용
kubectl taint nodes node01 env_type=production:NoSchedule
# 유지보수 모드
kubectl taint nodes node01 dedicated=special-workload:NoExecute
Taint 확인:
1
2
3
4
5
6
7
8
# Node의 모든 Taint 확인
kubectl describe node node01 | grep Taint
# 또는
kubectl get node node01 -o jsonpath='{.spec.taints}'
# 모든 노드의 Taint 확인
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
Taint 제거:
1
2
3
4
5
6
7
8
# KEY=VALUE:EFFECT 형식으로 제거
kubectl taint nodes node01 key=value:NoSchedule-
# KEY만으로도 제거 가능 (해당 키의 모든 Taint 제거)
kubectl taint nodes node01 gpu-
# CKA Mock Exam 예시 - 특정 Taint 제거
kubectl taint nodes node01 env_type=production:NoSchedule-
CKA Mock Exam 예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Mock Exam 3 - Q.7: 노드 Taint 설정 및 Toleration
# 1. node01을 Unschedulable로 Taint 설정
kubectl taint nodes node01 env_type=production:NoSchedule
# 2. dev-redis Pod 생성 (Toleration 없음 - 다른 노드에 배치됨)
kubectl run dev-redis --image=redis:alpine
# 3. prod-redis Pod 생성 (Toleration 있음 - node01에 배치 가능)
kubectl run prod-redis --image=redis:alpine --dry-run=client -o yaml > prod-redis.yaml
# YAML에 tolerations 추가 후 apply
# 배치 확인
kubectl get pods -o wide
Taint Effect:
1. NoSchedule
새로운 Pod가 스케줄링되지 않는다. 기존 Pod는 영향 없음.
2. PreferNoSchedule
가능하면 스케줄링하지 않는다 (Soft). 다른 노드가 없으면 스케줄링될 수 있다.
3. NoExecute
새로운 Pod 스케줄링 차단 + 기존 Pod도 제거 (Toleration 없는 경우).
1
kubectl taint nodes node01 maintenance=true:NoExecute
기존에 node01에서 실행 중이던 Pod들이 즉시 종료되고 다른 노드로 이동한다.
Toleration 추가:
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: tolerant-pod
spec:
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
containers:
- name: gpu-app
image: tensorflow/tensorflow:latest-gpu
Toleration Operators:
Equal (기본값)
1
2
3
4
5
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
key, value, effect 모두 일치해야 함.
Exists
1
2
3
4
tolerations:
- key: "key1"
operator: "Exists"
effect: "NoSchedule"
key와 effect만 일치하면 됨 (value 무관).
모든 Taint 용인:
1
2
tolerations:
- operator: "Exists"
NoExecute와 tolerationSeconds:
1
2
3
4
5
tolerations:
- key: "node.kubernetes.io/not-ready"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 300 # 5분간 유지 후 제거
노드가 Not Ready 상태가 되어도 5분 동안은 Pod를 유지한다.
Use Case:
1
2
3
4
5
6
7
8
9
10
11
# Control Plane 노드에 Taint 설정 (기본값)
kubectl taint nodes master-node node-role.kubernetes.io/control-plane:NoSchedule
# GPU 노드 전용화
kubectl taint nodes gpu-node gpu=true:NoSchedule
# 특정 팀 전용 노드
kubectl taint nodes team-a-node dedicated=team-a:NoSchedule
# 유지보수 모드
kubectl taint nodes node01 maintenance=true:NoExecute
Pod Affinity and Anti-Affinity
Pod 간의 관계를 기반으로 스케줄링한다.
Pod Affinity
특정 Pod와 같은 노드(또는 토폴로지)에 배치:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: kubernetes.io/hostname # 같은 노드
containers:
- name: web
image: nginx
이 Pod는 app=cache 라벨을 가진 Pod와 같은 노드에 배치된다.
Pod Anti-Affinity
특정 Pod와 다른 노드에 배치:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: with-pod-anti-affinity
labels:
app: web
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
containers:
- name: web
image: nginx
같은 app=web 라벨을 가진 Pod들이 서로 다른 노드에 분산된다.
Topology Key
kubernetes.io/hostname: 같은 노드topology.kubernetes.io/zone: 같은 가용 영역topology.kubernetes.io/region: 같은 리전
Deployment에서의 활용:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: redis
topologyKey: kubernetes.io/hostname
containers:
- name: redis
image: redis:6.2
3개의 Redis Pod가 서로 다른 노드에 배치되어 고가용성 확보.
Resource Requests and Limits
Scheduler는 리소스 요청량을 기반으로 Pod를 배치한다.
Requests vs Limits:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: app
image: myapp
resources:
requests: # 스케줄링 기준 (최소 보장)
cpu: "250m" # 0.25 core
memory: "256Mi" # 256 MiB
limits: # 최대 사용량
cpu: "500m" # 0.5 core
memory: "512Mi" # 512 MiB
CPU 단위:
1= 1 CPU core1000m= 1000 millicores = 1 core500m= 0.5 core100m= 0.1 core
메모리 단위:
128974848(bytes)129e6(129 * 10^6)129M(129 Megabytes)123Mi(123 Mebibytes = 123 * 1024 * 1024)1Gi(1 Gibibyte)
QoS Classes:
Kubernetes는 requests와 limits 설정에 따라 Pod를 세 가지 QoS 클래스로 분류한다.
1. Guaranteed (최우선)
모든 컨테이너가 requests == limits:
1
2
3
4
5
6
7
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "512Mi"
2. Burstable (중간)
requests < limits 또는 일부만 설정:
1
2
3
4
5
6
7
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
3. BestEffort (최하위)
requests와 limits를 설정하지 않음:
1
# resources 필드 없음
메모리 부족 시 제거 순서:
- BestEffort Pod 먼저
- Burstable Pod (limits 초과한 경우)
- Guaranteed Pod (limits 초과한 경우)
LimitRange
Namespace별 리소스 제한:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
namespace: production
spec:
limits:
- max: # 최대값
cpu: "2"
memory: "2Gi"
min: # 최소값
cpu: "100m"
memory: "128Mi"
default: # 기본 limits
cpu: "500m"
memory: "512Mi"
defaultRequest: # 기본 requests
cpu: "250m"
memory: "256Mi"
type: Container
ResourceQuota
Namespace 전체 리소스 제한:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: production
spec:
hard:
requests.cpu: "10"
requests.memory: "20Gi"
limits.cpu: "20"
limits.memory: "40Gi"
pods: "100"
services: "10"
persistentvolumeclaims: "20"
Priority and Preemption
Pod 우선순위를 설정하여 리소스 부족 시 우선순위가 낮은 Pod를 제거할 수 있다.
PriorityClass 생성:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000 # 높을수록 우선순위 높음
globalDefault: false
description: "Critical system pods"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: medium-priority
value: 1000
globalDefault: false
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority
value: 100
globalDefault: true # 기본값
Pod에 Priority 설정:
1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: critical-pod
spec:
priorityClassName: high-priority
containers:
- name: app
image: critical-app
Preemption 동작:
- 리소스 부족으로 high-priority Pod를 스케줄링할 수 없음
- Scheduler가 low-priority Pod를 찾음
- Low-priority Pod를 제거(Evict)
- High-priority Pod를 스케줄링
System Priority:
Kubernetes 시스템 Pod는 더 높은 우선순위를 가진다:
system-cluster-critical: 2000000000system-node-critical: 2000001000
Static Pods
kubelet이 직접 관리하는 Pod로, API Server 없이도 실행된다.
생성 방법:
/etc/kubernetes/manifests/ 디렉토리에 YAML 파일 배치:
1
2
3
4
5
6
7
8
9
10
cat <<EOF > /etc/kubernetes/manifests/static-web.yaml
apiVersion: v1
kind: Pod
metadata:
name: static-web
spec:
containers:
- name: nginx
image: nginx
EOF
kubelet이 자동으로 감지하여 Pod를 생성한다.
특징:
- API Server에 Mirror Pod로 표시됨
- kubectl delete로 삭제 불가 (파일 삭제 필요)
- Control Plane 컴포넌트가 Static Pod로 실행됨
확인:
1
2
3
4
5
kubectl get pods -n kube-system | grep master-node
# kube-apiserver-master-node
# kube-controller-manager-master-node
# kube-scheduler-master-node
# etcd-master-node
DaemonSet Scheduling
DaemonSet은 모든 (또는 특정) 노드에서 정확히 하나의 Pod를 실행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: monitoring-agent
spec:
selector:
matchLabels:
app: monitoring
template:
metadata:
labels:
app: monitoring
spec:
tolerations: # Control Plane에서도 실행
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
containers:
- name: agent
image: monitoring-agent:latest
특정 노드에만 실행:
1
2
3
4
5
spec:
template:
spec:
nodeSelector:
disktype: ssd # SSD가 있는 노드에만
Multiple Schedulers
커스텀 Scheduler를 생성하여 사용할 수 있다.
커스텀 Scheduler 배포:
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: my-custom-scheduler
namespace: kube-system
spec:
containers:
- name: scheduler
image: custom-scheduler:1.0
command:
- /usr/local/bin/custom-scheduler
- --scheduler-name=my-scheduler
Pod에서 Scheduler 지정:
1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
schedulerName: my-scheduler # 커스텀 스케줄러 사용
containers:
- name: nginx
image: nginx
실습 과제
1. Node Selector와 Node Affinity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 노드 라벨링
kubectl label nodes node01 disktype=ssd
kubectl label nodes node02 disktype=hdd
# Node Selector Pod 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: ssd-pod
spec:
nodeSelector:
disktype: ssd
containers:
- name: nginx
image: nginx
EOF
# 배치 확인
kubectl get pod ssd-pod -o wide
2. Taints and Tolerations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Taint 추가
kubectl taint nodes node01 gpu=true:NoSchedule
# Toleration 없는 Pod (실패)
kubectl run no-toleration --image=nginx
# Toleration 있는 Pod (성공)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: with-toleration
spec:
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
containers:
- name: nginx
image: nginx
EOF
# Taint 제거
kubectl taint nodes node01 gpu=true:NoSchedule-
3. Resource Requests and Limits
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: resource-pod
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
EOF
# QoS Class 확인
kubectl describe pod resource-pod | grep QoS
4. Priority Classes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# PriorityClass 생성
kubectl apply -f - <<EOF
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000
globalDefault: false
EOF
# High priority Pod 생성
kubectl run high-pod --image=nginx --restart=Never --dry-run=client -o yaml > high-pod.yaml
echo " priorityClassName: high-priority" >> high-pod.yaml
kubectl apply -f high-pod.yaml
참고 자료
- Kubernetes Scheduler
- Taints and Tolerations
- Node Affinity
- Resource Management
- Pod Priority and Preemption