Под в CrashLoopBackOff или Pending - это не диагноз, а только входная точка. Диагноз собирается из четырёх команд kubectl: get pods показывает статус и число рестартов, describe pod показывает события и причины, logs показывает сообщения приложения, exec и port-forward позволяют проверить поведение изнутри. Такой порядок экономит время: каждая следующая команда уточняет то, что показала предыдущая.
Первое правило: не начинайте с exec. Сессия внутри контейнера помогает, когда приложение стартует, но отвечает ошибкой или не видит соседний сервис. Если контейнер не поднимается, exec не подключится к процессу, и вы потеряете минуты на ожидании.
С чего начать отладку пода в Kubernetes
kubectl по умолчанию работает с namespace из текущего контекста kubeconfig. Если под запущен в другом namespace, команда вернёт «No resources found», хотя проблема никуда не делась. Проверьте контекст и namespace до всех остальных действий: назначение объектов API, включая Pods, Deployments, Services и Namespaces, разобрано в статье Архитектура кластера Kubernetes и ключевые объекты API.
Базовый порядок диагностики: get - describe - logs - exec
Команды идут по цепочке: состояние, события, логи приложения, проверка изнутри. Пример вывода kubectl get pods:
$ kubectl get pods -n prod NAME READY STATUS RESTARTS AGE web-7f9c8d5b6-x2k4m 0/1 CrashLoopBackOff 5 (48s ago) 6m12s api-6b8d9f7c4-l9q2v 1/1 Running 0 3h21m db-0 1/1 Running 0 11d
Колонка READY показывает готовность контейнеров к приёму трафика: 0/1 значит, что readiness-проба не проходит. STATUS фиксирует текущее состояние, RESTARTS - число перезапусков и время последнего, AGE - возраст пода. Под web перезапускался пять раз за шесть минут: контейнер доходит до старта и падает, значит причина осталась в предыдущем запуске.
Если вы только осваиваете кластер, начните с базового сценария запуска сервиса и первых команд kubectl: Kubernetes для начинающих: запуск сервисов с минимальной конфигурацией.
Чек-лист этого этапа:
- kubectl get pods -n prod - найти под со статусом, отличным от Running, или с растущим RESTARTS.
- kubectl describe pod web-7f9c8d5b6-x2k4m -n prod - прочитать блок Events снизу вверх.
- kubectl logs web-7f9c8d5b6-x2k4m -n prod --previous - посмотреть причину падения.
- kubectl logs web-7f9c8d5b6-x2k4m -n prod --tail=50 - посмотреть текущий запуск.
- kubectl exec -it web-7f9c8d5b6-x2k4m -n prod -- sh - проверить окружение, если контейнер жив.
Как быстро найти проблемный под и namespace
Когда подов десятки и они разбросаны по namespace, работают четыре приёма:
$ kubectl get pods -A $ kubectl get pods -n prod $ kubectl get pods -n prod -l app=web $ kubectl get pods -n prod --field-selector=status.phase!=Running $ kubectl get events -n prod --sort-by=.lastTimestamp
Флаг -A показывает поды всех namespace и сразу выдаёт, где живёт проблемный workload. Селектор -l app=web сужает список до одного сервиса, а field selector status.phase!=Running отсекает здоровые поды. Последняя команда полезна, когда describe уже прокручен, а картина по namespace нужна целиком: сортировка по .lastTimestamp ставит свежие события в конец списка.
kubectl describe pod: как читать события и статусы
Вывод describe состоит из блоков Name, Namespace, Node, Start Time, Labels, Status, IP, Containers, Conditions, Volumes и Events. Блок Events информативен больше остальных: там собраны решения планировщика, ответы kubelet и ошибки монтирования томов. События упорядочены по времени, свежие идут в конце. Полезная привычка: читать Events снизу вверх, потому что первопричина часто в первых строках, а ниже копятся повторы одной и той же ошибки.
describe не показывает stdout и stderr приложения. Событие BackOff сообщает, что контейнер падает, но не объясняет почему. За текстом ошибки идите в kubectl logs.
Типовые события: FailedScheduling, ImagePullBackOff, BackOff, OOMKilling
- FailedScheduling - планировщик не нашёл ноду. Причины: не хватает CPU или памяти, не подходит nodeSelector или affinity, на нодах taints без tolerations, не связан PVC. Следующий шаг: kubectl describe pod web-7f9c8d5b6-x2k4m -n prod, затем kubectl get nodes -o wide.
- ImagePullBackOff и ErrImagePull - kubelet не смог скачать образ: опечатка в имени или теге, приватный registry без imagePullSecrets, истёкший токен. ErrImagePull фиксирует первую неудачную попытку, ImagePullBackOff означает, что попытки повторяются с нарастающей паузой. Следующий шаг: найти в describe строку Failed to pull image и прочитать причину отказа registry дословно.
- BackOff - контейнер стартует и падает, kubelet увеличивает интервал между рестартами. Следующий шаг: kubectl logs web-7f9c8d5b6-x2k4m -n prod --previous, затем проверить command, args и livenessProbe в манифесте.
- OOMKilling - контейнер превысил memory limit и был убит ядром. В describe это видно в блоке Last State: Reason OOMKilled и Exit Code 137. Exit code 137 обычно означает, что процесс завершён сигналом SIGKILL (9), то есть его «жёстко» убили; типичный сценарий в Kubernetes - OOMKilled (Kubernetes CrashLoopBackOff: события, пробы, exit codes). Но 137 не обязательно означает именно OOM: тот же код может отображаться как Last State: Terminated, Reason: Error, Exit Code: 137 (Kubernetes Pods Terminated - Exit Code 137 - Stack Overflow). Следующий шаг: сравнить потребление с limits.resources.memory и либо поднять лимит, либо искать утечку.
- Evicted - под вытеснен с ноды под давлением ресурсов. Контейнер могут убить не только из-за OOM: eviction при давлении ресурсов на ноде, остановка ноды, ручное удаление пода (Kubernetes CrashLoopBackOff: события, пробы, exit codes). Следующий шаг: kubectl describe node worker-1 и проверка состояния ноды.
Статусы Pending, ContainerCreating, CrashLoopBackOff, Evicted
| Статус | Что означает | Следующий шаг |
|---|---|---|
| Pending | Под не размещён ни на одной ноде | kubectl describe pod, блок Events с FailedScheduling |
| ContainerCreating | kubelet создаёт контейнер, тянет образ или монтирует том | kubectl describe pod, события про volume и образ |
| CrashLoopBackOff | Контейнер падает и перезапускается по кругу | kubectl logs --previous, затем Last State |
| Evicted | Под вытеснен с ноды под давлением ресурсов | kubectl describe node worker-1 |
kubectl logs: чтение логов контейнера и предыдущего запуска
Базовый синтаксис: kubectl logs web-7f9c8d5b6-x2k4m -n prod. Для одного контейнера в поде этого достаточно. Флаг -f включает потоковое чтение, -c выбирает контейнер. Полный разбор параметров, включая разбор JSON-логов и работу с probe failures, собран в статье kubectl logs: полное руководство по анализу логов контейнеров.
Как читать логи предыдущего запуска через --previous
При рестарте kubelet создаёт новый контейнер, и его логи начинаются с нуля. Если приложение падает на старте, текущий вывод почти пуст, а вся причина остаётся в предыдущем экземпляре. Флаг -p (--previous) возвращает логи предыдущего экземпляра контейнера в поде, если он существует (kubectl logs | Kubernetes):
$ kubectl logs web-7f9c8d5b6-x2k4m -n prod --previous $ kubectl logs web-7f9c8d5b6-x2k4m -n prod -c api --previous
Пример вывода предыдущего контейнера:
2026-10-03 09:14:02 INFO starting server on :8080 2026-10-03 09:14:02 ERROR failed to connect to postgres:5432: connection refused 2026-10-03 09:14:02 FATAL giving up after 3 retries
При CrashLoopBackOff почти всегда нужно читать вывод именно прошлого запуска: текущие логи могут быть пустыми или относиться уже к новому контейнеру (Kubernetes CrashLoopBackOff: как читать logs, events, probes и exit codes). Если предыдущего экземпляра нет, флаг не вернёт логи прошлого запуска - в этом случае смотрите логи текущего контейнера.
Важное ограничение: kubectl logs читает файлы логов напрямую с локального хранилища ноды, где запущен под. Ротацию логов выполняет container runtime, и её политика влияет на то, насколько глубоко назад можно получить логи (How to Get Application Logs from a Kubernetes Pod | Last9). После ротации или переезда пода старые логи недоступны, поэтому разбор инцидента не откладывайте.
Фильтрация логов: --tail, --since, --timestamps
$ kubectl logs web-7f9c8d5b6-x2k4m -n prod --tail=50 $ kubectl logs web-7f9c8d5b6-x2k4m -n prod --since=30m $ kubectl logs web-7f9c8d5b6-x2k4m -n prod --since-time='2026-10-03T09:00:00Z' $ kubectl logs web-7f9c8d5b6-x2k4m -n prod --timestamps $ kubectl logs web-7f9c8d5b6-x2k4m -n prod --since=1h --tail=200
Флаг --tail выводит только последние N строк, например --tail=20. Флаг --since возвращает логи новее относительного интервала (5s, 2m, 3h); по умолчанию возвращаются все логи, и одновременно можно использовать только один из флагов --since-time или --since (kubectl logs | Kubernetes). --since-time принимает абсолютную метку времени в формате RFC3339 (How to Get Application Logs from a Kubernetes Pod | Last9). Флаги комбинируются: --since=1h --tail=200 даёт последние двести строк за час. --timestamps добавляет метку времени к каждой строке, что нужно, когда сопоставляете логи приложения с событиями кластера и графиками мониторинга. Флаг -f оставляйте для коротких сессий наблюдения: на шумном сервисе поток не остановить иначе как Ctrl+C, а вывод быстро вытесняет историю терминала.
Логи в multi-container поде: выбор контейнера через -c
Если в поде больше одного контейнера, kubectl без флага -c ответит ошибкой «a container name must be specified». Имена контейнеров выводятся одной командой:
$ kubectl get pod web-7f9c8d5b6-x2k4m -n prod -o jsonpath='{.spec.containers[*].name}'
web istio-proxy
Дальше указываем нужный: kubectl logs web-7f9c8d5b6-x2k4m -n prod -c istio-proxy. Для всех контейнеров сразу есть флаг --all-containers=true, в том числе в комбинации с селектором меток и -f: kubectl logs -f -l app=nginx --all-containers=true (kubectl logs | Kubernetes). Флаг --prefix добавляет к каждой строке источник лога (имя пода и контейнера). Логи init-контейнеров смотрятся так же, через -c с именем init-контейнера. Разбор sidecar-контейнеров, init-контейнеров и автоматизации сбора есть в статье Мониторинг логов пода с несколькими контейнерами в Kubernetes.
kubectl exec: вход в под и проверка изнутри
Синтаксис: kubectl exec -it web-7f9c8d5b6-x2k4m -n prod -- sh. Двойной дефис отделяет аргументы kubectl от команды внутри контейнера; без него kubectl попытается разобрать флаги команды как свои. Флаг -i подключает stdin, -t выделяет TTY, -c выбирает контейнер в поде.
Минимальные образы (distroless, alpine) часто не содержат bash: тогда работает sh. В distroless может не быть и shell, и exec завершится ошибкой «exec: sh: executable file not found in $PATH». Для таких образов проверку делают через ephemeral-контейнер либо обходятся логами и метриками.
exec выполняется с правами RBAC вашего пользователя в кластере. В продакшене выдавайте право на подресурс exec узкому кругу инженеров и используйте его для команд чтения: ps, env, cat. Любая запись внутри контейнера меняет состояние пода и не переживёт рестарт.
Как выполнить одну команду без входа в интерактивную сессию
$ kubectl exec web-7f9c8d5b6-x2k4m -n prod -- env $ kubectl exec web-7f9c8d5b6-x2k4m -n prod -- ps aux $ kubectl exec web-7f9c8d5b6-x2k4m -n prod -- curl -s localhost:8080/health $ kubectl exec web-7f9c8d5b6-x2k4m -n prod -- ls -la /app
Такие однострочники удобно складывать в чек-лист: вывод возвращается в терминал, код завершения виден в $?, а результат легко приложить к тикету. Проверка env показывает, дошли ли переменные из ConfigMap и Secret, а curl localhost отвечает на вопрос, слушает ли приложение порт внутри контейнера.
Проверка сети и DNS внутри пода
$ kubectl exec web-7f9c8d5b6-x2k4m -n prod -- cat /etc/resolv.conf $ kubectl exec web-7f9c8d5b6-x2k4m -n prod -- nslookup kubernetes.default $ kubectl exec web-7f9c8d5b6-x2k4m -n prod -- nslookup web.prod.svc.cluster.local $ kubectl exec web-7f9c8d5b6-x2k4m -n prod -- curl -v http://web.prod.svc.cluster.local:80/health
Если имя сервиса не резолвится, причина в CoreDNS или в сетевых политиках, а не в приложении. Если резолвится, но соединение не устанавливается, проверяйте порт: приложение может слушать не тот порт либо Service указывать на другой targetPort. Пошаговый алгоритм для ситуации «поды Running, а сервис не отвечает» разобран в статье Диагностика Kubernetes: поды недоступны через Service.
kubectl port-forward: локальная проверка сервиса
Проброс порта вытаскивает приложение из кластера на вашу машину: kubectl port-forward pod/web-7f9c8d5b6-x2k4m 8080:80 -n prod, после чего http://localhost:8080 открывается в браузере или проверяется через curl. Первое число - локальный порт, второе - порт контейнера. Трафик идёт через API-сервер, поэтому соединение живёт, пока запущена команда. Авторизацию проверяет API-сервер: пользователю нужно разрешение на доступ к целевому ресурсу (например, Pod или Service) и к соответствующему подресурсу; типичные требуемые разрешения включают pods/portforward (Use Port Forwarding to Access Applications in a Cluster | Kubernetes). Администраторам стоит ограничивать эти права, так как port-forward даёт прямой сетевой доступ к рабочим нагрузкам и может обходить сетевые контроли.
$ kubectl port-forward pod/web-7f9c8d5b6-x2k4m 8080:80 -n prod $ curl -I http://localhost:8080/ $ curl -I http://127.0.0.1:8080/
Port-forward до пода, deployment и service: в чём разница
Проброс в любом случае заканчивается на конкретном поде: kubectl разрешает указанный объект и выбирает один под. Тип ресурса по умолчанию - pod, а если критерию соответствует несколько подов, под выбирается автоматически (kubectl port-forward | Kubernetes). Разница в том, кого выбирает:
- pod/ - строго указанный под. Вариант для диагностики конкретного экземпляра.
- deployment/ - один под из deployment. Удобно, когда имя пода меняется при каждом рестарте: kubectl port-forward deployment/web 8080:80 -n prod.
- service/ - под, стоящий за сервисом. Заодно проверяет связку Service и endpoints: kubectl port-forward service/web 8080:80 -n prod. Если endpoints пустые, проброс завершится ошибкой, и это уже подсказка о причине.
Для проверки балансировки и всех реплик порт-форвард не подходит: он всегда ведёт на один под и удерживает сессию на нём. Когда под заменяется во время rollout, сессия завершается и команду нужно запускать заново (kubectl port-forward [Pod, Deployment and Service Examples]). Распределение трафика тестируйте изнутри кластера, обращаясь к имени сервиса.
Отдельный случай - Service без селектора: его нельзя использовать для port-forward даже при вручную созданных EndpointSlices, потому что API-сервер не проксирует port-forward трафик на endpoints, не привязанные к подам (kubectl port-forward [Pod, Deployment and Service Examples]).
Типовые ошибки port-forward и как их исправить
- error: unable to listen on port 8080 - локальный порт занят. Выберите другой: kubectl port-forward pod/web-7f9c8d5b6-x2k4m 18080:80 -n prod, а занявший порт ищите через ss -ltnp | grep 8080.
- error: unable to forward port - под не в статусе Running. Pending, ContainerCreating и CrashLoopBackOff блокируют проброс: сначала поднимите под через describe и logs.
- connection refused на localhost - проброс работает, приложение не слушает указанный порт. Проверьте порт внутри пода через exec и curl localhost:80.
- lost connection to pod - под перезапустился или был удалён. Перезапустите port-forward, а причину ищите в describe и events.
Port-forward открывает доступ к внутреннему сервису снаружи кластера, поэтому в продакшене он допустим как короткая диагностическая сессия, а не как постоянный канал. При включённом аудите такие подключения попадают в audit-логи вместе с именем пользователя.
Типовые ошибки подов и что делать: таблица диагностики
Таблица связывает статус или событие с первым действием. Держите её под рукой во время дежурства.
| Статус или событие | Вероятная причина | Команда следующего шага |
|---|---|---|
| CrashLoopBackOff | Процесс падает при старте: конфиг, миграции, недоступная зависимость | kubectl logs web-7f9c8d5b6-x2k4m -n prod --previous |
| ImagePullBackOff, ErrImagePull | Неверный тег или имя образа, нет imagePullSecrets, registry недоступен | kubectl describe pod, строка Failed to pull image |
| Pending | Не хватает ресурсов, не совпадают nodeSelector или taints | kubectl describe pod, затем kubectl get nodes |
| OOMKilled | Потребление памяти выше limits.resources.memory | kubectl describe pod, блок Last State |
| Evicted | Нода под давлением: нехватка памяти или места на диске | kubectl describe node worker-1 |
| ContainerCreating (долго) | Тяжёлый образ, проблемы с volume или CSI, ожидание секрета | kubectl describe pod, блок Events |
| FailedScheduling | Планировщик не нашёл подходящую ноду | kubectl describe pod, затем ресурсы нод |
CrashLoopBackOff: почему контейнер падает и как найти причину
- kubectl describe pod web-7f9c8d5b6-x2k4m -n prod - смотреть Events и Last State. Там указаны Reason, Exit Code и время последнего падения.
- kubectl logs web-7f9c8d5b6-x2k4m -n prod --previous - прочитать ошибку предыдущего запуска.
- kubectl get pod web-7f9c8d5b6-x2k4m -n prod -o yaml - проверить command, args, livenessProbe и volumeMounts.
Частые причины: command в манифесте перекрывает ENTRYPOINT образа, приложение не находит файл конфигурации, миграция падает при недоступной базе, livenessProbe с малым initialDelaySeconds убивает контейнер до полного старта. Если в Last State стоит Exit Code 0, процесс завершился успешно и вышел сам: тогда виноват command или аргументы, а не приложение.
ImagePullBackOff и ErrImagePull: проблемы с образом
Проверьте четыре вещи: имя образа и тег, наличие секрета в imagePullSecrets, доступность registry из кластера, срок жизни токена. ErrImagePull фиксирует первую неудачную попытку, ImagePullBackOff означает, что kubelet повторяет попытки с паузами. Команды для разбора: describe с событием Failed to pull image и kubectl get secret regcred -n prod -o yaml для проверки содержимого секрета. Причину отказа registry печатает само описание пода: not found, denied, no basic auth credentials.
Pending и FailedScheduling: под не размещается на ноде
Pending всегда означает, что планировщик не выбрал ноду. В событии FailedScheduling из describe указано, сколько нод отфильтровано и по какой причине. Дальше проверяйте сами ноды: kubectl get nodes, kubectl describe node worker-1, блоки Allocatable и Allocated resources. Частые причины: PVC в статусе Pending, taints без tolerations, жёсткий nodeSelector, несовпадение зоны или архитектуры. Если запрошено 4 CPU, а на нодах свободно по 2, под будет ждать освобождения ресурсов.
Чек-лист быстрой диагностики пода
- kubectl get pods -n prod - найти под со статусом не Running или с растущим RESTARTS.
- kubectl describe pod web-7f9c8d5b6-x2k4m -n prod - прочитать Events снизу вверх.
- kubectl logs web-7f9c8d5b6-x2k4m -n prod --previous - причина падения предыдущего запуска.
- kubectl logs web-7f9c8d5b6-x2k4m -n prod -f --tail=100 - наблюдение за текущим запуском.
- kubectl exec -it web-7f9c8d5b6-x2k4m -n prod -- sh - проверка переменных, процессов и файлов.
- kubectl port-forward pod/web-7f9c8d5b6-x2k4m 8080:80 -n prod - проверка сервиса локально через curl localhost:8080.
При CrashLoopBackOff начинайте не с логов текущего контейнера, а с describe и --previous: там лежит причина. При Pending шаги с exec и port-forward не имеют смысла, пока под не размещён. Чек-лист можно скопировать в заметки или в runbook дежурной смены, подставив свои namespace и имена подов.
Отдельно про безопасность: exec и port-forward дают доступ внутрь рабочей среды. Держите RBAC в режиме least privilege, выдавайте права на подресурсы exec и portforward только тем, кто разбирает инциденты, и отзывайте доступ по завершении работ. Диагностика должна оставлять след в audit-логах, а не превращаться в постоянный канал в прод.