Траблшутинг кластера: Pending, CrashLoop, узлы NotReady

Рейтинг: 75.8% · 12 голосов
Kubernetes для разработчиков: поды, деплойменты, сервисы, ingress, конфиги и отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
anton_k8s
Сообщения: 55
Зарегистрирован: 12 май 2026, 03:23

Траблшутинг кластера: Pending, CrashLoop, узлы NotReady

Сообщение anton_k8s »

Оглавление курса (48)
  1. Зачем нужен Kubernetes и из чего состоит кластер
  2. Поднимаем локальный кластер: minikube и kind
  3. Поды: базовая единица запуска
  4. Deployment и ReplicaSet: управляем репликами
  5. Service: сетевой доступ к подам
  6. ConfigMap и Secret: выносим конфигурацию
  7. Ingress: пускаем трафик снаружи
  8. Хранилище: Volumes и PersistentVolumeClaim
  9. Namespaces, requests и limits
  10. Health checks: liveness и readiness пробы
  11. Отладка: почему под не стартует
  12. Helm: пакетный менеджер для Kubernetes
  13. Базовая безопасность: RBAC и доступы
  14. Job и CronJob: разовые и периодические задачи
  15. StatefulSet и DaemonSet: stateful-нагрузки и системные агенты
  16. Стратегии обновления и планирование: rollout и rollback, graceful shutdown, nodeSelector, affinity, taints
  17. Автомасштабирование: HPA по метрикам, обзор VPA и Cluster Autoscaler
  18. Наблюдаемость: логи, метрики, events, обзор Prometheus и Grafana
  19. Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
  20. Архитектура control plane: api-server, etcd, scheduler
  21. Узел кластера: kubelet, kube-proxy и container runtime
  22. Декларативная модель: reconciliation, контроллеры, CRD
  23. kubectl профессионально: get, describe, explain, jsonpath
  24. Под глубже: init, sidecar, lifecycle hooks и QoS
  25. Метки, селекторы и организация ресурсов
  26. Сервисы и kube-proxy глубже: типы, IPVS, EndpointSlices
  27. Ingress, ingress-контроллеры и Gateway API
  28. Секреты в кластере: шифрование, External Secrets, Vault
  29. Хранилище глубже: PV, StorageClass, CSI и StatefulSet
  30. Сеть кластера: CNI, NetworkPolicy и CoreDNS
  31. Планировщик: affinity, taints, topology spread
  32. Ресурсы, QoS и вытеснение: requests, limits, eviction
  33. Helm глубже: шаблоны, хуки, зависимости, OCI
  34. Kustomize и управление конфигурацией без шаблонов
  35. RBAC и аутентификация глубже
  36. Admission и Pod Security: контроль на входе
  37. Policy as code: Kyverno и OPA Gatekeeper
  38. GitOps: Argo CD и Flux
  39. Операторы и CRD: расширяем Kubernetes
  40. Сервис-меш: Istio, Linkerd и когда он нужен
  41. Эксплуатация кластера: апгрейд, узлы, бэкап etcd
  42. Где запускать кластер: managed, self-hosted, k3s, Deckhouse
  43. Траблшутинг кластера: Pending, CrashLoop, узлы NotReady (вы здесь)
  44. Стоимость и эффективность кластера: FinOps
  45. CI/CD в Kubernetes: сборка, деплой, прогрессивные релизы
  46. Лучшие практики и антипаттерны Kubernetes
  47. Сквозной проект и путь дальше: от манифеста до прода
  48. Деплой приложений через Argo CD: Application, sync и App-of-Apps
Когда под не запускается, а ты не знаешь, с какого конца дёргать

Три часа ночи, алерт, под в проде висит в статусе Pending, рядом второй ушёл в CrashLoopBackOff, а ещё один узел только что стал NotReady. Паника тут плохой советчик: kubernetes troubleshooting - это не угадайка, а строгое дерево решений. Симптом виден сразу в

Код: Выделить всё

kubectl get pods
, и почти всегда он однозначно указывает, на каком слое искать причину. Pending - под ещё не назначен на узел, значит проблема в планировщике, ресурсах или томах. CrashLoopBackOff - контейнер стартует и падает, значит дело внутри: приложение, конфиг, проба или память. NotReady на узле - беда в kubelet, диске, сети или CRI на конкретной машине.

Главный принцип отладки kubernetes: не лечи симптом, найди слой. В кластере событие проходит цепочку: API-сервер принял манифест -> scheduler выбрал узел -> kubelet на узле дёрнул containerd через CRI -> containerd вытянул образ и поднял контейнер -> CNI выдал поду адрес -> kube-proxy/Cilium прописали правила для Service. Сломаться может любое звено, и каждое звено оставляет след - в events, в logs или в conditions. Твоя задача - читать эти следы, а не перезапускать наугад.

Запомни три рабочих инструмента, которыми закрывается 95 процентов разборов.

Код: Выделить всё

kubectl describe
- показывает поля объекта и, что важнее, хвост событий внизу (раздел Events).

Код: Выделить всё

kubectl logs
- стандартный вывод приложения, с флагом

Код: Выделить всё

--previous
- вывод предыдущего, уже упавшего контейнера.

Код: Выделить всё

kubectl get events
- лента того, что вообще происходило в неймспейсе. Плюс с версий 1.23+ есть

Код: Выделить всё

kubectl debug
- эфемерные контейнеры для залезания внутрь пода, у которого нет даже shell.

Изображение

Pod Pending: под назначить некуда

Pod pending почти всегда означает: scheduler не смог найти узел, который удовлетворяет требованиям пода. Первое и единственно правильное действие - смотреть события:

Код: Выделить всё

kubectl describe pod web-7d9f8 -n shop
Внизу будет блок Events. Самый частый случай - не хватает ресурсов:

Код: Выделить всё

Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  2m    default-scheduler  0/5 nodes are available:
           3 Insufficient cpu, 2 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }.
Читаем построчно. "0/5 nodes are available" - ни один из пяти узлов не подошёл. "3 Insufficient cpu" - на трёх узлах не хватает CPU под твой

Код: Выделить всё

requests.cpu
(внимание: scheduler смотрит на requests, а не на реальную загрузку и не на limits). "2 node(s) had untolerated taint control-plane" - оставшиеся два это мастера, на них taint, а у пода нет соответствующего toleration. Итог: либо снижай requests, либо добавляй узлы, либо чини nodeSelector/affinity.

Вторая частая причина Pending - PVC не привязался. Под ждёт том, тома нет:

Код: Выделить всё

Warning  FailedScheduling  pod has unbound immediate PersistentVolumeClaims
Идём смотреть сам PVC:

Код: Выделить всё

kubectl get pvc -n shop
NAME        STATUS    VOLUME   CAPACITY   STORAGECLASS   AGE
data-web    Pending                                      4m
PVC сам в Pending - значит провижионер не создал диск. Причины: неверный или несуществующий

Код: Выделить всё

storageClassName
, в облаке (Yandex Managed Kubernetes, VK Cloud) кончилась квота на диски, или CSI-драйвер лежит. Смотри

Код: Выделить всё

kubectl describe pvc data-web -n shop
- там в Events будет конкретика от провижионера.

Третья причина - сам ты затянул гайки affinity. Жёсткий

Код: Выделить всё

requiredDuringSchedulingIgnoredDuringExecution
по nodeAffinity или podAntiAffinity на узле, которого нет, даст вечный Pending. И отдельная классика: реплик больше, чем узлов, а у тебя podAntiAffinity "по одному на узел" - лишние реплики висят, и это by design.

ImagePullBackOff: контейнер не из чего создать

Под не Pending (узел назначен), но контейнер не стартует, потому что не качается образ. В

Код: Выделить всё

get pods
увидишь ImagePullBackOff или ErrImagePull. Опять describe:

Код: Выделить всё

  Warning  Failed  12s  kubelet  Failed to pull image "cr.yandex/crp.../api:v1.4":
           failed to resolve reference: unexpected status: 401 Unauthorized
401/403 - проблема доступа, в приватный реестр нужен imagePullSecret. Проверь, что секрет создан и прописан в поде:

Код: Выделить всё

kubectl create secret docker-registry regcred \
  --docker-server=cr.yandex --docker-username=json_key \
  --docker-password="$(cat key.json)" -n shop
и в spec пода -

Код: Выделить всё

imagePullSecrets: [{name: regcred}]
. Другие сигнатуры в Events: "manifest unknown" или "not found" - опечатка в теге или образа с таким тегом нет (особенно больно с , который кто-то переписал). "dial tcp ... i/o timeout" - узел физически не достучался до реестра: нет сети, закрыт фаервол, нет egress в нужный CIDR. Слово BackOff значит, что kubelet уже пробовал и теперь ждёт с экспоненциальной задержкой (до 5 минут между попытками) - не пугайся, что после фикса под не ожил мгновенно.

CrashLoopBackOff: стартует и падает по кругу

crashloopbackoff - самый пугающий с виду статус, но самый понятный по логике: контейнер запустился, процесс завершился, kubelet перезапустил, тот снова упал, и теперь kubelet ждёт перед следующей попыткой (тот же экспоненциальный backoff). Сам статус ничего не говорит о причине - причина в логах. И ключевой нюанс: к моменту, когда ты смотришь, текущий контейнер может ещё стартовать, а упал предыдущий. Поэтому:

Код: Выделить всё

kubectl logs web-7d9f8 -n shop --previous
Флаг

Код: Выделить всё

--previous
(или ) даёт вывод последнего упавшего экземпляра. Без него ты часто видишь пустоту или начало нового старта и теряешь время. Дальше - читаем, что приложение написало перед смертью. Типичные классы:
  • Бизнес-ошибка: "could not connect to postgres: connection refused", "config file not found", "panic: missing env DB_PASSWORD". Конфиг, секрет, недоступная зависимость.
  • Кривая команда: переопределил / в манифесте, опечатался в пути к бинарю - контейнер мгновенно падает с exit 127 (command not found) или 126.
  • Падение на пробах: приложение живо, но

    Код: Выделить всё

    livenessProbe
    не проходит, и kubelet сам убивает контейнер. В Events увидишь "Liveness probe failed: ... connection refused". Частая причина - проба на порт, который приложение ещё не открыло (нужен

    Код: Выделить всё

    startupProbe
    для медленного старта), или слишком короткий

    Код: Выделить всё

    initialDelaySeconds
    /

    Код: Выделить всё

    timeoutSeconds
    .
  • OOMKilled - о нём отдельно ниже.
Проверить exit code и причину последнего падения можно без логов:

Код: Выделить всё

kubectl get pod web-7d9f8 -n shop -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'
{"exitCode":1,"reason":"Error","startedAt":"...","finishedAt":"..."}
exitCode 0 - процесс завершился штатно (часто это не сервис, а скрипт, который отработал и вышел - такому место в Job, а не в Deployment). 1 - ошибка приложения, иди в logs. 127/126 - проблема с командой. 137 - SIGKILL, почти всегда нехватка памяти.

OOMKilled и код 137: память кончилась

Код 137 = 128 + 9, где 9 это SIGKILL. В Kubernetes это в подавляющем большинстве случаев OOM-killer ядра. Два сценария. Первый, частый: контейнер превысил собственный

Код: Выделить всё

resources.limits.memory
. kubelet ставит этот лимит как cgroup memory limit, и ядро убивает процесс, как только он упёрся в потолок. Видно так:

Код: Выделить всё

    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
Второй сценарий злее: памяти не хватило на всём узле, и ядро прибивает контейнеры по своему усмотрению - под мог даже не превышать свой лимит. Это уже намёк, что суммарные requests/limits на узле завышены или кто-то без лимитов сожрал всё. Лечение разное. В первом случае - либо реально оптимизировать приложение (утечка, слишком большой heap у JVM/Node, который не знает о cgroup-лимите), либо честно поднять лимит. Во втором - наводить порядок с requests на узле и не оставлять поды без лимитов памяти. Текущее потребление смотри так:

Код: Выделить всё

kubectl top pod -n shop
NAME          CPU(cores)   MEMORY(bytes)
web-7d9f8     45m          498Mi
Если 498Mi при лимите 512Mi - ты на грани, следующий всплеск убьёт под. Важно про память: это несжимаемый ресурс. Превысил CPU-лимит - тебя притормозят (throttling), и под живёт. Превысил memory-лимит - убьют без разговоров. Поэтому к лимитам памяти относись аккуратнее, чем к CPU.

Узел NotReady: машина выпала из обоймы

node notready - это здоровье конкретной ноды, а не отдельного пода. Сначала общая картина и conditions:

Код: Выделить всё

kubectl get nodes
NAME       STATUS     ROLES    AGE   VERSION
worker-3   NotReady   <none>   40d   v1.31.4

kubectl describe node worker-3
Смотри блок Conditions. Если все условия (MemoryPressure, DiskPressure, Ready) в состоянии Unknown - значит kubelet перестал слать heartbeat, узел для control-plane "пропал". Чаще всего сам kubelet лёг или потерял связь с API. Если

Код: Выделить всё

DiskPressure=True
или

Код: Выделить всё

MemoryPressure=True
- сработал eviction manager, узел в защите выгоняет поды. Дальнейшая диагностика - уже на самой ноде по SSH:

Код: Выделить всё

systemctl status kubelet
journalctl -u kubelet -n 200 --no-pager
Что искать в логах kubelet (это прямо рабочие сигнатуры): "PLEG is not healthy" - kubelet не достучался до CRI, обычно лежит containerd. "Container runtime network not ready" или "FailedCreatePodSandBox" с именем CNI - сломана сеть, нет валидного конфига в

Код: Выделить всё

/etc/cni/net.d/
или бинарей в

Код: Выделить всё

/opt/cni/bin/
. "no space left on device" / DiskPressure - забит диск под образы и логи в

Код: Выделить всё

/var/lib/containerd
и

Код: Выделить всё

/var/lib/kubelet
. "certificate has expired" - протух клиентский серт kubelet (классика на самосборных и долгоживущих кластерах). Быстрые проверки на ноде:

Код: Выделить всё

systemctl status containerd
df -h /var/lib/containerd /var/lib/kubelet
crictl ps          # что реально крутится в CRI
crictl rmi --prune # подчистить мусорные образы, если забит imagefs
Если диск забит образами - настрой ротацию логов (

Код: Выделить всё

containerLogMaxSize
,

Код: Выделить всё

containerLogMaxFiles
в конфиге kubelet) и garbage collection образов, иначе нода будет залипать снова. В managed-кластерах (Yandex, VK Cloud, Deckhouse) на ноду часто не зайти по SSH - тогда смотри Events узла, метрики и пересоздавай ноду через node group; в k3s глянь

Код: Выделить всё

systemctl status k3s-agent
.

DNS не резолвит и Service без endpoints

Под живой, но "не видит" соседей - почти всегда это DNS или сервисная связность. Быстрая проверка изнутри пода:

Код: Выделить всё

kubectl exec -it web-7d9f8 -n shop -- nslookup postgres.shop.svc.cluster.local
Если не резолвит - проверь, жив ли CoreDNS:

Код: Выделить всё

kubectl get pods -n kube-system -l k8s-app=kube-dns
. Отдельная коварная штука - ndots. По умолчанию в поде , и любое имя с меньшим числом точек сначала перебирается через search-домены (svc.cluster.local, cluster.local и т.д.) и только потом резолвится как есть. Внешний хост типа

Код: Выделить всё

api.partner.ru
(две точки, меньше 5) породит пачку лишних запросов и тормоза, пока не дойдёт до прямого резолва. Лечится либо точкой в конце (FQDN:

Код: Выделить всё

api.partner.ru.
), либо

Код: Выделить всё

dnsConfig
с меньшим ndots для конкретного пода.

Теперь Service без endpoints - топовая причина "сервис есть, а 503/connection refused". Проверяй цепочку:

Код: Выделить всё

kubectl get endpointslices -n shop -l kubernetes.io/service-name=postgres
NAME             ADDRESSTYPE   ENDPOINTS   PORTS
postgres-abc12   IPv4          <none>      5432
ENDPOINTS пусто - Service ни на кого не указывает. В 90 процентах случаев

Код: Выделить всё

selector
в Service не совпадает с подов (опечатка, разный регистр, лишний лейбл). Сверь:

Код: Выделить всё

kubectl get svc postgres -n shop -o jsonpath='{.spec.selector}'
kubectl get pods -n shop --show-labels
Вторая причина пустых endpoints - поды есть и матчатся, но не Ready (не прошли readinessProbe): в EndpointSlice попадают только готовые поды, это и есть смысл readiness. Чини пробу - вернутся endpoints. Если же endpoints на месте, а трафик не ходит между подами - подозревай NetworkPolicy. Политики работают по принципу "запретить лишнее": как только на под навесили хоть одну Ingress-политику, разрешено только то, что в ней явно описано, остальное режется. Смотри

Код: Выделить всё

kubectl get netpol -n shop
и проверь, что нужный источник разрешён. Если же политик нет, а связности всё равно нет - копай CNI (Cilium/Calico): глянь его поды в kube-system и его логи. На Cilium удобно

Код: Выделить всё

cilium connectivity test
.

Грабли и антипаттерны
  • logs без --previous при CrashLoop - смотришь живой контейнер вместо упавшего и не видишь ошибку.
  • Перезапуск как первая реакция. delete pod иногда "помогает" (под уехал на другой узел), но маскирует причину - завтра рванёт снова.
  • Чтение симптома вместо Events. CrashLoopBackOff/ImagePullBackOff - это не диагноз, а класс. Диагноз всегда в Events и logs.
  • Liveness вместо readiness. Повесил агрессивный livenessProbe на медленный старт - kubelet убивает приложение в цикле, и ты получаешь рукотворный CrashLoop. Для старта - startupProbe, для "не лей трафик пока" - readiness.
  • Поды без limits памяти. Один такой под на узле способен утащить в OOM соседей и сделать ноду NotReady по MemoryPressure.
  • requests == limits перепутаны со смыслом. Scheduler планирует по requests; поставил их в ноль - набьёшь узел и поймаешь node-level OOM.
Мини-лаба: воспроизведи и почини

Возьми любой dev-кластер (kind, minikube, k3s) и пройди руками:
  • Создай Deployment с

    Код: Выделить всё

    requests.cpu: "100"
    (сто ядер). Получи Pending, найди в

    Код: Выделить всё

    describe pod
    строку "Insufficient cpu". Снизь до 100m - под взлетит.
  • Укажи образ

    Код: Выделить всё

    nginx:nosuchtag
    . Поймай ImagePullBackOff, найди "manifest unknown" в Events. Поправь тег.
  • Запусти контейнер с

    Код: Выделить всё

    command: ["sh","-c","echo bye; exit 1"]
    . Получи CrashLoopBackOff, прочитай

    Код: Выделить всё

    logs --previous
    , проверь exitCode через jsonpath.
  • Поставь

    Код: Выделить всё

    resources.limits.memory: "16Mi"
    приложению, которое жрёт больше. Поймай Reason: OOMKilled и Exit Code 137 в describe.
  • Создай Service с селектором

    Код: Выделить всё

    app: wrong
    . Убедись, что EndpointSlice пустой. Поправь селектор - endpoints появятся.
Контрольные вопросы
  • Под висит в Pending. Какая первая команда и какой раздел её вывода ты читаешь? Назови три типовые причины.
  • Чем отличается

    Код: Выделить всё

    kubectl logs
    от

    Код: Выделить всё

    kubectl logs --previous
    и почему второй критичен при CrashLoopBackOff?
  • Что означает Exit Code 137, чем container-level OOM отличается от node-level и почему память - несжимаемый ресурс?
  • Service есть, а трафик идёт в 503. Как за две команды проверить, что проблема в селекторе или в readiness?
Итог

Отладка kubernetes сводится к дисциплине: симптом из

Код: Выделить всё

get pods
/

Код: Выделить всё

get nodes
определяет слой, а слой определяет инструмент. Pending - planner и ресурсы/тома, читаем Events. ImagePull - реестр и креды. CrashLoop - logs --previous и exit code. 137 - память. NotReady - kubelet, диск, CNI на самой ноде. DNS и пустые endpoints - селекторы, readiness и NetworkPolicy. Запомни связку describe -> events -> logs -> debug, и большинство ночных инцидентов перестанут быть страшными.
👍2 ❤️3 🔥 😄 🤔
Аватара пользователя
mont33yy
Сообщения: 1
Зарегистрирован: 29 май 2026, 08:44

Re: Траблшутинг кластера: Pending, CrashLoop, узлы NotReady

Сообщение mont33yy »

Поймал на себе грабли с logs без --previous - реально смотрел живой контейнер и не понимал почему лог пустой. Теперь всегда -p ставлю, спасибо
👍 ❤️1 🔥 😄 🤔1
Аватара пользователя
tormain
Сообщения: 1
Зарегистрирован: 03 июн 2026, 02:40

Re: Траблшутинг кластера: Pending, CrashLoop, узлы NotReady

Сообщение tormain »

Вопрос: а если узел NotReady в Yandex Managed, по SSH не зайти. Что смотреть кроме describe node и events? Метрики дисков узла откуда брать правильнее?
👍1 ❤️3 🔥 😄 🤔
Ответить
← Предыдущая глава
Где запускать кластер: managed, self-hosted, k3s, Deckhouse
Следующая глава →
Стоимость и эффективность кластера: FinOps

Все главы курса «Kubernetes: оркестрация контейнеров от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Ошибки nginx 403, 404, 504: диагностикаОшибка nginx 502 Bad Gateway: причины и решение

Вернуться в «Kubernetes на практике»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость