Выберите продукт

Kubernetes CrashLoopBackOff: как читать logs, events, probes и exit code

CrashLoopBackOff — не «ошибка Kubernetes», а симптом: контейнер падает, kubelet перезапускает его и увеличивает задержку (backoff). Разберём порядок диагностики через events, kubectl describe pod и kubectl logs --previous, а также probes и значения exit code.
Kubernetes CrashLoopBackOff: как читать logs, events, probes и exit code

CrashLoopBackOff — один из самых частых статусов в Kubernetes. Он почти всегда означает простую вещь: контейнер стартует, быстро завершается (или его убивают), kubelet пытается перезапустить его снова и снова, а затем увеличивает паузу между рестартами (backoff).

Ниже — рабочий порядок диагностики: какие команды запускать, где искать первопричину (логи, события, probes), и как правильно трактовать exit code (включая популярный 137).

Что именно происходит при CrashLoopBackOff

Kubernetes управляет жизненным циклом контейнера через kubelet. Если контейнер в Pod завершается, kubelet ориентируется на restartPolicy:

  • Always — перезапускать всегда (типично для Deployment/StatefulSet).
  • OnFailure — перезапускать при ненулевом коде.
  • Never — не перезапускать.

Когда завершения идут подряд, kubelet увеличивает задержку между попытками. На этом этапе вы и видите CrashLoopBackOff. Важно: это описание поведения перезапуска, а не причина падения.

Быстрый чек-лист: 5 минут до гипотезы

  1. Найдите конкретный Pod и контейнер, который падает (и сколько там рестартов).
  2. Посмотрите kubectl describe pod: блоки State/Last State и Events часто уже дают ответ.
  3. Снимите логи прошлого запуска через kubectl logs --previous.
  4. Проверьте probes: livenessProbe, readinessProbe, при необходимости startupProbe.
  5. Интерпретируйте 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: «старые» события могут уже исчезнуть, особенно в загруженных кластерах.

Пример вывода kubectl describe pod с блоком Events при CrashLoopBackOff

Если приложение разложено по очередям/воркерам и вы запускаете фоновые процессы через systemd (например, на VM), сравните поведение рестартов и логирования с подходом в Kubernetes: полезная шпаргалка — очереди, Supervisor и systemd для воркеров.

FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды Финляндия

Шаг 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>

Пример использования kubectl logs --previous для чтения логов упавшего запуска

Если вы централизуете логи в Loki, заранее продумайте разметку и фильтры: при массовых рестартах удобнее быстро вырезать нужный Pod/контейнер по меткам. См. заметку про labels и pipelines в Loki.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Как снизить шанс 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 и доступом к системным метрикам.

Поделиться статьей

Вам будет интересно

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

После одновременной остановки всех узлов Galera обычного запуска MariaDB недостаточно. Сначала найдите сервер с последней транзакц ...
CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Разберём, почему CoreDNS перезапускается или отвечает SERVFAIL, как обнаружить замкнутую пересылку DNS-запросов, проверить доступн ...
Apache Kafka KRaft на VDS: отдельные controller и broker

Apache Kafka KRaft на VDS: отдельные controller и broker

Соберём новый Kafka-кластер без ZooKeeper на шести Linux VDS: три узла controller образуют отказоустойчивый metadata quorum, а три ...