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

Три эффекта

# Добавить 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 — kube-apiserver автоматически ставит taint node-role.kubernetes.io/control-plane:NoSchedule. Именно поэтому обычные 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

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

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

Пример результата: 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

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

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

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

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