
В одной из моих инфраструктур — с большим числом серверов и проектной зоной для экспериментов — появилась задача выстроить надёжный и масштабируемый сбор телеметрии: метрик, логов и распределённых трассировок. Простые 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). Данные поступают в режиме реального времени, каждое входящее соединение валидируется.
Прием логов:
из файлов /
var/log/nginx/access.log
и/var/log/nginx/error.log
через ресиверыfilelog/access
иfilelog/error
.для каждого лога настраиваются парсеры regex_parser, которые выделяют ключевые поля (например, для access — ip_address, request_method, response_code, user_agent; для error — error_level, error_message, request_id).
Добавляются кастомные атрибуты (webapp_name, environment и другие).
Процессоры:
batch
: собирает трассировки и логи в пакеты для эффективной отправки , что минимизирует сетевые накладные расходы.attributes/hostname
: добавляет к каждому сообщению атрибут hostname с именем текущего сервера.attributes/loki_labels
: определяет и присваивает специальные лейблы (webapp_name, environment, response_code, request_method, error_level) для фильтрации и поиска в log-стореджах типа Loki или Elasticsearch.
Экспортеры:
Все метрики и логи передаются в указанный 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-культуры наблюдаемости в компании.
Внедрял всё это не «ради моды», а чтобы упростить управление системой, заложить возможности масштабирования и, главное, раньше пользователей обнаруживать и устранять инциденты.
Спасибо за внимание!