Docker & Kubernetes

Distributed Tracing em Kubernetes: Jaeger e OpenTelemetry Collector na Prática

13 min de leitura

Distributed Tracing em Kubernetes: Jaeger e OpenTelemetry Collector na Prática

O que é Distributed Tracing e por que você precisa disso em Kubernetes Distributed Tracing é uma técnica de observabilidade que permite rastrear uma requisição através de múltiplos serviços em um sistema distribuído. Diferentemente de logs tradicionais, que registram eventos isolados em cada serviço, o distributed tracing conecta essas informações em uma única "trace" que mostra todo o fluxo de execução, os tempos de latência em cada etapa e onde os gargalos acontecem. Em um ambiente Kubernetes com dezenas ou centenas de microserviços, essa visibilidade se torna crítica. Quando um usuário faz uma requisição em um sistema monolítico, o log é trivial: você vê a entrada, o processamento e a saída em um único lugar. Mas em Kubernetes, essa mesma requisição pode passar por um API Gateway, três serviços backend, um banco de dados, e um cache distribuído — cada um em um container diferente, possivelmente em nodes diferentes. Sem distributed tracing, você fica perdido tentando correlacionar logs de diversos

<h2>O que é Distributed Tracing e por que você precisa disso em Kubernetes</h2>

<p>Distributed Tracing é uma técnica de observabilidade que permite rastrear uma requisição através de múltiplos serviços em um sistema distribuído. Diferentemente de logs tradicionais, que registram eventos isolados em cada serviço, o distributed tracing conecta essas informações em uma única &quot;trace&quot; que mostra todo o fluxo de execução, os tempos de latência em cada etapa e onde os gargalos acontecem. Em um ambiente Kubernetes com dezenas ou centenas de microserviços, essa visibilidade se torna crítica.</p>

<p>Quando um usuário faz uma requisição em um sistema monolítico, o log é trivial: você vê a entrada, o processamento e a saída em um único lugar. Mas em Kubernetes, essa mesma requisição pode passar por um API Gateway, três serviços backend, um banco de dados, e um cache distribuído — cada um em um container diferente, possivelmente em nodes diferentes. Sem distributed tracing, você fica perdido tentando correlacionar logs de diversos serviços manualmente. Com distributed tracing, você tem um mapa visual completo, chamado de trace, que mostra exatamente onde a requisição foi, quanto tempo levou em cada passo, e onde ocorreram erros.</p>

<h2>Jaeger: A Solução de Tracing que você pode usar hoje</h2>

<h3>O que é Jaeger e como funciona</h3>

<p>Jaeger é um sistema open source de distributed tracing desenvolvido originalmente no Uber. Ele implementa o padrão OpenTracing (hoje evoluído para OpenTelemetry) e oferece coleta, armazenamento e visualização de traces. A arquitetura do Jaeger é composta por alguns componentes principais: o agent, que roda ao lado de cada aplicação e recebe spans; o collector, que agrega esses spans; e o backend de armazenamento, que pode ser Elasticsearch, Cassandra ou memória.</p>

<p>Um &quot;span&quot; é a unidade básica de um trace. Ele representa uma unidade de trabalho — pode ser uma chamada HTTP, uma query ao banco de dados, ou qualquer operação que você queira rastrear. Quando você inicia uma requisição, cria um span raiz (root span), e cada operação subsequente cria child spans. O trace é simplesmente uma coleção de todos os spans relacionados, identificados por um trace ID único que trafega junto com a requisição através de todos os serviços.</p>

<h3>Instalando Jaeger em Kubernetes com Helm</h3>

<p>A forma mais prática de instalar Jaeger em Kubernetes é usando Helm. Primeiro, adicione o repositório oficial:</p>

<pre><code class="language-bash">helm repo add jaegertracing https://jaegertracing.github.io/helm-charts

helm repo update</code></pre>

<p>Crie um arquivo <code>values.yaml</code> com uma configuração simples para desenvolvimento:</p>

<pre><code class="language-yaml"># jaeger-values.yaml

provisionDataStore:

cassandra: false

storage:

type: badger

badger:

ephemeral: false

directoryvolume:

size: 10Gi

agent:

enabled: true

service:

type: ClusterIP

port: 6831

protocols:

  • udp

collector:

enabled: true

service:

type: ClusterIP

port: 14268

query:

enabled: true

service:

type: ClusterIP

port: 16686</code></pre>

<p>Instale no seu cluster:</p>

<pre><code class="language-bash">helm install jaeger jaegertracing/jaeger -f jaeger-values.yaml -n observability --create-namespace</code></pre>

<p>Para acessar a UI do Jaeger, faça um port-forward:</p>

<pre><code class="language-bash">kubectl port-forward -n observability svc/jaeger-query 16686:16686</code></pre>

<p>Acesse em <code>http://localhost:16686</code> e você verá a interface de visualização de traces.</p>

<h2>OpenTelemetry Collector: O middleware inteligente para tracing</h2>

<h3>Por que você precisa do OpenTelemetry Collector</h3>

<p>OpenTelemetry é um conjunto de padrões abertos para instrumentação de código. O OpenTelemetry Collector é um daemon que roda em seu cluster Kubernetes, funciona como um middleware central que recebe sinais de telemetria (traces, metrics, logs) de suas aplicações, processa, enriquece, e envia para backends como Jaeger, Prometheus, ou Loki. Enquanto Jaeger é uma solução completa de tracing, OpenTelemetry Collector oferece flexibilidade para trabalhar com múltiplos backends e fazer transformações nos dados.</p>

<p>A principal vantagem é a separação de responsabilidades: suas aplicações não precisam saber onde os dados vão parar. Elas enviam para o Collector, que fica responsável por rotear, processar, samplear (reduzir volume), e enviar para os destinos finais. Isso facilita mudanças arquiteturais — você pode trocar o backend de tracing sem alterar o código das aplicações.</p>

<h3>Instalando OpenTelemetry Collector em Kubernetes</h3>

<p>Instale o Collector usando Helm:</p>

<pre><code class="language-bash">helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts

helm repo update</code></pre>

<p>Crie um arquivo <code>otel-collector-values.yaml</code>:</p>

<pre><code class="language-yaml"># otel-collector-values.yaml

mode: daemonset

config:

receivers:

otlp:

protocols:

grpc:

endpoint: 0.0.0.0:4317

http:

endpoint: 0.0.0.0:4318

jaeger:

protocols:

grpc:

endpoint: 0.0.0.0:14250

thrift_http:

endpoint: 0.0.0.0:14268

processors:

batch:

send_batch_size: 100

timeout: 10s

memory_limiter:

check_interval: 1s

limit_mib: 512

spike_limit_mib: 128

attributes:

actions:

  • key: deployment.name

value: myapp

action: insert

exporters:

jaeger:

endpoint: jaeger-collector:14250

tls:

insecure: true

service:

pipelines:

traces:

receivers: [otlp, jaeger]

processors: [memory_limiter, batch, attributes]

exporters: [jaeger]

service:

enabled: true

type: ClusterIP

serviceAccount:

create: true

name: otel-collector</code></pre>

<p>Instale:</p>

<pre><code class="language-bash">helm install otel-collector open-telemetry/opentelemetry-collector \

-f otel-collector-values.yaml \

-n observability</code></pre>

<h2>Instrumentando suas aplicações para enviar traces</h2>

<h3>Exemplo prático: Python com FastAPI e OpenTelemetry</h3>

<p>Vamos criar uma aplicação Python simples que envia traces para o OpenTelemetry Collector. Primeiro, instale as dependências:</p>

<pre><code class="language-bash">pip install fastapi uvicorn opentelemetry-api opentelemetry-sdk \

opentelemetry-exporter-otlp opentelemetry-instrumentation-fastapi \

opentelemetry-instrumentation-requests</code></pre>

<p>Crie um arquivo <code>app.py</code>:</p>

<pre><code class="language-python">from fastapi import FastAPI

from opentelemetry import trace

from opentelemetry.sdk.trace import TracerProvider

from opentelemetry.sdk.trace.export import BatchSpanProcessor

from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

from opentelemetry.instrumentation.requests import RequestsInstrumentor

import requests

Configurar o exporter OTLP (envia para o Collector)

otlp_exporter = OTLPSpanExporter(

endpoint=&quot;otel-collector.observability.svc.cluster.local:4317&quot;

)

Configurar o TracerProvider

trace_provider = TracerProvider()

trace_provider.add_span_processor(BatchSpanProcessor(otlp_exporter))

trace.set_tracer_provider(trace_provider)

Instrumentar FastAPI e requests automaticamente

app = FastAPI()

FastAPIInstrumentor.instrument_app(app)

RequestsInstrumentor().instrument()

Obter um tracer manualmente

tracer = trace.get_tracer(__name__)

@app.get(&quot;/api/users/{user_id}&quot;)

async def get_user(user_id: int):

&quot;&quot;&quot;

Este endpoint cria spans automaticamente graças ao

FastAPIInstrumentor, mas também adicionamos um span manual

&quot;&quot;&quot;

with tracer.start_as_current_span(&quot;fetch_user_from_db&quot;) as span:

span.set_attribute(&quot;user.id&quot;, user_id)

Simular chamada a banco de dados

with tracer.start_as_current_span(&quot;database_query&quot;):

Em uma app real, isso seria uma query ao banco

user = {&quot;id&quot;: user_id, &quot;name&quot;: f&quot;User {user_id}&quot;}

Simular chamada a outro serviço

with tracer.start_as_current_span(&quot;call_auth_service&quot;):

try:

response = requests.get(

&quot;http://auth-service:8080/validate&quot;,

params={&quot;user_id&quot;: user_id},

timeout=5

)

span.set_attribute(&quot;auth.status&quot;, response.status_code)

except Exception as e:

span.set_attribute(&quot;auth.error&quot;, str(e))

return user

if __name__ == &quot;__main__&quot;:

import uvicorn

uvicorn.run(app, host=&quot;0.0.0.0&quot;, port=8000)</code></pre>

<h3>Exemplo prático: Node.js com Express e OpenTelemetry</h3>

<p>Crie um aplicativo Express instrumentado:</p>

<pre><code class="language-bash">npm install express @opentelemetry/api @opentelemetry/sdk-node \

@opentelemetry/auto @opentelemetry/sdk-trace-node \

@opentelemetry/exporter-trace-otlp-grpc @opentelemetry/instrumentation-http \

@opentelemetry/instrumentation-express</code></pre>

<p>Crie um arquivo <code>tracing.js</code> (deve ser importado antes do app):</p>

<pre><code class="language-javascript">const { NodeSDK } = require(&quot;@opentelemetry/sdk-node&quot;);

const { getNodeAutoInstrumentations } = require(&quot;@opentelemetry/auto-instrumentations-node&quot;);

const { OTLPTraceExporter } = require(&quot;@opentelemetry/exporter-trace-otlp-grpc&quot;);

const { BatchSpanProcessor } = require(&quot;@opentelemetry/sdk-trace-node&quot;);

const traceExporter = new OTLPTraceExporter({

url: &quot;grpc://otel-collector.observability.svc.cluster.local:4317&quot;,

});

const sdk = new NodeSDK({

traceExporter,

instrumentations: [getNodeAutoInstrumentations()],

});

sdk.start();

console.log(&quot;Tracing initialized&quot;);

process.on(&quot;SIGTERM&quot;, () =&gt; {

sdk.shutdown()

.then(() =&gt; console.log(&quot;Tracing terminated&quot;))

.catch((log) =&gt; console.log(&quot;Error terminating tracing&quot;, log))

.finally(() =&gt; process.exit(0));

});</code></pre>

<p>Crie um arquivo <code>app.js</code>:</p>

<pre><code class="language-javascript">// IMPORTANTE: tracing.js deve ser importado PRIMEIRO

require(&quot;./tracing&quot;);

const express = require(&quot;express&quot;);

const { trace } = require(&quot;@opentelemetry/api&quot;);

const app = express();

const tracer = trace.getTracer(&quot;express-app&quot;);

app.get(&quot;/api/orders/:order_id&quot;, async (req, res) =&gt; {

const { order_id } = req.params;

// Criar um span manual

const span = tracer.startSpan(&quot;fetch_order&quot;);

try {

span.setAttributes({

&quot;order.id&quot;: order_id,

&quot;service.name&quot;: &quot;order-service&quot;,

});

// Simular operações

const orderData = await new Promise((resolve) =&gt; {

setTimeout(() =&gt; {

resolve({ id: order_id, total: 199.99 });

}, 100);

});

span.end();

res.json(orderData);

} catch (error) {

span.recordException(error);

span.end();

res.status(500).json({ error: error.message });

}

});

app.listen(3000, () =&gt; {

console.log(&quot;Server running on port 3000&quot;);

});</code></pre>

<h3>Deployment no Kubernetes</h3>

<p>Crie um arquivo <code>deployment.yaml</code> para sua aplicação Python:</p>

<pre><code class="language-yaml">apiVersion: apps/v1

kind: Deployment

metadata:

name: api-service

namespace: default

spec:

replicas: 2

selector:

matchLabels:

app: api-service

template:

metadata:

labels:

app: api-service

spec:

containers:

  • name: api-service

image: seu-registry/api-service:latest

ports:

  • containerPort: 8000

env:

  • name: OTEL_EXPORTER_OTLP_ENDPOINT

value: &quot;http://otel-collector.observability.svc.cluster.local:4318&quot;

  • name: OTEL_SERVICE_NAME

value: &quot;api-service&quot;

resources:

requests:

memory: &quot;128Mi&quot;

cpu: &quot;100m&quot;

limits:

memory: &quot;256Mi&quot;

cpu: &quot;500m&quot;</code></pre>

<p>Aplique no cluster:</p>

<pre><code class="language-bash">kubectl apply -f deployment.yaml</code></pre>

<h2>Visualizando e analisando traces</h2>

<p>Faça uma requisição para gerar um trace:</p>

<pre><code class="language-bash"># Forward para sua aplicação

kubectl port-forward svc/api-service 8000:8000

Em outro terminal, faça uma requisição

curl http://localhost:8000/api/users/123</code></pre>

<p>Acesse a UI do Jaeger em <code>http://localhost:16686</code>. Você verá:</p>

<ul>

<li>Uma lista de serviços descobertos automaticamente</li>

<li>Traces com latência total e breakdown por serviço</li>

<li>Spans individuais com atributos customizados</li>

<li>Informações de erro e stack traces</li>

<li>Visualização em cascata mostrando paralelismo</li>

</ul>

<p>Na UI, procure pelo serviço &quot;api-service&quot; e selecione uma operação. Clique em um trace para ver:</p>

<pre><code>Trace ID: a1b2c3d4e5f6g7h8

Service: api-service

Operation: GET /api/users/{user_id}

Duration: 245ms

Spans:

├─ GET /api/users/{user_id} [200ms]

│ ├─ fetch_user_from_db [120ms]

│ │ ├─ database_query [80ms]

│ │ └─ call_auth_service [40ms]

│ └─ serialize_response [5ms]</code></pre>

<h2>Conclusão</h2>

<p>Os três pontos fundamentais que você deve levar consigo: primeiro, <strong>distributed tracing é observabilidade de verdade</strong> — não é mais opcional em arquiteturas de microserviços em Kubernetes. Logs isolados em cada serviço deixam você cego. Com Jaeger ou similares, você vê o fluxo completo da requisição. Segundo, <strong>OpenTelemetry é o padrão de facto</strong> — enquanto Jaeger é a solução de backend, OpenTelemetry é como você instrumenta seu código. É agnóstico de vendor, o que significa que você não fica preso. Terceiro, <strong>a instrumentação não é complexa</strong> — com bibliotecas modernas e auto-instrumentação, você ganha tracing de graça em muitos casos, e adicionar spans customizados é trivial. Comece pequeno com uma aplicação, veja os traces sendo coletados, e expanda gradualmente.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.jaegertracing.io/docs/" target="_blank" rel="noopener noreferrer">Documentação oficial do Jaeger</a></li>

<li><a href="https://opentelemetry.io/docs/instrumentation/" target="_blank" rel="noopener noreferrer">OpenTelemetry - Guia de Getting Started</a></li>

<li><a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-fastapi" target="_blank" rel="noopener noreferrer">OpenTelemetry Python - FastAPI Integration</a></li>

<li><a href="https://github.com/open-telemetry/opentelemetry-helm-charts" target="_blank" rel="noopener noreferrer">Helm Charts - OpenTelemetry Collector</a></li>

<li><a href="https://www.oreilly.com/library/view/distributed-systems-observability/9781492033431/" target="_blank" rel="noopener noreferrer">Distributed Systems Observability - O&#039;Reilly (Yuri Shkuro)</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

Guia Completo de Probes em Kubernetes: Liveness, Readiness e Startup na Prática
Guia Completo de Probes em Kubernetes: Liveness, Readiness e Startup na Prática

Introdução aos Probes no Kubernetes Quando você deploya uma aplicação no Kube...

Guia Completo de Grafana em Kubernetes: Dashboards, Alertas e Loki para Logs
Guia Completo de Grafana em Kubernetes: Dashboards, Alertas e Loki para Logs

Introdução ao Grafana em Kubernetes Grafana é uma plataforma de visualização...

Guia Completo de Gerenciamento de Secrets em Kubernetes: Vault Agent e External Secrets
Guia Completo de Gerenciamento de Secrets em Kubernetes: Vault Agent e External Secrets

O Problema: Por Que Gerenciar Secrets em Kubernetes? Quando você trabalha com...