Что такое жизненный цикл пода и зачем его понимать
Жизненный цикл пода в 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. Наборы значений не пересекаются.
| Признак | Фаза пода | Статус контейнера |
|---|---|---|
| Поле в API | status.phase | status.containerStatuses[].state |
| Охват | Под целиком | Один контейнер |
| Значения | Pending, Running, Succeeded, Failed, Unknown | Waiting, Running, Terminated |
| Кто обновляет | kubelet, назначение узла отражает kube-scheduler | kubelet на основе ответов 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/1 | FailedScheduling, Pulling, FailedMount, FailedAttachVolume |
| Running | Под привязан к узлу, контейнеры созданы, минимум один работает или запускается | STATUS: Running, READY зависит от проб | Scheduled, Pulled, Created, Started, Unhealthy, BackOff |
| Succeeded | Все контейнеры завершились с кодом 0, перезапуска не будет | STATUS: Completed | Completed |
| Failed | Все контейнеры завершились, минимум один с ненулевым кодом или убит системой | STATUS: Error | Error, BackOff |
| Unknown | Статус пода не удалось получить, обычно из-за недоступности узла | STATUS: Unknown | NodeNotReady, ошибки связи с 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 |
|---|---|---|---|
| Waiting | state.waiting.reason, state.waiting.message | Контейнер ещё не запущен или перезапускается | ContainerCreating, ImagePullBackOff, ErrImagePull, CrashLoopBackOff, CreateContainerConfigError |
| Running | state.running.startedAt | Процесс запущен | - |
| Terminated | state.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. Код выхода объясняет, кто и почему завершил контейнер.
| ExitCode | Signal | Значение | Что делать |
|---|---|---|---|
| 0 | - | Успешное завершение | Для Job это норма; у долгоживущего сервиса проверьте, почему завершился main-процесс |
| 1 | - | Общая ошибка приложения | Логи текущего и предыдущего запуска, конфигурация, зависимости |
| 2 | - | Ошибка использования shell | Проверьте command и args: кавычки, пробелы, отсутствующие утилиты |
| 126 | - | Файл есть, но не исполняется | Права на бинарник, shebang в скрипте |
| 127 | - | Команда не найдена | Путь в PATH, опечатка в entrypoint |
| 137 | SIGKILL (9) | Контейнер убит принудительно, чаще всего OOMKilled | Лимит limits.memory, утечка памяти, размер heap |
| 143 | SIGTERM (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.
| Condition | True означает |
|---|---|
| PodScheduled | Планировщик назначил узел |
| PodReadyToStartContainers | Sandbox создан и сеть пода готова, контейнеры можно запускать (в версиях 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: пошаговый алгоритм
- Посмотрите фазу и события: kubectl describe pod $POD, затем блок Events внизу вывода.
- Есть FailedScheduling - причина на стороне планировщика: ресурсы, taints, nodeSelector, affinity.
- Есть Pulling или ImagePullBackOff - проблема с образом или доступом к registry.
- Есть FailedMount или unbound PVC - проверьте статус тома и StorageClass.
- Событий нет вообще - проверьте квоты 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 растёт, интервал между попытками тоже.
- Соберите логи текущего запуска: kubectl logs $POD.
- Логов нет - читайте предыдущий запуск: kubectl logs $POD --previous.
- Откройте describe pod и найдите блок Last State: Reason и Exit Code.
- Проверьте livenessProbe: слишком агрессивные параметры убивают здоровый контейнер.
- Проверьте лимиты памяти: 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.
Чек-лист диагностики пода
Последовательность шагов, которая закрывает большинство инцидентов с подами. Держите её под рукой на дежурстве.
- Найдите проблемный под: kubectl get pods -A -o wide. Ориентиры - STATUS, READY меньше ожидаемого, растущий RESTARTS.
- Соберите контекст: kubectl describe pod $POD - фаза, Conditions, блок Containers (State и Last State), Events.
- Проверьте хронологию: kubectl get events --sort-by=.metadata.creationTimestamp.
- Pending: Allocatable и Allocated resources узлов, taints и tolerations, nodeSelector и affinity, статус PVC, квоты namespace.
- CrashLoopBackOff: логи текущего запуска, затем kubectl logs $POD --previous, затем ExitCode и Reason в Last State, затем параметры livenessProbe и лимиты памяти.
- ImagePullBackOff: имя и тег образа, доступность registry, imagePullSecret в namespace.
- Unknown: состояние узла через kubectl get nodes и kubectl describe node $NODE.
- Зафиксируйте причину в тикете или постмортеме и примените исправление: манифест, ресурсы, образ или проба.
Расширенный набор команд для разбора логов, событий и типовых ошибок деплоя собран в руководстве диагностика и устранение проблем с подами и деплоями в Kubernetes. Начните с kubectl describe pod: этот один вывод обычно отвечает на вопрос, на какой фазе под остановился и кто именно его туда привёл.