ADG Оставить заявку
Блог DevOps 4 мин чтения

OpenTelemetry Collector 0.14: один агент вместо трёх

OTel Collector 0.14 достигает стабильности трассировок. Внедряем единую точку сбора телеметрии: OTLP из Go и Python, экспорт в Jaeger и Prometheus вместо Jaeger agent + StatsD + Filebeat.

Контекст момента

OpenTelemetry Collector 0.14: стабильный pipeline трассировок, OTLP стабилизирован, CNCF проект готов к production

На одном из клиентских проектов мы к осени дошли до классической ситуации: три разных агента на каждой ноде собирают телеметрию, каждый требует своего конфига, своего обслуживания и своих ресурсов. Jaeger agent для трассировок, StatsD-exporter для метрик приложений, Filebeat для структурированных логов. Когда вышел OpenTelemetry Collector 0.14 с заявленной стабильностью трейс-пайплайна, решили наконец разобраться что CNCF-проект реально может в production.

Что стабилизировалось в 0.14

OpenTelemetry как проект существует примерно с начала 2019 года - это слияние OpenCensus и OpenTracing под крылом CNCF. Collector - отдельный компонент, принимающий телеметрию по разным протоколам и перегоняющий её в backends. До версии 0.14 трейсинг-пайплайн менял API между релизами достаточно активно, что делало его неудобным для production: обновляться приходилось с осторожностью.

В 0.14 OTLP (OpenTelemetry Protocol) по gRPC объявлен стабильным для трейсов. Это означает обратную совместимость и предсказуемое поведение - не «мы постараемся не ломать», а явное обязательство. Метрики через OTLP пока в beta, логи в alpha, но трейсы как главная фича дошли до состояния когда их можно ставить без постоянного контроля.

Архитектура пайплайна

[Go app / Python app]
        |
     OTLP gRPC
        |
  OTel Collector
  ┌─────────────────────────────────┐
  │  receivers: [otlp]              │
  │  processors: [batch, memory_limiter] │
  │  exporters:                     │
  │    traces -> Jaeger (thrift)    │
  │    metrics -> Prometheus        │
  └─────────────────────────────────┘

Collector работает по схеме receiver -> processor -> exporter, конфиг описывается в одном YAML. Принципиально важное: один экземпляр Collector умеет одновременно принимать трейсы по OTLP и отдавать их в Jaeger, принимать метрики через OTLP и выставлять эндпоинт для Prometheus scrape - без отдельных процессов.

Минимальный рабочий конфиг для нашего сценария:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_mib: 256

exporters:
  jaeger:
    endpoint: jaeger-collector:14250
    tls:
      insecure: true
  prometheus:
    endpoint: "0.0.0.0:8889"

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

Инструментирование приложений

Go-приложение на проекте уже использовало OpenTracing API через Jaeger client. Миграция на OpenTelemetry SDK потребовала изменений в коде инициализации трейсера, остальное осталось практически нетронутым - главным образом потому что мы использовали абстракцию в одном месте:

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp"
    "go.opentelemetry.io/otel/label"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
    exporter, err := otlp.NewExporter(ctx,
        otlp.WithInsecure(),
        otlp.WithAddress("otel-collector:4317"),
    )
    if err != nil {
        return nil, err
    }
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(resource.New(
            label.String("service.name", "api-service"),
        )),
    )
    otel.SetTracerProvider(tp)
    return tp, nil
}

С Python сервисом было немного интереснее: там сидел собственный StatsD-клиент для метрик, который надо было параллельно убирать. Мы не стали мигрировать метрики с Python сразу - оставили StatsD на переходный период, переключив только трейсы. Collector по-прежнему принимал StatsD через отдельный receiver, так что два источника метрик какое-то время жили рядом. Это не идеально, но позволило не перекладывать всё сразу.

Что вышло на практике

Первое - замена Jaeger agent прошла без сюрпризов. Agent раньше запускался как sidecar в каждом поде, Collector встал как отдельный DaemonSet на ноде. Сетевые хопы стали чуть длиннее, но ресурсная картина улучшилась: один процесс вместо N sidecar-контейнеров.

Второе - конфиг стал единым местом правды для телеметрии. Раньше надо было помнить где живут конфиги Jaeger agent, где StatsD, где Filebeat. Теперь - один YAML, один деплоймент, одна точка мониторинга самого коллектора.

Третье - memory_limiter processor оказался не декоративным. При пиковой нагрузке Collector без ограничений съедал заметно больше памяти чем хотелось. С limit_mib: 256 он начинает отклонять входящие данные раньше чем упирается в OOM. Это лучше, чем падение агента.

Что пока вызывает вопросы: метрики в OTLP beta-статуса иногда ведут себя неожиданно при сложных агрегациях на стороне Collector. Мы пока используем Prometheus-receiver (scrape существующих эндпоинтов) как параллельный путь для метрик, которые критичны. Полный переход метрик на OTLP отложили до стабилизации.

Логи в alpha не трогали вовсе - Loki с Promtail, о котором писали неделю назад, пока справляется нормально.

На managed-сопровождении Collector ставим как стандартный компонент для новых Kubernetes-проектов, где есть трейсинг. Для существующих - смотрим по ситуации, миграция с Jaeger agent не болезненная, но требует согласованного переключения на стороне приложений.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.