CrashLoopBackOff — один из самых частых статусов в Kubernetes. Он почти всегда означает простую вещь: контейнер стартует, быстро завершается (или его убивают), kubelet пытается перезапустить его снова и снова, а затем увеличивает паузу между рестартами (backoff).
Ниже — рабочий порядок диагностики: какие команды запускать, где искать первопричину (логи, события, probes), и как правильно трактовать exit code (включая популярный 137).
Что именно происходит при CrashLoopBackOff
Kubernetes управляет жизненным циклом контейнера через kubelet. Если контейнер в Pod завершается, kubelet ориентируется на restartPolicy:
Always— перезапускать всегда (типично для Deployment/StatefulSet).OnFailure— перезапускать при ненулевом коде.Never— не перезапускать.
Когда завершения идут подряд, kubelet увеличивает задержку между попытками. На этом этапе вы и видите CrashLoopBackOff. Важно: это описание поведения перезапуска, а не причина падения.
Быстрый чек-лист: 5 минут до гипотезы
- Найдите конкретный Pod и контейнер, который падает (и сколько там рестартов).
- Посмотрите
kubectl describe pod: блоки State/Last State и Events часто уже дают ответ. - Снимите логи прошлого запуска через
kubectl logs --previous. - Проверьте probes:
livenessProbe,readinessProbe, при необходимостиstartupProbe. - Интерпретируйте
exit codeиReason:OOMKilled,Error,Completed.
Шаг 1. Найдите Pod и контейнер, который уходит в рестарт
Начните с общего списка:
kubectl get pods -n <namespace>
Дальше полезно посмотреть Pod детальнее (нода, IP, рестарты):
kubectl get pod -n <namespace> <pod-name> -o wide
Если в Pod несколько контейнеров, быстро выясните, какой именно рестартится и почему он сейчас в ожидании:
kubectl get pod -n <namespace> <pod-name> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.restartCount}{"\t"}{.state.waiting.reason}{"\n"}{end}'
Частая картина: падает контейнер приложения, а sidecar (agent/proxy) остаётся жив.
Шаг 2. kubectl describe pod: события (events) и причины
kubectl describe pod — ключевая команда при CrashLoopBackOff. Она показывает образ, env, тома, probes, а также состояние контейнера и события от kubelet.
kubectl describe pod -n <namespace> <pod-name>
Ищите самое ценное в двух местах:
- Containers → State и Last State:
Reason(например,Error,OOMKilled) иExit Code, плюс время старта/завершения. - Events внизу:
Back-off restarting failed container,Liveness probe failed, ошибки монтирования томов, permission, pull образа и т.д.
Отдельно про kubectl events
Иногда удобнее смотреть события отдельно, отсортировав по времени (и увидеть, что повторяется):
kubectl get events -n <namespace> --sort-by=.lastTimestamp
Учитывайте TTL: «старые» события могут уже исчезнуть, особенно в загруженных кластерах.

Если приложение разложено по очередям/воркерам и вы запускаете фоновые процессы через systemd (например, на VM), сравните поведение рестартов и логирования с подходом в Kubernetes: полезная шпаргалка — очереди, Supervisor и systemd для воркеров.
Шаг 3. Логи упавшего контейнера: kubectl logs --previous
Классическая ловушка: вы смотрите kubectl logs, а контейнер уже перезапущен — и логи пустые или «не те». При CrashLoopBackOff почти всегда нужно читать вывод именно прошлого запуска.
kubectl logs -n <namespace> <pod-name> -c <container-name> --previous
Если контейнер успевает стартовать на секунду и тут же падает, этот вывод часто содержит первопричину: ошибка конфигурации, отсутствие переменной окружения, невозможность подключиться к базе, миграции, несовместимость библиотек.
Если логов нет или они обрезаны
Если приложение пишет только в файл внутри контейнера, kubectl logs будет бесполезен: Kubernetes по умолчанию читает stdout/stderr. Для продакшена проще всего логировать в stdout/stderr и забирать логи агентом в централизованное хранилище (например, Loki/ELK).
Ещё один кейс — контейнер убивается настолько рано, что ничего не успевает вывести. Тогда основными источниками становятся Events и exit code/Reason.
Шаг 4. Exit code и Reason: как интерпретировать
В kubectl describe pod (и в .status.containerStatuses) вы увидите Exit Code и причину завершения (Reason). Это помогает быстро классифицировать проблему: OOM/kill, «приложение упало само», или вообще «не тот тип workload».
exit code 137
exit code 137 почти всегда означает, что процесс получил SIGKILL (9), а код завершения считается как 128 + 9 = 137. В Kubernetes типовые причины SIGKILL:
- OOMKilled: контейнер превысил лимит памяти и был убит ядром (cgroup OOM).
- Контейнер «дожали» при остановке Pod: процесс игнорировал
SIGTERM, истёкterminationGracePeriodSeconds, и kubelet отправилSIGKILL.
Как отличить OOM от «просто убили»:
- В
kubectl describe podв Last State часто будетReason: OOMKilled. - Если есть метрики, смотрите память прямо перед рестартом (пик у лимита — сильный сигнал).
- Если это «дожим», чаще видно корреляцию со временем деплоя/роллаута и завершением Pod.
exit code 1, 2 и прочие «обычные» коды
Это чаще всего означает, что приложение само завершилось с ошибкой. Главный источник правды — kubectl logs --previous. Типовые причины:
- невалидная конфигурация или отсутствующий секрет/ConfigMap;
- ошибка миграций БД;
- зависимый сервис (DB/queue) недоступен, а приложение настроено «умирать», а не ждать;
- не хватает прав на запись в volume;
- ошибка загрузки динамических библиотек/несовместимость ABI.
Completed (exit code 0) и всё равно рестарты
Иногда контейнер завершается с exit code 0 (успешно), но Pod всё равно рестартится и уходит в CrashLoopBackOff. Обычно это признак, что вы запустили «одноразовую» команду в объекте с restartPolicy: Always (например, Deployment).
Что делать: для одноразовых задач используйте Job/CronJob, либо меняйте команду контейнера так, чтобы процесс был долгоживущим (например, веб-сервер), а не разовой миграцией.
Шаг 5. Probes: как liveness/readiness приводят к CrashLoopBackOff
Распространённый сценарий: приложение «почти живое», но probes настроены слишком агрессивно — kubelet перезапускает контейнер, не дав ему нормально стартовать.
Разница между readinessProbe и livenessProbe
- readinessProbe: «можно ли отправлять трафик в Pod». Если readiness падает — Pod остаётся жив, но исключается из endpoints.
- livenessProbe: «процесс вообще живой». Если liveness падает — kubelet перезапускает контейнер, и это прямой путь к CrashLoopBackOff.
Если при CrashLoopBackOff в Events регулярно появляется
Liveness probe failed, вы уже нашли механизм рестарта. Дальше нужно понять, probe реально ловит зависание или это ложное срабатывание из-за старта/нагрузки.
Где смотреть ошибки probes
Практически всегда достаточно двух источников:
- Events в
kubectl describe pod(текст ответа, HTTP-код, timeout, connection refused); - описание контейнера в describe: параметры probe, задержки и таймауты.
Типовые ошибки в настройке probes
- Слишком маленький
initialDelaySeconds: приложение ещё не подняло HTTP, а liveness уже проверяет и «валит» контейнер. - Слишком маленький
timeoutSeconds: на холодном старте или при GC приложение не успевает ответить. - Неправильный путь/порт: probe ходит не туда, всегда 404/connection refused.
- Проверка внешних зависимостей в liveness (например, БД): зависимость моргнула — kubelet убил приложение. Зависимости чаще учитывают в readiness, а liveness делают «локальным».
Когда нужен startupProbe
Если сервис долго стартует (миграции, прогрев кэша, компиляция, загрузка моделей), лучше использовать startupProbe. Тогда Kubernetes не будет применять liveness до тех пор, пока startupProbe не станет успешным.
Частые корни проблемы и как быстро подтвердить
1) OOMKilled и лимиты памяти (часто exit code 137)
Если видите Reason: OOMKilled, проблема почти всегда в сочетании «лимит памяти» + «реальное потребление».
- Проверьте
resources.requests/resources.limitsу контейнера. - Сопоставьте с метриками памяти перед рестартом (если есть).
- Для JVM/Node часто нужно явно ограничить память рантайма (heap, max-old-space-size), иначе лимит контейнера легко пробивается.
2) Неверная команда/аргументы/entrypoint
Контейнер может падать из-за банального: неправильные command/args, нет бинарника, неверный путь, нет прав на исполнение. Подсказки:
- в Events: ошибки уровня
exec; - в логах прошлого запуска: usage/help, stack trace, «file not found».
Проверьте, не переопределили ли вы entrypoint образа случайно.
3) Конфиг/секреты/переменные окружения
Если приложение требует переменную окружения и при отсутствии завершается, CrashLoopBackOff будет мгновенным. Обычно спасает связка:
kubectl describe pod→ env/envFrom и ошибки монтирования секретов/ConfigMap;kubectl logs --previous→ точный текст «missing env var» или аналогичный.
4) Volume: права, readonly, недоступность
Если контейнеру нужен volume и он не смонтировался, это часто прямо видно в Events. Если том примонтирован, но права неверные — ошибка чаще уходит в логи приложения (permission denied).
Полезные команды для углубления (по ситуации)
Снять «текущие» логи и логи прошлого запуска
kubectl logs -n <namespace> <pod-name> -c <container-name>
kubectl logs -n <namespace> <pod-name> -c <container-name> --previous
Посмотреть lastState/exitCode по всем контейнерам в Pod
kubectl get pod -n <namespace> <pod-name> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\n state: "}{.state.waiting.reason}{"\n last: "}{.lastState.terminated.reason}{"\n code: "}{.lastState.terminated.exitCode}{"\n\n"}{end}'
Проверить, не упираетесь ли в pull образа (часто путают со CrashLoopBackOff)
Если проблема в pull, в Events обычно видно ErrImagePull или ImagePullBackOff — это другой класс проблем, но в общей аварии их часто смешивают.
kubectl describe pod -n <namespace> <pod-name>

Если вы централизуете логи в Loki, заранее продумайте разметку и фильтры: при массовых рестартах удобнее быстро вырезать нужный Pod/контейнер по меткам. См. заметку про labels и pipelines в Loki.
Как снизить шанс CrashLoopBackOff в проде
- Делайте liveness «узким»: проверяйте, что процесс жив, а не что внешние зависимости доступны.
- Readiness привязывайте к готовности обслуживать трафик: прогрев кэша, подключение к БД/очереди, прогрузка конфигов.
- Для долгого старта используйте startupProbe, а не «угаданный»
initialDelaySeconds. - Логируйте в stdout/stderr, чтобы
kubectl logsработал предсказуемо. - Задавайте ресурсы осмысленно: requests для планирования, limits как предохранитель; отдельно контролируйте память, чтобы не ловить неожиданный OOM.
- Проверяйте поведение на SIGTERM: корректное завершение снижает риск «дожима» SIGKILL и странных рестартов.
Итог: где правда при CrashLoopBackOff
В большинстве случаев ответ находится в комбинации трёх источников:
- Events из
kubectl describe pod— показывают, кто и почему рестартит. - Логи прошлого запуска через
kubectl logs --previous— показывают, почему приложение само завершилось. - Probes — объясняют «ложные» рестарты из-за слишком жёстких проверок.
А exit code помогает быстро классифицировать проблему: OOM/kill (часто 137), «приложение упало само» (1/2/…), либо «не тот workload для этой задачи» (0 + рестарты при Always).
Если вы диагностируете это на VM или в небольшом кластере и нужен предсказуемый контроль ресурсов, иногда проще выделить отдельную машину под конкретный сервис. Для таких задач обычно выбирают VDS с понятными лимитами CPU/RAM и доступом к системным метрикам.


