Жизненный цикл пода в Kubernetes: фазы, статусы контейнеров и диагностика переходов | AdminWiki

Жизненный цикл пода в Kubernetes: фазы, статусы контейнеров и диагностика переходов

03 октября 2026 15 мин. чтения

Что такое жизненный цикл пода и зачем его понимать

Жизненный цикл пода в Kubernetes состоит из пяти фаз: Pending, Running, Succeeded, Failed и Unknown. Их хранит поле status.phase в объекте Pod. Каждый контейнер внутри пода живёт по своему циклу и имеет один из трёх статусов: Waiting, Running или Terminated, которые лежат в status.containerStatuses[].state. Фаза описывает под целиком, статус описывает отдельный процесс внутри него.

Отсюда главный практический вывод: под в фазе Running может не обслуживать ни одного запроса. Типовая картина - STATUS: CrashLoopBackOff при нулевом READY и растущем RESTARTS. Чтобы поставить диагноз за минуту, смотрите не на одну колонку, а на связку: фаза пода, статус контейнера, условия (Conditions) и события (Events).

Порядок разбора дальше такой: пять фаз и условия переходов, затем три статуса контейнеров с полями Reason и ExitCode, затем практика чтения kubectl get pods и kubectl describe pod, и в конце два пошаговых алгоритма для Pending и CrashLoopBackOff плюс готовый чек-лист.

Примеры вывода и названия Conditions сверяйте со своей версией: kubectl version. Отдельные поля статуса и условия меняются между релизами, поэтому детали в describe pod могут отличаться.

Фазу меняют компоненты кластера: kube-scheduler выбирает узел и выставляет условие PodScheduled, kubelet на узле создаёт контейнеры через CRI (containerd, CRI-O) и обновляет их статусы, kube-apiserver отдаёт результат в kubectl. Полный путь от манифеста до запущенного процесса разобран в материале архитектура Kubernetes: компоненты кластера и путь пода от манифеста до запуска.

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

Чем фаза пода отличается от статуса контейнера

Разница видна и в API, и в выводе команд. Фаза читается из status.phase, статус контейнера - из status.containerStatuses[].state. Наборы значений не пересекаются.

ПризнакФаза подаСтатус контейнера
Поле в APIstatus.phasestatus.containerStatuses[].state
ОхватПод целикомОдин контейнер
ЗначенияPending, Running, Succeeded, Failed, UnknownWaiting, Running, Terminated
Кто обновляетkubelet, назначение узла отражает kube-schedulerkubelet на основе ответов container runtime
Что видно в kubectl get podsКолонка STATUSКолонки READY и RESTARTS
О чём говоритЖивёт ли под и на каком этапе он застрялЧто делает конкретный процесс и почему он остановился

Колонка STATUS хитрее, чем кажется. kubectl печатает в ней status.phase, но если у контейнера есть более информативная причина ожидания или завершения, на экран попадёт именно она: CrashLoopBackOff, ImagePullBackOff, Completed, Error, OOMKilled. Поэтому STATUS не всегда равен полю phase.

NAME                      READY   STATUS             RESTARTS       AGE
web-7c9d4f8b6d-4xk2p      0/1     CrashLoopBackOff   6 (54s ago)    5m12s

Здесь под находится в фазе Running: узел назначен, контейнер создан. При этом контейнер в статусе Waiting с reason CrashLoopBackOff, приложение не работает, RESTARTS уже 6, READY равен 0/1. Проверка занимает секунду:

kubectl get pod web-7c9d4f8b6d-4xk2p -o jsonpath='{.status.phase}'

Команда вернёт Running. Если смотреть только на неё, проблема останется незамеченной.

Фазы пода: Pending, Running, Succeeded, Failed, Unknown

Таблица ниже - шпаргалка по пяти фазам: что означает каждая, что печатает kubectl и какие записи появляются в событиях.

ФазаЧто означаетЧто видно в kubectl get podsХарактерные события
PendingПод принят API-сервером, ни один контейнер ещё не запущен: идёт планирование, скачивание образов или ожидание томаSTATUS: Pending, READY: 0/1FailedScheduling, Pulling, FailedMount, FailedAttachVolume
RunningПод привязан к узлу, контейнеры созданы, минимум один работает или запускаетсяSTATUS: Running, READY зависит от пробScheduled, Pulled, Created, Started, Unhealthy, BackOff
SucceededВсе контейнеры завершились с кодом 0, перезапуска не будетSTATUS: CompletedCompleted
FailedВсе контейнеры завершились, минимум один с ненулевым кодом или убит системойSTATUS: ErrorError, BackOff
UnknownСтатус пода не удалось получить, обычно из-за недоступности узлаSTATUS: UnknownNodeNotReady, ошибки связи с kubelet

Две последние строки объясняют частую путаницу: в kubectl фаза Succeeded печатается как Completed, а Failed - как Error, тогда как в API остаются исходные значения. Проверить связь легко: kubectl get pods --field-selector=status.phase=Failed покажет те же поды, что визуально помечены Error.

Pending: почему под не запускается

Pending - это ожидание, и причины делятся на три группы. Планировщик не нашёл подходящий узел. Узел найден, но kubelet не может скачать образ или подключить том. Под ждёт внешний ресурс: несвязанный PersistentVolumeClaim, освобождение квоты namespace.

kubectl describe pod $POD
kubectl get events --sort-by=.metadata.creationTimestamp

В секции Events ищите такие записи:

FailedScheduling: 0/3 nodes are available: 3 Insufficient cpu, 1 node(s) had untolerated taint
Pulling image "registry.example.com/web:1.8.2"
FailedMount: MountVolume.SetUp failed for volume "config" : configmap "web-config" not found
Scheduling: pod has unbound immediate PersistentVolumeClaims

Типовые причины Pending: суммарные requests подов выше свободных ресурсов на всех узлах; на узле стоит taint без подходящего toleration; nodeSelector или nodeAffinity не совпадают ни с одним узлом; PVC не перешёл в Bound; namespace исчерпал квоту (событие exceeded quota).

Пример арифметики: узел с 8 CPU allocatable, на нём уже зарезервировано 6 CPU через requests. Под с requests.cpu: 4 останется в Pending, даже если фактическая загрузка приложений куда меньше. Планировщик считает requests, а не реальное потребление.

Running: под работает, но это не значит, что всё хорошо

Фаза Running означает только одно: под привязан к узлу, все контейнеры созданы, минимум один запущен или запускается. Готовность приложения к трафику описывает условие Ready, и оно зависит от readinessProbe. Пока проба не проходит, под исключён из эндпоинтов Service, хотя процесс жив.

Что смотреть: колонку READY (1/1 значит все контейнеры готовы), счётчик RESTARTS и условие ContainersReady в describe pod. Рост RESTARTS при STATUS: Running говорит, что приложение падает и поднимается. Разбор ситуации «поды Running, а Service не отвечает» с проверкой endpoints, селекторов и проб собран в статье поды недоступны через Service: пошаговый разбор.

kubectl get pods -o wide
kubectl describe pod $POD
kubectl logs $POD --tail=50

Succeeded и Failed: завершение работы пода

Эти фазы появляются там, где контейнеры не перезапускаются бесконечно. Succeeded: все контейнеры вышли с кодом 0, под остаётся в кластере как история выполнения. Failed: хотя бы один контейнер завершился с ненулевым кодом или был убит системой. Обе фазы характерны для Job и CronJob с restartPolicy: OnFailure или Never.

Для Deployment, StatefulSet и DaemonSet действует restartPolicy: Always. При ней под не задерживается в Succeeded или Failed: kubelet перезапускает контейнер, и на экране вы видите Running, CrashLoopBackOff или Error уже в статусе контейнера. Разницу между контроллерами и их объектами API разбираем в статье архитектура кластера Kubernetes и ключевые объекты API.

kubectl get pods --field-selector=status.phase=Succeeded
kubectl get pods --field-selector=status.phase=Failed
kubectl get jobs

Для упавшей Job полезны kubectl logs job/my-job и kubectl describe job my-job: в describe видно, сколько попыток сделал контроллер и на какой из них он остановился.

Unknown: потеря связи с узлом

Фаза Unknown появляется, когда kube-apiserver не получает свежий статус пода: узел недоступен по сети, kubelet не отвечает или машина выключилась. Под может продолжать работать на узле, но кластер больше не знает его состояние.

kubectl get nodes
kubectl describe node $NODE
kubectl get pods -o wide

Флаг -o wide показывает последний известный узел пода, что помогает локализовать проблему за один шаг. Если узел не вернётся, сработает стандартный toleration node.kubernetes.io/unreachable с tolerationSeconds: 300, после чего контроллер создаст замену на другом узле. Дальше выбор простой: ждать пять минут или разбираться с узлом руками, если он должен вернуться.

Статусы контейнеров: Waiting, Running, Terminated

Статус контейнера лежит в status.containerStatuses[].state и содержит три возможных поля: waiting, running, terminated. В каждый момент времени заполнено ровно одно. У каждого статуса есть reason, а у terminated ещё exitCode и signal.

СтатусПоляЧто означаетТипичные reason
Waitingstate.waiting.reason, state.waiting.messageКонтейнер ещё не запущен или перезапускаетсяContainerCreating, ImagePullBackOff, ErrImagePull, CrashLoopBackOff, CreateContainerConfigError
Runningstate.running.startedAtПроцесс запущен-
Terminatedstate.terminated.reason, exitCode, signal, startedAt, finishedAtПроцесс завершилсяCompleted, Error, OOMKilled, ContainerCannotRun

Waiting: ContainerCreating, ImagePullBackOff, CrashLoopBackOff

Waiting не всегда проблема. ContainerCreating - нормальное состояние на старте: kubelet готовит sandbox, монтирует тома, скачивает образ. Затянувшийся ContainerCreating указывает на сбой в CRI, томах или сети. Остальные причины читаются по полю Reason:

  • ContainerCreating - создание контейнера в процессе. Тянется минутами: проверьте FailedMount и события узла.
  • ErrImagePull и ImagePullBackOff - образ не скачивается: опечатка в имени или теге, нет доступа к registry, истёк или не указан imagePullSecret. ErrImagePull появляется первым, затем сменяется ImagePullBackOff по мере роста задержки повторов.
  • CrashLoopBackOff - контейнер стартовал и упал, kubelet перезапускает его с растущим интервалом.
  • CreateContainerConfigError - нет ConfigMap или Secret, на которые манифест ссылается через env или envFrom.
kubectl describe pod $POD
kubectl get events --field-selector=involvedObject.name=$POD --sort-by=.metadata.creationTimestamp

В describe pod смотрите блок Containers: для каждого контейнера там указаны State, Ready, Restart Count, Image и Last State. Именно Last State хранит причину предыдущего падения, когда текущее состояние уже сменилось на Running.

Running: что проверять, если контейнер работает

Статус Running заполняет только startedAt: время старта процесса. Он ничего не говорит о том, отвечает ли приложение и проходит ли readinessProbe. Проверяйте три вещи: готовность в колонке READY, ошибки в логах и ответ приложения изнутри кластера.

kubectl logs $POD
kubectl logs $POD -f --tail=100
kubectl exec -it $POD -- sh
kubectl port-forward $POD 8080:8080

Для пода с несколькими контейнерами указывайте имя: kubectl logs $POD -c имя-контейнера. Флаг -f удобен при разборе старта, когда нужно поймать первую ошибку, а не читать историю задним числом. Если приложение отвечает внутри пода, но недоступно снаружи, ищите причину в Service, endpoints или NetworkPolicy.

Terminated: ExitCode и Reason как ключ к причине

Terminated заполняется после остановки процесса и содержит reason, exitCode и signal. Код выхода объясняет, кто и почему завершил контейнер.

ExitCodeSignalЗначениеЧто делать
0-Успешное завершениеДля Job это норма; у долгоживущего сервиса проверьте, почему завершился main-процесс
1-Общая ошибка приложенияЛоги текущего и предыдущего запуска, конфигурация, зависимости
2-Ошибка использования shellПроверьте command и args: кавычки, пробелы, отсутствующие утилиты
126-Файл есть, но не исполняетсяПрава на бинарник, shebang в скрипте
127-Команда не найденаПуть в PATH, опечатка в entrypoint
137SIGKILL (9)Контейнер убит принудительно, чаще всего OOMKilledЛимит limits.memory, утечка памяти, размер heap
143SIGTERM (15)Штатное завершение или evictionПроверьте, кто снял под: rollout, eviction, ручной delete

Правило перевода простое: 128 плюс номер сигнала. 137 = 128 + 9 (SIGKILL), 143 = 128 + 15 (SIGTERM). Комбинация reason: OOMKilled и exitCode: 137 означает, что контейнер перешёл limits.memory и был убит ядром.

Когда контейнер уже перезапустился, логи текущего запуска пусты или содержат только строку приветствия. Тогда читайте логи предыдущего запуска:

kubectl logs $POD --previous
kubectl describe pod $POD

В describe pod блок Last State покажет Reason: OOMKilled, Exit Code: 137 и время старта с завершением. Точка отсчёта для разбора найдена.

Как читать kubectl get pods и kubectl describe pod

Вывод kubectl get pods состоит из пяти колонок: NAME, READY, STATUS, RESTARTS, AGE. READY показывает готовые контейнеры к общему числу (1/1, 0/1, 2/2), STATUS - фазу или более информативную причину, RESTARTS - счётчик перезапусков с временем последнего в скобках, AGE - возраст пода.

Полезные флаги и фильтры kubectl get pods

kubectl get pods -A -o wide
kubectl get pods --field-selector=status.phase=Pending
kubectl get pods --sort-by=.status.startTime
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.status.containerStatuses[*].restartCount}{"\n"}{end}'
  • -A или --all-namespaces - все namespace сразу, выручает когда неизвестно, где живёт проблемный сервис.
  • -o wide - добавляет IP пода, узел, nominated node и readiness gates.
  • --field-selector - фильтр на стороне API-сервера: по фазе, имени узла, involvedObject.
  • --sort-by=.status.startTime - сортировка по времени старта, чтобы первыми видеть свежесозданные поды.
  • -o yaml или -o json - полный статус, включая условия и статусы контейнеров.

Последняя команда в блоке выше выводит имя пода и число перезапусков по каждому контейнеру. В кластере с сотнями подов это быстрый способ найти кандидатов на разбор, не листая вывод руками.

Секции describe pod: Conditions и Events

describe pod собирает в одном выводе спецификацию, статусы контейнеров, условия и события. Порядок чтения сверху вниз: Status, Conditions, Containers (State и Last State), Events.

ConditionTrue означает
PodScheduledПланировщик назначил узел
PodReadyToStartContainersSandbox создан и сеть пода готова, контейнеры можно запускать (в версиях 1.29+ пришло на смену устаревшему Initialized)
InitializedВсе init-контейнеры завершились успешно (в новых релизах уступает место PodReadyToStartContainers)
ContainersReadyВсе контейнеры прошли readinessProbe
ReadyПод готов принимать трафик и попадает в endpoints Service

Events - самый информативный раздел. Здесь видны сообщения планировщика (FailedScheduling с причиной), kubelet (Pulling, Pulled, Created, Started), проб (Unhealthy с текстом ошибки) и контроллеров. Срок жизни событий ограничен: kube-apiserver хранит их час по умолчанию, поэтому смотреть нужно по горячим следам.

kubectl get events --sort-by=.metadata.creationTimestamp
kubectl get events -A --field-selector=type=Warning --sort-by=.metadata.creationTimestamp

Пример записи: Warning FailedScheduling ... 0/3 nodes are available: 1 node(s) had untolerated taint, 2 Insufficient memory. Это готовый ответ на вопрос, почему под в Pending.

Диагностика застревания в Pending: пошаговый алгоритм

  1. Посмотрите фазу и события: kubectl describe pod $POD, затем блок Events внизу вывода.
  2. Есть FailedScheduling - причина на стороне планировщика: ресурсы, taints, nodeSelector, affinity.
  3. Есть Pulling или ImagePullBackOff - проблема с образом или доступом к registry.
  4. Есть FailedMount или unbound PVC - проверьте статус тома и StorageClass.
  5. Событий нет вообще - проверьте квоты namespace и правила LimitRange.

Проверка ресурсов, taints и nodeSelector

kubectl describe nodes
kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memory
kubectl describe node $NODE

В describe nodes смотрите два блока: Allocatable (сколько ресурсов узел готов отдать) и Allocated resources (сколько уже зарезервировано через requests). Разница между ними и есть запас для нового пода. Дальше проверьте в describe node строку Taints и сопоставьте её с tolerations в манифесте. Последний шаг - nodeSelector и nodeAffinity: если под ждёт метку disktype: ssd, а такой нет ни на одном узле, планировщик честно оставит под в Pending.

Если под стоит из-за taint, событие выглядит так: FailedScheduling ... node(s) had untolerated taint {dedicated: ci}. Лечится либо tolerations с тем же key, value и effect в манифесте, либо снятием taint с узла.

Проблемы с образами и PVC

kubectl describe pod $POD
kubectl get pvc -A
kubectl describe pvc $PVC
kubectl get storageclass

Для ошибок образа смотрите Events и поле Message: там будет точная причина вида failed to resolve reference или unauthorized. ImagePullBackOff держится и после исправления реестра, пока не истечёт текущая задержка повторов. С томами проще: пока PVC в статусе Pending, под останется в Pending с событием unbound immediate PersistentVolumeClaims. В describe pvc проверьте, подходит ли StorageClass, поддерживает ли provisioner нужный access mode и не упёрлись ли вы в квоту на хранилище.

Диагностика CrashLoopBackOff: почему контейнер падает

CrashLoopBackOff - это reason в статусе Waiting контейнера, отдельной фазы пода для него нет. Механика такая: контейнер стартует, завершается с ненулевым кодом, kubelet перезапускает его с растущей задержкой. Первый повтор через 10 секунд, затем 20, 40, 80, 160 и дальше до предела 300 секунд (5 минут). Счётчик RESTARTS растёт, интервал между попытками тоже.

  1. Соберите логи текущего запуска: kubectl logs $POD.
  2. Логов нет - читайте предыдущий запуск: kubectl logs $POD --previous.
  3. Откройте describe pod и найдите блок Last State: Reason и Exit Code.
  4. Проверьте livenessProbe: слишком агрессивные параметры убивают здоровый контейнер.
  5. Проверьте лимиты памяти: reason OOMKilled и exitCode 137 указывают на предел limits.memory.

Чтение логов текущего и предыдущего запуска

Основной инструмент - kubectl logs. Полный набор флагов, включая фильтрацию JSON-логов, собран в руководстве kubectl logs: анализ логов контейнеров в Kubernetes. Базовые варианты:

kubectl logs $POD
kubectl logs $POD -c имя-контейнера
kubectl logs $POD --previous
kubectl logs $POD -f --tail=100

Порядок работы: сначала текущий запуск, потом предыдущий. Если приложение падает на старте (нет переменной окружения, недоступна база, нет прав на файл), в текущих логах будет одна и та же строка ошибки, а в логах предыдущего запуска - полный стек. Для подов с несколькими контейнерами не забывайте -c: без него kubectl выберет первый контейнер и вы получите чужой вывод.

Проверка livenessProbe, readinessProbe и лимитов памяти

Падение бывает рукотворным: kubelet сам убивает контейнер, когда livenessProbe не проходит failureThreshold раз подряд. Признак - событие Unhealthy с текстом пробы и reason Error или OOMKilled в Last State. Проверьте параметры: initialDelaySeconds (хватит ли приложению времени на старт), periodSeconds, timeoutSeconds и failureThreshold. Если приложение поднимается 60 секунд, а livenessProbe стучится с 10-й секунды при failureThreshold: 3 и periodSeconds: 10, контейнер умрёт на 40-й секунде и уйдёт в перезапуск по кругу. Для медленного старта ставьте startupProbe: она отключает liveness и readiness до первого успеха.

readinessProbe контейнер не убивает, но держит под вне endpoints Service, пока проверка не пройдёт. Снаружи это выглядит как «под Running, а трафик не идёт». Отличие важно при разборе: рост RESTARTS указывает на liveness или падение приложения, а нулевой READY при живом контейнере - на readiness.

Второй источник перезапусков - нехватка памяти. Контейнер, превысивший limits.memory, получает SIGKILL: reason OOMKilled, exitCode 137. Проверка:

kubectl describe pod $POD
kubectl get pod $POD -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}'

Дальше два пути: поднять лимит, если на узле есть запас (иначе под уйдёт в Pending), или разобраться с потреблением: утечка, размер heap у JVM, кеш без ограничений. Фактическое потребление покажет kubectl top pod, если в кластере установлен metrics-server.

Чек-лист диагностики пода

Последовательность шагов, которая закрывает большинство инцидентов с подами. Держите её под рукой на дежурстве.

  1. Найдите проблемный под: kubectl get pods -A -o wide. Ориентиры - STATUS, READY меньше ожидаемого, растущий RESTARTS.
  2. Соберите контекст: kubectl describe pod $POD - фаза, Conditions, блок Containers (State и Last State), Events.
  3. Проверьте хронологию: kubectl get events --sort-by=.metadata.creationTimestamp.
  4. Pending: Allocatable и Allocated resources узлов, taints и tolerations, nodeSelector и affinity, статус PVC, квоты namespace.
  5. CrashLoopBackOff: логи текущего запуска, затем kubectl logs $POD --previous, затем ExitCode и Reason в Last State, затем параметры livenessProbe и лимиты памяти.
  6. ImagePullBackOff: имя и тег образа, доступность registry, imagePullSecret в namespace.
  7. Unknown: состояние узла через kubectl get nodes и kubectl describe node $NODE.
  8. Зафиксируйте причину в тикете или постмортеме и примените исправление: манифест, ресурсы, образ или проба.

Расширенный набор команд для разбора логов, событий и типовых ошибок деплоя собран в руководстве диагностика и устранение проблем с подами и деплоями в Kubernetes. Начните с kubectl describe pod: этот один вывод обычно отвечает на вопрос, на какой фазе под остановился и кто именно его туда привёл.

Поделиться: