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 не болезненная, но требует согласованного переключения на стороне приложений.
- Grafana Loki 2.0: уходим от ELK в пользу лёгкого агрегатора логов для Kubernetes · 16 ноября 2020
- Grafana 7.2: library panels и трансформации без ETL · 19 октября 2020