Логи: kubectl logs deep
kubectl logs — это первая команда, которую вызываешь, когда что-то сломалось. Снаружи она выглядит просто: kubectl logs my-pod. На деле — это thin client поверх длинной цепочки: apiserver → kubelet → container runtime → файл на ноде → SPDY/WebSocket stream обратно. Понимать эту цепочку важно, потому что её слабые звенья определяют, какие логи ты увидишь, а какие — потеряны навсегда.
journalctl: единый журнал systemd
Что делает kubectl logs под капотом
Когда ты вызываешь kubectl logs <pod>, происходит следующее:
- kubectl делает
GET /api/v1/namespaces/<ns>/pods/<name>/log— это subresource Pod-а, не отдельный объект. - apiserver проксирует запрос на kubelet ноды, где запущен Pod (endpoint
/containerLogs/<ns>/<pod>/<container>через kubelet API). - kubelet не разговаривает напрямую с приложением. Он читает файлы с диска ноды, которые туда положил container runtime.
- На path-е
/var/log/pods/<ns>_<pod>_<uid>/<container>/0.logлежат stdout+stderr контейнера в CRI log format (<timestamp> <stream> <P|F> <message>). - kubelet парсит эти файлы, фильтрует по
--since,--tailи стримит обратно.
Если ваше приложение пишет логи в файл внутри контейнера (например, /var/log/app.log) — kubectl logs их не увидит. CRI собирает только stdout и stderr. Это первая ошибка миграции legacy-приложений в K8s: переводите логи на stdout либо настройте sidecar для tail-ирования файла.
Ключевые флаги
# Базовое
kubectl logs my-pod
# Multi-container Pod
kubectl logs my-pod -c app # конкретный container
kubectl logs my-pod -c init-db # init-container логи (только после init)
kubectl logs my-pod --all-containers # все containers вместе
kubectl logs my-pod/app # сокращение для -c app
# Время
kubectl logs my-pod --since=10m
kubectl logs my-pod --since-time=2026-05-13T10:00:00Z
kubectl logs my-pod --tail=100
kubectl logs my-pod --timestamps
# Stream и предыдущий instance
kubectl logs -f my-pod # follow (как tail -f)
kubectl logs my-pod --previous # логи предыдущего container instance
kubectl logs my-pod -p # сокращение --previous
# По selector — у всех matching Pods
kubectl logs -l app=web --tail=50 --prefix=true
-c <container>нужен всегда в multi-container Pod, иначе kubectl откажется работать без default-а.--all-containers=trueобъединяет вывод. Без--prefixнепонятно, какой контейнер пишет — ставьте--prefix=true.-l <selector>стримит из всех Pods, чьи labels matches. Удобно для Deployment:kubectl logs -l app=web -f --max-log-requests=10.
--previous: единственный шанс увидеть, почему контейнер упал
Когда контейнер крашится и kubelet рестартует его, старый файл логов перезаписывается новым. К этому моменту kubelet сохраняет предыдущий — но только один. Это значит:
- Контейнер упал → старые логи доступны через
kubectl logs -p. - Контейнер упал, рестартовал, упал второй раз → старые-старые логи потеряны навсегда.
-pпокажет только последний крашнутый instance, не оригинальный.
Если Pod в CrashLoopBackOff и ты ничего не делаешь — kubelet продолжает рестартовать, и каждый рестарт затирает логи предыдущего краша. Сразу делай kubectl logs <pod> -p > crash.log, иначе оригинальная причина крэша уйдёт в небытие.
Лимиты ротации и почему нужен centralized logging
Container runtime ротирует логи. У containerd дефолты:
- Размер файла — 10 MB.
- Количество файлов на контейнер — 5.
- Итого: максимум 50 MB на контейнер на ноде.
После этого старые логи удаляются runtime-ом. kubectl logs --tail=10000 ограничен только тем, что осталось в неротированных файлах. Для production это категорически недостаточно — нужен centralized logging:
- Fluent Bit / Fluentd DaemonSet на каждой ноде читает
/var/log/pods/*и отправляет в backend. - Backend: Loki (cheap, label-based, integration с Grafana), Elasticsearch / OpenSearch (full-text search), Cloud-native (CloudWatch, Stackdriver).
- Альтернативный шаблон — sidecar logger: контейнер
fluent-bitв Pod-е читает stdout другого контейнера через shared emptyDir.
Для CKAD достаточно знать kubectl logs со всеми флагами и понимать, что --previous работает только на одно поколение. Production-grade logging (Loki, Elastic) — больше CKA / SRE scope, но появляется в реальных задачах постоянно.
Init-контейнеры и их логи
Init-containers выполняются до запуска основных containers Pod-а. Их логи доступны как обычно — через -c:
kubectl logs my-pod -c db-migration
kubectl logs my-pod -c wait-for-redis
Если init упал — Pod в Init:CrashLoopBackOff. kubectl logs my-pod -c <init-name> --previous покажет, почему init упал в прошлый раз. Без -c kubectl откажется — init-containers не считаются default-ом для log-команды.
Killer-моменты
--previousработает только один раз. После второго краша оригинальная причина потеряна.- Файл логов на ноде —
/var/log/pods/<ns>_<pod>_<uid>/<container>/0.log. На kind / minikube node-логи доступны черезdocker exec/kind get nodes. - kubelet, не runtime, читает логи. Если runtime восстановится, а kubelet нет —
kubectl logsсломан, хотя контейнеры работают. - stdout/stderr — единственный source of truth для kubectl logs. Файлы внутри контейнера не видны.
- Селектор
-l— must-have для отладки Deployment:kubectl logs -l app=web --tail=20 --prefix=true | grep ERROR.