4. Централизованные логи

Архитектура сбора и доставки в распределенных системах

«Программы, которые болтают с пользователем или сбрасывают мусор в файлы, — сущее наказание. Создавайте потоки, легко объединяемые в конвейеры».
— Дуглас Макилрой

В предыдущей статье мы рассмотрели библиотеки структурированного логирования внутри самого Go-приложения. Однако умение генерировать безупречный JSON с контекстными атрибутами совершенно бесполезно, если эти записи остаются запертыми внутри изолированного контейнера.

В современной облачной архитектуре с сотнями микросервисов и тысячами динамических подов Kubernetes подход «зайти по SSH на сервер и полистать лог через tail -f или less» больше не работает. Контейнеры эфемерны: они перезапускаются, перемещаются планировщиком между физическими нодами, а их локальная файловая система уничтожается при падении.

Попытка искать причину аварии запуском kubectl logs <pod_name> по сотне реплик превращается в мучительный хаос. Для эффективной работы бэкенда необходима Централизованная система логирования (Centralized Logging Pipeline).


От файлов к потокам данных

В манифесте двенадцатифакторных приложений (The Twelve-Factor App) одиннадцатый фактор формулирует непреложный закон: «Рассматривайте логи как потоки событий (Event Streams)».

Приложение не должно управлять файлами логов на диске, заботиться о путях, создавать директории или самостоятельно реализовывать ротацию. Приложение просто выбрасывает непрерывный поток небуферизованных событий в стандартный поток вывода (stdout) и поток ошибок (stderr). Вся дальнейшая логика доставки, фильтрации, обогащения и долговременного хранения делегируется инфраструктурной платформе.


Архитектура Pipeline сбора логов

Классический пайплайн доставки логов от пользовательского кода до экрана дежурного инженера состоит из шести четко разграниченных этапов:

flowchart LR
    A["Go App<br>stdout/stderr"] --> B["Container Runtime<br>Docker/Containerd"]
    B --> C["Log File<br>/var/log/containers/..."]
    C --> D["Agent<br>DaemonSet: Promtail/Filebeat"]
    D -->|"Push<br>TCP/HTTP"| E["Aggregator<br>Loki/Logstash"]
    E --> F["Storage<br>Object Storage/Disk"]
    F --> G["UI<br>Grafana/Kibana"]

    style A fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#fff
    style B fill:#312e81,stroke:#818cf8,stroke-width:2px,color:#fff
    style D fill:#78350f,stroke:#f59e0b,stroke-width:2px,color:#fff
    style E fill:#065f46,stroke:#10b981,color:#fff
    style G fill:#0f172a,stroke:#475569,color:#fff
  

Каждое звено этой цепи решает строгую задачу:

  1. Приложение (Go App): генерирует структурированные записи и сбрасывает их в файловые дескрипторы 1 и 2.
  2. Контейнерный рантайм (Containerd / CRI-O): перехватывает пайпы процесса и сохраняет строки в стандартные файлы хоста.
  3. Локальный агент (DaemonSet Agent): непрерывно читает файлы логов на ноде, обогащает их метаданными Kubernetes и пачками отправляет в сеть.
  4. Агрегатор / Приемник (Loki / Logstash): валидирует поток, строит индексы и записывает сжатые блоки данных в хранилище.
  5. Хранилище (Storage): объектное хранилище (S3/MinIO) или дисковые кластеры для долгосрочного архива.
  6. Пользовательский интерфейс (UI): веб-консоль для визуализации, фильтрации и расследования инцидентов.

Mechanical Sympathy: Stdout vs File

В традиционных монолитных системах сервисы писали логи напрямую в файлы на диске (/var/log/app.log). В среде контейнеризации и Kubernetes этот паттерн категорически запрещен: приложение обязано писать исключительно в stdout/stderr.

Почему это лучше?

  1. Unified Interface: Контейнерный рантайм (Docker, Containerd, CRI-O) перехватывает потоки stdout/stderr и сам решает, куда их писать (обычно в JSON-файлы на хост-машине). Вам не нужно настраивать пути к файлам внутри приложения.
  2. Rotation: Рантайм автоматически управляет ротацией логов (чтобы не переполнить диск). Если вы пишете в файл сами, вы должны сами настроить logrotate или библиотечный rotation, что добавляет сложности (системные вызовы, переоткрытие файлов).
  3. Separation of Concerns: Приложение не знает о инфраструктуре. Инженеры SRE могут менять драйвер логирования (например, отправлять сразу в Fluentd), не меняя код приложения.

Under the Hood: Как это работает в Go?

Когда вы пишете fmt.Println или slog.Info в Go:

  1. Данные попадают в файловый дескриптор 1 (stdout) или 2 (stderr).
  2. Это простой write syscall ядра Linux.
  3. Контейнерный рантайм читает этот пайп и пишет на диск хоста (обычно /var/log/pods/... в формате JSON, где есть поле log и time).
Text
[ Go Process (FD 1) ] ──► [ Linux Pipe ] ──► [ Containerd CRI ] ──► [ /var/log/pods/.../*.log ]

Агенты сбора (Log Agents)

Следующий шаг — забрать логи с ноды. Для этого используется DaemonSet (под, который крутится на каждой ноде кластера). Популярные агенты: Promtail (для Loki), Filebeat (для Elastic), Fluent Bit.

Их задача:

  1. Следить за файлами в /var/log/containers/.
  2. Парсить строки (JSON → Fields).
  3. Отправлять в хранилище.

Агент монтирует системную директорию ноды /var/log/pods в режиме read-only и держит открытыми файловые дескрипторы на активные логи (механизм tail -f).

Label-based Routing

Агенты используют Kubernetes Metadata для enrichment (обогащения) логов. К логу добавляются лейблы: app=payment-service, namespace=prod, pod_name=....
Это критически важно, так как позволяет фильтровать логи в UI: "Покажи мне логи только сервиса Payment".

Благодаря этому разработчик избавлен от необходимости хардкодить имя пода или namespace в коде на Go — инфраструктура добавляет эти метаданные автоматически на границе ноды.


Сравнение подходов к хранению

Существует два основных типа хранилищ для логов, и выбор между ними влияет на архитектуру вашего бэкенда.

1. Elasticsearch (ELK Stack)

  • Принцип: Полная индексация всех полей (Inverted Index).
  • Плюсы: Молниеносный поиск по любому тексту (Full Text Search).
  • Минусы: Очень дорого. Индекс занимает место, оперативная память нужна для кэша. Высокая кардинальность полей (например, ID сессий) убивает кластер Elastic.
  • Для кого: Если вам нужен сложный аналитический поиск по тексту (как Google).

2. Grafana Loki

  • Принцип: Индексируем только лейблы (метаданные), сам текст логов не индексируется. Поиск происходит методом "grep" по сжатым чанкам (boltdb-shipper).
  • Плюсы: Дешево. Стоимость хранения в 10-100 раз ниже Elastic. Идеально подходит под парадигму метрик (Prometheus-like experience).
  • Минусы: Поиск по содержимому лога (LogQL {app="auth"} |= "error") медленнее, чем в Elastic, так как происходит в момент запроса.

Потеря сообщений и Backpressure

В распределенных системах всегда есть риск, что хранилище логов (Loki/ES) упадет или сеть ляжет. Что будет с вашим Go-приложением?

  1. Приложение -> Stdout: Приложение не блокируется. Оно пишет в пайп ядра ОС. Если агент не успевает читать, пайп переполняется (буфер ядра Linux обычно 64 КБ). Если буфер забит, системный вызов write(2) заблокирует горутину, если поток открыт в блокирующем режиме.
  2. Агент -> Storage: Агент имеет буфер в оперативной памяти и на диске. Если буфер полон, у агента есть стратегии:
    • Block: Остановить чтение с диска (приводит к росту файлов логов на ноде).
    • Drop: Выбрасывать новые логи (Data Loss, но система жива).

Это называется Backpressure. В продакшене часто настраивают Drop логов, чтобы падение мониторинга не убило само приложение (например, не забило диск ноды логами).

Бизнес-транзакции клиентов всегда имеют высший приоритет над телеметрией: если система вынуждена выбирать между отказом в оплате заказа и потерей лог-записи об этой оплате, зрелая архитектура жертвует логом, гарантируя работоспособность сервиса.


Итог

  1. Stdout is King: В Kubernetes пишите в stdout/stderr. Считайте логи потоком событий, а не файлами.
  2. Agents: Используйте Promtail или Fluent Bit для сбора и обогащения метаданными K8s.
  3. Storage: Выбирайте Loki для дешевого и эффективного хранения структурированных логов, Elastic — для полнотекстового поиска.
  4. Observability: Убедитесь, что у вас есть Trace ID в логах. Без этого связь между сервисами потеряна.

В следующей статье мы сравним конкретные системы агрегации логов и их применимость: 5. Log aggregation системы.