Жизненный цикл 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», но не на «почему».
Важно: 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 | что означает |
|---|---|
PodScheduled | Scheduler выбрал node, spec.nodeName записан |
Initialized | Все init containers завершились успешно |
ContainersReady | Все обычные контейнеры прошли свой readinessProbe (или если probe нет — просто запустились) |
Ready | Pod готов принимать трафик. Обычно совпадает с ContainersReady, но может отличаться при readinessGates |
DisruptionTarget | Pod помечен для 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.
Команда быстрой диагностики: kubectl describe pod <name> показывает conditions и events в человекочитаемом виде. kubectl get pod <name> -o yaml — полный snapshot для глубокого разбора.
containerStatuses: что с каждым контейнером
Третий ключевой массив — status.containerStatuses. Один элемент на каждый контейнер в Pod. Поля:
name— имя контейнера из specimage/imageID— какой образ запущен (imageID — digest)containerID— runtime ID (например,containerd://abc123...)ready— прошёл readinessProberestartCount— сколько раз kubelet перезапускалstate— текущее состояние:waiting,running,terminatedlastState— предыдущее состояние (полезно когда контейнер только что перезапустился)
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 | что произошло |
|---|---|
ContainerCreating | kubelet запустил создание (CNI выдаёт IP, mount volumes). Нормальное короткое состояние. |
PodInitializing | Init containers ещё не закончили. |
ImagePullBackOff | Не удаётся pull образ, kubelet ждёт перед retry. См. урок про imagePullPolicy. |
ErrImagePull | Свежая ошибка pull (до перехода в backoff). |
CrashLoopBackOff | Контейнер падает после старта, kubelet ждёт backoff. |
CreateContainerConfigError | Ошибка в конфигурации Pod: ссылка на несуществующий Secret/ConfigMap, неправильный env. Pod не запустится, пока конфиг не починят. |
CreateContainerError | Runtime отверг создание (например, USER 1000 при readOnly rootfs не может писать). |
InvalidImageName | Имя образа не парсится. |
State: terminated — exit codes
Когда контейнер в state.terminated, поле reason плюс exitCode рассказывают, как он умер.
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 достаёт логи прошлого упавшего инстанса.
В Pod с несколькими контейнерами kubectl logs <pod> без флага -c <container> берёт логи первого контейнера в spec. Если у вас sidecar и app, и упал app — без -c app вы можете смотреть не туда.
Termination flow: как Pod умирает
kubectl delete pod, scale-down, rolling update — все приводят к одинаковой последовательности:
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? Две настоящих причины:
- Legacy-приложения, которые не обрабатывают SIGTERM (вышли — и всё). Для них preStop запускает shell-команду, которая шлёт правильный сигнал или зовёт API:
nginx -s quit,kill -SIGUSR1 $(pidof envoy). - 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-паттерн.
preStop вычитается из terminationGracePeriodSeconds. Если grace period = 30s, а preStop спит 25 секунд, у приложения остаётся 5 секунд на SIGTERM — может не успеть. Балансируйте.