Tracing distribuído com OpenTelemetry: rastreamento de requests entre serviços
1. Fundamentos do Tracing Distribuído
O tracing distribuído é uma técnica essencial para entender o fluxo de requisições em arquiteturas de microsserviços. Diferente de logs (eventos discretos) e métricas (agregações numéricas), o tracing captura a jornada completa de uma requisição através de múltiplos serviços.
Conceitos fundamentais:
- Trace: Representa uma requisição completa, do início ao fim, abrangendo todos os serviços envolvidos.
- Span: Unidade atômica de trabalho dentro de um trace. Cada span contém nome, duração, atributos e referência ao span pai.
- Contexto de propagação: Mecanismo que transporta o identificador do trace entre serviços via headers HTTP, gRPC metadata ou mensagens.
A diferença crucial entre tracing, logging e métricas está no escopo: logs são eventos pontuais, métricas são agregações estatísticas, enquanto traces fornecem a visão因果 completa de uma requisição individual.
Desafios em microsserviços:
- Sincronização de relógios entre serviços
- Propagação confiável de contexto através de fronteiras de rede
- Volume massivo de dados gerados em sistemas de alta throughput
2. Arquitetura do OpenTelemetry para Tracing
OpenTelemetry (OTel) é o padrão CNCF para observabilidade, oferecendo uma arquitetura modular e vendor-neutral.
Componentes principais:
[Aplicação] → [SDK OTel] → [Exporter] → [Collector OTel] → [Backend]
- API: Define interfaces para criar spans, adicionar atributos e propagar contexto.
- SDK: Implementação concreta da API, com gerenciamento de spans e sampling.
- Collector: Pipeline de dados que recebe, processa e exporta traces para múltiplos backends.
- Exporters: Conectores para Jaeger, Zipkin, Grafana Tempo, Prometheus, entre outros.
Propagação de contexto segue o padrão W3C Trace Context, utilizando headers HTTP como traceparent e tracestate:
Header: traceparent
Formato: 00-{trace_id}-{span_id}-{trace_flags}
Exemplo: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
Sampling estratégico:
- Head-based sampling: Decisão tomada no início do trace, antes da execução. Simples, mas pode perder traces raros.
- Tail-based sampling: Decisão após o trace completo, permitindo preservar traces de erro ou alta latência.
3. Instrumentação Automática vs Manual
Instrumentação automática utiliza agentes ou bibliotecas que interceptam chamadas a frameworks populares:
# Exemplo com Python Flask (instrumentação automática)
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap --action=install
OTEL_SERVICE_NAME="meu-servico" \
OTEL_EXPORTER_OTLP_ENDPOINT="http://collector:4318" \
python app.py
Instrumentação manual oferece controle granular sobre spans e atributos:
from opentelemetry import trace
from opentelemetry.trace import SpanKind
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("processar_pagamento", kind=SpanKind.SERVER) as span:
span.set_attribute("valor.total", 150.00)
span.set_attribute("metodo.pagamento", "cartao_credito")
# Criar span filho para suboperação
with tracer.start_as_current_span("validar_cartao") as child_span:
child_span.set_attribute("bandeira", "visa")
# lógica de validação
Propagação entre serviços:
# Serviço A (produtor HTTP)
from opentelemetry import propagate
from opentelemetry.propagators import inject
headers = {}
propagate.inject(headers)
# headers agora contém 'traceparent' e 'tracestate'
requests.post("http://servico-b/api", headers=headers)
# Serviço B (consumidor HTTP)
from opentelemetry.propagators import extract
context = extract(request.headers)
tracer.start_span("processar_requisicao", context=context)
4. Rastreamento de Requests Multi-serviço
Considere um fluxo completo: API Gateway → Serviço de Autenticação → Serviço de Pedidos → Banco de Dados.
Estrutura de spans:
Trace ID: abc123
├── Span: "api_gateway.handler" (root)
│ ├── Span: "autenticacao.validar_token"
│ │ └── Span: "redis.get" (cache de token)
│ └── Span: "pedidos.criar_pedido"
│ ├── Span: "pedidos.validar_estoque"
│ │ └── Span: "mysql.query" (consulta estoque)
│ └── Span: "pedidos.processar_pagamento"
│ └── Span: "http.post" (gateway de pagamento externo)
Relação causal é estabelecida via parent-child spans, onde o span pai contém o span_id referenciado pelo filho.
Propagação de baggage permite transportar atributos contextuais:
# Injetar baggage
from opentelemetry.baggage import set_baggage
set_baggage("user.id", "12345")
# Extrair em serviço downstream
from opentelemetry.baggage import get_baggage
user_id = get_baggage("user.id")
5. Configuração do Collector OpenTelemetry
O Collector atua como middleware de observabilidade, recebendo dados de múltiplas fontes e exportando para diversos backends.
Pipeline básico:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
send_batch_size: 1024
memory_limiter:
check_interval: 1s
limit_mib: 512
attributes:
actions:
- key: environment
value: production
action: upsert
exporters:
jaeger:
endpoint: jaeger:14250
tls:
insecure: true
prometheus:
endpoint: 0.0.0.0:8889
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch, attributes]
exporters: [jaeger, prometheus]
Filtros e amostragem no Collector:
processors:
tail_sampling:
decision_wait: 30s
policies:
- name: error-sampling
type: status_code
config:
status_codes: [ERROR]
- name: latency-sampling
type: latency
config:
threshold_ms: 500
6. Análise de Performance com Tracing
Visualização em waterfall chart (Jaeger UI):
Serviço A (15ms) ████████████████
├── Serviço B (8ms) ████████
│ └── DB (3ms) ███
└── Serviço C (5ms) █████
└── Cache (1ms) █
Identificação de gargalos:
- Caminho crítico: Soma das durações no caminho mais longo do trace.
- Latência por span: Spans com duração anormalmente alta indicam problemas.
- Taxa de erro: Spans com status code ERROR indicam falhas.
Métricas derivadas de traces:
# Exemplo de métricas via OpenTelemetry Metrics
from opentelemetry import metrics
meter = metrics.get_meter(__name__)
latency_histogram = meter.create_histogram(
"request.latency",
unit="ms",
description="Latência das requisições"
)
histogram.record(span.end_time - span.start_time, {
"service": "pedidos",
"operation": "criar_pedido"
})
7. Troubleshooting e Debugging com Traces
Correlação trace-log-métrica usando atributos comuns:
# Log estruturado com trace_id
import logging
from opentelemetry import trace
logger = logging.getLogger(__name__)
span = trace.get_current_span()
logger.info("Pedido processado", extra={
"trace_id": span.get_span_context().trace_id,
"span_id": span.get_span_context().span_id,
"order_id": "ORD-12345"
})
Exemplo prático: Debugging de timeout em cascata
Cenário: Requisição ao Serviço de Pedidos leva 10 segundos, causando timeout no API Gateway.
Trace ID: def456
API Gateway (span: "handler.pedidos") - duração: 5000ms (timeout!)
└── Serviço Pedidos (span: "criar_pedido") - duração: 3000ms
├── Serviço Estoque (span: "validar_estoque") - duração: 2500ms
│ └── DB MySQL (span: "query_estoque") - duração: 2000ms (gargalo!)
└── Serviço Pagamento (span: "processar_pagamento") - duração: 500ms
Análise: O span query_estoque com 2 segundos indica problema no banco de dados. Ao correlacionar com logs, descobrimos lock de tabela.
Identificação de dependências ocultas:
# Service Graph (Jaeger)
Serviço A → Serviço B → Serviço C → DB
↓
Serviço D (dependência não documentada!)
8. Boas Práticas e Padrões para Tracing Distribuído
Nomenclatura consistente:
# Padrão recomendado
Span name: "{serviço}.{recurso}.{operação}"
Exemplo: "pedidos.api.criar_pedido"
# Atributos padronizados
span.set_attribute("http.method", "POST")
span.set_attribute("http.status_code", 200)
span.set_attribute("db.system", "postgresql")
span.set_attribute("db.statement", "SELECT * FROM orders WHERE id = ?")
Gerenciamento de cardinalidade:
- Evite atributos com alta cardinalidade (ex: IDs de usuário individuais)
- Use baggage para dados contextuais, mas limite a 10-20 atributos por span
- Configure sampling adaptativo para reduzir volume em picos de tráfego
Privacidade e segurança:
# Sanitização de dados sensíveis no Collector
processors:
attributes:
actions:
- key: db.statement
action: hash
- key: http.request.body
action: delete
- key: user.email
action: redact
Padrões recomendados:
- Sempre propagar contexto, mesmo em cenários assíncronos (filas, eventos)
- Definir SLOs baseados em percentis de latência derivados de traces
- Utilizar tags de ambiente, versão e região para filtrar traces em troubleshooting
Referências
- OpenTelemetry Documentation - Traces — Documentação oficial sobre conceitos de tracing, spans e propagação de contexto no OpenTelemetry
- W3C Trace Context Specification — Especificação oficial do padrão W3C para propagação de contexto de tracing em HTTP
- Jaeger Documentation - Architecture — Guia completo da arquitetura do Jaeger, incluindo visualização de traces e service graphs
- Grafana Temo Documentation - Distributed Tracing — Documentação do Grafana Temo para armazenamento e consulta de traces em larga escala
- OpenTelemetry Collector - Configuration — Guia de configuração do Collector OpenTelemetry, incluindo pipelines, processadores e exporters
- CNCF - OpenTelemetry Overview — Visão geral do projeto OpenTelemetry pela Cloud Native Computing Foundation
- Honeycomb - Distributed Tracing Best Practices — Artigo técnico sobre boas práticas de tracing distribuído em produção