Tracing distribuído no Kubernetes com Jaeger ou Tempo

1. Fundamentos do Tracing Distribuído em Ambientes Cloud-Native

Em arquiteturas de microsserviços executadas no Kubernetes, a observabilidade tradicional baseada apenas em métricas e logs mostra-se insuficiente. Uma requisição que atravessa dezenas de serviços pode falhar em qualquer ponto, e sem um mapa claro do fluxo, o debug se torna um exercício de adivinhação.

O tracing distribuído resolve esse problema introduzindo três conceitos fundamentais:

  • Span: unidade de trabalho individual dentro de um serviço (ex: consulta ao banco, chamada HTTP)
  • Trace: conjunto de spans que representam o caminho completo de uma requisição
  • Context Propagation: mecanismo que transporta o identificador do trace entre serviços via headers HTTP/gRPC

O OpenTelemetry tornou-se o padrão da indústria para instrumentação, oferecendo SDKs unificados e um formato de exportação chamado OTLP (OpenTelemetry Protocol). Tanto Jaeger quanto Tempo aceitam OTLP nativamente, permitindo que você troque o backend sem alterar o código da aplicação.

2. Arquitetura e Componentes do Jaeger no Kubernetes

O Jaeger é o sistema mais maduro de tracing distribuído, com componentes bem definidos:

Jaeger Collector: recebe spans, valida e armazena. Pode escalar horizontalmente.
Jaeger Query Service: API e UI para consultar traces armazenados.
Jaeger Agent: sidecar ou DaemonSet que faz buffering e roteamento de spans para o Collector.

Para armazenamento, as opções incluem Elasticsearch (recomendado para produção), Cassandra ou Badger (apenas all-in-one). A instalação no Kubernetes é simplificada pelo Jaeger Operator:

kubectl create namespace observability
kubectl apply -f https://github.com/jaegertracing/jaeger-operator/releases/latest/download/jaeger-operator.yaml -n observability

Após o operator estar pronto, crie uma instância Jaeger:

apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: simplest
  namespace: observability
spec:
  strategy: production
  storage:
    type: elasticsearch
    options:
      es:
        server-urls: http://elasticsearch:9200

O operator gerencia automaticamente deployments, services e configurações de rede.

3. Arquitetura e Componentes do Tempo no Kubernetes

Tempo é a alternativa da Grafana Labs, projetada para ser distribuída, escalável e de baixo custo. Seu diferencial é usar object storage (S3, GCS, Azure Blob) em vez de bancos relacionais ou de busca.

Componentes principais:

  • Distributor: recebe spans via OTLP e os replica para os Ingesters
  • Ingester: mantém spans em memória por blocos de tempo e faz flush para o storage
  • Querier: busca traces no storage e nos Ingesters ativos
  • Compactor: mescla blocos pequenos em blocos maiores para eficiência

A instalação via Helm é direta:

helm repo add grafana https://grafana.github.io/helm-charts
helm upgrade --install tempo grafana/tempo -n observability --create-namespace \
  --set tempo.storage.trace.backend=s3 \
  --set tempo.storage.trace.s3.bucket=tempo-traces \
  --set tempo.storage.trace.s3.endpoint=minio:9000

4. Instrumentação de Aplicações com OpenTelemetry

A instrumentação pode ser feita manualmente (SDK) ou via auto-instrumentação (pacotes que interceptam chamadas HTTP, gRPC e banco de dados).

Exemplo em Python com auto-instrumentação:

pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install

Configure o exportador via variáveis de ambiente:

OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo-distributor:4318
OTEL_SERVICE_NAME=meu-servico-python
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1

Para propagação manual em Go:

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/propagation"
)

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
    tracer := otel.Tracer("meu-servico")
    ctx, span := tracer.Start(ctx, "processar-requisicao")
    defer span.End()
    // lógica do serviço
}

5. Deploy e Configuração no Cluster Kubernetes

Para Jaeger com Helm:

helm repo add jaegertracing https://jaegertracing.github.io/helm-charts
helm install jaeger jaegertracing/jaeger -n observability \
  --set collector.service.otlp.enabled=true \
  --set storage.elasticsearch.host=elasticsearch

Para Tempo com Grafana Stack (inclui Grafana, Loki e Prometheus):

helm upgrade --install loki-stack grafana/loki-stack -n observability \
  --set grafana.enabled=true \
  --set tempo.enabled=true

Configure network policies para permitir tráfego OTLP:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-otlp
  namespace: observability
spec:
  podSelector:
    matchLabels:
      app: tempo-distributor
  ingress:
    - ports:
        - port: 4318

6. Sampling, Performance e Boas Práticas

O sampling é crucial para controlar custos e desempenho:

  • Head-based: decide no início do trace se ele será amostrado (ex: 10% de todas as requisições)
  • Tail-based: amostra após o trace completo, permitindo capturar erros raros
  • Rate-limiting: limita o número de spans por segundo por pod

Configuração de sampling no Tempo:

OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.05  # 5% das requisições

Para evitar sobrecarga de storage, defina retenção apropriada. No Tempo, configure blocos e retenção:

tempo:
  retention: 336h  # 14 dias
  storage:
    trace:
      block:
        bloom_filter_false_positive: 0.05
        encoding: zstd

7. Debugging e Análise de Problemas com Traces

Com traces em mãos, é possível identificar gargalos. Na Jaeger UI, busque por http.status_code=500 ou error=true para encontrar falhas. Use tags como db.statement para ver queries lentas.

No Grafana Explore com Tempo:

{resource.service.name="api-gateway"} | latency > 500ms

Exemplo de análise: um trace mostra que o serviço pedidos leva 2s para responder. Dentro do trace, um span consulta-estoque ocupa 1.8s. Isso indica um problema no banco de dados de estoque ou na rede entre serviços.

8. Comparação Final: Jaeger vs. Tempo no Ecossistema DevOps

Característica Jaeger Tempo
Armazenamento Elasticsearch, Cassandra Object storage (S3, GCS)
Maturidade Muito maduro (2015+) Crescendo rápido
Integração Grafana Via plugin Nativo
Custo de storage Alto (ES indexa tudo) Baixo (object storage)
Escalabilidade Boa Excelente
Complexidade operacional Média (gerenciar ES) Baixa

Escolha Jaeger se você já tem Elasticsearch na stack, precisa de uma UI rica e madura, ou trabalha com times que já conhecem a ferramenta.

Escolha Tempo se prioriza baixo custo de armazenamento, escalabilidade massiva, ou já usa Grafana como ferramenta central de observabilidade.

Ambos integram bem com Prometheus (métricas) e Loki (logs), formando a tríade de observabilidade cloud-native.

Referências