Наблюдаемость и диагностика
Когда под не работает, кластер не угадывает причину за вас — он лишь даёт инструменты; учимся читать метрики, логи и события и чинить падающие поды.
Наблюдаемость (observability) — способность по внешним сигналам (метрики, логи, события) понять, что происходит внутри системы, не залезая в неё отладчиком.
Зачем это на практике
В базовом разделе мы настраивали liveness- и readiness-пробы, чтобы кластер сам перезапускал нездоровые поды. Но самовосстановление — это половина дела: оно лечит симптом, а инженеру нужно найти причину. Под рестартится каждые 30 секунд — почему? Сервис отдаёт 500 — где затык? Без наблюдаемости вы отвечаете на эти вопросы гаданием и перезапусками наугад. С ней — за минуты находите корень. Наблюдаемость стоит на трёх китах: метрики (что), логи (детали), события (что сделал сам кластер).
Метрики: Prometheus и Grafana
Стандарт де-факто для метрик в Kubernetes — Prometheus. Его модель — pull: Prometheus сам периодически опрашивает (scrape) HTTP-эндпоинт /metrics каждого приложения и узла, складывая значения в свою time-series базу. Приложение лишь выставляет метрики в текстовом формате:
# HELP http_requests_total Всего HTTP-запросов
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200"} 14237
http_requests_total{method="GET",status="500"} 12
Запросы к этим данным пишут на языке PromQL. Например, скорость ошибок в секунду за 5 минут:
rate(http_requests_total{status="500"}[5m])
Grafana — это слой визуализации поверх Prometheus: дашборды, графики, алерты. Связка стандартная: Prometheus собирает и хранит, Grafana показывает. Метрики отвечают на вопрос «что не так» на уровне трендов — рост latency, всплеск ошибок, утечка памяти видны именно здесь раньше, чем кто-то пожалуется.
Логи в кластере
Приложение в Kubernetes должно писать логи в stdout/stderr, а не в файл внутри контейнера. Причина: под эфемерен — при пересоздании его файловая система исчезает вместе с логами. Kubernetes же подхватывает stdout/stderr и хранит их, пока жив под:
# логи текущего контейнера
kubectl logs web
# логи предыдущего, упавшего экземпляра — бесценно при CrashLoop
kubectl logs web --previous
# живой поток + последние 100 строк
kubectl logs web -f --tail=100
Флаг --previous — ключевой при отладке падений: он показывает логи прошлого контейнера, который уже умер и был заменён. Именно там лежит сообщение об ошибке, из-за которой под упал. Поскольку kubectl logs теряет историю при пересоздании пода, в проде логи собирают централизованно (агент вроде Fluent Bit на каждом узле шлёт их в Loki или Elasticsearch), но контракт тот же: пишите в stdout.
События: что делал сам кластер
Метрики и логи — про приложение. А что делал сам Kubernetes? Почему под не запланирован, почему образ не скачался? Это рассказывают события (Events) — журнал действий control plane:
kubectl get events --sort-by=.lastTimestamp
# или сразу всё про конкретный под — события внизу вывода
kubectl describe pod web
Команда kubectl describe — главный инструмент диагностики: внизу её вывода всегда секция Events с хронологией («Scheduled», «Pulling image», «Failed», «Back-off restarting»). Именно отсюда узнаёшь, что узел отказал в размещении из-за нехватки памяти или что реестр вернул «нет доступа».
Отладка CrashLoopBackOff по шагам
CrashLoopBackOff — самый частый «красный» статус. Он значит: контейнер запускается, почти сразу падает, Kubernetes ждёт нарастающую паузу (back-off: 10с, 20с, 40с… до 5 минут) и пробует снова. Это не ошибка планирования — под размещён, но приложение внутри умирает. Алгоритм разбора:
# 1. что вообще со статусом и сколько рестартов
kubectl get pod web
# web 0/1 CrashLoopBackOff 5 (40s ago) 3m
# 2. почему упал ПРЕДЫДУЩИЙ запуск — здесь обычно вся правда
kubectl logs web --previous
# 3. что говорит сам кластер: причина и exit code
kubectl describe pod web
Типичные причины и куда смотреть:
| Симптом | Вероятная причина |
| exit 137 в describe | OOMKilled — мало limits.memory |
| в логах трассировка/ошибка конфигурации | баг приложения, нет переменной окружения, недоступна БД |
| падает мгновенно, логи пусты | неверная команда/ENTRYPOINT, нет файла, опечатка в пути |
| liveness-проба валит здоровый под | слишком жёсткий initialDelaySeconds — приложение не успевает стартовать |
Отдельно стоит ImagePullBackOff — это уже не падение приложения, а невозможность скачать образ: опечатка в теге, приватный реестр без imagePullSecrets, недоступность registry. Логов приложения тут нет вовсе (контейнер не стартовал) — ответ ищут в kubectl describe.
Как это работает под капотом
Статус пода формирует kubelet на узле: он запускает контейнер, ловит его код выхода и, если контейнер завершился, применяет политику рестарта с экспоненциальным back-off. Само слово BackOff в статусе — это и есть пауза перед очередной попыткой, растущая вдвое, чтобы не молотить кластер бесконечными перезапусками падающего контейнера. События же — это объекты Kubernetes (kind Event), которые компоненты (scheduler, kubelet, controller) создают в API; у них короткий TTL (по умолчанию около часа), поэтому события «протухают» — разбирать инцидент нужно по горячим следам или иметь централизованный сбор. А kubectl logs не хранит ничего сам: он в реальном времени читает лог-файл контейнера на узле через kubelet, и как только под пересоздан — старый файл недоступен (кроме как через --previous, и то лишь для одного прошлого экземпляра).
Частые ошибки
- Смотреть
kubectl logsбез--previousпри CrashLoop. Текущий контейнер только что стартовал и ещё «чистый»; причина — в логах упавшего предыдущего. - Игнорировать секцию Events в
describe. Половина диагнозов (OOM, ошибка размещения, проблема образа) лежит именно там. - Писать логи в файл внутри контейнера. Они не видны в
kubectl logsи исчезают при пересоздании пода. - Путать CrashLoopBackOff с ImagePullBackOff. Первый — приложение падает (есть логи), второй — образ не скачался (логов нет, смотри describe).
- Лечить CrashLoop перезапусками.
kubectl delete podлишь сбрасывает счётчик back-off; причина никуда не делась — её надо найти, а не перезапускать.
Итоги
- Три источника правды: метрики (тренды, Prometheus/Grafana), логи (детали, stdout) и события (действия кластера).
- Prometheus собирает метрики по pull-модели с эндпоинтов
/metrics, запросы пишут на PromQL, графики строит Grafana. - Логи пишите в stdout/stderr;
kubectl logs --previousпоказывает причину падения упавшего контейнера. kubectl describe podи секция Events — первое, что смотрят при любой проблеме пода.- CrashLoopBackOff = приложение падает по кругу (back-off растёт); ищите причину в логах и exit code, а не перезапускайте.