KubernetesMiddleSystem design

Что такое taints и tolerations? Чем они отличаются от nodeSelector и nodeAffinity?

Taints/tolerations — механизм отталкивания: узел отталкивает поды (taint), под разрешает себя туда поставить (toleration). nodeSelector и nodeAffinity — механизм притяжения: под выбирает узлы по labels. Taints отталкивают всех, affinity притягивает конкретных.

Taints, tolerations, nodeSelector и nodeAffinity

Kubernetes предоставляет несколько механизмов для управления тем, на каких узлах запускаются поды. Они работают в разных направлениях и с разной гибкостью.

Taints и Tolerations

Taint устанавливается на узел и говорит: «не размещай сюда поды, если они не имеют подходящего toleration».

# Добавить taint на узел
kubectl taint nodes gpu-node-1 gpu=true:NoSchedule

# Эффекты:
# NoSchedule — новые поды без toleration не будут сюда планироваться
# PreferNoSchedule — мягкий запрет (постарается не ставить, но может)
# NoExecute — выселяет существующие поды без toleration

# Удалить taint
kubectl taint nodes gpu-node-1 gpu=true:NoSchedule-

Toleration в поде разрешает его размещение на узлах с matching taint:

spec:
  tolerations:
    - key: "gpu"
      operator: "Equal"
      value: "true"
      effect: "NoSchedule"
  # Toleration не ПРИВЛЕКАЕТ под к узлу,
  # оно только РАЗРЕШАЕТ туда попасть

Типичные use cases taints:

  • Dedicated узлы для GPU-workloads: nvidia.com/gpu=present:NoSchedule.
  • Изоляция control-plane: node-role.kubernetes.io/control-plane:NoSchedule (дефолт в kubeadm).
  • Временное «maintenance» узла: maintenance=true:NoExecute с опциональным tolerationSeconds.

nodeSelector

Простейший механизм: под планируется только на узлы с заданными labels.

spec:
  nodeSelector:
    disktype: ssd
    zone: us-east-1a

Ограничение: только равенство, AND-логика, нет гибкости. Если узел с такими labels недоступен — под зависнет в Pending.

nodeAffinity

Расширенная версия nodeSelector с операторами, OR-логикой и мягкими предпочтениями.

spec:
  affinity:
    nodeAffinity:
      # Жёсткое требование (=nodeSelector, но гибче)
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: kubernetes.io/arch
                operator: In
                values: ["amd64"]
              - key: node.kubernetes.io/instance-type
                operator: NotIn
                values: ["t3.nano"]
      # Мягкое предпочтение
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 80
          preference:
            matchExpressions:
              - key: zone
                operator: In
                values: ["us-east-1a"]
        - weight: 20
          preference:
            matchExpressions:
              - key: zone
                operator: In
                values: ["us-east-1b"]

Операторы: In, NotIn, Exists, DoesNotExist, Gt, Lt.

Совместное использование

Типичный паттерн для выделенных GPU-узлов:

# На узле:
# kubectl taint nodes gpu-node-1 nvidia.com/gpu=present:NoSchedule
# kubectl label nodes gpu-node-1 hardware=gpu

spec:
  tolerations:
    - key: "nvidia.com/gpu"
      operator: "Exists"
      effect: "NoSchedule"
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: hardware
                operator: In
                values: ["gpu"]

Toleration позволяет поду попасть на GPU-узел. nodeAffinity гарантирует, что под пойдёт именно туда, а не на любой другой узел.

Подводные камни

  • Toleration без affinity не гарантирует попадание на нужный узел. Под просто может попасть туда, если свободно. Для гарантии нужна пара taint + affinity.
  • NoExecute выселяет поды немедленно. При добавлении taint NoExecute существующие поды без toleration получают SIGTERM. Используйте tolerationSeconds для graceful period.
  • IgnoredDuringExecution. Affinity проверяется только при планировании. Если labels узла изменились после запуска пода — под не выселяется. Для этого нужен RequiredDuringExecution (beta в K8s 1.27).
  • Системные taints. При недоступности узла K8s автоматически добавляет node.kubernetes.io/unreachable:NoExecute и node.kubernetes.io/not-ready:NoExecute. Системные поды (CoreDNS) имеют toleration с большим tolerationSeconds.
  • podAffinity/podAntiAffinity — дорогая операция. При тысячах подов scheduler тратит значительное время на вычисление affinity. Используйте topologySpreadConstraints как более эффективную альтернативу.
  • Конфликт с Cluster Autoscaler. Если taint+affinity требует узел с конкретными labels, Autoscaler должен знать об этих labels в node group конфигурации — иначе не создаст нужный узел.

Common mistakes

  • Путать taint и label
  • Считать toleration обязательным placement
  • Не знать effects

What the interviewer is testing

  • Различает repel и attract
  • Понимает combination pattern
  • Знает NoSchedule и NoExecute

Sources

Related topics