Por qué elegir OpenTelemetry frente a un agente propietario
OpenTelemetry es un proyecto graduado de CNCF que unifica los tres pilares de la observabilidad — trazas, métricas y logs — bajo un estándar neutral al proveedor. A diferencia de los agentes Datadog o New Relic que te encierran en un contrato y formato propietario, el OTel Collector recibe, transforma y exporta la telemetría a cualquier backend compatible. Puedes cambiar de almacenamiento sin tocar una sola línea de tu código.
Los SDKs cubren Node.js, Python, Go, Java, PHP, Ruby y la mayoría de los lenguajes modernos. Los propagadores de contexto W3C TraceContext y B3 están incluidos: transmiten automáticamente el trace-id entre tus microservicios vía headers HTTP, sin configuración adicional.
Lo que te da el stack OTel + Grafana Tempo
- Trazado distribuido sin índice externo — Grafana Tempo v2.x almacena las trazas en bloques comprimidos (parquet) en disco local o almacenamiento de objetos, sin Cassandra ni Elasticsearch.
- Consultas TraceQL — el lenguaje nativo de Tempo permite filtrar trazas por duración, servicio, estado de error o atributos personalizados en segundos.
- Compatibilidad multi-protocolo — Tempo acepta OTLP, Zipkin y Jaeger: tus aplicaciones existentes se conectan sin reinstrumentación completa.
- Stack ligero en VPS — 2 GB de RAM son suficientes para un entorno de dev o staging; en producción bajo carga, 4 GB proporcionan margen cómodo.
- Coste controlado — un VPS autohospedado desde 99 DH/mes reemplaza una suscripción a Datadog APM facturada a unos 23 $ por host y mes.
- Correlación trazas–logs–métricas — vinculando Tempo con Loki y Prometheus en Grafana, saltas de una traza a sus logs y métricas en un solo clic.
- Sin vendor lock-in — si migras a Jaeger mañana, reconfiguras un exportador en el Collector; tus SDKs y código no cambian.
Requisitos previos del VPS
Tu VPS necesita al menos 2 GB de RAM (4 GB recomendados en producción) y acceso root o sudo. Docker Engine ≥ 24 y Docker Compose v2 deben estar instalados.
Planifica un subdominio dedicado — por ejemplo grafana.tu-dominio.com — para exponer la interfaz Grafana detrás de un proxy inverso TLS. Los puertos 4317 (OTLP/gRPC) y 4318 (OTLP/HTTP) deben estar abiertos en tu firewall en el lado privado para recibir telemetría desde tus aplicaciones; no los expongas en la interfaz pública. Los puertos 3200 (Tempo) y 3000 (Grafana) permanecen internos a la red Docker.
Planifica al menos 10 GB libres para los bloques de trazas. El montaje por defecto en /tmp/tempo no sobrevive a los reinicios: reemplázalo con un volumen Docker o un disco dedicado antes de ir a producción.
Arquitectura del stack: del span a la visualización
El flujo de datos sigue cuatro pasos:
1. Tu aplicación instrumenta sus peticiones con un SDK OTel y exporta spans al OTel Collector (OTLP gRPC puerto 4317 o HTTP puerto 4318).
2. El OTel Collector recibe, agrupa en lotes y exporta a Tempo vía OTLP gRPC interno. Puede enviar simultáneamente métricas a Prometheus y logs a Loki.
3. Grafana Tempo almacena bloques de trazas comprimidos en disco (o S3). Expone una API TraceQL en el puerto 3200.
4. Grafana consulta Tempo con TraceQL, muestra flamegraphs, grafos de servicios y span metrics.
TraceQL es el lenguaje de consulta nativo de Tempo: {service.name="api" && status=error && duration>500ms}.
Span Metrics es un pipeline de Tempo que deriva automáticamente métricas RED de las trazas entrantes, sin añadir un solo contador a tu código.
Desplegar el stack OTel + Tempo en siete pasos
Crear la estructura de directorios
Organiza una carpeta
otel-stack/en tu VPS:mkdir -p otel-stack/config cd otel-stackColocarás aquí tres archivos de configuración:
tempo.yaml,otel-collector-config.yamlydocker-compose.yml.Escribir la configuración de Tempo (tempo.yaml)
Crea
config/tempo.yamlcon backend de almacenamiento local y soporte TraceQL:server: http_listen_port: 3200 distributor: receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 ingester: max_block_bytes: 1_000_000 max_block_duration: 5m compactor: compaction: block_retention: 168h # 7 días storage: trace: backend: local wal: path: /tmp/tempo/wal local: path: /tmp/tempo/blocksEscribir la configuración del OTel Collector
Crea
config/otel-collector-config.yaml:receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: batch: timeout: 5s send_batch_size: 1000 exporters: otlp: endpoint: tempo:4317 tls: insecure: true service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [otlp]Escribir el docker-compose.yml
Crea
docker-compose.ymlen la raíz deotel-stack/:services: otel-collector: image: otel/opentelemetry-collector-contrib:latest volumes: - ./config/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml ports: - "4317:4317" - "4318:4318" networks: - observability depends_on: - tempo tempo: image: grafana/tempo:latest command: ["-config.file=/etc/tempo.yaml"] volumes: - ./config/tempo.yaml:/etc/tempo.yaml - tempo-data:/tmp/tempo ports: - "3200:3200" networks: - observability grafana: image: grafana/grafana:latest ports: - "3000:3000" environment: - GF_AUTH_ANONYMOUS_ENABLED=true - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin networks: - observability depends_on: - tempo volumes: tempo-data: networks: observability:Inicia el stack:
docker compose up -dy verifica:docker compose ps.Configurar Grafana Tempo como fuente de datos
Abre Grafana en
http://tu-vps:3000. Ve a Connections → Data sources → Add data source y selecciona Tempo. Establece la URL ahttp://tempo:3200. Activa Service graph y Span metrics. Si tu stack incluye Loki, vincula ambas fuentes de datos para navegar de un span a sus logs en un clic.Enviar tu primera traza de prueba
Prueba que el stack recibe trazas con
telemetrygen:docker run --rm --network otel-stack_observability \ ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest \ traces \ --otlp-endpoint otel-collector:4317 \ --otlp-insecure \ --duration 5s \ --rate 10En Grafana, abre Explore, selecciona la fuente de datos Tempo y ejecuta la consulta
{}para ver todas las trazas recibidas.Consultar tus trazas con TraceQL
En el panel Explore de Grafana (fuente de datos Tempo), introduce una consulta TraceQL:
-
{service.name="mi-api"}— todas las trazas del servicio
-{status=error}— todas las trazas con error
-{duration>1s && service.name="mi-api"}— trazas lentas
-{span.http.route="/api/orders" && status=error}— errores en una ruta concretaHaz clic en una traza para abrir el flamegraph detallado.
Configurar la retención y el almacenamiento de trazas
Tempo ofrece dos opciones principales de almacenamiento.
Almacenamiento local en volumen Docker: recomendado para un VPS standalone. Ajusta block_retention en tempo.yaml: 72 h para staging, 168 a 720 h para producción. Cada hora de trazas comprimidas ocupa aproximadamente 100–300 MB.
Almacenamiento de objetos compatible con S3: para un stack multi-nodo o almacenamiento ilimitado. Tempo es compatible con AWS S3, MinIO, Scaleway Object Storage y cualquier backend con API S3. La WAL siempre permanece en disco local para garantizar la durabilidad de las trazas en vuelo.
El compactor de Tempo fusiona bloques pequeños y aplica la política de retención automáticamente cada 5 minutos.
Instrumentar una aplicación Node.js
La auto-instrumentación OTel para Node.js captura automáticamente llamadas HTTP salientes, peticiones Express/Fastify, consultas a bases de datos y más, sin modificar tu lógica de negocio.
Instala los paquetes:
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-grpcCrea tracing.js:
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({
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'grpc://localhost:4317',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();Inicia tu aplicación:
export OTEL_SERVICE_NAME=mi-api
export OTEL_EXPORTER_OTLP_ENDPOINT=http://tu-vps:4317
node --require ./tracing.js app.jsInstrumentar una aplicación Python
Instala y bootstrap:
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a installInicia tu aplicación:
export OTEL_SERVICE_NAME=mi-servicio-python
export OTEL_EXPORTER_OTLP_ENDPOINT=http://tu-vps:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
opentelemetry-instrument python app.pyDjango, Flask, FastAPI, SQLAlchemy y los clientes HTTP se instrumentan automáticamente. Para instrumentación manual:
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("procesar-pedido") as span:
span.set_attribute("pedido.id", pedido_id)OTel + Tempo vs Jaeger vs Zipkin vs Datadog APM
Desplace la tabla
| Criterio | OTel + Grafana Tempo | Jaeger | Datadog APM |
|---|---|---|---|
| Coste mensual | Solo alojamiento (99 DH/mes VPS) | Solo alojamiento (similar) | ~23 $ por host al mes |
| Backend de almacenamiento | Local, S3, GCS, Azure Blob | Cassandra, Elasticsearch, Badger | SaaS gestionado (opaco) |
| Lenguaje de consulta | TraceQL (nativo, potente) | Jaeger Query UI (básico) | Lenguaje propietario Datadog |
| Auto-instrumentación | SDK OTel universal | Librerías Jaeger o OTel | Agente propietario Datadog |
| Correlación trazas–logs | Sí (Loki + Grafana) | Limitada | Sí (integrado, de pago) |
| Span Metrics (RED) | Sí (pipeline nativo Tempo) | No nativo | Sí (calculado por Datadog) |
| Vendor lock-in | Ninguno (estándar CNCF) | Bajo (compatible OTel) | Alto (agente y formato propietarios) |
| Complejidad de instalación | Media (3 contenedores) | Baja a media | Baja (agente automático) |
Alertas y SLOs en Grafana
Grafana 10+ permite definir alertas directamente sobre métricas derivadas de trazas. Las Span Metrics de Tempo generan automáticamente tres métricas por servicio: tasa de peticiones, histograma de latencia y contador de errores.
Para crear un SLO sobre la tasa de error de tu API, ve a Alerting → Alert rules → New alert rule en Grafana, elige la fuente de datos Prometheus e introduce una consulta que calcule la proporción de errores. Define el umbral (por ejemplo > 0.05 para una tasa de error superior al 5%) y la ventana de evaluación. Configura un punto de contacto: Alertmanager, PagerDuty, Slack o webhook. Las alertas basadas en trazas son mucho más precisas que las alertas sobre métricas sintéticas.
Combinar trazas, logs y métricas en un solo Collector
El OTel Collector no se limita a las trazas. Añadiendo un filelog receiver recoges los logs de tus contenedores Docker y los envías a Loki. Un prometheus receiver hace scraping de tus endpoints /metrics y los exporta a Prometheus. Un solo proceso gestiona toda la telemetría de tu VPS.
En Grafana, vincula Tempo y Loki mediante derived fields: cuando un log contiene un trace_id, un enlace clicable te lleva directamente al flamegraph correspondiente. Esta navegación cruzada reduce considerablemente el tiempo de diagnóstico.
Resolver problemas comunes
No llegan spans a Tempo. Comprueba primero que el OTel Collector es accesible desde tu aplicación (telnet tu-vps 4317). Revisa los logs del Collector: un error connection refused en tempo:4317 indica que Tempo no ha arrancado o su puerto interno no está expuesto en la red Docker.
Conexión rechazada desde la aplicación al Collector. En UFW o firewalld, permite los puertos 4317 y 4318 solo para las IPs de tus aplicaciones. En producción, aplicaciones y Collector deben estar en la misma red privada.
Trazas truncadas o spans faltantes. El batch processor puede tardar hasta 5 s en enviar. Para desarrollo, reduce timeout: 1s. Si los spans faltan sistemáticamente, verifica que el propagador de contexto W3CTraceContextPropagator está activo en tu SDK.
Tempo consume demasiado espacio en disco. Reduce block_retention y reinicia Tempo. El compactor aplicará la nueva política en el siguiente ciclo.
Grafana no encuentra trazas desde los logs de Loki. Verifica que tus aplicaciones inyectan trace_id en sus logs y que el campo Derived fields en la fuente de datos Loki apunta al patrón correcto.