Логи в Kubernetes: DaemonSet и стек EFK

В Kubernetes логирование устроено иначе: поды живут минуты, и сбор организуют через агент на каждом узле.

EFK — вариант стека для Kubernetes, где роль сборщика и обработчика играет Fluentd или Fluent Bit (вместо Logstash), а Elasticsearch и Kibana остаются на своих местах.

Почему Kubernetes особенный

В обычной системе лог-файл живёт на диске сервера долго. В Kubernetes контейнер пишет в stdout/stderr, а под — эфемерен: упал, переехал на другой узел, перезапустился — и его логи на диске исчезли. Плюс подов сотни, они появляются и исчезают динамически, на разных узлах. Ставить Filebeat в каждый под нереально и неправильно. Нужен другой паттерн сбора.

Паттерн DaemonSet

Kubernetes сам пишет stdout/stderr каждого контейнера в файлы на узле (обычно /var/log/containers/*.log). Идея: запустить сборщик не в каждом поде, а по одному на узел — ровно для этого есть объект DaemonSet (гарантирует «один под на каждый узел кластера»). Этот под-сборщик читает логи всех контейнеров узла и обогащает их метаданными Kubernetes (имя пода, namespace, label'ы).

  УЗЕЛ kubernetes
  ┌─────────────────────────────────────────┐
  │  pod-A  pod-B  pod-C   (пишут в stdout)  │
  │    │      │      │                        │
  │    └──────┴──────┴──> /var/log/containers/│
  │                          │                │
  │              [ сборщик-DaemonSet, 1 на узел ]
  │                          │                │
  └──────────────────────────┼────────────────┘
                             ↓
                      Elasticsearch

Сборщик добавляет к каждой строке kubernetes.pod.name, kubernetes.namespace, kubernetes.labels — без этого в общем потоке невозможно понять, какой под что написал.

Fluentd и Fluent Bit вместо Logstash

В мире Kubernetes сборщиком чаще выступает не Filebeat/Logstash, а Fluentd или его лёгкий собрат Fluent Bit — они входят в экосистему CNCF (как и сам Kubernetes), хорошо умеют обогащение метаданными K8s и легче по ресурсам. Fluent Bit особенно популярен как DaemonSet: написан на C, потребляет единицы мегабайт. Отсюда и буква F в EFK. При этом и сам Filebeat умеет работать в Kubernetes (Elastic Agent / Filebeat DaemonSet) — выбор между EFK и ELK-в-K8s часто вопрос экосистемы.

Как работает под капотом: обогащение метаданными

Сборщик в DaemonSet видит на узле файл вида app-7d9f_default_web-abc123.log — имя кодирует под, namespace, контейнер. Сборщик парсит это имя и/или обращается к Kubernetes API, чтобы подтянуть актуальные label'ы пода, и приклеивает их к каждой строке. Благодаря этому в Kibana вы фильтруете kubernetes.labels.app: "checkout" и видите логи всех подов сервиса checkout, даже если конкретные поды уже сто раз пересоздались. Метаданные K8s — это то, что возвращает смысл эфемерным логам.

Особенности логов контейнеров

Логирование в Kubernetes приносит специфические сложности, которых нет на обычных серверах. Во-первых, формат: среда выполнения контейнеров оборачивает каждую строку stdout в свою структуру (с временем и потоком stdout или stderr), и сборщику нужно сначала её распаковать, прежде чем добраться до самого лога приложения. Во-вторых, многострочность в контейнерах коварнее: стектрейс может быть разбит и на уровне приложения, и на уровне обёртки среды, поэтому склейка multiline требует аккуратной настройки. В-третьих, объём: в Kubernetes легко запустить сотни реплик, и суммарный поток логов растёт взрывообразно с масштабированием подов — то, что на одном сервере было терпимым, в кластере из сотен подов становится серьёзной нагрузкой.

Отдельная тема — что вообще логировать в Kubernetes. Помимо логов приложений, есть логи системных компонентов самого кластера (kubelet, API-сервер), события Kubernetes (events) и логи аудита. Зрелая установка собирает их все, потому что многие проблемы приложений на самом деле вызваны кластером — под убит из-за нехватки памяти (OOMKilled), не смог стартовать из-за нехватки ресурсов, переехал из-за вытеснения. Эти причины видны не в логах самого приложения, а в событиях и логах Kubernetes, поэтому сбор только логов приложений оставляет вас полуслепым при разборе инцидентов в кластере.

Частые ошибки

  • Sidecar-сборщик в каждом поде. Антипаттерн при большом числе подов: дублирование агентов жрёт ресурсы. DaemonSet (один на узел) — стандарт; sidecar оправдан лишь для особых случаев (лог-файл внутри контейнера, не в stdout).
  • Не обогащать метаданными K8s. Логи без pod.name/namespace/label'ов в Kubernetes почти бесполезны — нельзя связать строку с сервисом.
  • Тяжёлый Fluentd на ресурсолимитах. На узлах с жёсткими лимитами полноценный Fluentd может не влезть; Fluent Bit легче и часто предпочтительнее как DaemonSet.

Итоги

  • Поды эфемерны: их локальные логи исчезают при перезапуске, поэтому сбор обязателен и устроен иначе.
  • Паттерн — DaemonSet: один сборщик на узел читает stdout всех контейнеров из /var/log/containers.
  • EFK = Fluentd/Fluent Bit (сбор+обогащение K8s) + Elasticsearch + Kibana; Fluent Bit лёгкий, на C.
  • Обогащение метаданными Kubernetes (pod, namespace, labels) возвращает смысл эфемерным логам.
Проверьте себя
1. Почему в Kubernetes не ставят Filebeat внутрь каждого пода?
AFilebeat не работает в контейнерах
BПодов сотни и они эфемерны; правильный паттерн — один сборщик на узел через DaemonSet, читающий stdout всех контейнеров
CKubernetes запрещает агенты
DПоды не пишут логи
2. Что означает буква F в аббревиатуре EFK?
AFilebeat
BFluentd или Fluent Bit как сборщик и обработчик вместо Logstash
CFlink
DFirehose
3. Зачем сборщик логов в Kubernetes обогащает строки метаданными (pod.name, namespace, labels)?
AДля красоты
BЧтобы можно было связать строку лога с сервисом и фильтровать по label, несмотря на то что конкретные поды постоянно пересоздаются
CЧтобы уменьшить объём логов
DЭто требование Elasticsearch