TL;DR — Resumo Rápido
OpenTelemetry unifica traces, métricas e logs num padrão neutro. Domine o OTel Collector, auto-instrumentação e backends Grafana para observabilidade unificada.
OpenTelemetry (OTel) é o padrão CNCF de código aberto que unifica a coleta de traces, métricas e logs sob uma API, SDK e protocolo de rede neutros em relação ao fornecedor. Antes do OTel, cada fornecedor de observabilidade — Datadog, New Relic, Dynatrace, Elastic — exigia seu próprio agente proprietário, forçando as equipes a instrumentar o código várias vezes. O OTel elimina essa dependência: instrumente uma vez, envie para qualquer backend. Este guia cobre a arquitetura completa do OTel, configuração do Collector, instrumentação automática e manual, rastreamento distribuído, padrões de implantação no Kubernetes e um exemplo prático de microsserviços Node.js + Python com Grafana.
Pré-requisitos
- Familiaridade com Docker e Kubernetes básico (para as seções de Kubernetes)
- Uma aplicação alvo em Node.js, Python, Java, .NET ou Go
- Docker Compose ou um cluster Kubernetes para o exemplo prático
- Compreensão básica do que são traces, métricas e logs conceitualmente
kubectlconfigurado se usar as seções do operador Kubernetes
Arquitetura do OpenTelemetry
O OTel é organizado em três camadas claramente separadas:
API — Interfaces específicas da linguagem (ex.: tracer.startSpan()). Isso é o que o código da sua aplicação chama. A API sozinha não faz nada; ela precisa de uma implementação SDK injetada em tempo de execução.
SDK — A implementação da API. O SDK lida com decisões de amostragem, detecção de recursos (hostname, nome do pod k8s, região cloud) e exportação de telemetria para um backend via OTLP.
Collector — Um binário independente (ou contêiner Docker) que age como pipeline de telemetria: recebe das apps → processa → exporta para backends.
OTLP (OpenTelemetry Line Protocol) é o formato de rede canônico: protobuf sobre gRPC (porta 4317) ou HTTP/JSON (porta 4318).
O OpenTelemetry Collector
O Collector é o componente mais poderoso em uma implantação OTel de produção, funcionando como um pipeline com três estágios:
Receivers
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
prometheus:
config:
scrape_configs:
- job_name: "minha-app"
static_configs:
- targets: ["app:8080"]
filelog:
include: ["/var/log/app/*.log"]
operators:
- type: json_parser
hostmetrics:
collection_interval: 30s
scrapers:
cpu: {}
memory: {}
disk: {}
network: {}
Processors
processors:
memory_limiter:
check_interval: 1s
limit_mib: 400
batch:
send_batch_size: 10000
timeout: 10s
attributes:
actions:
- key: environment
value: production
action: insert
filter:
traces:
span:
- 'attributes["http.target"] == "/health"'
tail_sampling:
decision_wait: 10s
policies:
- name: politica-erros
type: status_code
status_code: {status_codes: [ERROR]}
- name: politica-traces-lentos
type: latency
latency: {threshold_ms: 1000}
- name: politica-probabilistica
type: probabilistic
probabilistic: {sampling_percentage: 10}
O tail_sampling executa no Collector e toma decisões depois de ver o trace completo, capturando todos os erros enquanto amostra apenas uma fração dos traces saudáveis.
Exporters e Pipelines
exporters:
otlp/tempo:
endpoint: "tempo:4317"
tls:
insecure: true
prometheus:
endpoint: "0.0.0.0:9464"
loki:
endpoint: "http://loki:3100/loki/api/v1/push"
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch, tail_sampling]
exporters: [otlp/tempo]
metrics:
receivers: [otlp, prometheus, hostmetrics]
processors: [memory_limiter, batch, attributes]
exporters: [prometheus]
logs:
receivers: [otlp, filelog]
processors: [memory_limiter, batch]
exporters: [loki]
Auto-Instrumentação
A auto-instrumentação adiciona OTel sem modificar o código da aplicação, interceptando chamadas HTTP, de banco de dados e de mensageria automaticamente.
Java — Execute o agente Java do OTel como flag da JVM:
java -javaagent:/otel-javaagent.jar \
-Dotel.service.name=meu-servico \
-Dotel.exporter.otlp.endpoint=http://collector:4317 \
-jar minha-app.jar
Python — Use opentelemetry-instrument:
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap --action=install
opentelemetry-instrument \
--service_name meu-servico \
--exporter_otlp_endpoint http://collector:4317 \
python app.py
Node.js — Importe o SDK antes de qualquer código da aplicação:
// tracing.js — deve ser carregado primeiro via --require
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const sdk = new NodeSDK({
serviceName: 'meu-servico-node',
traceExporter: new OTLPTraceExporter({ url: 'http://collector:4317' }),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
Instrumentação Manual
Criando Spans
const opentelemetry = require('@opentelemetry/api');
const tracer = opentelemetry.trace.getTracer('meu-servico', '1.0.0');
async function processarPedido(pedidoId) {
return tracer.startActiveSpan('processarPedido', async (span) => {
try {
span.setAttribute('pedido.id', pedidoId);
const resultado = await cobrarPagamento(pedidoId);
span.addEvent('pagamento.cobrado', { 'pagamento.valor': resultado.valor });
return resultado;
} catch (err) {
span.recordException(err);
span.setStatus({ code: opentelemetry.SpanStatusCode.ERROR });
throw err;
} finally {
span.end();
}
});
}
Criando Métricas
const meter = opentelemetry.metrics.getMeter('meu-servico', '1.0.0');
const contadorRequisicoes = meter.createCounter('http.server.requests');
const histogramaLatencia = meter.createHistogram('http.server.duration', { unit: 'ms' });
const conexoesAtivas = meter.createUpDownCounter('db.connections.active');
meter.createObservableGauge('process.memory.usage').addCallback((result) => {
result.observe(process.memoryUsage().heapUsed);
});
Rastreamento Distribuído
Um trace é um grafo acíclico dirigido de spans representando uma requisição de ponta a ponta. Cada span tem: TraceId, SpanId, ParentSpanId, SpanKind, atributos, eventos e status.
Amostragem baseada em cabeça — decide no span raiz, simples mas não pode preferir traces lentos ou com erro. Amostragem baseada em cauda — coleta o trace completo primeiro e decide com base no resultado, capturando 100% dos erros e apenas 10% do tráfego saudável.
Os exemplars vinculam observações do histograma do Prometheus a TraceIds do Tempo, habilitando navegação com um clique de um pico de latência P99 diretamente para um trace representativo.
Kubernetes com o OTel Operator
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: otel-instrumentacao
spec:
exporter:
endpoint: http://otel-agente:4317
propagators: [tracecontext, baggage]
sampler:
type: parentbased_traceidratio
argument: "0.1"
nodejs:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest
python:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
Anote um Deployment para injetar o agente Node.js automaticamente:
annotations:
instrumentation.opentelemetry.io/inject-nodejs: "true"
Backends e Comparação
| Backend | Sinais | Armazenamento |
|---|---|---|
| Jaeger | Apenas traces | Cassandra/Elasticsearch |
| Prometheus | Apenas métricas | TSDB local |
| Grafana Loki | Apenas logs | Armazenamento de objetos |
| Grafana Tempo | Apenas traces | Armazenamento de objetos |
| SigNoz | Traces + Métricas + Logs | ClickHouse |
| Plataforma | Lock-in | Modelo de custo |
|---|---|---|
| OpenTelemetry + Grafana | Nenhum | Gratuito (auto-hospedado) |
| Datadog | Alto | Por host + ingestão |
| New Relic | Médio | Por GB ingerido |
| Dynatrace | Muito alto | Por unidade de host |
| Elastic APM | Médio | Por GB |
Exemplo Prático: Node.js + Python com Grafana Stack
Uma configuração com dois serviços: um gateway API em Node.js que chama um serviço de pedidos em Python. Toda telemetria flui por um único Collector para Tempo, Prometheus e Loki, visualizados no Grafana.
services:
collector:
image: otel/opentelemetry-collector-contrib:latest
volumes:
- ./collector-config.yaml:/etc/otel/config.yaml
ports: ["4317:4317", "4318:4318", "9464:9464"]
node-api:
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317
- OTEL_SERVICE_NAME=node-api
python-pedidos:
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317
- OTEL_SERVICE_NAME=python-pedidos
No Grafana: abra Explore → Tempo → pesquise por service.name=node-api → clique em um trace → use o link de exemplar para ver a métrica P95 do Prometheus correlacionada → clique no link do Loki para ver a linha de log exata dessa requisição.
Armadilhas e Casos Extremos
Desvio de relógio — Traces distribuídos dependem de relógios sincronizados. Desvio NTP maior que 1ms causa problemas de ordenação de spans. Use chrony ou sincronização de tempo nativa da nuvem.
Tail sampling exige roteamento consistente por TraceId — Se você executar múltiplas réplicas do Collector gateway com tail_sampling, todos os spans do mesmo trace devem chegar à mesma instância. Use o exporter loadbalancing com routing_key: traceId.
Explosão de cardinalidade — Nunca use valores de alta cardinalidade (IDs de usuário, IDs de pedido) como valores de label de métricas. Atributos OTel em spans são seguros em qualquer cardinalidade; dimensões de métricas não são.
Resumo
- OpenTelemetry fornece uma API, SDK e protocolo de rede (OTLP) únicos e neutros para traces, métricas e logs
- O OTel Collector desacopla aplicações de backends; use agentes (DaemonSet) alimentando um gateway (Deployment) com tail sampling em produção
- Auto-instrumentação adiciona traces sem alterações no código; instrumentação manual adiciona contexto de negócio com
startActiveSpanecreateCounter/createHistogram - Exemplars vinculam observações de métricas do Prometheus a TraceIds do Tempo para drill-down com um clique
- O Grafana Stack (Tempo + Prometheus + Loki + Grafana) é a combinação de backends OTel open-source mais popular