소개
스케줄러는 일반적으로 조건을 충족하는 노드 중 하나를 선택합니다. 레이블과 어피니티는 워크로드의 선호 사항을 표현하고, 테인트는 일치하는 톨러레이션이 없는 Pod 를 노드에서 배제합니다.
이 실습에서는 준비된 노드에 이러한 제어 기능을 단계적으로 적용하고, Pod 이벤트를 사용해 강제 스케줄링 규칙과 유연한 선호 조건을 구분합니다.
다음과 같은 개념으로 이해하면 됩니다. 레이블은 노드를 설명하고, 셀렉터와 어피니티는 Pod 가 원하는 조건을 표현하며, 테인트는 노드가 거부하는 대상을 나타냅니다. 톨러레이션은 Pod 가 해당 거부 규칙과 일치할 때 이를 통과하도록 허용합니다. 이러한 메커니즘 중 어느 것도 이미 실행 중인 Pod 를 직접“이동”시키지는 않습니다.
배치를 위한 노드 레이블 지정
이 단계에서는 설명용 레이블을 추가하고 이를 열로 표시합니다.
여기서 사용하는 레이블은 애플리케이션의 정체성이 아니라 워크로드 배치와 관련된 정보입니다. workload-zone=local은 위치를 나타내고, disk-class=fast는 기능을 나타냅니다. Kubernetes 는 레이블이 비즈니스 측면에서 무엇을 의미하는지 검증하지 않으며, 이후에 정의하는 스케줄링 규칙이 그 의미를 부여합니다.
cd /home/labex/project/pod-scheduling
kubectl create namespace practice-scheduling
kubectl label node labex-v135 workload-zone=local disk-class=fast
kubectl get nodes -L workload-zone,disk-class
레이블은 셀렉터와 어피니티가 사용하는 정보이며, 컨트롤러가 대체 Pod 를 생성하지 않는 한 기존 Pod 를 자동으로 이동시키지는 않습니다.
-L 옵션은 각 레이블 키에 해당하는 열을 하나씩 추가합니다. 노드 행에 local과 fast가 표시되면, 스케줄러가 이후 단계에서 사용할 규칙을 평가할 수 있다는 뜻입니다.
NAME STATUS ROLES ... VERSION WORKLOAD-ZONE DISK-CLASS
labex-v135 Ready control-plane ... v1.35.5 local fast
또한 업그레이드된 환경에서 Kubernetes v1.35.5 가 실행 중임을 확인할 수 있습니다. 사용자 지정 열 두 개는 방금 추가한 레이블에서 생성되었습니다.
nodeSelector 로 특정 노드 요구하기
이 단계에서는 가장 단순한 정확한 일치 배치 규칙을 사용하는 Pod 를 생성합니다.
nodeSelector는 필수 동등성 조건을 나타내는 맵입니다. Pod 는 모든 키가 요청된 값을 가진 노드에서만 실행될 수 있습니다. 간단한 필수 배치 조건에 적합한 방식입니다.
cd /home/labex/project/pod-scheduling
cat <<'EOF' > selected-pod.yaml
apiVersion: v1
kind: Pod
metadata: {name: selected-pod, namespace: practice-scheduling}
spec:
nodeSelector: {disk-class: fast}
containers:
- {name: worker, image: busybox:1.36, imagePullPolicy: IfNotPresent, command: ["sh", "-c", "while true; do sleep 30; done"]}
EOF
Pod 를 적용하고 준비 상태를 기다린 다음, NODE 열에서 할당된 노드를 확인합니다.
kubectl apply -f selected-pod.yaml
kubectl wait --for=condition=Ready pod/selected-pod -n practice-scheduling --timeout=120s
kubectl get pod selected-pod -n practice-scheduling -o wide
nodeSelector는 필수 조건입니다. 요청한 레이블을 모두 가진 노드가 없으면 Pod 는 Pending 상태로 남습니다.
선택된 노드는 labex-v135여야 합니다. 스케줄링에 성공했다는 것은 노드의 disk-class=fast 레이블이 Pod 의 요구 사항과 일치했다는 의미입니다.
pod/selected-pod condition met
NAME READY STATUS ... NODE
selected-pod 1/1 Running ... labex-v135
NODE 열은 스케줄링 결과이고, 매니페스트의 nodeSelector는 해당 노드가 선택 가능한 이유입니다.
필수 어피니티와 선호 어피니티 비교
이 단계에서는 로컬 영역을 필수로 요구하고 빠른 디스크를 선호하는 Pod 를 생성합니다.
노드 어피니티는 nodeSelector보다 표현력이 뛰어납니다. 필수 섹션은 조건에 맞지 않는 노드를 제외하고, 선호 섹션은 일치하는 노드에 가중치를 부여하지만 해당 조건을 반드시 충족하도록 강제하지는 않습니다.
긴 필드 이름은 규칙이 적용되는 시점을 나타냅니다. 이 규칙은 스케줄링 중에 평가되고 실행 중에는 무시됩니다. 이후 레이블이 변경되더라도 이 어피니티 규칙만을 이유로 Kubernetes 가 실행 중인 Pod 를 축출하지는 않습니다.
cd /home/labex/project/pod-scheduling
cat <<'EOF' > affinity-pod.yaml
apiVersion: v1
kind: Pod
metadata: {name: affinity-pod, namespace: practice-scheduling}
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions: [{key: workload-zone, operator: In, values: [local]}]
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions: [{key: disk-class, operator: In, values: [fast]}]
containers:
- {name: worker, image: busybox:1.36, imagePullPolicy: IfNotPresent, command: ["sh", "-c", "while true; do sleep 30; done"]}
EOF
Pod 를 적용하고 스케줄링이 완료될 때까지 기다립니다.
kubectl apply -f affinity-pod.yaml
kubectl wait --for=condition=Ready pod/affinity-pod -n practice-scheduling --timeout=120s
kubectl get pod affinity-pod -n practice-scheduling -o wide
필수 규칙은 실행 가능한 노드를 걸러 내고, 선호 규칙은 남은 노드의 점수를 계산합니다. IgnoredDuringExecution은 이후 레이블이 변경되어도 실행 중인 Pod 를 축출하지 않는다는 뜻입니다.
결과 표에도 labex-v135가 표시됩니다. 이 클러스터에는 노드가 하나뿐이므로 필수 조건인 workload-zone=local이 실행 가능 여부를 결정합니다. 빠른 디스크 선호 조건은 비교를 위해 두 번째 노드를 추가하지 않고도 점수 계산 문법을 보여 줍니다.
테인트와 톨러레이션 사용
이 단계에서는 노드에 테인트를 적용하고, 톨러레이션이 없는 Pod 가 Pending 상태로 남는 것을 확인한 다음, 실행이 허용된 Pod 를 생성합니다.
테인트의 키는 practice-reserved, 값은 true, 효과는 NoSchedule입니다. 이 효과는 일치하는 톨러레이션이 없는 새 Pod 의 배치를 차단하지만, 해당 노드에서 이미 실행 중인 Pod 를 축출하지는 않습니다.
kubectl taint node NODE key=value:effect 명령은 노드에 해당 규칙을 추가합니다. 다음 kubectl run 명령은 톨러레이션이 없는 독립 실행형 Pod 를 생성합니다. PodScheduled 조건을 기다리면 스케줄러가 최대 10 초 동안 예상되는 실패를 보여 줍니다.
cd /home/labex/project/pod-scheduling
kubectl taint node labex-v135 practice-reserved=true:NoSchedule
kubectl run blocked-pod -n practice-scheduling --image=busybox:1.36 --image-pull-policy=IfNotPresent --restart=Never -- sh -c 'sleep 3600'
kubectl wait --for=condition=PodScheduled pod/blocked-pod -n practice-scheduling --timeout=10s || true
kubectl events -n practice-scheduling --for pod/blocked-pod
대기 시간 초과는 예상된 결과이며 || true를 사용하면 실습을 계속 진행할 수 있습니다. 스케줄링 이벤트에는 톨러레이션이 없는 테인트가 언급되어야 하며, Pod 가 Pending 상태인 이유를 확인할 수 있습니다.
error: timed out waiting for the condition on pods/blocked-pod
LAST SEEN TYPE REASON OBJECT MESSAGE
... Warning FailedScheduling Pod/blocked-pod 0/1 nodes are available: 1 node(s) had untolerated taint(s)...
이 경우 Pending은 올바른 실험 결과입니다. 스케줄러가 Pod 를 평가했지만, 해당 Pod 가 사용할 수 있도록 허용된 노드를 찾지 못했다는 뜻입니다.
일치하는 톨러레이션을 생성합니다. 톨러레이션은 테인트를 통과해 스케줄링될 수 있도록 허용할 뿐, 그 자체로 노드를 선택하지는 않습니다.
cat <<'EOF' > tolerated-pod.yaml
apiVersion: v1
kind: Pod
metadata: {name: tolerated-pod, namespace: practice-scheduling}
spec:
nodeSelector: {workload-zone: local}
tolerations:
- {key: practice-reserved, operator: Equal, value: "true", effect: NoSchedule}
containers:
- {name: worker, image: busybox:1.36, imagePullPolicy: IfNotPresent, command: ["sh", "-c", "while true; do sleep 30; done"]}
EOF
kubectl apply -f tolerated-pod.yaml
kubectl wait --for=condition=Ready pod/tolerated-pod -n practice-scheduling --timeout=120s
kubectl delete pod blocked-pod -n practice-scheduling
톨러레이션이 있는 Pod 에는 nodeSelector도 포함되어 있습니다. 따라서 한 규칙이 노드를 선택하고, 다른 규칙이 해당 노드에 배치되는 것을 허용합니다. 의도적으로 차단했던 Pod 를 삭제하면 네임스페이스가 깨끗하고 정상적인 상태로 유지됩니다.
pod/tolerated-pod condition met 메시지는 일치하는 톨러레이션을 추가함으로써 결과가 달라졌음을 확인해 줍니다. 앞 단계의 Pod 가 계속 실행되는 이유는 NoSchedule이 새 배치에만 영향을 주고 이미 실행 중인 Pod 를 축출하지 않기 때문입니다.
요약
레이블, nodeSelector, 필수 및 선호 어피니티, 스케줄러 이벤트, 테인트와 톨러레이션을 사용해 배치 의도와 노드 접근 허용을 서로 독립적으로 표현했습니다.


