← До програми курсу

Модуль 13 / 19

Scheduling: nodeSelector, affinity, taints, tolerations і topology

KUBERNETES

Зміст
01

1. Цілі та результат модуля

Ви навчитеся пояснювати рішення scheduler, виражати обов’язкові та бажані правила розміщення й діагностувати Pending Pod за constraints та Events.

  • nodeSelector — проста hard-умова
  • node affinity підтримує hard і weighted soft rules
  • pod affinity пов’язує placement з іншими Pods
  • taint відштовхує, toleration лише дозволяє
  • topology spread керує skew між доменами
02

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.

03

3. Node labels і nodeSelector

nodeSelector вимагає точного збігу всіх labels. Labels описують архітектуру, pool, hardware або topology, але довіряти довільним labels для security не можна. Для ізоляції використовують захищені namespaces labels і NodeRestriction admission.

spec.nodeName обходить scheduler і не є заміною selector: він жорстко прив’язує Pod та ускладнює діагностику.

04

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.

05

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.

06

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 лабораторію.

07

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
08

8. Лабораторна робота: nodeSelector і preference

Розмітьте Node. selected вимагає label, а preferred просить відсутню zone лише як preference і все одно запускається.

terminal
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 wide
09

9. Лабораторна робота: hard anti-affinity

Дві replicas вимагають різні hostname domains. На одному Node одна працює, друга Pending; Event прямо повідомляє про pod anti-affinity.

terminal
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

10. Лабораторна робота: taint і toleration

Тимчасово додайте NoSchedule. Звичайний Pod блокується, tolerated запускається. Зніміть taint і переконайтеся, що перший Pod тепер розміщено.

terminal
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=120s
11

11. Topology і діагностика FailedScheduling

М’який spread не створює нові failure domains: усі replicas опиняться на єдиному Node.

terminal
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.

terminal
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 impossible
12

12. Контроль, критерії засвоєння та 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?
terminal
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.