Learning Platform
Глоссарий Troubleshooting
Урок 05.02 · 25 мин
Продвинутый
Pod lifecyclephaseconditionscontainerStatusesgraceful shutdownpreStop

Жизненный цикл Pod

Когда kubectl get pods показывает nginx-7d5c5 0/1 Running, это значит ровно две вещи: Pod в фазе Running, и ноль контейнеров из одного готовы. Этого недостаточно, чтобы понять, что происходит. Реальная информация о состоянии живёт в трёх местах: status.phase, status.conditions и status.containerStatuses. Этот урок — про то, как читать эти поля, какие переходы между состояниями возможны и какие коды ошибок встречаются на CKAD-экзамене и в продакшене каждый день.


Lifecycle процесса: ready, running, blocked, zombie

Phase: high-level состояние

status.phase — это обобщающее поле с пятью возможными значениями. Оно отвечает на вопрос «что в целом происходит с этим Pod», но не на «почему».

Pod phase transitions
PendingОбъект Pod создан в etcd, но не все контейнеры запущены. Возможные причины: scheduler ещё не выбрал node, image тянется, init container ещё работает, нет доступного PV.
контейнеры стартанули
RunningPod назначен на node, минимум один контейнер запущен или перезапускается. Не означает 'всё хорошо' — означает 'kubelet пытается'.
SucceededВсе контейнеры завершились с exit code 0 и не будут перезапущены (restartPolicy: OnFailure или Never). Финальное состояние для Job.
FailedВсе контейнеры завершились, хотя бы один — с non-zero exit code. Финальное состояние при restartPolicy: Never.
UnknownAPI server не может получить статус: kubelet потерял связь, node NotReady. Редкое состояние, требует разбираться с node, а не с Pod.

Важно: Running — это не «приложение работает». Это «kubelet видит, что контейнеры либо запущены, либо ожидают перезапуска». CrashLoopBackOff живёт внутри Running phase — контейнер падает и перезапускается, Pod при этом остаётся в Running.

kubectl get pod nginx -o jsonpath='{.status.phase}'
# Running

Conditions: булевы флаги с причинами

status.conditions — это массив структурированных условий. Каждое condition — это type / status (True|False|Unknown) / reason / message / lastTransitionTime. Это самый полезный источник информации при диагностике.

condition typeчто означает
PodScheduledScheduler выбрал node, spec.nodeName записан
InitializedВсе init containers завершились успешно
ContainersReadyВсе обычные контейнеры прошли свой readinessProbe (или если probe нет — просто запустились)
ReadyPod готов принимать трафик. Обычно совпадает с ContainersReady, но может отличаться при readinessGates
DisruptionTargetPod помечен для terminated controller-ом, eviction-ом или scheduler-ом. Status=True означает «сейчас будут убивать»
kubectl get pod nginx -o jsonpath='{.status.conditions}' | jq

Пример вывода для Pod, который висит в Pending:

[
  {
    "type": "PodScheduled",
    "status": "False",
    "reason": "Unschedulable",
    "message": "0/3 nodes are available: 3 Insufficient memory."
  }
]

Этот message — прямой ответ на вопрос «почему мой Pod в Pending». Не надо гуглить — надо посмотреть в status.conditions.

TIP

Команда быстрой диагностики: kubectl describe pod <name> показывает conditions и events в человекочитаемом виде. kubectl get pod <name> -o yaml — полный snapshot для глубокого разбора.


containerStatuses: что с каждым контейнером

Третий ключевой массив — status.containerStatuses. Один элемент на каждый контейнер в Pod. Поля:

  • name — имя контейнера из spec
  • image / imageID — какой образ запущен (imageID — digest)
  • containerID — runtime ID (например, containerd://abc123...)
  • ready — прошёл readinessProbe
  • restartCount — сколько раз kubelet перезапускал
  • state — текущее состояние: waiting, running, terminated
  • lastState — предыдущее состояние (полезно когда контейнер только что перезапустился)
containerStatuses:
- name: app
  image: myapp:1.0
  ready: false
  restartCount: 5
  state:
    waiting:
      reason: CrashLoopBackOff
      message: back-off 40s restarting failed container
  lastState:
    terminated:
      exitCode: 137
      reason: OOMKilled
      startedAt: "2026-05-13T10:00:00Z"
      finishedAt: "2026-05-13T10:00:42Z"

В этом примере вся история боли: state.waiting.reason: CrashLoopBackOff (сейчас ждём), lastState.terminated.reason: OOMKilled, exitCode: 137 (предыдущий запуск убит OOM Killer). Чинить нужно лимит памяти, а не «передеплоить».


State: waiting — типичные reasons

Когда контейнер в state.waiting, в reason будет одно из:

reasonчто произошло
ContainerCreatingkubelet запустил создание (CNI выдаёт IP, mount volumes). Нормальное короткое состояние.
PodInitializingInit containers ещё не закончили.
ImagePullBackOffНе удаётся pull образ, kubelet ждёт перед retry. См. урок про imagePullPolicy.
ErrImagePullСвежая ошибка pull (до перехода в backoff).
CrashLoopBackOffКонтейнер падает после старта, kubelet ждёт backoff.
CreateContainerConfigErrorОшибка в конфигурации Pod: ссылка на несуществующий Secret/ConfigMap, неправильный env. Pod не запустится, пока конфиг не починят.
CreateContainerErrorRuntime отверг создание (например, USER 1000 при readOnly rootfs не может писать).
InvalidImageNameИмя образа не парсится.

State: terminated — exit codes

Когда контейнер в state.terminated, поле reason плюс exitCode рассказывают, как он умер.

Exit codes, которые нужно знать
exit 0Нормальное завершение. reason: Completed. Для Job это значит успех, для Deployment Pod — restart согласно policy (Always — перезапустится).
exit 1Generic error. Приложение вышло с ошибкой. reason: Error. Смотрите kubectl logs для деталей.
exit 137128 + 9 (SIGKILL). Kernel убил процесс. В K8s две причины: (1) OOMKilled — превышен memory limit; (2) eviction — kubelet вытеснил Pod при нехватке ресурсов на node. Reason покажет какое именно.
exit 143128 + 15 (SIGTERM). Процесс получил graceful shutdown, обработал и вышел. Нормально при scale-down, rolling update, kubectl delete pod.
reason: OOMKilledcgroup memory.max превышен, kernel OOM-killer убил процесс в этом cgroup. Решение: увеличить memory limit или починить утечку.
reason: DeadlineExceededJob activeDeadlineSeconds истёк или Pod terminationGracePeriodSeconds истёк во время SIGTERM — kubelet послал SIGKILL.
reason: ContainerCannotRunRuntime не смог запустить контейнер: например, command не существует в образе, или USER не имеет прав на entrypoint.

OOMKilled особенно коварен: процесс убит мгновенно, никаких логов о причине вы не увидите. Логи приложения оборваны посередине. Поэтому 137 + reason — единственный сигнал.


Killer момент: что гуглить при 0/1 Running

Допустим, вы видите:

NAME             READY   STATUS    RESTARTS   AGE
backend-7d8     0/1     Running   3          2m

0/1 — ноль контейнеров готовы из одного. Running — phase. 3 — три рестарта. Алгоритм:

# 1. Conditions: какое именно условие False и почему
kubectl get pod backend-7d8 -o yaml | yq '.status.conditions'

# 2. containerStatuses: lastState поможет понять прошлые падения
kubectl get pod backend-7d8 -o yaml | yq '.status.containerStatuses'

# 3. Логи текущего контейнера
kubectl logs backend-7d8

# 4. Логи ПРЕДЫДУЩЕГО запуска — самое важное при CrashLoopBackOff
kubectl logs backend-7d8 --previous

# 5. Events: что K8s видел вокруг этого Pod
kubectl describe pod backend-7d8

--previous — это самая частая забываемая команда CKAD. Когда контейнер сейчас не запущен (CrashLoopBackOff между запусками), обычный kubectl logs ничего не покажет. --previous достаёт логи прошлого упавшего инстанса.

WARNING

В Pod с несколькими контейнерами kubectl logs <pod> без флага -c <container> берёт логи первого контейнера в spec. Если у вас sidecar и app, и упал app — без -c app вы можете смотреть не туда.


Termination flow: как Pod умирает

kubectl delete pod, scale-down, rolling update — все приводят к одинаковой последовательности:

Graceful shutdown Pod
1. Delete requestkubectl или controller отправляет DELETE в API server. Pod получает deletionTimestamp в metadata. Условие DisruptionTarget может стать True.
2. Endpoints обновляютсяEndpointsSlice controller убирает Pod из endpoints соответствующих Services. Параллельно kube-proxy/Cilium обновляют iptables/eBPF. Новый трафик к Pod не идёт.
3. preStop hookЕсли задан spec.containers[].lifecycle.preStop, kubelet вызывает его СИНХРОННО. Hook должен успеть до terminationGracePeriodSeconds. Только потом — SIGTERM.
4. SIGTERMkubelet шлёт SIGTERM процессу PID 1 в контейнере. Приложение должно: остановить приём новых соединений, дослужить in-flight, закрыть БД, exit 0. Получит exit 143.
ждём terminationGracePeriodSeconds (default 30s)
5. SIGKILLЕсли процесс не вышел за grace period — kubelet шлёт SIGKILL. Никакой возможности перехватить, мгновенная смерть. Exit code 137. Логи могут быть обрезаны.
6. Cleanupkubelet вызывает CNI DEL (отдаёт IP), отмонтирует volumes, удаляет sandbox. API server удаляет объект Pod из etcd.
spec:
  terminationGracePeriodSeconds: 60   # default 30, можно увеличить
  containers:
  - name: app
    image: myapp:1.0
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "nginx -s quit; sleep 5"]

preStop hook: для чего реально нужен

Логичный вопрос: зачем preStop, если приложение всё равно получит SIGTERM и должно само обработать graceful shutdown? Две настоящих причины:

  1. Legacy-приложения, которые не обрабатывают SIGTERM (вышли — и всё). Для них preStop запускает shell-команду, которая шлёт правильный сигнал или зовёт API: nginx -s quit, kill -SIGUSR1 $(pidof envoy).
  2. Race condition с Endpoints: между удалением Pod из endpoints (шаг 2) и закрытием соединений приложением (шаг 4) есть микро-окно, когда kube-proxy на других нодах ещё может слать трафик. Стандартный паттерн — preStop: sleep 5 или sleep 10, чтобы дать kube-proxy на всех нодах обновить правила, и только потом отправить SIGTERM.
lifecycle:
  preStop:
    exec:
      command: ["sleep", "10"]

Это «сэндвич» с traffic draining — известный production-паттерн.

WARNING

preStop вычитается из terminationGracePeriodSeconds. Если grace period = 30s, а preStop спит 25 секунд, у приложения остаётся 5 секунд на SIGTERM — может не успеть. Балансируйте.


Проверка знанийKnowledge check
Pod в фазе Running, но READY показывает 0/1. Какое поле API расскажет, почему контейнер не Ready?
ОтветAnswer
status.containerStatuses[0] — там state.waiting.reason или информация о failed readinessProbe. Также проверяйте status.conditions — condition Ready будет False с причиной.
Проверка знанийKnowledge check
Контейнер последний раз умер с exitCode 137 и reason OOMKilled. Что делать?
ОтветAnswer
Увеличить spec.containers[].resources.limits.memory или починить утечку памяти в приложении. exit 137 (SIGKILL) от OOM-killer означает, что cgroup memory limit превышен. kubectl logs не покажет причину — процесс был убит мгновенно.
Проверка знанийKnowledge check
Зачем добавлять preStop с sleep 10 в Pod, если приложение умеет обрабатывать SIGTERM?
ОтветAnswer
Чтобы дать kube-proxy на других node-ах обновить iptables/eBPF и убрать Pod из endpoint slices ДО того, как приложение начнёт graceful shutdown. Иначе в окне между deletionTimestamp и SIGTERM на Pod может прилететь свежий трафик, который потеряется. Sleep 10 закрывает race.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. kubectl get pod показывает: 'app 0/1 Running 5 2m'. Где искать причину, по которой контейнер не Ready?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5