OpenTelemetry Collector: зачем он нужен и как на него правильно смотреть

Зачем нужен отдельный слой сбора телеметрии, как Collector устроен, сколько ресурсов потребляет, какие deployment-паттерны бывают, типичные ошибки и когда он действительно оправдан

OpenTelemetry Collector часто появляется в архитектурных схемах слишком рано и слишком абстрактно. Его любят рисовать как обязательный прямоугольник между приложением и любым backend, а потом уже по ходу проекта разбираться, зачем он вообще нужен, где он должен стоять и не стал ли он ещё одной хрупкой зависимостью.

В реальности Collector полезен не потому, что «так сейчас модно строить observability», а потому что он помогает отделить генерацию телеметрии от её доставки, обработки и маршрутизации. Если смотреть на него именно так, он быстро становится понятнее: это не волшебная коробка, а отдельный слой обработки telemetry data со своими плюсами, ограничениями и операционной ценой.

OpenTelemetry: Go, Java, Rust и Python за мониторинговым пультом

В статье

Что такое OpenTelemetry Collector

Collector — это отдельный процесс или сервис, который принимает телеметрию от приложений, при необходимости обрабатывает её и отправляет дальше в один или несколько backend.

Если упростить, то у него три главные роли:

  • приём telemetry от приложений и систем;
  • обработка: batching, ограничение памяти, enrichment, filtering;
  • экспорт в tracing, metrics и logs backend.

Это важно, потому что без отдельного слоя приложениям часто приходится знать слишком много о downstream-системах: куда именно слать traces, как переключаться между backend, как делать retry, как резать данные, как переживать временную недоступность приёмника.

Collector как раз и нужен, чтобы не размазывать эту логику по каждому сервису.

flowchart LR A["Go / Java / Rust сервисы"] --> B["OTLP"] B --> C["OpenTelemetry Collector"] C --> D["Traces backend"] C --> E["Metrics backend"] C --> F["Logs backend"]

flowchart LR
  A["Go / Java / Rust сервисы"] --> B["OTLP"]
  B --> C["OpenTelemetry Collector"]
  C --> D["Traces backend"]
  C --> E["Metrics backend"]
  C --> F["Logs backend"]
Collector отделяет генерацию телеметрии от её обработки и доставки

Когда он действительно нужен

Collector особенно полезен, когда выполняется хотя бы одна из этих вещей:

  • у вас больше одного сервиса и не хочется прошивать адреса backend в каждый из них;
  • нужно отправлять телеметрию в несколько систем одновременно;
  • хочется централизованно делать batching, filtering, enrichment или sampling;
  • backend могут меняться, а приложения лучше от этого изолировать;
  • нужно принимать телеметрию не только от приложений, но и от host/node/infrastructure sources.

То есть Collector хорошо работает как routing и processing layer для observability.

Если же у вас один небольшой сервис и один backend, отдельный Collector не всегда обязателен. В некоторых сценариях прямой экспорт из SDK действительно проще.

Сколько это стоит: ресурсы и overhead

Collector — это дополнительный hop в data path. Прежде чем добавлять его в архитектуру, полезно понимать, сколько он потребляет.

Потребление ресурсов

Типичные порядки величин для Collector на Go-based runtime:

Нагрузка CPU RAM Примечание
Idle (нет данных) ~0.01 core 30–50 MB Базовый footprint процесса
1 000 spans/sec ~0.05 core 60–100 MB Типичный небольшой сервис
10 000 spans/sec ~0.2–0.5 core 150–300 MB Средняя нагрузка, несколько сервисов
50 000+ spans/sec 1+ core 500 MB–1 GB Нужно думать о горизонтальном масштабировании

Числа зависят от количества и сложности processors, размера spans/metrics, количества exporters. batch processor увеличивает пиковое потребление RAM (буферизует данные перед отправкой), но снижает CPU и network overhead.

Latency overhead

Дополнительный hop через Collector добавляет 1–5 мс к latency доставки телеметрии. Для traces и metrics это обычно несущественно — телеметрия не находится в критическом пути запроса пользователя. Но если вы используете Collector для real-time alerting на основе свежих данных, эту задержку стоит учитывать.

С включённым batch processor задержка увеличивается на величину timeout (обычно 1–5 секунд) — это сознательный trade-off ради эффективности.

Docker-образ

Официальный otel/opentelemetry-collector-contrib — около 200 MB. Core-версия (otel/opentelemetry-collector) — около 60 MB. Если размер критичен, можно собрать кастомный образ через OCB (OpenTelemetry Collector Builder) только с нужными компонентами — получится 20–40 MB.

Из каких частей он состоит

Официальная модель конфигурации у Collector довольно прозрачная. В ней есть несколько базовых сущностей.

Receivers — откуда приходят данные

Принимают телеметрию извне. На практике в 90% случаев достаточно двух:

  • otlp — универсальный приёмник для traces, metrics и logs от приложений, инструментированных через OpenTelemetry SDK. Поддерживает gRPC (порт 4317) и HTTP (порт 4318). Это receiver по умолчанию — если не знаете, с чего начать, начинайте с него.
  • hostmetrics — собирает CPU, RAM, disk, network с хоста. Полезен, когда Collector стоит как agent на ноде и заменяет node_exporter.

Реже, но бывает нужен prometheus receiver — для pull-модели, когда приложение экспонирует /metrics endpoint, а Collector его скрейпит. Это удобно для миграции с чистого Prometheus на OTEL без переделки приложений.

Processors — что происходит по дороге

Обрабатывают данные между приёмом и экспортом. Порядок важен — Collector применяет их последовательно.

Четвёрка, которая нужна почти всегда:

  • memory_limiter — страховка от OOM. Ставьте первым в цепочке. Без него Collector при всплеске нагрузки может съесть всю память и упасть, забрав с собой всю телеметрию.
  • batch — группирует данные перед отправкой. Снижает количество HTTP/gRPC-запросов к backend в 10–100 раз. Trade-off: добавляет задержку (обычно 1–5 секунд).
  • resource — добавляет или модифицирует resource attributes (environment, service.name, region). Полезен, когда приложение не отправляет нужные атрибуты, или когда нужно унифицировать naming.
  • filter — отбрасывает ненужные данные. Например, health check spans, debug-логи, метрики, которые генерируются, но никем не используются. Фильтрация на уровне Collector может сэкономить 30–50% трафика и стоимости хранения.

Остальные processors (transform, tail_sampling, span, attributes) — для более зрелых сценариев. Не добавляйте их «про запас».

Exporters — куда уходят данные

Отправляют данные в backend. Конкретный выбор зависит от вашего стека:

  • otlp — универсальный, отправляет в любой OTLP-совместимый backend (Tempo, Jaeger, другой Collector, SaaS вроде Grafana Cloud или Datadog). Выбор по умолчанию.
  • prometheus — экспонирует metrics endpoint, который скрейпит Prometheus. Удобно, если Prometheus уже есть и менять его не планируете.
  • debug — выводит данные в stdout. Незаменим при отладке pipeline, бесполезен в production.

Ключевой момент: один exporter может быть подключён к нескольким pipelines, и один pipeline может иметь несколько exporters. Это и есть fan-out — одна из главных причин использовать Collector.

Connectors

Связывают один pipeline с другим и работают как exporter в одном месте и receiver в другом. Это уже не самый первый уровень знакомства, но полезно понимать, что Collector умеет не только «принял и переслал», а и более сложную маршрутизацию между pipelines.

Extensions

Дополнительные возможности самого Collector, не участвующие напрямую в обработке telemetry. Самый полезный — health_check (endpoint для liveness probe). Включайте его всегда.

Базовый pipeline на практике

Если отбросить экзотику, почти любой стартовый pipeline выглядит примерно так:

  1. приложение шлёт traces/metrics/logs по OTLP;
  2. Collector принимает их через otlp receiver;
  3. прогоняет через memory_limiter и batch;
  4. отправляет в backend через exporter.
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
    spike_limit_mib: 128
  batch:
    timeout: 2s
    send_batch_size: 1024

exporters:
  otlp:
    endpoint: tempo:4317
    tls:
      insecure: true
  prometheus:
    endpoint: 0.0.0.0:8889

extensions:
  health_check:
    endpoint: 0.0.0.0:13133

service:
  extensions: [health_check]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus]

Что здесь важно заметить:

  • сам факт объявления receiver, processor или exporter ещё не включает их в работу;
  • реально они начинают жить только когда подключены в service.pipelines;
  • один и тот же receiver или exporter можно использовать в нескольких pipelines;
  • memory_limiter и batch — очень хороший production baseline почти для любого non-trivial Collector.

Deployment patterns: где ставить Collector

Collector можно разместить по-разному, и выбор pattern влияет на надёжность, latency и операционную сложность.

Agent (DaemonSet / sidecar на каждой ноде)

flowchart LR subgraph Node1["Нода 1"] A1["Сервис A"] --> C1["Collector agent"] A2["Сервис B"] --> C1 end subgraph Node2["Нода 2"] A3["Сервис C"] --> C2["Collector agent"] end C1 --> G["Gateway Collector"] C2 --> G G --> B1["Backend"]

flowchart LR
  subgraph Node1["Нода 1"]
    A1["Сервис A"] --> C1["Collector agent"]
    A2["Сервис B"] --> C1
  end
  subgraph Node2["Нода 2"]
    A3["Сервис C"] --> C2["Collector agent"]
  end
  C1 --> G["Gateway Collector"]
  C2 --> G
  G --> B1["Backend"]
Agent pattern: Collector рядом с приложением

Collector стоит на каждой ноде (DaemonSet в Kubernetes) или как sidecar рядом с каждым подом. Приложение отправляет телеметрию на localhost — минимальный latency, нет сетевого hop за пределы ноды.

Когда подходит: кластер с десятками сервисов, нужен сбор host-метрик, хочется минимизировать сетевой трафик между нодами.

Цена: N инстансов Collector (по одному на ноду), каждый потребляет 50–300 MB RAM.

Gateway (централизованный)

Один или несколько инстансов Collector за load balancer. Все сервисы отправляют телеметрию по сети в gateway.

Когда подходит: небольшой кластер, docker-compose, несколько сервисов. Проще в управлении — один конфиг, один процесс.

Цена: single point of failure (если один инстанс). Сетевой hop от каждого сервиса. При недоступности gateway — потеря телеметрии.

Agent + Gateway (двухуровневый)

Комбинация: agent на ноде делает первичную обработку (batching, enrichment, filtering), gateway — финальную маршрутизацию и fan-out в backend.

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

Цена: максимальная операционная сложность. Оправдана только при реальной необходимости.

Сравнение

Pattern Сложность Надёжность Latency Для кого
Gateway Низкая SPOF без HA Сетевой hop Docker-compose, малые кластеры
Agent Средняя Нет SPOF Localhost Kubernetes, средние кластеры
Agent + Gateway Высокая Максимальная Localhost + hop Крупные production-системы

Для старта на docker-compose — gateway. Один контейнер, один конфиг, минимум операционной сложности. Масштабировать можно потом.

Что важно для надёжности и эксплуатации

Collector — не просто конфиг-файл с роутингом. Это отдельный процесс в data path телеметрии. А значит, у него есть собственные operational вопросы.

1. Не делать его «невидимой» критичной зависимостью

Если все сервисы шлют телеметрию только в Collector, а он недоступен, вы теряете observability. Это не страшно, если команда это осознаёт. Это плохо, если никто даже не думал про такой сценарий.

2. Следить за самим Collector

Collector тоже надо мониторить. Он экспонирует собственные метрики через Prometheus endpoint (по умолчанию :8888). Ключевые метрики:

  • otelcol_receiver_accepted_spans / _refused_spans — сколько данных принято и отвергнуто;
  • otelcol_exporter_sent_spans / _send_failed_spans — успешность доставки в backend;
  • otelcol_processor_dropped_spans — потери на уровне processors (фильтрация, memory limit);
  • process_runtime_total_alloc_bytes — потребление памяти Go runtime.

Если refused или send_failed растут — Collector не справляется или backend недоступен. Пустые дашборды — часто симптом не отсутствия данных, а проблемы в Collector.

3. Аккуратно относиться к batching и buffering

Batching почти всегда полезен, но он добавляет trade-off:

  • лучше throughput;
  • меньше network chatter;
  • но выше задержка доставки и выше цена потери буфера при аварии.

4. Отделять удобство от надёжности

Один Collector в docker-compose — это удобно. Один Collector как единственная точка приёма всей production-телеметрии без понимания отказов — уже риск. Если observability действительно важна, нужно заранее думать о deployment pattern, а не считать Collector просто «ещё одним контейнером».

Типичные ошибки и анти-паттерны

1. Ставить Collector «на всякий случай»

Если нет ни маршрутизации, ни обработки, ни нескольких backend, ни проблемы vendor isolation, он может просто добавить лишний hop и операционную сложность.

2. Смешивать в одном месте всё подряд без дисциплины

Очень частая история:

  • traces, metrics, logs;
  • разные команды;
  • разный retention mindset;
  • разные backend;
  • всё складывается в один огромный Collector config.

В какой-то момент это превращается не в observability layer, а в трудночитаемый монолит конфигурации.

3. Думать, что Collector «сам всё починит»

Он не исправит:

  • плохую инструментализацию;
  • бессмысленные метрики;
  • шумные логи;
  • слишком дорогие spans;
  • неподходящий backend.

Collector полезен как слой обработки и маршрутизации, но он не заменяет инженерное мышление о самой телеметрии.

4. Игнорировать memory pressure

Если Collector принимает много данных, он сам становится нагрузочным сервисом. При 50 000 spans/sec без memory_limiter он может легко съесть 2+ GB RAM и упасть по OOM. Поэтому memory_limiter первым в цепочке processors — не рекомендация, а правило.

Когда можно обойтись без Collector

Collector не обязателен всегда.

Конкретный пример: у вас один Go-сервис, один Prometheus для метрик, один Tempo для traces. Сервис инструментирован через OpenTelemetry SDK и отправляет данные напрямую — метрики через Prometheus exporter, traces через OTLP exporter в Tempo. Всё работает, конфигурация в одном месте (в коде сервиса), нет дополнительного процесса, который нужно деплоить и мониторить.

В этом сценарии Collector добавит:

  • ещё один контейнер в docker-compose;
  • ещё один конфиг, который нужно поддерживать;
  • ещё одну точку отказа;
  • задержку в доставке (batching);
  • потребление 50–100 MB RAM.

А даст — только потенциальную гибкость «на будущее». Если будущее наступит (появится второй сервис, второй backend, потребность в фильтрации) — тогда и добавите. Direct export из SDK вполне production-ready.

Хороший вопрос здесь звучит не как «почему у нас ещё нет Collector», а как «какую проблему он у нас решает».

Короткий checklist перед внедрением

Перед тем как добавлять Collector в систему, полезно ответить на несколько вопросов:

  1. Зачем он нужен именно вам: routing, processing, fan-out, isolation или просто «так принято»?
  2. Что будет, если Collector недоступен?
  3. Какие сигналы вы через него пропускаете: traces, metrics, logs?
  4. Какие processors реально нужны, а какие добавлены «про запас»?
  5. Кто и как будет наблюдать за самим Collector?
  6. Один ли это Collector, или уже нужен другой deployment pattern?

Если на эти вопросы нет ответа, Collector легко становится ещё одним сложным компонентом, полезность которого никто до конца не может объяснить.

Итог

OpenTelemetry Collector — хороший инструмент, когда нужно отделить приложения от способов доставки и обработки телеметрии. Он особенно уместен в системах, где observability уже перестала быть «одним экспортом в один backend» и стала отдельным инженерным контуром.

Но он не нужен автоматически каждой системе. Как и с Redis, Kafka или любым другим middleware, правильный вопрос звучит не как «надо ли добавить его в схему», а как «какую конкретную проблему он у нас решает и во что нам обойдётся».

Если смотреть на Collector именно так, он очень быстро перестаёт быть модным прямоугольником на диаграмме и становится понятным инструментом с ясной ролью.

Что дальше в серии

Эта статья — часть цикла про observability. Дальше:

  • Vector как data pipeline для логов и метрик — альтернативный подход к обработке телеметрии
  • Structured logging в Go: slog + OpenTelemetry — как связать логи с traces через trace_id (готовится)
  • Grafana-стек для self-hosted observability — Tempo + Prometheus + Loki + Grafana на docker-compose (готовится)

Документация

Обсуждение в Telegram

Присоединиться →

Комментарии