← Back to notes

Scheduling Constraints: управление
размещением Pod


Kubernetes scheduler назначает Pod на Node в два этапа: Filter (отсеивает неподходящие Node) и Score (ранжирует оставшиеся по весу). Механизмы ниже влияют на один или оба этапа и позволяют контролировать, где именно в кластере запустится Pod.

source: kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/

Taints и Tolerations

Taint — это метка на Node, которая отталкивает Pod. Toleration — разрешение на Pod, позволяющее ему игнорировать конкретный taint. Taint не притягивает Pod на ноду — он только отталкивает тех, кто не имеет toleration.

Три эффекта

EffectПоведение
NoScheduleНовые Pod без toleration не назначаются на эту Node
PreferNoScheduleScheduler старается избегать, но размещает если нет альтернатив
NoExecuteНе назначает новые Pod + вытесняет уже запущенные без toleration
# Добавить taint
kubectl taint nodes node1 dedicated=gpu:NoSchedule

# Убрать taint (дефис в конце)
kubectl taint nodes node1 dedicated=gpu:NoSchedule-

# Посмотреть taints на ноде
kubectl describe node node1 | grep Taints

Toleration в Pod

apiVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "gpu"
    effect: "NoSchedule"
  containers:
  - name: app
    image: gpu-app:1.0

Два оператора:

  • Equal — key, value и effect должны точно совпасть с taint
  • Exists — достаточно совпадения key (value не указывается, любое значение подходит)

Toleration с tolerationSeconds для NoExecute — Pod будет вытеснен через указанное время:

tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 300

Kubernetes автоматически добавляет toleration node.kubernetes.io/not-ready и node.kubernetes.io/unreachable с tolerationSeconds: 300 ко всем Pod. Это стандартный grace period перед вытеснением при проблемах с нодой.

Где используются Taints

Control plane nodes — kubeadm при init/join ставит taint node-role.kubernetes.io/control-plane:NoSchedule (другие инсталляторы, например k3s, этого не делают). Именно поэтому обычные Pod не попадают на control plane ноды.

GPU/специализированные ноды — тaint dedicated=gpu:NoSchedule на GPU-нодах. Только Pod с GPU-запросом получают toleration, остальные Pod не занимают дорогие GPU-ноды.

Автоматические taints от node controller — при проблемах с нодой контроллер ставит node.kubernetes.io/not-ready, node.kubernetes.io/unreachable, node.kubernetes.io/memory-pressure и другие.

source: kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/

nodeSelector

Простейший способ ограничить, на каких нодах может запуститься Pod. Hard constraint — если нет Node с нужными label, Pod остаётся в Pending.

# Поставить label на ноду
kubectl label nodes node1 disktype=ssd
apiVersion: v1
kind: Pod
metadata:
  name: ssd-pod
spec:
  nodeSelector:
    disktype: ssd
  containers:
  - name: app
    image: my-app:1.0

nodeSelector удобен для простых случаев. Как только нужны операторы (not in, greater than) или мягкие предпочтения — переходи на Node Affinity.

Node Affinity

Более гибкая альтернатива nodeSelector. Поддерживает операторы In, NotIn, Exists, DoesNotExist, Gt, Lt и два режима: hard (обязательное) и soft (предпочтительное).

source: kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity

requiredDuringSchedulingIgnoredDuringExecution (hard)

Pod не назначается, если условие не выполнено. Работает на этапе Filter. IgnoredDuringExecution означает: уже запущенные Pod не вытесняются, если нода перестала соответствовать условию.

apiVersion: v1
kind: Pod
metadata:
  name: zone-pod
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values:
            - eu-west-1a
            - eu-west-1b
  containers:
  - name: app
    image: my-app:1.0

Несколько элементов в nodeSelectorTerms работают как OR. Несколько matchExpressions внутри одного term — как AND.

preferredDuringSchedulingIgnoredDuringExecution (soft)

Scheduler предпочитает Node, удовлетворяющую условию, но не обязан. Работает на этапе Score.

spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
      - weight: 20
        preference:
          matchExpressions:
          - key: network-speed
            operator: In
            values:
            - 10gbe

weight от 1 до 100 — относительный приоритет при наличии нескольких preferred-правил. Суммируется с другими Score-факторами.

Pod Affinity и Anti-Affinity

Управляет размещением Pod относительно других Pod, а не нод. Использует topologyKey для определения домена.

source: kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity

topologyKey

topologyKeyДомен
kubernetes.io/hostnameКонкретная нода (уникально для каждой ноды)
topology.kubernetes.io/zoneAvailability Zone
topology.kubernetes.io/regionRegion

podAffinity — разместить рядом

Cache-сервис рядом с frontend на одной ноде (снижение latency):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cache
spec:
  replicas: 2
  selector:
    matchLabels:
      app: cache
  template:
    metadata:
      labels:
        app: cache
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - frontend
            topologyKey: kubernetes.io/hostname
      containers:
      - name: cache
        image: redis:7

podAntiAffinity — разнести реплики

3 реплики web по разным зонам:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web
            topologyKey: topology.kubernetes.io/zone
      containers:
      - name: web
        image: nginx:1.25

Важно: required podAntiAffinity с topologyKey: kubernetes.io/hostname и 3 репликами требует минимум 3 ноды. Если нод меньше — Pod уйдут в Pending и останутся там.

topologySpreadConstraints

Более точный и гибкий контроль равномерного распределения Pod по доменам. Решает проблему, когда реплик больше, чем доменов: при required podAntiAffinity 5 реплик на 3 зоны уйдут в Pending; с topologySpreadConstraints — распределятся 2-2-1.

source: kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 5
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: web
      containers:
      - name: web
        image: nginx:1.25

Ключевые поля:

ПолеОписание
maxSkewМаксимально допустимая разница в количестве Pod между самым загруженным и самым пустым доменами
topologyKeyLabel ноды, определяющий домен
whenUnsatisfiableDoNotSchedule (hard, не назначать если нарушает) или ScheduleAnyway (soft, назначать, но предпочитать равномерность)
labelSelectorКакие Pod считать при подсчёте баланса

Пример результата: 5 реплик, 3 зоны, maxSkew: 1 → распределение 2-2-1 (разница не превышает 1).

Комбинировать несколько ограничений

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      app: web
- maxSkew: 1
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: ScheduleAnyway
  labelSelector:
    matchLabels:
      app: web

Сначала обязательно по зонам, предпочтительно — по нодам внутри зон.

Сводная таблица механизмов

МеханизмЭтап schedulerHard/SoftОписание
Taint + TolerationFilterHard/SoftОтталкивает Pod от Node
nodeSelectorFilterHardПривязка по label Node
Node Affinity requiredFilterHardГибкая привязка к Node (операторы)
Node Affinity preferredScoreSoftПредпочтение Node
Pod Affinity requiredFilterHardСближение Pod
Pod Affinity preferredScoreSoftПредпочтение сближения
Pod Anti-Affinity requiredFilterHardРазнесение Pod (строго один на домен)
Pod Anti-Affinity preferredScoreSoftПредпочтение разнесения
topologySpreadConstraints DoNotScheduleFilterHardРавномерное распределение с maxSkew
topologySpreadConstraints ScheduleAnywayScoreSoftПредпочтение равномерности

Когда использовать

Taints/Tolerations — изоляция специализированных нод (GPU, bare-metal, spot/preemptible). Нода должна быть доступна только для конкретных рабочих нагрузок.

nodeSelector / Node Affinity — Pod должен работать на нодах определённого типа: SSD, конкретная архитектура (arm64/amd64), нода в конкретной зоне или с определённым количеством памяти.

Pod AntiAffinity required — критичные stateless сервисы, где каждая реплика должна быть на отдельной ноде или в отдельной зоне. Допустимо только если реплик не больше, чем доменов.

topologySpreadConstraints — предпочтительный выбор для равномерного распределения stateless сервисов с произвольным числом реплик. Работает лучше podAntiAffinity для большинства современных деплойментов.

Pod Affinity — latency-sensitive компоненты, которые должны быть физически рядом (cache и app на одной ноде).

Типичные ошибки

Pod застрял в Pending после required podAntiAffinity. Если required podAntiAffinity + topologyKey: kubernetes.io/hostname и реплик больше, чем нод — Pod никогда не запустятся. Либо уменьши replicaCount, либо перейди на preferred или topologySpreadConstraints.

# Диагностика Pending Pod
kubectl describe pod <pod-name> | grep -A 10 Events
# Events: Warning  FailedScheduling  0/3 nodes available: 3 node(s) didn't match pod anti-affinity rules

Toleration с Exists на key="" — разрешает всё. Toleration без key и с operator: Exists совпадает со всеми taints кластера. Обычно это не намеренно.

# Осторожно  совпадает со всеми taints
tolerations:
- operator: "Exists"

nodeSelector и Node Affinity одновременно — AND. Если указаны оба, Pod назначается только на ноды, которые удовлетворяют обоим условиям.

Забыть label topology.kubernetes.io/zone на нодах. topologySpreadConstraints по зонам не работает, если у нод нет соответствующих label. В managed-кластерах (EKS, GKE, AKS) эти label ставятся автоматически. В bare-metal кластерах — нужно добавлять вручную.

maxSkew: 1 при малом числе реплик и нод. Если нод больше, чем реплик, некоторые домены будут пустыми. Разница между 1 Pod и 0 Pod = 1, это нарушает maxSkew: 1 при DoNotSchedule. Используй ScheduleAnyway или увеличь maxSkew.

Альтернативы

Cluster Autoscaler — вместо ручного управления размещением, автоматически добавляет ноды при нехватке ресурсов. Работает совместно с topologySpreadConstraints: может добавить ноды в нужную зону.

source: kubernetes.io/docs/concepts/cluster-administration/cluster-autoscaling/

Descheduler — отдельный компонент, который перераспределяет уже запущенные Pod для соблюдения баланса. Стандартный scheduler только размещает новые Pod, существующие не двигает. Descheduler периодически проверяет и вытесняет Pod с нарушенным балансом.

Priority Classes и PriorityPreemption — управление приоритетами Pod. Высокоприоритетный Pod может вытеснить низкоприоритетный, если нет свободных ресурсов. Это не scheduling constraint в прямом смысле, но влияет на то, какие Pod получают ноды первыми.

source: kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/

Custom Scheduler — Kubernetes позволяет запустить несколько schedulers. Для специфических задач (машинное обучение, особые hardware requirements) можно написать собственный scheduler и указать его через spec.schedulerName в Pod.


17: NetworkPolicy | 19: kube-apiserver

Scheduling Constraints: управление размещением Pod | Aleksandr Suprun