Learning Platform
Глоссарий Troubleshooting
Урок 05.03 · 18 мин
Средний
restartPolicyCrashLoopBackOffbackoffkubectl logsdiagnostics

Restart policy и CrashLoopBackOff

Когда контейнер в Pod завершается, kubelet смотрит на spec.restartPolicy и решает: запустить заново, оставить как есть, или повторить только если выход был с ошибкой. Эта механика лежит в основе двух классов задач: stateless приложений (всегда перезапускаем) и batch-задач (запустить один раз и забыть). И именно она порождает наш любимый статус — CrashLoopBackOff, который на CKAD-экзамене встречается чаще, чем все остальные ошибки вместе взятые.


--restart: жизненный цикл контейнера в Docker

Три значения restartPolicy

spec.restartPolicy — поле на уровне Pod, не контейнера. Применяется ко всем контейнерам Pod одинаково:

restartPolicy: что когда перезапускать
AlwaysDefault для голых Pod и обязательно для Deployment/ReplicaSet/StatefulSet/DaemonSet. kubelet перезапустит контейнер всегда: при exit 0, exit 1, OOMKilled, любом исходе. Используется для long-running сервисов.
OnFailureПерезапуск только при non-zero exit. Если exit 0 — контейнер не перезапускается, Pod переходит в Succeeded. Используется для Job, который должен повторить попытку при ошибке.
NeverНикогда не перезапускать. Pod после exit переходит в Succeeded (если exit 0) или Failed (если non-zero). Используется для Job, где повторный запуск нежелателен (миграция БД).

Главное ограничение: контроллеры верхнего уровня требуют конкретный restartPolicy.

WorkloadrestartPolicy
Deployment, ReplicaSet, StatefulSet, DaemonSetтолько Always (валидируется API server)
Job, CronJobтолько OnFailure или Never
Голый Pod (kubectl run)любое из трёх

Это потому что Deployment предполагает «должно быть N replicas, всегда». Если контейнер вышел — Deployment не пересоздаёт Pod (это сделал бы только если Pod исчез). Он рассчитывает, что kubelet перезапустит контейнер внутри того же Pod-а.

apiVersion: v1
kind: Pod
metadata:
  name: one-shot
spec:
  restartPolicy: OnFailure
  containers:
  - name: migrate
    image: myapp:1.0
    command: ["./migrate.sh"]

Exponential backoff

Когда контейнер падает быстро (упал, kubelet запустил, снова упал), kubelet не хочет тратить CPU на бесконечный restart loop. Поэтому каждый следующий restart происходит с возрастающей паузой.

Backoff между перезапусками
restart #1После первого падения kubelet ждёт 10 секунд и перезапускает.
#2: 20sУдвоение паузы.
#3: 40sУдвоение.
#4: 80sУдвоение.
#5: 160sУдвоение.
#6+: 300sCap = 5 минут. Дальше пауза всегда 5 минут.
resetЕсли контейнер проработал успешно более 10 минут — счётчик backoff сбрасывается, следующее падение снова начнёт с 10s.

В это время state.waiting.reason = CrashLoopBackOff и message: back-off Xs restarting failed container. Pod в фазе Running, но restartCount растёт каждый цикл.

С v1.33 настройки backoff для отдельных рабочих нагрузок (KEP-4603 / per-workload backoff) ещё не общедоступны — это поведение жёстко зашито в kubelet. Не пытайтесь искать restartBackoffMaxSeconds в Pod spec для GA-кластеров.


CrashLoopBackOff — это симптом, не причина

CrashLoopBackOff — это состояние kubelet, не контейнера. Оно означает: «контейнер падает быстрее, чем я могу его перезапускать, поэтому я жду». Сама причина падения — где-то в логах или конфиге.

Что НЕ помогает:

kubectl delete pod backend-7d8

Если Pod управляется Deployment-ом, Deployment пересоздаст его с тем же образом и той же конфигурацией. Получится точно такой же crash. Удаление Pod лечит только редкие случаи (засевший corrupt state), но не системные причины — bug в коде, неправильный env, отсутствующий Secret.

Что помогает:

# 1. Логи последнего падения
kubectl logs backend-7d8

# 2. Логи ПРЕДЫДУЩЕГО запуска — критично
kubectl logs backend-7d8 --previous

# 3. События K8s вокруг этого Pod
kubectl describe pod backend-7d8

# 4. Статус контейнеров: lastState.terminated.reason и exitCode
kubectl get pod backend-7d8 -o yaml | yq '.status.containerStatuses'

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

TIP

Алгоритм CKAD при CrashLoopBackOff: (1) kubectl describe pod <name> — смотрим Events внизу; (2) kubectl logs <name> --previous — смотрим, что писало приложение перед смертью; (3) проверяем exitCode и reason в containerStatuses. Большинство кейсов закрываются на шаге 1 или 2.


Типичные причины crash-loop

ПричинаЧто увидитеКак лечить
exit 1 + log с stack tracekubectl logs --previous покажет ошибку приложенияЧинить код или окружение
exit 137, reason OOMKilledlastState.terminated.reason: OOMKilledУвеличить memory limit или починить утечку
exit 1, нет логовПриложение падает до записи логовПроверить ENTRYPOINT/CMD, env variables, ConfigMap
CreateContainerConfigErrorReason в waiting stateНе существует Secret/ConfigMap из envFrom или volumeMount
Падает на startup probeEvents: Liveness probe failed: ...Увеличить initialDelaySeconds или починить health endpoint
ContainerCannotRun + exit 127exitCode 127 — «command not found»command/args ссылается на бинарник, которого нет в образе
ContainerCannotRun + exit 126exitCode 126 — «permission denied»runAsUser не имеет прав на запуск entrypoint

CKAD типовая задача

Из реальных задач сертификации:

Pod app-broken в namespace dev находится в CrashLoopBackOff. Определите причину и почините, не пересоздавая Pod вручную.

Решение шаг за шагом:

# 1. Что говорит K8s
kubectl describe pod app-broken -n dev | tail -20
# Events:
#   Warning  BackOff  10s (x12 over 3m)  kubelet  Back-off restarting failed container

# 2. Логи предыдущего запуска
kubectl logs app-broken -n dev --previous
# Error: cannot find environment variable DATABASE_URL

# 3. Проверим, откуда должен браться DATABASE_URL
kubectl get pod app-broken -n dev -o yaml | yq '.spec.containers[0].envFrom'
# - configMapRef:
#     name: app-config

# 4. Существует ли этот ConfigMap?
kubectl get configmap app-config -n dev
# Error from server (NotFound): configmaps "app-config" not found

# 5. Чиним:
kubectl create configmap app-config -n dev \
  --from-literal=DATABASE_URL=postgres://...

# 6. kubelet сам подхватит изменение при следующем restart-попытке (в пределах backoff)
kubectl get pod app-broken -n dev -w

Заметьте: ничего не пересоздавали. Pod в Running после следующего restart-tick — потому что envFrom ConfigMap резолвится при старте контейнера, и теперь ConfigMap есть.

WARNING

Если бы это было CreateContainerConfigError — kubelet вообще не запускает контейнер, пока конфиг не починят. В этом случае restart backoff не применяется (нечего перезапускать), Pod просто висит в waiting. После создания ConfigMap kubelet увидит изменение и попробует снова — но иногда нужно kubectl delete pod (Deployment пересоздаст), чтобы ускорить. Это исключение из правила «не удаляйте Pod».


Зачем нужен Never

restartPolicy: Never кажется бесполезным — зачем не перезапускать? Реальные кейсы:

  • Миграция БД: должна выполниться один раз. Если упала — нужно вручную разобраться, не повторять автоматически. Job с restartPolicy: Never и backoffLimit: 0 гарантирует «одна попытка».
  • Debug-pod: kubectl run debug --rm -it --restart=Never --image=busybox -- /bin/sh. После exit shell Pod удаляется.
  • Один билд в CI-под: запустил, отработал, забыл.
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
spec:
  backoffLimit: 0
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: migrate
        image: myapp:1.0
        command: ["./migrate.sh"]

Проверка знанийKnowledge check
Pod в CrashLoopBackOff с restartCount=8 и текущей паузой 300s. Сколько ещё ждать после восьмого падения?
ОтветAnswer
300 секунд. Backoff достиг cap на 5-м или 6-м рестарте и дальше всегда 5 минут. Чтобы счётчик сбросился, контейнер должен проработать без падений 10 минут — но пока он падает на старте, этого не произойдёт.
Проверка знанийKnowledge check
Можно ли создать Deployment с restartPolicy: OnFailure?
ОтветAnswer
Нет. API server отклонит такой манифест: Deployment, ReplicaSet, StatefulSet, DaemonSet требуют restartPolicy: Always (это валидируется). OnFailure и Never разрешены только для Job, CronJob и голых Pod.
Проверка знанийKnowledge check
kubectl logs <pod> при CrashLoopBackOff возвращает пустой вывод. Почему и что делать?
ОтветAnswer
Контейнер сейчас не запущен — kubelet ждёт backoff между падениями. Используйте флаг --previous: kubectl logs <pod> --previous покажет логи последнего упавшего инстанса. Это самая частая забываемая команда CKAD.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Какие значения restartPolicy допустимы для Deployment?

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

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

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

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