Рано или поздно myapp упадёт. Под не поднимется, образ не скачается, приложение не достучится до PostgreSQL — это нормальная часть жизни в Kubernetes. Хорошая новость: у вас уже есть всё, чтобы быстро понять, что именно сломалось. В этой главе разберём базовый инструментарий отладки — от чтения логов до удобного терминального UI — и наметим, куда расти, когда базовых команд станет мало.

Главная мысль главы

Отладка в Kubernetes почти всегда идёт по одному маршруту. Сначала смотрим события (что вообще произошло на уровне кластера), потом describe (детали конкретного пода), потом логи (что сказало само приложение). Запомните эту цепочку events → describe → logs — она вытащит вас из большинства ситуаций.

Логи: kubectl logs, -f, --previous и дашборд Tilt

Логи — первое, куда смотрят, когда приложение запустилось, но ведёт себя не так. Базовая команда:

bash
1kubectl logs <pod> -n myapp

Чтобы смотреть логи в реальном времени (как tail -f), добавьте -f (follow) — поток будет идти, пока вы его не прервёте:

bash
1kubectl logs -f <pod> -n myapp

Отдельно стоит выделить флаг -p (он же --previous). Он показывает логи предыдущего, уже упавшего экземпляра контейнера. Это ключ к отладке CrashLoopBackOff: текущий контейнер ещё не стартовал (или только что снова упал), поэтому обычный kubectl logs покажет пусто или начало нового запуска. А настоящую причину падения хранят логи прошлого инстанса:

bash
1kubectl logs -p <pod> -n myapp

Важно: -f и -p логически не комбинируются — нельзя «стримить логи того, что уже умерло».

Несколько флагов, которые экономят время:

bash
1# конкретный контейнер в multi-container поде
2kubectl logs <pod> -c <container> -n myapp
3
4# последние 50 строк с временными метками
5kubectl logs <pod> --tail=50 --timestamps -n myapp
6
7# только за последние 10 минут
8kubectl logs <pod> --since=10m -n myapp
9
10# логи всех подов с меткой app=myapp, все контейнеры
11kubectl logs -l app=myapp --all-containers -n myapp

--since принимает значения вроде 5s, 2m, 3h. По умолчанию --tail показывает все строки (или последние 10, если вы используете селектор -l). Полный список флагов — в официальном reference по kubectl logs.

Совет про multi-container поды: если в поде несколько контейнеров и вы не указали -c, kubectl возьмёт контейнер по умолчанию — и это легко окажется не тот, что вам нужен. Когда логи выглядят «не теми», первым делом проверьте, тот ли контейнер вы читаете.

Логи в Tilt

Если вы уже работаете через Tilt (см. главу про Tilt), то в большинстве случаев лезть в kubectl logs руками не придётся. Tilt собирает логи всех ресурсов в свой web-UI на http://localhost:10350. Там есть два режима:

  • Resource Overview — список всех ресурсов со статусами (отдельно статус сборки и статус рантайма), Pod ID и эндпоинтами.
  • Resource Detail — фокус на одном ресурсе с его логами.

Tilt подтягивает не только stdout приложения, но и ошибки сборки Docker и события Kubernetes — всё в одном месте, с фильтрами по источнику и поиском по подстроке/regex. Это сильно ускоряет цикл, когда вы итеративно правите myapp и хотите видеть реакцию сразу. Порт UI при необходимости меняется флагами --host/--port. Подробности — в туториале по Tilt UI.

kubectl describe pod — читаем Events

Если под вообще не запустился (а значит, логов приложения ещё нет), переходим к describe:

bash
1kubectl describe pod <pod> -n myapp

Эта команда выдаёт полную картину по поду: его конфигурацию, текущий статус, conditions и — самое ценное для отладки — секцию Events внизу вывода. Этих событий нет в kubectl get, они доступны только здесь (и через kubectl get events, см. ниже).

Читайте Events снизу вверх и ищите строки с типом Warning и reason'ами вроде Failed, BackOff, FailedScheduling. Типичные события жизненного цикла:

  • Scheduled — под назначен на ноду;
  • Pulling / Pulled — образ скачивается / скачан;
  • Failed — что-то не получилось (например, не стянулся образ);
  • BackOff — Kubernetes ждёт перед очередной попыткой перезапуска.

Ключевой нюанс: describe говорит вам, что случилось на уровне Kubernetes (например, «контейнер падает, поэтому BackOff»), но почему падает само приложение — расскажут только логи (kubectl logs -p). Поэтому связка одна: увидели BackOff в Events → пошли в logs --previous за настоящей ошибкой. Подробнее про отладку запущенных подов — в официальной документации Kubernetes.

kubectl get events, статусы подов и что они означают

Чтобы увидеть события не по одному поду, а по всему namespace или кластеру, используйте kubectl get events. Один важный момент: по умолчанию события не отсортированы хронологически, поэтому почти всегда добавляйте --sort-by:

bash
1kubectl get events --sort-by='.lastTimestamp' -n myapp
2
3# все namespace'ы, в режиме слежения
4kubectl get events -A --watch

События — это структурированные объекты API с типом (Normal или Warning), reason'ом и сообщением. Полезные команды и флаги описаны в гайде по отладке кластера.

Важная оговорка: у событий ограниченный срок жизни (TTL порядка часа). Если инцидент случился вчера, событий уже не будет — придётся опираться на логи и состояние ресурсов.

Фазы пода против того, что вы видите в STATUS

Здесь новички часто путаются, поэтому разберём аккуратно. У пода есть пять официальных фаз (phase), описанных в Pod Lifecycle:

  • Pending — под принят кластером, но контейнеры ещё не запущены. Сюда входит и ожидание планирования на ноду, и скачивание образа.
  • Running — под привязан к ноде, хотя бы один контейнер запущен.
  • Succeeded — все контейнеры успешно завершились и не будут перезапускаться.
  • Failed — все контейнеры завершились, и минимум один — с ошибкой.
  • Unknown — состояние пода не удалось получить.

А теперь сюрприз: знакомые всем CrashLoopBackOff и ImagePullBackOff, которые вы видите в колонке STATUS у kubectl get pods, — это НЕ фазы пода. Это display-поля, которые kubectl собирает из состояний контейнеров для удобства. Фаза при этом остаётся, например, Pending (под так и не вышел в Running). Не путайте одно с другим — это сэкономит нервы при чтении документации.

Что означают самые частые статусы:

  • CrashLoopBackOff — контейнер запускается, падает, Kubernetes ждёт и пробует снова, по кругу. Между попытками растёт пауза (экспоненциальный backoff). В первичной документации гарантированы только минимум 100ms и потолок 5 минут; популярную последовательность вроде «10с, 20с, 40с…» воспринимайте как иллюстрацию из блогов, а не как точное обещание. Поведение перезапуска зависит от restartPolicy пода (Always по умолчанию, либо OnFailure/Never). Диагностика — kubectl logs -p + describe.
  • ImagePullBackOff — не удаётся скачать образ. Для myapp это типично, если опечатались в теге или не запушили образ в локальный registry k3d-registry.localhost:5000 (см. главу про контейнеризацию). Смотрите Events в describe — там будет конкретная причина (нет такого тега, registry недоступен и т.п.).
  • Pending + FailedScheduling — планировщик не нашёл подходящей ноды. Причины: не хватает CPU/памяти, заданы nodeSelector/affinity, на нодах taints, упёрлись в квоту. В локальном k3d чаще всего это нехватка ресурсов.

Чтобы быстро увидеть статусы и на какой ноде живёт под:

bash
1kubectl get pods -n myapp
2kubectl get pod <pod> -o wide -n myapp

port-forward и exec — проверяем изнутри

Иногда под Running, но непонятно, отвечает ли приложение и видит ли оно базу. Тут помогают две команды.

kubectl port-forward создаёт временный TCP-туннель с вашего локального порта на под или сервис — трафик идёт через API-сервер Kubernetes, без всякого LoadBalancer или Ingress. Удобно ткнуться в myapp напрямую:

bash
1# через сервис
2kubectl port-forward svc/myapp 8080:80 -n myapp
3
4# или прямо в под
5kubectl port-forward pod/<pod> 8080:8080 -n myapp

Обратите внимание на порты: при форварде на сервис целевой порт — это порт самого Service (80), который дальше проксируется на targetPort: 8080 контейнера (см. главу про манифесты и главу про сеть). А при форварде прямо в под вы бьёте в порт приложения 8080 напрямую — тот же порт, что мы пробрасывали через Tilt в главе про Tilt. Локальный порт слева (8080) в обоих случаях ваш и может быть любым свободным.

После этого curl http://localhost:8080/healthz пойдёт прямо в под. Подробнее про модель туннеля — в гайде по port-forward. Одна особенность: туннель рвётся, если под перезапустился или пересоздался, — это нормально, просто запустите команду заново.

kubectl exec выполняет команду внутри контейнера. Самое частое — открыть shell и осмотреться:

bash
1kubectl exec -it <pod> -n myapp -- sh
2
3# конкретный контейнер + посмотреть переменные окружения
4kubectl exec -it <pod> -c <container> -n myapp -- env

Изнутри удобно проверить env-переменные (правильные ли DB_HOST/DB_PASSWORD и т.д.?), наличие файлов и сетевую связность до PostgreSQL. Как и с логами, в multi-container поде указывайте -c, иначе попадёте не в тот контейнер.

Когда в контейнере нет shell

Если вы собрали myapp на distroless-образе (минимальный образ без shell и утилит — хорошая практика для прода), то kubectl exec ... -- sh просто не сработает: шелла там нет. На этот случай есть kubectl debug — он подсаживает к поду временный (ephemeral) контейнер с нужными инструментами, не перезапуская под и не меняя его spec (стабильно начиная с Kubernetes 1.25):

bash
1kubectl debug <pod> -it --image=busybox --target=<container> -n myapp

--target подключает временный контейнер к процессному пространству целевого, так что вы видите его процессы и сеть. Все такие действия фиксируются в audit-логах API. Подробности и опции (--share-processes, --copy-to) — в той же документации по отладке подов и в разборе отладки distroless-контейнеров.

k9s: навигация по кластеру в одном окне

Команды kubectl — это хорошо, но переключаться между get pods, logs, describe, port-forward десятки раз в час утомительно. k9s — терминальный UI, который постоянно следит за кластером и показывает ресурсы в реальном времени, прямо в одном окне. Запуск тривиальный:

bash
1k9s -n myapp

Дальше вы навигируете по подам, деплойментам, сервисам стрелками, а типовые операции делаете хоткеями: посмотреть логи, описать ресурс, зайти в shell, port-forward, перезапуск, скейлинг. Шорткаты адаптируются под контекст — над подом доступно одно, над деплойментом другое. Есть мощная фильтрация, переход между связанными ресурсами, поддержка CRD, RBAC и оформления (skins). Для новичка это, пожалуй, самый быстрый способ «почувствовать» кластер, и с локальным k3d он работает идеально. Проект живёт на github.com/derailed/k9s.

Под капотом k9s делает ровно то же, что и вы руками через kubectl, — просто без печатания команд. Поэтому понимать базовые команды из этой главы всё равно нужно: k9s ускоряет работу, но не заменяет понимание того, что происходит.

Базовая наблюдаемость: куда расти

Всё, о чём шла речь выше, — это отладка: вы реагируете на конкретную проблему здесь и сейчас. Когда myapp повзрослеет (особенно после переезда в прод), захочется видеть состояние сервиса постоянно, а не только когда что-то уже сломалось. Это и есть наблюдаемость (observability), и держится она на трёх столпах:

  • Метрики (metrics) — числовые показатели во времени: CPU, память, сеть, число запросов, латентность. Они отвечают на вопрос «есть ли проблема» (например, latency пополз вверх). Индустриальный стандарт сбора — Prometheus, визуализация — Grafana. Самый первый шаг в локальном кластере — поставить metrics-server, чтобы заработал kubectl top pods.
  • Логи (logs) — то, что мы разбирали всю главу. Они дают детали конкретной ошибки.
  • Трассировки (traces) — путь одного запроса через несколько сервисов. Они отвечают на вопрос «почему» и где именно потерялось время. Стандарт здесь — OpenTelemetry: приложение порождает спаны, они уходят в OTel Collector, а оттуда в бэкенд вроде Jaeger или Zipkin для просмотра.

Короткая мнемоника: метрики — что, логи — детали, трейсы — почему. За обзорным взглядом на тему можно заглянуть в материалы по observability в Kubernetes 2026 года.

Важно не перегибать. Для локальной разработки myapp полноценный стек Prometheus + Grafana + OpenTelemetry обычно избыточен — на этом этапе вам хватает логов, describe, событий и k9s. Воспринимайте этот раздел как направление роста: когда сервис поедет в прод и появится несколько взаимодействующих компонентов, вы будете знать, в какую сторону копать. Часть этих тем мы ещё затронем, когда будем приближать локалку к проду.

Источники

Нужна помощь с отладкой нагрузок в Kubernetes?
Застряли на CrashLoopBackOff, ImagePullBackOff или поде, который никак не выходит в ready? Помогу выстроить надёжный процесс отладки и наблюдаемости в Kubernetes.