Распределённый трейсинг: Tempo, Jaeger, sampling

↑ Этап 7 · Observability и эксплуатация

Желательно~9 мин чтенияУровень: middle

Зачем это на собесе

«Запрос в микросервисной системе тормозит, как найти где?» Метрики показывают, что медленно, логи что произошло, а трейс показывает путь одного запроса через все сервисы. Ждут, что вы знаете разницу между сборщиком и хранилищем трассировок, понимаете sampling и умеете связать трейсы с логами и метриками.

Подтемы

  • Трейс, span, контекст
  • Путь данных: приложение, Collector, хранилище
  • Tempo и Jaeger
  • Sampling: head и tail
  • Корреляция с логами и метриками

Объяснение

Основные понятия

  • Trace — путь одного запроса через систему; span — одна операция внутри него со временем начала и длительностью, родителем и атрибутами.
  • Контекст трассировки передаётся между сервисами в заголовке (traceparent по стандарту W3C Trace Context), поэтому спаны разных сервисов собираются в одно дерево.
sequenceDiagram
    participant U as Клиент
    participant G as API Gateway
    participant O as Orders
    participant P as Payments
    U->>G: запрос (traceparent: 00-TRACE-SPAN1-01)
    G->>O: запрос (тот же trace id, новый span)
    O->>P: запрос (тот же trace id)
    P-->>O: ответ
    O-->>G: ответ
    G-->>U: ответ
    Note over G,P: Каждый сервис отправляет свои спаны<br/>с одним trace id в коллектор

Путь данных

flowchart LR
    A["Приложение<br/>(OpenTelemetry SDK)"] -->|OTLP| C["OpenTelemetry Collector<br/>(batch, sampling, фильтры)"]
    C -->|OTLP| T[("Tempo / Jaeger")]
    T --> G["Grafana<br/>поиск и просмотр трейсов"]

Приложение отправляет спаны по протоколу OTLP в Collector (OpenTelemetry Collector). Он батчит, фильтрует, семплирует и пересылает в хранилище.

Tempo и Jaeger

Grafana TempoJaeger
Хранениеобъектное хранилище (S3-совместимое), индексирует только trace idElasticsearch/OpenSearch, Cassandra и другие бэкенды
Поискпо trace id, в новых версиях TraceQL по атрибутампо сервису, операции, тегам, времени
Стоимостьнизкая (дешёвое хранение объектов)выше при индексировании атрибутов
Интеграциитесно с Grafana, Loki, Prometheusсобственный UI
ПринимаетOTLP, Jaeger, ZipkinOTLP (v2 построен на OpenTelemetry Collector)

Tempo хорош, когда уже есть Grafana-стек и нужна дешёвая долговременная память. Jaeger удобен, когда нужен самостоятельный UI с поиском по тегам.

Sampling: сколько трейсов хранить

Хранить все трейсы дорого. Поэтому семплируют.

ВидГде решаетсяПлюсы и минусы
Head samplingпри создании трейса, например 10 %просто и дёшево, но можно потерять редкие ошибки и медленные запросы
Tail samplingпосле сбора всего трейсаможно оставить все ошибки и медленные, но Collector должен накопить спаны трейса в памяти

Обычная стратегия: все ошибки и запросы дольше порога плюс небольшой процент остальных (tail sampling в Collector).

Корреляция сигналов

  • В логи пишут trace_id и span_id: из лога переходят к трейсу в Grafana.
  • Exemplars привязывают к точке метрики конкретный trace id: из графика задержки переход к типичному медленному трейсу.
  • Атрибуты спанов (service.name, http.route, db.system) унифицируют по всем сервисам.

Примеры

Collector с tail sampling и экспортом в Tempo

receivers:
  otlp:
    protocols:
      grpc: {}
      http: {}
processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow
        type: latency
        latency: { threshold_ms: 500 }
      - name: sample-rest
        type: probabilistic
        probabilistic: { sampling_percentage: 10 }
  batch: {}
exporters:
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [tail_sampling, batch]
      exporters: [otlp/tempo]

tail_sampling входит в сборку opentelemetry-collector-contrib. Все спаны одного трейса должны попадать в один экземпляр Collector (балансировка по trace id).

Включение трассировки в ASP.NET Core

builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService("orders"))
    .WithTracing(t => t
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter());          // адрес берётся из OTEL_EXPORTER_OTLP_ENDPOINT

Подробности о библиотеках и метриках: Observability на бэкенде.

Нюансы и подводные камни

  • Контекст должен проходить везде. Если один сервис не передаёт traceparent (старая библиотека, очередь без заголовков), трейс рвётся на две части.
  • Очереди и фоновые задачи нужно связывать вручную: кладите контекст в заголовки сообщения.
  • Объём. Без sampling хранилище и сеть быстро переполняются.
  • Атрибуты с персональными данными. Не записывайте в спаны пароли, токены, тела запросов.
  • Высокая кардинальность атрибутов в индексируемых бэкендах (Jaeger на Elasticsearch) удорожает хранение.
  • Время. Часы серверов должны быть синхронизированы, иначе спаны «едут» по временной шкале.

Вопросы с ответами

Связанные темы