Соберём мини-систему из двух сервисов на 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. Поэкспериментируйте
- Сделайте в service B вызов третьего сервиса (или второго экземпляра B, рекурсивно) — дерево станет глубже.
- Добавьте
time.sleep(1)в B и обновите трейс: найдите «толстый» сегмент. - Остановите Jaeger (
docker stop jaeger), дёргайте /do — приложение должно продолжать работать (экспорт отказоустойчив); поднимите — трейсы снова появятся. - Позапускайте curl несколько раз подряд: убедитесь, что каждый запрос = отдельный trace ID.
Мини-квиз
1. Почему спаны двух разных процессов попали в один трейс? — OTel передал span context в заголовке traceparent, оба экспортировали в общий Jaeger.
2. Что происходит с приложением при недоступности Jaeger? — Ничего страшного: экспорт асинхронный и «отказоустойчивый» — запросы работают, трейсы копятся/режутся.
3. Откуда в трейсе клиентский спан GET? — Инструментирование библиотеки requests создало child-спан исходящего вызова.