스케줄러가 노드를 고르는 과정
- 거르기(Filter) — 조건에 안 맞는 노드를 뺀다: 자원 부족, nodeSelector 불일치, 견디지 못하는 taint, cordon, 볼륨 제약 …
- 점수(Score) — 남은 노드에 점수를 매겨 가장 좋은 곳에 둔다(선호 affinity, 고른 분산 등).
남는 노드가 없으면 파드는 Pending 이고 이벤트에 이유가 나옵니다.
0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: },
2 node(s) didn't match Pod's node affinity/selector.
이 문장을 끝까지 읽는 것이 스케줄링 트러블슈팅의 전부입니다.
노드 고르기 네 가지
① nodeName — 스케줄러를 건너뛰고 노드를 직접 지정(가장 강제적, 조건 검사 없음).
② nodeSelector — 노드 레이블이 일치하는 곳에만.
kubectl label node node02 disktype=ssd
kubectl get nodes -L disktype # 레이블을 열로
spec:
nodeSelector:
disktype: ssd
③ nodeAffinity — 더 풍부한 조건(In, NotIn, Exists, Gt …)과 “꼭”/“되도록”.
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 꼭
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values: [ssd, nvme]
preferredDuringSchedulingIgnoredDuringExecution: # 되도록
- weight: 50
preference:
matchExpressions:
- key: zone
operator: In
values: [a]
문서에서 “Assign Pods to Nodes using Node Affinity” 를 검색하면 이 예제가 그대로 있습니다. 외우지 말고 가져다 고치세요.
④ podAffinity / podAntiAffinity — 다른 파드의 위치 기준. “캐시 파드와 같은 노드에”, “같은 앱 파드끼리 다른 노드에”.
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname # '노드' 단위로 떨어뜨려라
taint 와 toleration
노드 쪽의 밀어내기(taint) + 파드 쪽의 견디기(toleration).
kubectl taint node node01 dedicated=gpu:NoSchedule # 걸기
kubectl taint node node01 dedicated=gpu:NoSchedule- # 풀기(끝에 -)
kubectl describe node node01 | grep -i taint
| effect | 새 파드 | 이미 있는 파드 |
|---|---|---|
| NoSchedule | 못 들어옴 | 그대로 |
| PreferNoSchedule | 되도록 안 들어옴 | 그대로 |
| NoExecute | 못 들어옴 | 쫓겨남 |
spec:
tolerations:
- key: dedicated
operator: Equal # 값까지 일치(기본)
value: gpu
effect: NoSchedule
- key: node-role.kubernetes.io/control-plane
operator: Exists # 값 무관
effect: NoSchedule
toleration 은 입장권일 뿐 배정표가 아닙니다. “GPU 노드에서만 돌게 하라”는 문제는 toleration(들어갈 수 있게) + nodeSelector/affinity(거기로 가게) 둘 다 필요합니다.
DaemonSet — 노드마다 하나
로그 수집기, 모니터링 에이전트, CNI 처럼 모든(또는 특정) 노드에 하나씩 떠야 하는 것.
kubectl create daemonset 은 없습니다. 디플로이먼트 뼈대에서 바꿉니다.
kubectl create deployment agent --image=busybox:1.37 $do > ds.yaml
# kind: Deployment → DaemonSet, replicas · strategy · status 줄 삭제
컨트롤 플레인 노드에도 띄우려면 control-plane taint 를 견디는 toleration 을 넣습니다.
static pod
API 서버가 아니라 노드의 kubelet 이 직접 띄우는 파드. 컨트롤 플레인 부품이 이렇게 돕니다(아키텍처 강의).
ssh node01
grep staticPodPath /var/lib/kubelet/config.yaml # 보통 /etc/kubernetes/manifests
kubectl run static-web --image=nginx:1.27 $do > /etc/kubernetes/manifests/static-web.yaml
exit
kubectl get pods -o wide # static-web-node01 (이름 뒤에 노드 이름이 붙는다)
지우려면 그 노드에서 파일을 지웁니다. kubectl delete pod 로 지워도 곧 다시 생깁니다.
cordon · drain
kubectl cordon node01 # 새 파드 금지(기존 파드는 그대로)
kubectl uncordon node01
kubectl drain node01 --ignore-daemonsets --delete-emptydir-data # 비우기(유지보수)
drain 은 자세히 노드 유지보수에서 다룹니다.
PriorityClass
자원이 모자랄 때 누구를 먼저 배치할지(높은 우선순위는 낮은 것을 밀어낼 수 있음).
kubectl create priorityclass high-priority --value=100000 --description="중요 서비스"
spec:
priorityClassName: high-priority