05 · Observability5.3 · Трейсы, алертинг, SLO5.3.1сложный

Distributed tracing и OpenTelemetry

Зачем это нужно

Inference request проходит: Ingress → Istio → churn-serving → Feature API → (cache) → model forward. Latency выросла на 200 ms. Metrics показывают p95; trace показывает, какой hop съел время. Без tracing вы оптимизируете model, а виноват сетевой вызов фич.

OpenTelemetry (OTel) — открытый стандарт traces (и metrics/logs). В курсе — propagation через Istio и instrumentation в Python-сервисе.

Основные идеи

Trace, span, context.

  • Trace — один end-to-end request (trace_id).

  • Span — один участок работы (HTTP call, model.predict).

  • Parent-child spans формируют дерево.

W3C Trace Context. Header traceparent передаётся между сервисами. Istio sidecar генерирует/продолжает trace.

OpenTelemetry components.

Piece Role
OTel SDK Instrument app, create spans
OTel Collector Receive, process, export to backend
Backend Jaeger, Tempo, Zipkin

Auto vs manual instrumentation.

  • Auto: some frameworks HTTP clients.

  • Manual: @tracer.start_as_current_span("predict") around model inference — видеть GPU forward отдельно от HTTP handler.

Sampling. 100% traces дорого. Head-based sampling 1–10% in prod; always sample errors (tail sampling in Collector — advanced).

Istio + tracing. Mesh config enables tracing provider; spans for ingress and outbound calls without app changes — good baseline.

ML-specific spans.

  • validate_input

  • fetch_features

  • preprocess

  • model_inference

  • postprocess

Compare p95 model_inference vs fetch_features — guides optimization.

Correlation.

  • trace_id in structured logs (5.2.1).

  • Grafana: jump from trace to logs with same trace_id.

Tempo vs Jaeger. Grafana Tempo integrates natively with Loki/Prometheus; Jaeger classic UI. Both accept OTLP export.

Как это выглядит на практике

Python (conceptual):

` from opentelemetry import trace tracer = trace.get_tracer("churn-serving")

@app.post("/predict") async def predict(data: Features): with tracer.start_as_current_span("predict") as span: with tracer.start_as_current_span("fetch_features"): feats = await feature_client.get(data.user_id) with tracer.start_as_current_span("model_inference"): score = model.predict(feats) span.set_attribute("model.version", MODEL_VERSION) return {"score": score} `

Incident: p95 800 ms, GPU idle.

  1. Open Grafana Tempo → filter service.name=churn-serving.

  2. Trace waterfall: fetch_features 750 ms.

  3. Drill Feature API traces — DB query slow.

Fix index upstream; inference team not retraining model.

Istio telemetry: enable tracing sample 5%; compare app span vs sidecar span for overhead audit.

Что сделать после занятия

  • Нарисуйте span tree для /predict с минимум 4 spans.

  • Объясните, зачем trace_id в JSON logs.

  • Прочитайте OTel docs: что такое OTLP exporter.

Официальные материалы