Зачем понимать 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, детальная картина
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:
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
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
readinessProbe:
httpGet: { path: /ready, port: 8080 }initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
- Не убивает pod, просто снимает endpoint из Service при failure.
- Возвращается в Service когда снова passes.
- Идеально для warmup (cache не прогрет, БД-конект ещё не установлен).
liveness-probe
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
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
Проверка:
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).
Дебаг:
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 podevents. 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 <init-name>.
Полезные kubectl-команды
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