Как централизованно собирать логи из множества запущенных реплик (контейнеров, подов)?

«Как централизованно собирать логи из множества запущенных реплик (контейнеров, подов)?» — вопрос из категории DevOps, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В распределенных системах, таких как Kubernetes, где приложение работает в нескольких репликах (подах), централизованный сбор логов — обязательная практика. Я настраиваю его следующим образом:

Основной подход: Агент-сборщик логов (Logging Agent). В каждый узел (ноду) Kubernetes устанавливается легковесный агент (например, Fluentd, Fluent Bit или Filebeat), который:

  1. Отслеживает логи из всех контейнеров на узле (из директории /var/log/containers/).
  2. Обогащает записи метаданными (имя пода, контейнера, неймспейс, метки).
  3. Отправляет их в централизованное хранилище.

Архитектура (EFK/ELK Stack):

[Pods (Replica 1, 2, 3)]
        | (пишут в stdout/stderr)
        v
[Docker/Container Runtime] -> /var/log/containers/*.log
        |
        v
[Fluent Bit DaemonSet на каждой Node]
        | (обогащает + пересылает)
        v
[Central Logging Backend: Elasticsearch]
        |
        v
[Kibana / Grafana Loki UI] <-- Для визуализации и поиска

Конфигурация Fluent Bit (упрощенный DaemonSet манифест):

# fluent-bit-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    metadata:
      labels:
        app: fluent-bit
    spec:
      containers:
      - name: fluent-bit
        image: fluent/fluent-bit:latest
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: config
          mountPath: /fluent-bit/etc/fluent-bit.conf
          subPath: fluent-bit.conf
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: config
        configMap:
          name: fluent-bit-config
---
# Конфигурация Fluent Bit (ConfigMap)
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
data:
  fluent-bit.conf: |
    [SERVICE]
        Parsers_File parsers.conf

    [INPUT]
        Name tail
        Path /var/log/containers/*.log
        Parser docker
        Tag kube.*
        Mem_Buf_Limit 5MB
        # Добавляем метаданные Kubernetes
        DB /var/log/flb_kube.db

    [FILTER]
        Name kubernetes
        Match kube.*
        Merge_Log On
        K8S-Logging.Parser On
        K8S-Logging.Exclude On

    [OUTPUT]
        Name es # Elasticsearch
        Match *
        Host elasticsearch-logging.monitoring.svc.cluster.local
        Port 9200
        Logstash_Format On
        Logstash_Prefix fluent-bit
        Retry_Limit False

Ключевые практики, которые я применяю:

  • Структурированное логирование (JSON): Логирую в JSON, чтобы агент мог легко парсить и индексировать поля. Использую библиотеки типа structlog для Python или аналоги.
    import structlog
    logger = structlog.get_logger()
    logger.info("request_processed", path="/api/users", duration_ms=45, replica_id=os.getenv("HOSTNAME"))
    # Выход: {"event": "request_processed", "path": "/api/users", "duration_ms": 45, "replica_id": "app-7cbbd6f5d-abc12", ...}
  • Контекст и трассировка: Включаю correlation_id или trace_id в каждую запись лога, чтобы собрать все логи по одному запросу из разных сервисов и реплик.
  • Уровни логирования и семплирование: Настраиваю разные уровни детализации (DEBUG для dev, INFO/WARNING для prod) и семплирование для высоконагруженных эндпоинтов, чтобы не перегружать систему логирования.
  • Ротация и хранение: Настраиваю политики индексов в Elasticsearch (ILM — Index Lifecycle Management) для автоматического удаления или архивации старых логов.