Программа курса

Модуль 02 / Урок 7 из 27

Распространение контекста

Теория и практика

Трейс сшивается только тогда, когда контекст доезжает от сервиса к сервису. Здесь живёт 90% проблем «трейсы есть, но разорванные»: чужие прокси, очереди без заголовков, потоки без контекста.

Механика: extract / inject

Библиотеки делают две операции:

  • inject: при исходящем вызове сериализовать текущий span context в носитель (заголовки HTTP, свойства сообщения);
  • extract: при входящем — прочитать контекст и сделать его родителем нового спана.

Всё автоматическое инструментирование (из урока 2.2) делает это за вас. Проблемы начинаются там, где библиотеки нет:

# Python: вручную для кастомного HTTP-клиента
from opentelemetry.propagate import inject, extract

headers = {}
inject(headers)                        # положит traceparent (+baggage)
requests.get(url, headers=headers)

# на стороне получателя:
ctx = extract(request.headers)
with tracer.start_as_current_span("process", context=ctx):

Пропагаторы

Формат заголовков задаётся пропагаторами. По умолчанию в OTel — W3C TraceContext + W3C Baggage. Для совместимости с legacy-системами:

  • B3 (Zipkin): X-B3-TraceId, X-B3-SpanId… — встречается повсюду в старых системах;
  • Jaeger: uber-trace-id;
  • Пропагаторы комбинируются: tracecontext,baggage,b3multi — сервис поймает оба формата. Отправка — в оба тоже (лишние заголовки безвредны).
# Java-агент: W3C + B3 вместе
-Dotel.propagators=tracecontext,baggage,b3multi

Где контекст рвётся: чек-лист

  • Обратные прокси/LB: nginx/envoy не вырезают заголовки по умолчанию, но кастомные proxy_set_header или очищающие фильтры — запросто;
  • Сторонние HTTP-клиенты без инструментирования: ручной urllib, net/http без wrapper, экзотические SDK — трейс разорвётся;
  • Очереди: контекст кладут в свойства сообщения; если продюсер/консюмер без инструментирования — разрыв (подробно — урок 6.2);
  • Асинхронность в самом процессе: thread pools, executor'ы, горутины — контекст не передаётся «сам»: Java-агент умеет в Kotlin coroutines/executor'ы частично, в остальных случаях передавайте Context явно;
  • Планировщики (cron): у фоновых задач нет входящего запроса — заводите корневой спан сами (scheduler.run-report);
  • gRPC metadata vs HTTP headers: разные носители, но одна идея.

Диагностика разрывов

  1. Откройте «разорванный» трейс: где именно дерево кончается?;
  2. Проверьте на границе: доходит ли traceparent? (логируйте заголовки одного запроса или ловите tcpdump/mitmproxy на деве);
  3. Если доходит, но не срастается — вероятно, получатель не извлекает (нет инструментария фреймворка) или другой пропагатор (B3 vs W3C);
  4. Если не доходит — ищите очистку заголовков на прокси/клиенте.

Практика

  1. Разорвите трейс намеренно: сделайте вызов B неинструментированным клиентом (голый requests без instrumentation) — увидите два отдельных трейса.
  2. Почините вручную через inject/extract из этого урока.
  3. Переключите пропагатор на b3multi на одном сервисе и убедитесь, что W3C-соседи перестали сшиваться; верните комбинированный список.
  4. Поставьте nginx перед сервисом B, добавьте proxy_set_header Traceparent ""; — воспроизведите «прокси режет заголовки» и найдите это по UI.
Мини-квиз

1. Инструментированы оба сервиса, трейс всё равно разорван. Первая гипотеза? — Заголовок не доходит: прокси/клиент между ними, либо разные пропагаторы.

2. Можно ли включить два пропагатора одновременно? — Да, список через запятую; чтение попытается всеми, запись — запишет всеми.

3. Как сшить трейс для cron-задачи? — Создать корневой спан с именем задачи — контекста извне нет и не нужно.