Как стать автором
Обновить

Наблюдаемость “по-взрослому”: опыт внедрения OpenTelemetry

Уровень сложностиСредний
Время на прочтение10 мин
Количество просмотров1.7K

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

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

С чего стоить начать
С чего стоить начать

Также настоятельно рекомендую обратиться к официальный документации , так как на русскоязычных форумах на удивление мало актуальной информации по этому продукту.

Ключевая идея решения

  1. Максимальная изоляция: всё запускаю в контейнерах с заранее настроенными конфигурационными файлами и дашбордами.

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

  3. Единый шлюз (Gateway): маршрутизация по типу данных.

  4. Привычные хранилища: Prometheus, Loki, Tempo.

  5. Grafana: единые дашборды и алерты.


Мой авторский канал - сообщество, где делятся опытом

https://t.me/IT_Chuyana


Архитектура мониторинга

Схема системы мониторинга
Схема системы мониторинга

В ходе проектирования я решил сделать двух агентов:

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

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

    Для теста разрешил отслеживать трейсы и логи, формируемые при съеме метрик
    Для теста разрешил отслеживать трейсы и логи, формируемые при съеме метрик

Уровень

Элемент

Что происходит

1.1

Collector Agent

Снимает метрики системы через node-exporter, читает системные логи.

1.2

Collector Agent app

Снимает метрики /metrics от SDK приложения, читает логи контейнеров, пересылает трейсы.

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:

для Loki
для Loki
для Tempo
для Tempo

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

переход на трейс лога
переход на трейс лога
изучаем трейс
изучаем трейс

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

Это существенно ускоряет диагностику и анализ работы системы.


Итоги и выводы

  1. Унификация. OpenTelemetry Collector стал единым шлюзом для любой телеметрии.

  2. Гибкая маршрутизация. Любые изменения поведения бэкендов не требуют доработки клиентов.

  3. Простое масштабирование. Все сервисы упакованы в контейнеры, обновляются через Ansible или CI/CD.

  4. Широкие возможности. Grafana объединяет метрики, логи и трейсы в единой визуальной среде с алертами.

В результате я получил полнофункциональную observability-платформу: теперь вижу, где возникают задержки, когда появляются ошибки и как запросы «путешествуют» между сервисами. Переход на OpenTelemetry и знакомые бэкенды — это не только про технологический стек, но и про формирование DevOps-культуры наблюдаемости в компании.

Внедрял всё это не «ради моды», а чтобы упростить управление системой, заложить возможности масштабирования и, главное, раньше пользователей обнаруживать и устранять инциденты.

Спасибо за внимание!

Теги:
Хабы:
+3
Комментарии3

Публикации

Работа

DevOps инженер
32 вакансии

Ближайшие события