kai.kzLab

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)   7m12s

RESTARTS показывает, сколько раз контейнер уже перезапускали и как давно был последний раз. 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 StartErrorruntime не запустил процесс, например executable file not found in $PATHcommand, entrypoint, образ
137128 + 9, SIGKILL: OOM killer или принудительная остановкаReason: OOMKilled или события проб
139128 + 11, SIGSEGV: ошибка сегментациибаг в приложении или несовместимая библиотека
143128 + 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 restartedstartup probe, проверить путь, порт и timeoutSeconds
Недоступна зависимость: база, очередь, DNSв логах connection refused или таймаутпроверить Service и его endpoints; приложение должно повторять подключение, а не падать
Процесс завершается самReason: Completed, Exit Code 0держать процесс на переднем плане; разовые задачи — через Job
Неверная команда или entrypointReason: StartError, executable file not found in $PATH или код 126–127исправить command и args, проверить содержимое образа
Нет прав на файл или портв логах permission deniedsecurityContext, владелец файлов в образе, порт выше 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) — номера сигналов.

Читать дальше