Модуль 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 нельзя. Для изоляции используют защищённые label namespaces и 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.