# Kubernetes pod lifecycle - от Pending до Terminated _Контейнеры (бонус) · LinuxLab Knowledge Base_ **TL;DR:** Pod проходит фазы Pending → Running → Succeeded/Failed/Unknown. Init-containers выполняются последовательно до основных. Probes: startup → readiness/liveness. SIGTERM + grace period при удалении. ## Зачем понимать lifecycle В k8s pod, атом deployment'а: 1+ контейнеров с общим network/IPC namespace и томами. Между моментом `kubectl apply` и окончательным Running pod проходит цепь состояний, в каждом могут быть проблемы. Если знаешь lifecycle, диагностируешь "почему не стартует" / "почему перезагружается" / "почему не может graceful-shutdown" за минуту. ## Phases, поле `.status.phase` Pod имеет ровно одну фазу: | Phase | Что значит | |-------|------------| | **Pending** | принят API, ещё не Running. Может быть scheduling, image-pull, init-container'ы. | | **Running** | scheduled на ноду, **минимум один контейнер запущен**. Не означает что все проходят readiness! | | **Succeeded** | все контейнеры завершились с exit 0 (для Job/CronJob) | | **Failed** | минимум один контейнер завершился с non-zero и не будет рестартован | | **Unknown** | kubelet не смог сообщить статус (нода обвалилась, network split) | Phase, **слишком грубый**. Реальное состояние, в `.status.conditions`. ## Conditions, детальная картина ```yaml status: phase: Running conditions: - type: PodScheduled status: "True" lastTransitionTime: "..." - type: Initialized status: "True" # все init-containers OK - type: ContainersReady status: "True" # все основные containers ready - type: Ready status: "True" # = ContainersReady && readiness-probe pass ``` - **PodScheduled**, scheduler нашёл ноду - **Initialized**, init-containers завершились с exit 0 - **ContainersReady**, все основные контейнеры **прошли readiness-probe** - **Ready**, pod готов получать трафик (включается в Service endpoints) Pod может быть **Running но не Ready**, это нормальная ситуация при старте или после liveness-probe сбоя. ## Init containers Контейнеры в `spec.initContainers` запускаются **последовательно ДО** основных, каждый должен exit 0: ```yaml apiVersion: v1 kind: Pod spec: initContainers: - name: wait-for-db image: busybox command: ['sh', '-c', 'until nc -z db 5432; do sleep 1; done'] - name: migrate image: myapp:v1 command: ['./migrate'] containers: - name: app image: myapp:v1 ``` Семантика: - Если init-container падает, pod **рестартует с первого** init-контейнера - `restartPolicy: Always` для pod не применяется к init (init всегда рестартует на failure) - Init-containers могут иметь свои resources, securityContext Use cases: - Wait for dependencies (DB, configmap) - DB migrations - Permissions fix на mount'е (chown /data перед основным процессом) - Generate config из templates с `envsubst` ## Probes, startup, readiness, liveness Три типа health-check, выполняемых **kubelet'ом на ноде**: ### startup-probe ```yaml startupProbe: httpGet: { path: /healthz, port: 8080 } failureThreshold: 30 periodSeconds: 10 ``` - Проверяется **первой**, отключает остальные probes пока работает. - Защищает медленно стартующие приложения от ложного liveness-fail. - При success, больше не запускается, начинают работать readiness/liveness. - При failure threshold, kubelet убивает контейнер. Без startup-probe медленный стартап → liveness-probe убивает, бесконечный restart-loop. ### readiness-probe ```yaml readinessProbe: httpGet: { path: /ready, port: 8080 } initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 3 ``` - Не убивает pod, просто **снимает endpoint из Service** при failure. - Возвращается в Service когда снова passes. - Идеально для warmup (cache не прогрет, БД-конект ещё не установлен). ### liveness-probe ```yaml livenessProbe: httpGet: { path: /healthz, port: 8080 } periodSeconds: 10 failureThreshold: 3 ``` - **Убивает контейнер** при failure (рестартует через restartPolicy). - Используй **ТОЛЬКО** для самовосстановления "deadlock'нувшего" приложения. Не для проверки upstream-зависимостей. - Анти-паттерн: проверять БД в liveness, если БД лагает, k8s убьёт все pod'ы → каскадный сбой. ### Probe types | Type | Что | |------|-----| | `httpGet` | HTTP 200-399 = OK; на любом path/port | | `tcpSocket` | TCP connect возможен = OK | | `exec` | command exit 0 = OK | | `grpc` (1.27+) | gRPC health-check stream | ## Restart Policy - **Always** (default для Deployment/ReplicaSet/StatefulSet), рестартует при любом exit - **OnFailure** (Job/CronJob default), рестартует только при non-zero - **Never**, не рестартует вообще Только для Pod-level. На уровне controller'ов (Deployment), другая логика (replicas). ## Termination, graceful shutdown Когда `kubectl delete pod` или scale-down: ``` 1. API: pod.metadata.deletionTimestamp = now 2. Pod удаляется из Service-endpoints (Ready=False) 3. preStop hook (если есть) ← синхронно 4. SIGTERM в pid 1 контейнера ← начинается grace period 5. ... ждём terminationGracePeriodSeconds (default 30s) 6. SIGKILL если не завершился 7. Pod удалён из API ``` ```yaml spec: terminationGracePeriodSeconds: 60 containers: - name: app lifecycle: preStop: exec: command: ['sh', '-c', 'sleep 5 && kill -TERM 1'] ``` preStop: - Выполняется **до SIGTERM** - Полезен для drain-режима (сообщить LB "не шли мне больше") - `sleep 5`, типичная пауза для distribution-of-removal в Service Главные ошибки: - **PID 1 не обрабатывает SIGTERM**, некоторые языки/shell-script не пробрасывают сигнал. Используй `tini` или `dumb-init` как PID 1. - **Долгая graceful-операция > terminationGracePeriodSeconds**, k8s SIGKILL'ит. Увеличь grace, или сделай работу неблокирующей. ## OOM в pod'е k8s ставит cgroup-limit на memory. При превышении: - **Контейнер OOMKilled**, kernel OOM-killer убивает процесс контейнера - В status: `lastState.terminated.reason: OOMKilled` - Pod рестартует если restartPolicy=Always Проверка: ```bash kubectl describe pod mypod | grep -A2 'Last State' # Last State: Terminated # Reason: OOMKilled # Exit Code: 137 ← 128 + 9 (SIGKILL) ``` Лечение: увеличить `resources.limits.memory` или fix-leak в приложении. ## ImagePullBackOff и CrashLoopBackOff - **ImagePullBackOff**, registry недоступен / image-tag неверный / нет imagePullSecret. `kubectl describe pod` → events. - **CrashLoopBackOff**, контейнер падает быстро после старта, kubelet **экспоненциально увеличивает delay** между рестартами (10s → 20s → 40s ... до 5 min). Дебаг: ```bash kubectl logs mypod -c container1 # текущий kubectl logs mypod -c container1 --previous # предыдущий instance kubectl describe pod mypod # events внизу kubectl get events --sort-by='.lastTimestamp' # глобальные events ``` ## Когда что-то пошло не так - **Pod stuck в Pending**, нет ноды с нужными resources/labels. `kubectl describe pod` → events: `FailedScheduling`. Проверь `kubectl describe node` (Allocatable, taints). - **Pod stuck в ContainerCreating**, image pulls слишком долго или volume не маунтится. `kubectl describe pod` events. - **`OOMKilled` без явных причин**, limits слишком низкие, или JVM не знает про cgroup-limit (надо `-XX:+UseContainerSupport` для Java 8u131+, на Java 11+ default). - **Liveness-probe убивает pod после deploy'а**, `initialDelaySeconds` мал, приложение ещё стартует. Используй startup-probe. - **`kubectl delete pod` висит**, finalizer'ы или `terminationGracePeriodSeconds` очень большой. `kubectl delete pod --grace-period=0 --force`. - **Контейнер живой, но не получает трафик**, readiness-probe failed. `kubectl describe pod` → conditions: ContainersReady=False. - **Init-container завершается, но pod не движется**, exit non-zero. `kubectl logs mypod -c `. ## Полезные kubectl-команды ```bash kubectl get pod -o wide # узнать ноду + IP kubectl get pod -o jsonpath='{.status.containerStatuses[*].state}' kubectl exec -it mypod -- sh kubectl debug -it mypod --image=busybox # ephemeral container 1.25+ kubectl port-forward pod/mypod 8080:8080 # local-test без Service kubectl rollout status deployment/myapp kubectl rollout restart deployment/myapp # rolling-rollout без правки spec ``` ## Команды ```bash kubectl get pod mypod -o jsonpath='{.status.phase}' ``` Текущая phase pod'а - первая checkbox при дебаге ```bash kubectl describe pod mypod | grep -A3 'Last State' ``` Причина рестарта (OOMKilled, Error, Completed) с exit-code ```bash kubectl logs mypod --previous -c container1 ``` Логи прошлого instance контейнера - что было перед рестартом ```bash kubectl get events --sort-by='.lastTimestamp' -A | tail -30 ``` Последние events кластера - источник 'почему pod не стартует' ```bash kubectl exec -it mypod -- sh ``` Зайти в контейнер - debug изнутри (если sh есть) ```bash kubectl debug -it mypod --image=busybox --target=container1 ``` Ephemeral debug-container в pod'е - не нужен sh в исходном image ```bash kubectl delete pod mypod --grace-period=0 --force ``` Принудительное удаление - используй когда terminating висит ## См. также - [Linux namespaces](/kb/namespaces.md) - [cgroups (v2)](/kb/cgroups.md) - [Сигналы (SIGTERM, SIGKILL, SIGHUP)](/kb/signals.md) - [runc, runsc, kata - container runtimes](/kb/runc-and-runsc.md) - [OCI spec - стандарт контейнеров](/kb/oci-spec.md) - [kubelet - архитектура агента ноды Kubernetes](/kb/kubelet-internals.md) - [Kubernetes Service и Ingress - сетевая публикация подов](/kb/kubernetes-services-and-ingress.md) - [Kubernetes storage - PV, PVC, StorageClass, CSI](/kb/kubernetes-storage.md)