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

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 к пользе там хуже.

Контакт

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

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