Модуль 13 / 19
Scheduling: nodeSelector, affinity, taints, tolerations і topology
Зміст
1. Цілі та результат модуля
Ви навчитеся пояснювати рішення scheduler, виражати обов’язкові та бажані правила розміщення й діагностувати Pending Pod за constraints та Events.
- nodeSelector — проста hard-умова
- node affinity підтримує hard і weighted soft rules
- pod affinity пов’язує placement з іншими Pods
- taint відштовхує, toleration лише дозволяє
- topology spread керує skew між доменами
2. Теорія: scheduling cycle
Scheduler обробляє unscheduled Pod: фільтрує непридатні Nodes, оцінює решту, обирає Node та записує binding. Kubelet потім запускає Pod. Scheduled=True не означає Running чи Ready; image pull, mount і probes відбуваються пізніше.
Preemption може звільнити ресурси для пріоритетнішого Pod, але не виправляє несумісний label, taint чи affinity rule.
3. Node labels і nodeSelector
nodeSelector вимагає точного збігу всіх labels. Labels описують архітектуру, pool, hardware або topology, але довіряти довільним labels для security не можна. Для ізоляції використовують захищені namespaces labels і NodeRestriction admission.
spec.nodeName обходить scheduler і не є заміною selector: він жорстко прив’язує Pod та ускладнює діагностику.
4. Node і Pod affinity
requiredDuringSchedulingIgnoredDuringExecution — обов’язковий filter під час scheduling; preferred... — score з weight 1–100. IgnoredDuringExecution означає, що зміна label зазвичай не виселяє вже працюючий Pod.
Pod affinity/anti-affinity зіставляє labels Pods у topology domains. Hard anti-affinity за hostname вимагає різні Nodes і на одновузловому кластері блокує другу replica.
5. Taints і tolerations
Taint має key, optional value та effect. NoSchedule забороняє нові placements без toleration; PreferNoSchedule — м’яке побажання; NoExecute також виселяє нетерпимі Pods, а tolerationSeconds може відкласти виселення.
Toleration не притягує Pod до tainted Node. Для dedicated pool зазвичай поєднують taint+toleration з node affinity.
6. Topology spread і single-node межа
topologySpreadConstraints порівнює число matching Pods за доменами topologyKey. maxSkew обмежує дисбаланс; DoNotSchedule — hard, ScheduleAnyway — soft. Ураховуються лише eligible domains і правильно розмічені Nodes.
На одному Node три replicas можуть працювати в одному hostname domain: це не перевірка високої доступності. Розподіл між Nodes/zones переноситься в Advanced/HA лабораторію.
7. Методичні вказівки
- Спочатку перевіряйте requests і Node readiness
- Зберіть усі hard filters: selector, affinity, taints, volumes
- Відокремлюйте required від preferred
- Не вважайте toleration вибором Node
- Перевіряйте topology labels і eligible domains
- Не змінюйте system taints без rollback
- Читайте FailedScheduling message повністю
- Видаляйте навчальні labels і taints
8. Лабораторна робота: nodeSelector і preference
Розмітьте Node. selected вимагає label, а preferred просить відсутню zone лише як preference і все одно запускається.
mkdir -p /tmp/k8s-m13
kubectl create namespace k8s-m13
kubectl label node k8s-fundamentals-1 course.goretskiy.pro/pool=fundamentals --overwrite
cat > /tmp/k8s-m13/placement.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata: {name: selected, namespace: k8s-m13}
spec:
nodeSelector: {course.goretskiy.pro/pool: fundamentals}
containers: [{name: app, image: busybox:1.37, command: ["sh", "-c", "sleep 3600"]}]
---
apiVersion: v1
kind: Pod
metadata: {name: preferred, namespace: k8s-m13}
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions: [{key: course.goretskiy.pro/zone, operator: In, values: [absent]}]
containers: [{name: app, image: busybox:1.37, command: ["sh", "-c", "sleep 3600"]}]
EOF
kubectl apply --dry-run=server -f /tmp/k8s-m13/placement.yaml
kubectl apply -f /tmp/k8s-m13/placement.yaml
kubectl -n k8s-m13 wait --for=condition=Ready pod/selected pod/preferred --timeout=120s
kubectl -n k8s-m13 get pod selected preferred -o wide9. Лабораторна робота: hard anti-affinity
Дві replicas вимагають різні hostname domains. На одному Node одна працює, друга Pending; Event прямо повідомляє про pod anti-affinity.
cat > /tmp/k8s-m13/anti-affinity.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata: {name: anti-affinity, namespace: k8s-m13}
spec:
replicas: 2
selector: {matchLabels: {app: anti-affinity}}
template:
metadata: {labels: {app: anti-affinity}}
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: {matchLabels: {app: anti-affinity}}
topologyKey: kubernetes.io/hostname
containers: [{name: app, image: nginx:1.29-alpine}]
EOF
kubectl apply --dry-run=server -f /tmp/k8s-m13/anti-affinity.yaml
kubectl apply -f /tmp/k8s-m13/anti-affinity.yaml
sleep 5
kubectl -n k8s-m13 get pods -l app=anti-affinity -o wide
pending=$(kubectl -n k8s-m13 get pods -l app=anti-affinity --field-selector=status.phase=Pending -o jsonpath='{.items[0].metadata.name}')
kubectl -n k8s-m13 describe pod "$pending"10. Лабораторна робота: taint і toleration
Тимчасово додайте NoSchedule. Звичайний Pod блокується, tolerated запускається. Зніміть taint і переконайтеся, що перший Pod тепер розміщено.
kubectl taint node k8s-fundamentals-1 course.goretskiy.pro/dedicated=lab:NoSchedule
cat > /tmp/k8s-m13/taints.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata: {name: blocked-by-taint, namespace: k8s-m13}
spec:
containers: [{name: app, image: busybox:1.37, command: ["sh", "-c", "sleep 3600"]}]
---
apiVersion: v1
kind: Pod
metadata: {name: tolerated, namespace: k8s-m13}
spec:
tolerations:
- {key: course.goretskiy.pro/dedicated, operator: Equal, value: lab, effect: NoSchedule}
containers: [{name: app, image: busybox:1.37, command: ["sh", "-c", "sleep 3600"]}]
EOF
kubectl apply --dry-run=server -f /tmp/k8s-m13/taints.yaml
kubectl apply -f /tmp/k8s-m13/taints.yaml
sleep 5
kubectl -n k8s-m13 get pod blocked-by-taint tolerated -o wide
kubectl -n k8s-m13 describe pod blocked-by-taint
kubectl taint node k8s-fundamentals-1 course.goretskiy.pro/dedicated:NoSchedule-
kubectl -n k8s-m13 wait --for=condition=Ready pod/blocked-by-taint --timeout=120s11. Topology і діагностика FailedScheduling
М’який spread не створює нові failure domains: усі replicas опиняться на єдиному Node.
cat > /tmp/k8s-m13/spread.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata: {name: spread, namespace: k8s-m13}
spec:
replicas: 3
selector: {matchLabels: {app: spread}}
template:
metadata: {labels: {app: spread}}
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector: {matchLabels: {app: spread}}
containers: [{name: app, image: nginx:1.29-alpine}]
EOF
kubectl apply --dry-run=server -f /tmp/k8s-m13/spread.yaml
kubectl apply -f /tmp/k8s-m13/spread.yaml
kubectl -n k8s-m13 rollout status deployment/spread --timeout=120s
kubectl -n k8s-m13 get pods -l app=spread -o custom-columns=POD:.metadata.name,NODE:.spec.nodeName
kubectl get nodes -L kubernetes.io/hostname,topology.kubernetes.io/zoneПотім вимагайте відсутній label. Діагностуйте через Pod conditions, Events та фактичні Node labels, після чого видаліть лише несправний Pod.
kubectl -n k8s-m13 run impossible --image=busybox:1.37 --overrides='{"spec":{"nodeSelector":{"course.goretskiy.pro/pool":"missing"},"containers":[{"name":"impossible","image":"busybox:1.37","command":["sh","-c","sleep 3600"]}]}}'
sleep 3
kubectl -n k8s-m13 get pod impossible -o wide
kubectl -n k8s-m13 describe pod impossible
kubectl get nodes --show-labels
kubectl -n k8s-m13 get events --field-selector involvedObject.name=impossible --sort-by=.metadata.creationTimestamp
kubectl -n k8s-m13 delete pod impossible12. Контроль, критерії засвоєння та cleanup
- Що робить scheduler після фільтрації?
- Scheduled дорівнює Ready?
- nodeSelector — hard чи soft?
- Що означає IgnoredDuringExecution?
- Чому друга anti-affinity replica Pending?
- Toleration обирає Node?
- Відмінності трьох taint effects?
- Що таке topology domain і skew?
- Чому single-node не доводить spread?
- Чому preemption не виправить label mismatch?
kubectl taint node k8s-fundamentals-1 course.goretskiy.pro/dedicated:NoSchedule- 2>/dev/null || true
kubectl label node k8s-fundamentals-1 course.goretskiy.pro/pool-
kubectl delete namespace k8s-m13 --wait=true
rm -rf /tmp/k8s-m13
kubectl get namespace k8s-m13 2>&1 || trueМодуль засвоєно, якщо ви прогнозуєте placement чотирьох сценаріїв, знаходите блокувальний hard constraint і лишаєте Node без навчальних taints/labels.