CrashLoopBackOff в Kubernetes: почему под перезапускается по кругу и как найти причину
Команда KAI Lab · · 9 мин чтения
CrashLoopBackOff значит, что контейнер в поде запускается, падает, и kubelet перед каждым новым запуском выжидает паузу, которая растёт от 10 секунд до 5 минут. Причина почти всегда внутри контейнера: приложение завершается с ошибкой, упирается в лимит памяти или его убивает liveness probe, поэтому искать её надо в коде выхода, логах предыдущего запуска и событиях пода.
Сам статус ничего не объясняет: это не причина, а режим ожидания между попытками. Причина записана в поле Last State и в логах того экземпляра контейнера, который упал последним. Ниже — порядок проверки, который закрывает большинство случаев, и таблица типичных причин.
Что означает CrashLoopBackOff
У пода есть поле restartPolicy со значениями Always, OnFailure и Never. По умолчанию стоит Always, а для Deployment это единственное допустимое значение. Когда контейнер завершается, kubelet перезапускает его на той же ноде, но не сразу. Задержка растёт экспоненциально: 10, 20, 40 секунд и дальше до потолка в 300 секунд. Пока идёт пауза, контейнер находится в состоянии Waiting с причиной CrashLoopBackOff.
Если после очередного запуска контейнер проработал 10 минут без проблем, kubelet сбрасывает таймер, и следующее падение снова считается первым. Поэтому сервис, который падает раз в полчаса, может ни разу не показать CrashLoopBackOff: вы увидите только растущий счётчик перезапусков.
Колонка STATUS в kubectl get pods — поле для людей, а не фаза пода. Документация Kubernetes отдельно просит их не путать. Под в CrashLoopBackOff формально находится в фазе Running: он назначен на ноду, контейнеры созданы, просто один из них постоянно перезапускается.
Потолок задержки можно менять. С Kubernetes 1.35 функция KubeletCrashLoopBackOffMax в бета-версии и включена по умолчанию: на уровне ноды в конфигурации kubelet задаётся crashLoopBackOff.maxContainerRestartPeriod от 1 до 300 секунд. Есть и альфа-функция ReduceDefaultCrashLoopBackOffDecay (с 1.33, по умолчанию выключена): начальная задержка становится 1 секунда, максимальная — 60.
Как выглядит CrashLoopBackOff
$ kubectl get pods -n shop
NAME READY STATUS RESTARTS AGE
orders-6b8f9d7c4-r7x2n 0/1 CrashLoopBackOff 5 (86s ago) 7m12sRESTARTS показывает, сколько раз контейнер уже перезапускали и как давно был последний раз. READY 0/1 значит, что трафик через Service на этот под не идёт.
Пошаговая диагностика
Шаг 1. Причина и код последнего завершения
$ kubectl describe pod orders-6b8f9d7c4-r7x2n -n shop
...
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: Error
Exit Code: 1
Started: Fri, 25 Sep 2026 10:41:58 +0500
Finished: Fri, 25 Sep 2026 10:42:01 +0500
Ready: False
Restart Count: 5
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulled 2m3s (x6 over 7m12s) kubelet Container image "registry.example.kz/shop/orders:2.7.0" already present on machine
Warning BackOff 11s (x28 over 6m50s) kubelet Back-off restarting failed container orders in pod orders-6b8f9d7c4-r7x2n_shop(3c1f6a52-8e0d-4b7a-9d41-6f2e0b9c7a13)Смотрите на блок Last State. Reason: Error и ненулевой Exit Code — приложение завершилось само с ошибкой. OOMKilled — процесс убило ядро за превышение лимита памяти. Completed с кодом 0 — процесс просто закончил работу. StartError — runtime не смог даже запустить процесс. Время между Started и Finished тоже подсказка: три секунды — это падение на старте, а не под нагрузкой.
Для нескольких подов быстрее вытащить эти поля одной командой:
$ kubectl get pod orders-6b8f9d7c4-r7x2n -n shop \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{" "}{.lastState.terminated.reason}{" "}{.lastState.terminated.exitCode}{"\n"}{end}'
orders Error 1Шаг 2. Логи упавшего экземпляра
Обычный kubectl logs показывает текущий экземпляр контейнера, а он мог только что стартовать и ещё ничего не написать. Логи того, который упал, отдаёт флаг --previous (коротко -p):
$ kubectl logs orders-6b8f9d7c4-r7x2n -n shop --previous
2026-09-25T05:41:59Z INFO orders 2.7.0 starting
2026-09-25T05:42:01Z FATAL config: environment variable PAYMENTS_URL is requiredВ поде с несколькими контейнерами добавьте -c имя-контейнера. Если приложение пишет логи в файл, а не в stdout, kubectl logs будет пустым. Тогда помогает поле terminationMessagePolicy: FallbackToLogsOnError: при падении с ошибкой Kubernetes положит хвост вывода, до 80 строк или 2048 байт, в сообщение Last State.
Шаг 3. События пода
$ kubectl events --for pod/orders-6b8f9d7c4-r7x2n -n shopТо же самое без новой подкоманды: kubectl get events -n shop --field-selector involvedObject.name=orders-6b8f9d7c4-r7x2n. Если контейнер убивает проба, это видно сразу:
Warning Unhealthy 10s (x3 over 30s) kubelet Liveness probe failed: Get "http://10.244.1.17:8080/healthz": dial tcp 10.244.1.17:8080: connect: connection refused
Normal Killing 10s kubelet Container orders failed liveness probe, will be restartedШаг 4. Расшифровать код выхода
Когда процесс завершается по сигналу N, код выхода равен 128 + N. Номера сигналов — в man 7 signal.
| Код | Что значит | Что проверить |
|---|---|---|
| 0 | процесс завершился сам, без ошибки | основной процесс не должен уходить в фон; разовую задачу запускают как Job |
| 1 и другие до 125 | ошибка приложения | kubectl logs --previous |
| 126 | команда найдена, но не запускается (если её вызывает shell) | права на файл, command и args |
| 127 | команда не найдена (если её вызывает shell) | путь к бинарнику в образе |
| 128, Reason StartError | runtime не запустил процесс, например executable file not found in $PATH | command, entrypoint, образ |
| 137 | 128 + 9, SIGKILL: OOM killer или принудительная остановка | Reason: OOMKilled или события проб |
| 139 | 128 + 11, SIGSEGV: ошибка сегментации | баг в приложении или несовместимая библиотека |
| 143 | 128 + 15, SIGTERM: контейнер попросили остановиться | liveness probe, удаление пода |
Про 137 подробно — в статье OOMKilled и exit code 137: этот код не всегда означает нехватку памяти.
Шаг 5. Проверить liveness и startup probe
Частая причина CrashLoopBackOff — не баг, а слишком нетерпеливая liveness probe. По умолчанию проба выполняется каждые 10 секунд, ждёт ответа 1 секунду, а после 3 неудач подряд kubelet перезапускает контейнер. Приложение, которому на старт нужна минута, не успевает открыть порт, получает рестарт и уходит на новый круг.
Для медленного старта в документации Kubernetes рекомендуют startup probe на тот же эндпоинт. Пока она не прошла, liveness и readiness не выполняются:
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10Здесь у приложения до 300 секунд (30 × 10) на запуск. Ещё одно правило из документации: liveness должна ловить только неустранимое состояние вроде дедлока. Проба, которая проверяет базу данных, при сбое базы перезапустит все поды разом, и проблема станет больше.
Шаг 6. Запустить копию пода с shell
Если по логам причина неясна, создайте копию пода с другой командой и посмотрите изнутри: какие файлы на месте, какие переменные окружения видит процесс, что будет при ручном запуске.
$ kubectl debug orders-6b8f9d7c4-r7x2n -n shop -it --copy-to=orders-debug --container=orders -- shКопия не запускает исходную команду, поэтому не падает. После проверки удалите её: kubectl delete pod orders-debug -n shop.
Шаг 7. Найти, что изменилось
CrashLoopBackOff редко появляется на пустом месте. Сверьте время первого перезапуска с последним релизом, изменением ConfigMap или Secret, обновлением образа базы:
$ kubectl rollout history deployment/orders -n shop
$ kubectl rollout undo deployment/orders -n shopОткат возвращает сервис, пока вы разбираетесь с причиной. Удалять под бесполезно: Deployment создаст новый с той же ошибкой.
Причины CrashLoopBackOff: таблица
| Причина | Как узнать | Как исправить |
|---|---|---|
| Ошибка конфигурации: нет переменной, неверный адрес, битый файл | Exit Code 1, в logs --previous сообщение о конфигурации | поправить ConfigMap, Secret или манифест, откатить релиз |
| Не хватает памяти | Reason: OOMKilled, Exit Code 137 | замерить потребление, поднять limits.memory или найти утечку |
| Liveness probe убивает до старта | события Unhealthy и failed liveness probe, will be restarted | startup probe, проверить путь, порт и timeoutSeconds |
| Недоступна зависимость: база, очередь, DNS | в логах connection refused или таймаут | проверить Service и его endpoints; приложение должно повторять подключение, а не падать |
| Процесс завершается сам | Reason: Completed, Exit Code 0 | держать процесс на переднем плане; разовые задачи — через Job |
| Неверная команда или entrypoint | Reason: StartError, executable file not found in $PATH или код 126–127 | исправить command и args, проверить содержимое образа |
| Нет прав на файл или порт | в логах permission denied | securityContext, владелец файлов в образе, порт выше 1024 для non-root |
Если контейнер вообще не создаётся, а в статусе CreateContainerConfigError, это другая ошибка: kubelet не смог собрать конфигурацию, например нет Secret. Её разбор — в статье CreateContainerConfigError. А если под не может скачать образ, смотрите ImagePullBackOff.
Как не узнавать о CrashLoopBackOff от пользователей
Под в CrashLoopBackOff с READY 0/1 из балансировки выпадает, но если упали все реплики, сервис лежит целиком. Что настроить заранее:
- алерт на рост счётчика перезапусков: в kube-state-metrics это метрика
kube_pod_container_status_restarts_total, а текущая причина ожидания —kube_pod_container_status_waiting_reasonс меткойreason="CrashLoopBackOff"; terminationMessagePolicy: FallbackToLogsOnError, чтобы причина падения была видна вkubectl describeбез поиска по логам;- startup probe для всего, что стартует дольше 30 секунд;
- проверку выкатки в CI:
kubectl rollout statusзавершится с ошибкой, если новые поды не поднимутся заprogressDeadlineSeconds(по умолчанию 600 секунд).
Тот же сценарий есть и вне Kubernetes: в Docker контейнер с restart: always перезапускается молча и снаружи выглядит как сервис, который работает через раз. Как его ловить, мы разобрали в статье контейнер Docker постоянно перезапускается. А вопрос «что будете делать с подом в CrashLoopBackOff» — один из самых частых на собеседовании по Kubernetes.
Частые вопросы
Чем CrashLoopBackOff отличается от Error и OOMKilled? CrashLoopBackOff — состояние ожидания между перезапусками. Error, OOMKilled и Completed — причина последнего завершения контейнера. В kubectl get pods вы видите то одно, то другое, в зависимости от момента: во время паузы — CrashLoopBackOff, сразу после падения — причину.
Сколько kubelet ждёт между перезапусками? 10 секунд, потом 20, 40 и так далее, но не больше 300 секунд. После 10 минут нормальной работы таймер сбрасывается. С Kubernetes 1.35 потолок можно уменьшить для ноды через crashLoopBackOff.maxContainerRestartPeriod в конфигурации kubelet.
Почему kubectl logs пустой? Скорее всего, вы смотрите новый экземпляр контейнера, который ещё ничего не написал. Нужен флаг --previous. Если пусто и с ним, приложение пишет не в stdout или падает раньше, чем успевает что-то вывести. Тогда смотрите Last State в kubectl describe и запускайте копию пода через kubectl debug.
Поможет ли удалить под? Нет. Контроллер создаст новый под из того же шаблона, и он упадёт так же. Удаление сбрасывает только счётчик и задержку. Помогает исправление причины или откат: kubectl rollout undo.
Можно ли сделать так, чтобы kubelet перезапускал быстрее? Можно уменьшить потолок задержки на ноде через maxContainerRestartPeriod, но причину это не устраняет. Если контейнер падает на старте, быстрые перезапуски только нагружают ноду и забивают события.
Потренироваться на практике
Разбирать CrashLoopBackOff проще всего, когда он случился у вас. В симуляторе DevOps KAI Lab в месяце про Kubernetes есть тикет «Под в CrashLoopBackOff»: консоль в браузере отдаёт реальные выводы kubectl, и причину нужно найти самому, по тем же шагам, что в этой статье. Начать можно с бесплатного теста навыков DevOps.
Источники
- Kubernetes. Pod Lifecycle — CrashLoopBackOff, задержки 10 с → 300 с, сброс через 10 минут,
restartPolicy, функцииKubeletCrashLoopBackOffMaxиReduceDefaultCrashLoopBackOffDecay. - Kubernetes. Debug Running Pods —
kubectl describe,kubectl logs --previous,kubectl debug --copy-to. - Kubernetes. Liveness, Readiness and Startup Probes — значения по умолчанию и предупреждение о каскадных сбоях.
- Kubernetes. Configure Liveness, Readiness and Startup Probes — события
Unhealthy, startup probe на 300 секунд. - Kubernetes. Determine the Reason for Pod Failure —
terminationMessagePolicy: FallbackToLogsOnError. - Kubernetes. Deployments —
progressDeadlineSeconds,kubectl rollout status,kubectl rollout undo. - Kubernetes. Feature Gates — стадии
KubeletCrashLoopBackOffMax(бета с 1.35) иReduceDefaultCrashLoopBackOffDecay(альфа с 1.33). - kube-state-metrics. Pod Metrics —
kube_pod_container_status_restarts_total,kube_pod_container_status_waiting_reason. - bash(1), раздел EXIT STATUS — коды 126, 127 и 128 + N; signal(7) — номера сигналов.