Выберите продукт

Kubernetes: Pod stuck Pending — разбор scheduling, taints/tolerations и ограничений ресурсов

Если Pod долго висит в Pending, чаще всего проблема в планировании: нет подходящей ноды, мешают taints, не совпал nodeSelector/affinity или не хватает CPU/памяти по requests. Ниже — короткий чеклист диагностики и типовые правки манифестов.
Kubernetes: Pod stuck Pending — разбор scheduling, taints/tolerations и ограничений ресурсов

Что значит Pod Pending и почему это не «ошибка запуска»

Статус Pending означает, что Pod создан в API, но ещё не назначен на конкретную ноду или не перешёл к фактическому запуску. В большинстве реальных инцидентов он даже не доходит до pull образа: проблема возникает на шаге scheduling, когда планировщик (обычно default-scheduler) выбирает узел.

Важно быстро разделить два сценария:

  • Pod не назначен на ноду — поле .spec.nodeName пустое. Это «чистая» история про scheduling: taints, affinity/selector, requests, квоты.
  • Pod назначен на ноду, но не стартует — это часто уже не про scheduling (тома, сеть, образ), хотя статус всё ещё может оставаться Pending. Ниже фокус на первом сценарии, как на самом частом.

Быстрый чеклист диагностики (10 минут до корня)

1) Сразу смотрим события Pod

Почти всегда ответ уже есть в Events: планировщик прямо пишет, почему Pod не подходит ни одной ноде.

kubectl get pod -n NAMESPACE PODNAME -o wide
kubectl describe pod -n NAMESPACE PODNAME

Ищите блок Events и строки вида:

0/3 nodes are available: 3 Insufficient cpu.
0/3 nodes are available: 1 node(s) had taint {key: value}, that the pod didn't tolerate.
0/3 nodes are available: 2 node(s) didn't match Pod's node affinity/selector.

Если Events «пустые» или хотите смотреть их потоково по namespace:

kubectl get events -n NAMESPACE --sort-by=.lastTimestamp

2) Проверяем, на какие ноды вообще можно попасть

kubectl get nodes -o wide
kubectl describe node NODENAME

В describe node нас интересуют: Labels, Taints, состояние (Ready/NotReady), а также Allocated resources — сколько CPU/памяти уже «зарезервировано» запросами.

3) Сверяем требования Pod: selector/affinity, tolerations, requests

kubectl get pod -n NAMESPACE PODNAME -o yaml

Смотрите разделы spec.nodeSelector, spec.affinity, spec.tolerations и resources.requests у контейнеров.

Начинайте с одной строки из Events. Формат «из N нод доступны 0: …» — это не “шум”, а самая короткая дорожка к причине.

Вывод kubectl describe pod с событиями планировщика

Причина №1: Insufficient cpu / memory из-за resource requests

Планировщик опирается не на фактическое потребление, а на requests. Если вы запросили 2 CPU, Pod запланируется только на ноду, где по расчёту осталось минимум 2 CPU свободных по сумме requests всех уже размещённых Pod.

Типичный кейс: кластер «живой», мониторинг показывает низкое реальное потребление CPU, но Pod висит в Pending с Insufficient cpu. Это означает, что CPU уже зарезервирован requests’ами других Pod.

Как быстро подтвердить

Смотрите Events Pod и «Allocated resources» на ноде:

kubectl describe pod -n NAMESPACE PODNAME
kubectl describe node NODENAME

В выводе ноды будет примерно так:

Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests   Limits
  cpu                3900m (97%)  6000m (150%)
  memory             7Gi (88%)    10Gi (125%)

Если requests по CPU/памяти близки к 100% — планировщик честно не видит места.

Что делать

  • Скорректировать requests под реальную потребность (особенно у фоновых/батч задач).
  • Добавить ёмкость: больше нод или больше ресурсов на нодах.
  • Подумать про HPA/VPA, но сначала убрать заведомо завышенные requests.

Если вы упёрлись в «не хватает железа», проще планировать расширение пула заранее. Частый путь для продакшена — вынести ресурсоёмкие компоненты на отдельные VDS (под ваш контролируемый нод-пул) и масштабировать кластер предсказуемо.

Пример: аккуратные requests/limits

resources:
  requests:
    cpu: "200m"
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

Если requests не заданы, планировщик считает их нулевыми — Pod легко «помещается», но дальше повышается риск нестабильности на ноде и eviction при давлении ресурсов.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Причина №2: LimitRange «подкладывает» requests/limits, и вы не замечаете

Pod может попасть в Pending из-за requests, которые вы явно не задавали. Виновник — объект LimitRange в namespace: он способен проставить default requests/limits контейнерам при создании, если вы их не указали.

Как проверить LimitRange

kubectl get limitrange -n NAMESPACE
kubectl describe limitrange -n NAMESPACE LIMITRANGENAME

Затем сравните с тем, что реально оказалось в Pod после admission:

kubectl get pod -n NAMESPACE PODNAME -o jsonpath='{.spec.containers[*].resources}'

Если вы ожидали «без requests», а получили, например, cpu: 1000m — объяснение найдено.

Типичные решения

  • Явно задайте разумные resources.requests в Deployment/StatefulSet.
  • Пересмотрите политику LimitRange: дефолты должны соответствовать большинству workload в namespace (и не быть «на всякий случай в 1 CPU»).

Причина №3: ResourceQuota не даёт создать или масштабировать Pod

ResourceQuota ограничивает суммарное потребление ресурсов в namespace: CPU, память, количество Pod, PVC и т. д. При превышении Kubernetes может запретить создание новых Pod или требовать обязательные requests/limits.

Как распознать

Иногда это видно в Events Pod, но чаще — по факту того, что ReplicaSet/Deployment не может создать Pod (поэтому важно смотреть describe у контроллеров).

kubectl get resourcequota -n NAMESPACE
kubectl describe resourcequota -n NAMESPACE QUOTANAME
kubectl describe deploy -n NAMESPACE DEPLOYNAME
kubectl describe rs -n NAMESPACE RSNAME

Ищите сообщения про превышение лимита или требование указать requests/limits.

Что делать

  • Увеличить квоту (если вы владелец платформы).
  • Уменьшить requests/limits или число реплик.
  • Разнести нагрузку по namespace, если политика это допускает.

Причина №4: taints и отсутствующие tolerations

Taints на ноде говорят: «сюда нельзя, если Pod не умеет это терпеть». Tolerations в Pod — явное разрешение. Это частый источник Pending в production: выделенные ноды, системные ноды, GPU-пулы, spot/preemptible и т. п.

Как увидеть taints на нодах

kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
kubectl describe node NODENAME

Событие планировщика часто выглядит так:

0/5 nodes are available: 2 node(s) had taint {dedicated: infra}, that the pod didn't tolerate.

Как правильно добавить toleration

Если политика действительно разрешает запуск на tainted-нодах, добавьте toleration. Пример для taint dedicated=infra:NoSchedule:

tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "infra"
    effect: "NoSchedule"

Важное различие эффектов:

  • NoSchedule — не планировать новые Pod без toleration.
  • PreferNoSchedule — по возможности не планировать, но допускается при отсутствии альтернатив.
  • NoExecute — дополнительно выселяет уже запущенные Pod без toleration (с возможным grace через tolerationSeconds).

Не лечите Pending добавлением toleration «на всё подряд». Это ломает изоляцию пулов и быстро превращает кластер в «всё запускается везде».

Причина №5: nodeSelector, nodeName и несовпадающие labels

nodeSelector — жёсткое требование: Pod должен попасть на ноду с указанными label. Ошибка в ключе/значении, смена label после апгрейда или перенос кластера — и вы получаете 0 подходящих нод.

Диагностика

kubectl describe pod -n NAMESPACE PODNAME
kubectl get nodes --show-labels
kubectl get node NODENAME -o jsonpath='{.metadata.labels}'

В Events будет что-то вроде:

0/4 nodes are available: 4 node(s) didn't match Pod's node affinity/selector.

Типовой пример nodeSelector

nodeSelector:
  nodepool: "apps"

Проверьте, что на целевых нодах label действительно есть и совпадает по регистру и значению.

Причина №6: nodeAffinity (required vs preferred) и «слишком умные» правила

affinity даёт более гибкие условия, чем nodeSelector. Но из-за гибкости там часто прячутся ошибки: неверные matchExpressions, оператор In с пустым списком, или «required»-правила, которым в реальности не соответствует ни одна нода.

Проверьте requiredDuringSchedulingIgnoredDuringExecution

Если вы используете requiredDuringSchedulingIgnoredDuringExecution, это железобетонное условие. Для отладки иногда полезно временно перевести часть требований в preferredDuringSchedulingIgnoredDuringExecution, чтобы Pod хотя бы запланировался и вы подтвердили гипотезу.

Пример: required affinity по архитектуре и пулу

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
            - key: "kubernetes.io/arch"
              operator: "In"
              values: ["amd64"]
            - key: "nodepool"
              operator: "In"
              values: ["apps"]

Сверьте labels на нодах: если у части нод ключ nodepool отсутствует или называется иначе — Pod будет в Pending бесконечно.

Схема labels и taints нод для понимания правил размещения

Причина №7: PodAntiAffinity / topology spread — вы сами запретили размещение

Ограничения на «не рядом» (anti-affinity) и распределение по зонам/нодам полезны, но могут привести к тупику: например, когда у вас всего 2 ноды, а правило требует 3 разных хоста. В Events это обычно выглядит как «didn't match pod anti-affinity rules» или «didn't match topology spread constraints».

Проверяйте:

  • Количество нод/зон против желаемой топологии.
  • Правильность label selector, по которому считаются «соседи».
  • Жёсткость правила: required vs preferred.

Причина №8: nodeName уже задан (и это ловушка)

Если в Pod явно прописан spec.nodeName, планировщик не выбирает ноду — вы уже выбрали её сами. Любые проблемы с ресурсами, taints или доступностью этой ноды превращаются в «вечный Pending» без шанса переехать.

Проверьте:

kubectl get pod -n NAMESPACE PODNAME -o jsonpath='{.spec.nodeName}'

Если поле заполнено — разберите, кто его выставил (ручной манифест, кастомный контроллер) и можно ли убрать эту привязку.

Глубже: как читать сообщение планировщика «0/N nodes are available»

Сообщения в Events обычно суммируют причины по нодам. Удобный приём — выписать причины и пройтись по ним по порядку, после каждой правки заново читая Events (причина часто «переключается» на следующую):

  1. Insufficient cpu/memory — правим resources.requests или добавляем ёмкость.
  2. didn't match affinity/selector — сверяем labels и правила nodeSelector/affinity.
  3. had taint … that the pod didn't tolerate — добавляем точечные tolerations или используем правильный пул нод.
  4. exceeded quota — смотрим ResourceQuota и контроллер (Deployment/RS).

Мини-шпаргалка: что править в манифесте в зависимости от причины

Insufficient cpu

  • Снизить resources.requests.cpu (если завышен).
  • Уменьшить число реплик.
  • Добавить ноды или увеличить ресурсы нод.

Taints/Tolerations

  • Добавить точечный tolerations под конкретный taint.
  • Либо убрать taint с ноды (если он лишний), но делайте это осознанно.

nodeSelector / affinity

  • Исправить ключ/значение label.
  • Заменить слишком жёсткий required на preferred (где допустимо).
  • Проверить, что label реально присутствует на целевых нодах.

LimitRange / ResourceQuota

  • Понять, какие requests/limits выставляются автоматически.
  • Согласовать дефолты в namespace с реальными профилями приложений.
  • Пересчитать квоты под рост нагрузки.

Практический сценарий: «Pod Pending, а в кластере CPU вроде много»

Самый частый кейс из эксплуатации:

  • Pod в Pending.
  • Events: Insufficient cpu.
  • Мониторинг показывает низкую загрузку CPU по факту.

Алгоритм:

  1. Открыть kubectl describe node и посмотреть «Allocated resources» по Requests.
  2. Найти workload’ы с завышенными requests (обычно старые «на всякий случай»).
  3. Снижать requests безопасно: сначала небольшое уменьшение и наблюдение, затем доведение до целевого профиля.

В качестве ориентира для планирования ёмкости и типовых профилей ресурсов может пригодиться материал про выбор конфигурации сервера: как прикинуть CPU и RAM под нагрузку.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Заключение

Pod в Pending — почти всегда прозрачная история, если дисциплинированно читать Events и понимать механику scheduling: фильтры по ресурсам (requests), ограничения по размещению (nodeSelector/affinity), изоляция пулов через taints/tolerations, и политики namespace (LimitRange, ResourceQuota).

Держите цикл «Events → подтверждение на нодах → минимальная правка → снова Events». Так вы быстрее доберётесь до реальной причины и не «перелечите» кластер широкими tolerations или случайным ослаблением affinity.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: конфликт systemd-resolved DNSStubListener на 127.0.0.53 с dnsmasq, Unbound и BIND OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: конфликт systemd-resolved DNSStubListener на 127.0.0.53 с dnsmasq, Unbound и BIND

Если локальный DNS в Debian или Ubuntu не стартует с ошибкой address already in use, причина часто в systemd-resolved и DNSStubLis ...
Debian/Ubuntu: как исправить NFS mount.nfs: access denied by server while mounting OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить NFS mount.nfs: access denied by server while mounting

Ошибка mount.nfs: access denied by server while mounting в Debian и Ubuntu обычно указывает на проблему на стороне NFS-сервера: не ...
Debian/Ubuntu: как устранить конфликт systemd-resolved DNSStubListener с BIND9, dnsmasq и AdGuard Home OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как устранить конфликт systemd-resolved DNSStubListener с BIND9, dnsmasq и AdGuard Home

Если в Debian или Ubuntu DNS-сервер не стартует из-за ошибки port 53 busy, часто причина в systemd-resolved с локальным слушателем ...