Отладка подов в Kubernetes: kubectl logs, exec, describe и port-forward | AdminWiki

Отладка подов в Kubernetes: kubectl logs, exec, describe и port-forward

03 октября 2026 14 мин. чтения
Содержание статьи

Под в 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 для начинающих: запуск сервисов с минимальной конфигурацией.

Чек-лист этого этапа:

  1. kubectl get pods -n prod - найти под со статусом, отличным от Running, или с растущим RESTARTS.
  2. kubectl describe pod web-7f9c8d5b6-x2k4m -n prod - прочитать блок Events снизу вверх.
  3. kubectl logs web-7f9c8d5b6-x2k4m -n prod --previous - посмотреть причину падения.
  4. kubectl logs web-7f9c8d5b6-x2k4m -n prod --tail=50 - посмотреть текущий запуск.
  5. 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
ContainerCreatingkubelet создаёт контейнер, тянет образ или монтирует том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 или taintskubectl describe pod, затем kubectl get nodes
OOMKilledПотребление памяти выше limits.resources.memorykubectl describe pod, блок Last State
EvictedНода под давлением: нехватка памяти или места на дискеkubectl describe node worker-1
ContainerCreating (долго)Тяжёлый образ, проблемы с volume или CSI, ожидание секретаkubectl describe pod, блок Events
FailedSchedulingПланировщик не нашёл подходящую нодуkubectl describe pod, затем ресурсы нод

CrashLoopBackOff: почему контейнер падает и как найти причину

  1. kubectl describe pod web-7f9c8d5b6-x2k4m -n prod - смотреть Events и Last State. Там указаны Reason, Exit Code и время последнего падения.
  2. kubectl logs web-7f9c8d5b6-x2k4m -n prod --previous - прочитать ошибку предыдущего запуска.
  3. 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, под будет ждать освобождения ресурсов.

Чек-лист быстрой диагностики пода

  1. kubectl get pods -n prod - найти под со статусом не Running или с растущим RESTARTS.
  2. kubectl describe pod web-7f9c8d5b6-x2k4m -n prod - прочитать Events снизу вверх.
  3. kubectl logs web-7f9c8d5b6-x2k4m -n prod --previous - причина падения предыдущего запуска.
  4. kubectl logs web-7f9c8d5b6-x2k4m -n prod -f --tail=100 - наблюдение за текущим запуском.
  5. kubectl exec -it web-7f9c8d5b6-x2k4m -n prod -- sh - проверка переменных, процессов и файлов.
  6. 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-логах, а не превращаться в постоянный канал в прод.

Поделиться: