
В одной из моих инфраструктур — с большим числом серверов и проектной зоной для экспериментов — появилась задача выстроить надёжный и масштабируемый сбор телеметрии: метрик, логов и распределённых трассировок. Простые Prometheus-агенты уже не справлялись: не хватало сквозной корреляции событий, гибкой маршрутизации и единой точки управления, да и число сервисов стало слишком много, в них стало легко путаться.
Перед любым внедрением нового решения я начинаю с фундаментальной базы — книги экономят часы гуглинга и спасают от архитектурных ошибок. В итоге мой выбор упал на стек OpenTelemetry в связке с привычными open-source бэкендами: Prometheus, Loki, Tempo и Grafana.

Также настоятельно рекомендую обратиться к официальный документации, так как на русскоязычных форумах на удивление мало актуальной информации по этому продукту.
Ключевая идея решения
Максимальная изоляция: всё запускаю в контейнерах с заранее настроенными конфигурационными файлами и дашбордами.
Collector-ы на узлах: универсальный агент для хоста и специализированный агент для приложения собирают метрики, логи и трассировки. Управляем через Ansible.
Единый шлюз (Gateway): маршрутизация по типу данных.
Привычные хранилища: Prometheus, Loki, Tempo.
Grafana: единые дашборды и алерты.
Архитектура мониторинга

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

Коллектор-агент приложения собирает определенные метрики с приложения, логи контейнеров и трейсы приложения. Его параметры специфичны для конкретного приложения.

Для теста разрешил отслеживать трейсы и логи, формируемые при съеме метрик
Уровень | Элемент | Что происходит |
1.1 | Collector Agent | Снимает метрики системы через node-exporter, читает системные логи. |
1.2 | Collector Agent app | Снимает метрики |
2 | Collector Gateway | Принимает всё по gRPC, добавляет лейблы, раскидывает по бэкендам. |
3 | Хранилища | Prometheus → метрики, Loki → логи, Tempo → трейсы. |
4 | Grafana | Дашборды, алерты, корреляция трёх видов данных. |
1.1 Клиентские агенты хоста (Collector Agent)
Collector Agent отвечает за сбор системных метрик и логов авторизации на каждом хосте. Для этих целей используется контейнер с Node Exporter (метрики), а также парсятся логи /var/log/fail2ban.log и /var/log/auth.log для событий блокировки и аутентификации. Перед отправкой все метрики и логи обогащаются нужными лейблами/атрибутами, такими как имя хоста, имя сервиса и др.
Необходимую среду мы поднимаем с помощью Docker-Compose:
Скрытый текст
x-logging: &default-logging driver: "json-file" options: max-size: "10m" max-file: "1" tag: "{{.Name}}" x-common-labels: &default-labels logging: "promtail" logging_jobname: "containerlogs" stackname: "docker-monitoring-stack-gpnc" services: # --- monitoring otel-collector-agent: image: otel/opentelemetry-collector-contrib:0.128.0 container_name: otel-collector-agent restart: always user: "0:4" cpus: 0.15 mem_limit: 256m ports: - 4317:4317 volumes: - "./configs/otel-collector-agent/config.yaml:/etc/otel-collector/config.yaml:ro" - "/var/log/fail2ban.log:/var/log/fail2ban.log:ro" - "/var/log/auth.log:/var/log/auth.log:ro" command: ["--config=/etc/otel-collector/config.yaml"] environment: - GATEWAY_ENDPOINT=${MONITOR_HOST} - HOST_HOSTNAME=${HOST_HOSTNAME} networks: - monitor-net depends_on: - node-exporter labels: <<: *default-labels component: "otel-collector" logging: *default-logging node-exporter: image: prom/node-exporter:v1.9.1 container_name: node-exporter restart: always cpus: 0.15 mem_limit: 256m pid: "host" volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro command: - '--path.procfs=/host/proc' - '--path.rootfs=/rootfs' - '--path.sysfs=/host/sys' - '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)' networks: - monitor-net labels: <<: *default-labels component: "node-exporter" logging: *default-logging networks: monitor-net: driver: bridge
Конфигурация коллектора:
Скрытый текст
# configs/otel-collector-agent/config.yaml service: pipelines: metrics: receivers: [prometheus] processors: [batch] exporters: - otlp/gateway # - debug logs: receivers: - filelog/fail2ban - filelog/auth processors: - attributes/custom - attributes/add_loki_label - batch exporters: - otlp/gateway # - debug # --- точки экспорта метрик exporters: otlp/gateway: endpoint: "${GATEWAY_ENDPOINT}:4317" tls: insecure: true timeout: 15s sending_queue: enabled: true num_consumers: 10 queue_size: 5000 retry_on_failure: enabled: true initial_interval: 10s max_interval: 30s max_elapsed_time: 2m # уровни дебага detailed, normal, basic debug: verbosity: detailed receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 # логирование filelog/fail2ban: include: - /var/log/fail2ban.log poll_interval: 5s start_at: end include_file_path: true include_file_name: true operators: # кастомный атрибут - type: add field: attributes.service_name value: fail2ban - type: regex_parser # ! Основной парсер. Разбирает fail2ban-строку на части: regex: '(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}).*?(?P<detected_level>NOTICE|INFO)\s+\[(?P<target>[^\]]+)\] (?P<msg>.*)' parse_to: attributes filelog/auth: include: - /var/log/auth.log poll_interval: 5s start_at: end include_file_path: true include_file_name: true operators: # кастомный атрибут - type: add field: attributes.service_name value: auth - type: regex_parser regex: '.*New session\s+(?P<login_session>\d+)\s+of user\s+(?P<login_user>\S+)' parse_to: attributes # --- prometheus node_exporter prometheus: config: scrape_configs: - job_name: node-exporter scrape_interval: 30s scrape_timeout: 20s metrics_path: /metrics static_configs: - targets: ["node-exporter:9100"] labels: container: 'node-exporter' node: ${HOST_HOSTNAME} processors: batch: timeout: 10s # ← отправлять не реже, чем раз в 10 секунд send_batch_size: 200 # ← при достижении 200 метрик/логов отправить батч сразу # --- атрибуты # --- кастомные attributes/custom: actions: - action: insert key: node value: ${HOST_HOSTNAME} # --- лейблы attributes/add_loki_label: actions: - action: insert key: loki.attribute.labels value: service_name, node, detected_level, login_session, login_user
В коллекторе реализованы следующие ключевые функции:
Сбор метрик
через ресивер
prometheusс Node Exporter, опрашивается каждые 30 секунд для получения данных о состоянии системы.
Сбор логов:
из файлов
/var/log/fail2ban.logи/var/log/auth.logчерез ресиверыfilelog/fail2banиfilelog/auth.Для каждого лога настраиваются парсеры
regex_parser, которые вытаскивают полезные поля (например, для fail2ban —timestamp,detected_level,target,msg; для auth —login_session,login_user).Добавляются кастомные атрибуты (
service_nameи другие).
Процессоры:
batch: группирует метрики и логи, чтобы отправлять их пакетами (batch), что повышает производительность передачи.attributes/custom: добавляет дополнительный атрибутnodeс именем текущего хоста.attributes/add_loki_label: формирует набор лейблов (service_name,node,detected_level,login_session,login_user) для дальнейшей фильтрации в системах логирования вроде Loki.
Экспортеры:
Все метрики и логи передаются в указанный otlp/gateway . Поддерживаются очереди, ретраи на случай ошибок сети.
Есть отладочный экспортер
debugдля локальной разработки.Вся конфигурация легко масштабируется и кастомизируется, переменные окружения используются для гибкости.
1.2 Клиентские агенты приложения (Collector Agent app)
Collector Agent приложения собирает метрики приложения (например, с помощью prometheus_fastapi_instrumentator), читает и парсит логи, а также собирает трейсы из кода приложения (opentelemetry.instrumentation.fastapi).
Рекомендация: Старайтесь, чтобы логи приложения были в формате ключ-значение, например JSON. Это значительно упрощает их дальнейший разбор на атрибуты.
Конфигурация коллектора:
Скрытый текст
# configs/otel-collector-agent/config.yaml service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: - otlp/gateway # - debug metrics: receivers: [prometheus] processors: [batch] exporters: [otlp/gateway] logs: receivers: [filelog/docker] processors: - filter/drop_unavailable # - filter/drop_service # - filter/drop_info - attributes/custom - attributes/add_loki_label - attributes/delete_attributes - batch exporters: - otlp/gateway # - debug # --- точки экспорта метрик exporters: otlp/gateway: endpoint: "${GATEWAY_ENDPOINT}:4317" tls: insecure: true timeout: 15s sending_queue: enabled: true num_consumers: 10 queue_size: 5000 retry_on_failure: enabled: true initial_interval: 10s max_interval: 30s max_elapsed_time: 2m # уровни дебага detailed, normal, basic debug: verbosity: detailed # --- точки импорта метрик receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 # --- docker logs via filelog filelog/docker: include: - /var/lib/docker/containers/*/*-json.log poll_interval: 5s start_at: end include_file_path: true include_file_name: true operators: - type: json_parser id: parser-docker parse_from: body parse_to: attributes on_error: drop - type: json_parser parse_from: attributes.log parse_to: attributes on_error: drop if: 'attributes.log != nil and attributes.log matches "^\\{"' # кастомный атрибут - type: add field: attributes.app value: chuyan # --- prometheus для метрик prometheus: config: scrape_configs: - job_name: cadvisor scrape_interval: 30s scrape_timeout: 20s static_configs: - targets: [ 'cadvisor:8080' ] labels: container: 'cadvisor' node: ${HOST_HOSTNAME} - job_name: app scrape_interval: 30s scrape_timeout: 20s metrics_path: /metrics static_configs: - targets: ["front0ui:8010"] labels: container: 'front0ui' node: ${HOST_HOSTNAME} # --- обработка данных processors: batch: timeout: 10s # отправлять не реже, чем раз в 10 секунд send_batch_size: 200 # при достижении 200 метрик/логов отправить батч сразу # --- атрибуты # --- кастомные attributes/custom: actions: - action: insert key: node value: ${HOST_HOSTNAME} # --- удаляем лишние attributes/delete_attributes: actions: - action: delete key: log.file.path # - action: delete # key: log.file.path # --- лейблы attributes/add_loki_label: actions: - action: insert key: loki.attribute.labels value: app, node, container_name, trace_id, span_id # --- фильтрация # --- Ошибка UNAVAILABLE filter/drop_unavailable: error_mode: ignore logs: log_record: - 'IsMatch(body, "(?i)StatusCode\\.UNAVAILABLE") or IsMatch(attributes["log"], "(?i)StatusCode\\.UNAVAILABLE")' # --- сервисов filter/drop_service: error_mode: ignore logs: log_record: - 'IsMatch(attributes["log"], "(?i)(/health|/metrics)")' # --- логов по info filter/drop_info: error_mode: ignore logs: log_record: - 'IsMatch(attributes["level"], "(?i)(info)")'
В это коллекторе реализуется функции:
Прием трассировок:
через ресивер otlp, принимаются span-данные от приложений (например, от микросервисов, использующих OpenTelemetry SDK). Данные поступают в режиме реального времени, каждое входящее соединение валидируется.
Прием логов:
filelog/docker: читает docker-логи из каждого контейнера, путь:
/var/lib/docker/containers//-json.log.Для парсинга применяется каскад json_parser (разбор тела лога и атрибута log) — таким образом, все заметки переводятся в формат атрибутов OpenTelemetry.
Добавляется кастомный аттрибут
app: chuyan.
Процессоры:
batch: собирает трассировки и логи в пакеты для эффективной отправки , что минимизирует сетевые накладные расходы.filter/drop_unavailable— убирает логи с ошибками статуса UNAVAILABLE.attributes/loki_labels: определяет и присваивает специальные лейблы (webapp_name, environment, response_code, request_method, error_level) для фильтрации и поиска в log-стореджах типа Loki или Elasticsearch.Удаляется технический аттрибут
log.file.path.
Экспортеры:
Все метрики и логи передаются в указанный otlp/gateway. При отладке можно переключить на де��аг в консоль.
2 Коллектор-шлюз (Collector Gateway)
Получает всё по gRPC от агентов.
Фильтрует и добавляет атрибуты (лейблы хоста, сервиса).
Маршрутирует данные в нужный бэкенд.
Конфигурация коллектора-шлюза:
Скрытый текст
# configs/otelcol/otelcol.yaml # --- точки экспорта метрик exporters: prometheus: endpoint: "0.0.0.0:9464" loki: endpoint: "http://loki:3100/loki/api/v1/push" otlp/tempo: endpoint: tempo:4417 tls: insecure: true debug: verbosity: detailed # уровни дебага detailed, normal, basic # --- точки импорта метрик receivers: otlp: protocols: grpc: { endpoint: 0.0.0.0:4317 } http: { endpoint: 0.0.0.0:4318 } processors: batch: {} service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: - otlp/tempo # - debug metrics: receivers: [otlp] processors: [batch] exporters: [prometheus] logs: receivers: [otlp] processors: [batch] exporters: [loki]
На практике всё просто: при необходимости можно централизованно добавлять лейблы и атрибуты к телеметрии, а также осуществлять фильтрацию данных.
3 Хранилища + 4 Grafana
Наш сервер мониторинга состоит из следующих компонентов:
grafana — веб-интерфейс для визуализации метрик, логов и трассировок. Работает как дашборд для наблюдения за всей системой.
prometheus — система сбора и хранения метрик. Опрашивает экспортёры (например, node-exporter, cadvisor) и другие источники, хранит и отдает метрики для анализа.
loki — cистема сбора и хранения логов от контейнеров и других сервисов, тесно интегрирована с Grafana.
tempo — распределённое хранилище трассировок запросов (tracing), чтобы отслеживать путь запросов через сервисы (Distributed Tracing).
cadvisor — сборщик метрик использования ресурсов контейнерами Docker (CPU, память, диски и др.). Источник данных для мониторинга контейнеров для Prometheus.
node-exporter — экспортирует метрики состояния хоста (нагрузка CPU, память, диски, файловые системы и др.) для Prometheus.
alertmanager — обработчик алертов Prometheus, отправляет уведомления при срабатывании правил (в Slack, email и т.д.).
uncomplicated-alert-receiver — простой веб-приемник алертов из alertmanager, далее опционально может пересылать их или обрабатывать.
promtail — агент, собирает логи контейнеров и отправляет их в Loki. Можно использовать только opel-коллектор, но я решил его оставить, чтобы навык работы с ним не потерять.
Трассировки и корреляция
Трейсы собираются через OTEL SDK и отправляются в агент. Главная сложность — не в сборе, а в связывании данных:
В логах оставляем атрибут
trace_id.
В Grafana создаем связи в разделе Data Sources для Loki и Tempo:


Теперь, открывая нужный лог в Grafana, можно перейти к соответствующему трейсy.


Аналогично — от трейса можно перейти к логам.


Это существенно ускоряет диагностику и анализ работы системы.
Итоги и выводы
Унификация. OpenTelemetry Collector стал единым шлюзом для любой телеметрии.
Гибкая маршрутизация. Любые изменения поведения бэкендов не требуют доработки клиентов.
Простое масштабирование. Все сервисы упакованы в контейнеры, обновляются через Ansible или CI/CD.
Широкие возможности. Grafana объединяет метрики, логи и трейсы в единой визуальной среде с алертами.
В результате я получил полнофункциональную observability-платформу: теперь вижу, где возникают задержки, когда появляются ошибки и как запросы «путешествуют» между сервисами. Переход на OpenTelemetry и знакомые бэкенды — это не только про технологический стек, но и про формирование DevOps-культуры наблюдаемости в компании.
Внедрял всё это не «ради моды», а чтобы упростить управление системой, заложить возможности масштабирования и, главное, раньше пользователей обнаруживать и устранять инциденты.
Спасибо за внимание!

