Grafana Tempo в production: S3 вместо Elasticsearch и прощание с Jaeger
Tempo 1.2 вышел с улучшенной поддержкой OpenTelemetry и search backend. Документируем перевод продакшн-трейсинга с Jaeger на Tempo: S3 как хранилище, retention policy и реальные greps.
Grafana Tempo 1.2 выходит с улучшенным search backend и интеграцией с OpenTelemetry
В сентябре мы рассматривали Grafana 8 + Tempo как стек для unified observability в контексте одного клиентского проекта - тогда в минимальной конфигурации с LocalStorage. Tempo 1.2 вышел на прошлой неделе, принёс улучшенный search backend и ряд улучшений по части OpenTelemetry-совместимости. Мы воспользовались поводом и перевели ещё один production-проект с Jaeger на Tempo - на этот раз уже с S3 как хранилищем и нормальной retention policy. Документируем.
Почему Jaeger начал раздражать
Jaeger у этого клиента работал полтора года. Данные хранились в Elasticsearch - это стандартная схема для Jaeger production. Проблема не в том, что Elasticsearch плохой инструмент, а в том, что держать Elasticsearch ради трейсов - это избыточно. Три data node, snapshot policy, мониторинг самого ES, периодическая чистка старых индексов вручную, потому что ILM настроили не идеально, и так далее. При этом объём трейсов у клиента - не гигантский, это не Netflix.
Tempo предлагает другую философию: трейсы не нужно индексировать, потому что вы не ищете по атрибутам через UI трейсинга - вы приходите в Tempo с конкретным Trace ID из лога или метрики. Хранение - объектное хранилище. S3 в той же AWS-инфраструктуре клиента стоит принципиально дешевле Elasticsearch-кластера в пересчёте на гигабайт.
Tempo 1.2: что конкретно изменилось
Search backend - в 1.2 появилась поддержка поиска трейсов без знания Trace ID: можно фильтровать по сервису, duration, тегам. По умолчанию не активирован - нужно явно включить search_enabled: true в конфиге tempo и поднять storage для индекса поиска. Это отдельный компонент поверх основного object storage.
OpenTelemetry - в 1.2 существенно поправили совместимость с otlp grpc receiver. До этого у нас были случаи, когда часть span-ов терялась на стороне приёма при высоком трафике - не массово, но заметно. После обновления пока не воспроизвелось.
Разворачиваем: S3 как хранилище
Helm-чарт Tempo мы использовали из официального grafana/tempo репозитория, версия 0.7.x под Tempo 1.2. Конфигурация хранилища в values:
tempo:
storage:
trace:
backend: s3
s3:
bucket: company-tempo-traces
endpoint: s3.eu-central-1.amazonaws.com
region: eu-central-1
access_key: ${AWS_ACCESS_KEY_ID}
secret_key: ${AWS_SECRET_ACCESS_KEY}
Credentials передаём через Kubernetes Secret, не хардкодим в values. S3-бакет с lifecycle policy: объекты старше 30 дней удаляются автоматически на уровне S3. Это резервный уровень - Tempo сам делает compaction и retention через compactor.
Retention в Tempo настраивается через compactor:
compactor:
compaction:
block_retention: 720h # 30 дней
Два уровня retention - и в S3 lifecycle, и в Tempo compactor - это намеренная избыточность. Compactor чистит блоки по retention, S3 lifecycle - страховка на случай если compactor не отработал корректно.
Миграция с Jaeger
Данные из Jaeger мы не переносили - исторические трейсы не настолько ценны, чтобы городить миграцию. Перелючили exporter в сервисах с Jaeger на otlp в Tempo, убедились что трейсы пишутся, дали поработать неделю параллельно, потом Jaeger отключили.
Jaeger Collector в схеме заменяется OpenTelemetry Collector - он принимает трейсы от приложений (через otlp или через Jaeger-протокол для тех, кто ещё не мигрировал на OTEL SDK) и отправляет в Tempo. Это удобно: OTel Collector становится единой точкой приёма, и переход сервисов с Jaeger-клиента на OTEL SDK можно делать постепенно, не меняя адрес отправки.
# OTel Collector pipeline
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
jaeger:
protocols:
grpc:
endpoint: 0.0.0.0:14250
exporters:
otlp:
endpoint: tempo:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp, jaeger]
exporters: [otlp]
Sampling
100% трейсов писать не нужно - это как раз то, что убивает Jaeger/ES при росте нагрузки. Настроили head-based sampling на уровне OTel Collector: 20% запросов семплируются, при этом все трейсы с ошибками пишутся всегда через always_sample для error spans.
Хвостовой (tail-based) sampling - правильнее с точки зрения полноты данных, но требует stateful OTel Collector с буферизацией, это сложнее в операционном плане. Для текущего объёма клиента head-based достаточно.
Что получилось
Elasticsearch-кластер отключён. Tempo занимает один pod в кластере (для текущей нагрузки monolithic mode достаточно), S3 хранит трейсы. Retention работает автоматически. Стоимость хранения упала значительно - Elasticsearch требовал compute и disk, S3 берёт только за объём.
Из неудобного: Tempo без включённого search backend - это всё ещё «знай Trace ID, прежде чем открывать Tempo». Search включили, но нужен отдельный storage под индекс, и это прибавило конфигурации. Пока оставили search в тестовом режиме - смотрим на нагрузку.
Для корреляции в Grafana всё работает так же, как описывали в сентябре: derived fields в Loki datasource, Trace ID из лога открывает Tempo. Разница только в том, что теперь за этим стоит S3, а не Elasticsearch.
Для клиентов на managed-сопровождении эту конфигурацию берём как базовую для новых проектов с трейсингом. Jaeger с Elasticsearch остаётся рабочим вариантом, но отношение compute к пользе там хуже.
- Grafana 8 + Tempo: разворачиваем distributed tracing и связываем его с метриками и логами · 6 сентября 2021
- Helm 3.7 + Harbor OCI: charts и образы в одном registry · 18 октября 2021