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

Модуль 01 / Урок 4 из 27

Практикум: первый трейс

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

Соберём мини-систему из двух сервисов на Python: сервис A вызывает сервис B, оба пишут трейсы в Jaeger. Цель — увидеть свой первый сквозной трейс и понять каждое его звено.

Шаг 1. Стенд

docker run -d --name jaeger -p 16686:16686 -p 4317:4317 -p 4318:4318 \
  jaegertracing/all-in-one:latest

Проверьте UI: http://localhost:16686.

Шаг 2. Два сервиса

Сервис B (service_b.py) — «поставщик»:

from flask import Flask
from opentelemetry.instrumentation.flask import FlaskInstrumentor
import time, random

app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)   # авто-спан на каждый HTTP-запрос

@app.route("/work")
def work():
    time.sleep(random.uniform(0.05, 0.15))   # «работа»
    return "done"

app.run(port=8081)

Сервис A (service_a.py) — «оркестратор»:

import requests, time
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.instrumentation.wsgi import CollectingHook  # (для Flask ниже)
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from flask import Flask

app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)
RequestsInstrumentor().instrument()          # авто-спан на исходящие HTTP

@app.route("/do")
def do():
    time.sleep(0.02)
    r = requests.get("http://localhost:8081/work")
    return r.text

app.run(port=8080)

Шаг 3. Зависимости и экспорт

pip install flask requests \
  opentelemetry-sdk opentelemetry-api \
  opentelemetry-instrumentation-flask opentelemetry-instrumentation-requests \
  opentelemetry-exporter-otlp

# Точка приёма и имя сервиса — через стандартные переменные OTel:
export OTEL_SERVICE_NAME=service-a
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
opentelemetry-instrument python service_a.py
# в другом терминале:
OTEL_SERVICE_NAME=service-b OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
  opentelemetry-instrument python service_b.py

Утилита opentelemetry-instrument подключает инструментирование и настраивает экспорт без единой строчки кода о телеметрии — подробно это разберём в модуле 2.

Шаг 4. Смотрим трейс

curl http://localhost:8080/do

В Jaeger UI: Service = service-a → Find Traces. Откройте трейс и проверьте:

  • Корневой спан GET /do (service-a);
  • Ребёнок GET из библиотеки requests (service-a) — клиентский HTTP-спан;
  • Ребёнок GET /work уже в service-b — контекст доехал по traceparent!
💡 Это главный «вау-момент» курса: два независимых процесса, ноль ручной передачи ID — и они уже в одном трейсе. Всё дальше — вариации этой механики.

Шаг 5. Поэкспериментируйте

  1. Сделайте в service B вызов третьего сервиса (или второго экземпляра B, рекурсивно) — дерево станет глубже.
  2. Добавьте time.sleep(1) в B и обновите трейс: найдите «толстый» сегмент.
  3. Остановите Jaeger (docker stop jaeger), дёргайте /do — приложение должно продолжать работать (экспорт отказоустойчив); поднимите — трейсы снова появятся.
  4. Позапускайте curl несколько раз подряд: убедитесь, что каждый запрос = отдельный trace ID.
Мини-квиз

1. Почему спаны двух разных процессов попали в один трейс? — OTel передал span context в заголовке traceparent, оба экспортировали в общий Jaeger.

2. Что происходит с приложением при недоступности Jaeger? — Ничего страшного: экспорт асинхронный и «отказоустойчивый» — запросы работают, трейсы копятся/режутся.

3. Откуда в трейсе клиентский спан GET? — Инструментирование библиотеки requests создало child-спан исходящего вызова.