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
  • kubectl configurado 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

BackendSinaisArmazenamento
JaegerApenas tracesCassandra/Elasticsearch
PrometheusApenas métricasTSDB local
Grafana LokiApenas logsArmazenamento de objetos
Grafana TempoApenas tracesArmazenamento de objetos
SigNozTraces + Métricas + LogsClickHouse
PlataformaLock-inModelo de custo
OpenTelemetry + GrafanaNenhumGratuito (auto-hospedado)
DatadogAltoPor host + ingestão
New RelicMédioPor GB ingerido
DynatraceMuito altoPor unidade de host
Elastic APMMédioPor 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 startActiveSpan e createCounter/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

Artigos Relacionados