13. SPM con Prometheus
13. SPM con Prometheus
Service Performance Monitoring (SPM) es la vista “Monitor” en la UI de Jaeger: muestra RED metrics (Rate, Errors, Duration) por servicio y operación, derivadas de las trazas.
flowchart LR
APP[Apps] -->|trazas OTLP| OC[OTel Collector\nspanmetrics processor]
OC -->|trazas| J[Jaeger Collector]
OC -->|métricas\nderivadas| P[(Prometheus)]
JQ[Jaeger Query] -.lee métricas.-> P
UI[UI - Monitor tab] --> JQ
La idea: cada span genera, además de los datos de la traza, métricas agregadas (counters de requests, histogramas de latencia). Esas métricas van a Prometheus. La UI de Jaeger las lee y las muestra.
Por qué importa
Las trazas son útiles para el detalle pero no para el agregado rápido. Si querés saber “¿cuál es la latencia p99 del endpoint POST /orders en la última hora?”, no querés iterar trazas — querés métricas pre-agregadas.
SPM cierra esa brecha sin que tengas que instrumentar métricas a mano: las saca de los spans que ya estás generando.
Componentes
| Pieza | Función |
|---|---|
OTel Collector con spanmetrics processor | Convierte spans en métricas RED |
| Prometheus | Almacena las métricas |
Jaeger Query con METRICS_STORAGE_TYPE=prometheus | Lee Prometheus y sirve la UI |
En Jaeger v2 esto se simplifica: el spanmetrics processor está incluido en el binario, ya no hace falta un OTel Collector separado para esto.
Setup con Jaeger v1 + OTel Collector externo
docker-compose
services:
prometheus:
image: prom/prometheus:v2.55.0
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
command:
- --config.file=/etc/prometheus/prometheus.yml
- --enable-feature=exemplar-storage
ports:
- "9090:9090"
otel-collector:
image: otel/opentelemetry-collector-contrib:0.110.0
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
command: ["--config=/etc/otel-collector-config.yaml"]
ports:
- "4317:4317"
- "4318:4318"
- "8889:8889"
jaeger:
image: jaegertracing/all-in-one:1.62
environment:
COLLECTOR_OTLP_ENABLED: "true"
METRICS_STORAGE_TYPE: prometheus
PROMETHEUS_SERVER_URL: "http://prometheus:9090"
PROMETHEUS_QUERY_NORMALIZE_CALLS: "true"
PROMETHEUS_QUERY_NORMALIZE_DURATION: "true"
ports:
- "16686:16686"
- "14250:14250"
otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
spanmetrics:
namespace: traces.spanmetrics
metrics_flush_interval: 30s
histogram:
explicit:
buckets: [10ms, 50ms, 100ms, 250ms, 500ms, 1s, 2s, 5s, 10s]
dimensions:
- name: http.method
- name: http.status_code
- name: deployment.environment
exporters:
otlp/jaeger:
endpoint: jaeger:14250
tls: { insecure: true }
prometheus:
endpoint: "0.0.0.0:8889"
namespace: ""
service:
pipelines:
traces:
receivers: [otlp]
processors: [spanmetrics, batch]
exporters: [otlp/jaeger]
metrics:
receivers: [spanmetrics]
exporters: [prometheus]
Notá:
- El processor
spanmetricsgenera métricas como subproducto de procesar las trazas. - Las trazas siguen yendo a Jaeger.
- Las métricas se exponen en
:8889/metricsy Prometheus las scrap.
prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'spanmetrics'
static_configs:
- targets: ['otel-collector:8889']
Apps
Las apps no cambian — siguen mandando OTLP al OTel Collector (que reemplaza directamente al collector de Jaeger en el flujo de las apps):
export OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
Setup con Jaeger v2 (más simple)
En v2 todo en un único binario Jaeger:
service:
extensions: [jaeger_storage, jaeger_query]
pipelines:
traces:
receivers: [otlp]
processors: [spanmetrics, batch]
exporters: [jaeger_storage_exporter, prometheus]
metrics/spanmetrics:
receivers: [spanmetrics]
exporters: [prometheus]
extensions:
jaeger_storage:
backends:
mem: { memory: { max_traces: 100000 } }
jaeger_query:
storage:
traces: mem
metrics:
type: prometheus
prometheus:
endpoint: http://prometheus:9090
receivers:
otlp:
protocols: { grpc: {}, http: {} }
processors:
batch:
spanmetrics:
histogram:
explicit:
buckets: [10ms, 100ms, 500ms, 1s, 2s, 5s, 10s]
exporters:
jaeger_storage_exporter:
trace_storage: mem
prometheus:
endpoint: 0.0.0.0:8889
Métricas generadas
Por cada span se generan (con namespace configurable):
| Métrica | Tipo | Qué mide |
|---|---|---|
traces_spanmetrics_calls_total | Counter | Cantidad de calls |
traces_spanmetrics_duration_milliseconds | Histogram | Distribución de duración |
Con labels:
service_namespan_name(operation)span_kindstatus_code-
- las dimensions extra que configuraste (
http.method, etc.)
- las dimensions extra que configuraste (
Queries útiles en Prometheus
# Request rate por servicio
sum(rate(traces_spanmetrics_calls_total{span_kind="SPAN_KIND_SERVER"}[5m])) by (service_name)
# Error rate %
sum(rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}[5m])) by (service_name)
/ sum(rate(traces_spanmetrics_calls_total[5m])) by (service_name)
# p99 de duración
histogram_quantile(0.99,
sum(rate(traces_spanmetrics_duration_milliseconds_bucket[5m]))
by (service_name, span_name, le)
)
Cardinalidad: cuidado
spanmetrics puede explotar la cardinalidad si lo configurás mal:
Mal
spanmetrics:
dimensions:
- name: user.id
- name: http.url
Cada combinación (servicio, operación, user.id, url) es una serie temporal en Prometheus. Con miles de usuarios y miles de URLs, vas a tener millones de series. Tu Prometheus se va a la nieve.
Bien
spanmetrics:
dimensions:
- name: http.method # 4-5 valores
- name: http.status_code # ~20 valores
- name: deployment.environment # 2-3 valores
Atributos con bajo cardinality (méthodos HTTP, status codes, environments). Para alta cardinality usá las trazas, no las métricas.
Funcionalidades del Monitor de Jaeger
Con SPM activo, la pestaña Monitor muestra:
- Latency: gráficos p50, p95, p99 por servicio.
- Request rate: requests por minuto.
- Error rate: porcentaje de errores.
- Top operations: las N operaciones más lentas / más usadas.
Click en cualquier endpoint te lleva a search filtrado por ese servicio + operación + ventana temporal del gráfico. El puente perfecto entre métrica agregada y trace individual.
Errores comunes
”No metrics available”
- Verificá que el OTel Collector está exponiendo métricas en
:8889. - Verificá que Prometheus está scraping ese endpoint (mirá
/targetsen la UI de Prometheus). - Verificá que
METRICS_STORAGE_TYPE=prometheusyPROMETHEUS_SERVER_URLapunta correcto en Jaeger Query.
Métricas existen pero el Monitor está vacío
- Probablemente el formato no coincide con lo que Jaeger Query espera. Las flags
PROMETHEUS_QUERY_NORMALIZE_CALLS=trueyPROMETHEUS_QUERY_NORMALIZE_DURATION=truearreglan compatibility issues comunes. - En Jaeger v2 estos defaults son distintos — revisá la documentación de tu versión exacta.
Cardinalidad explotó
Bajá las dimensions o no incluyas atributos high-cardinality.
Alternativa: Grafana directo
Si ya tenés Grafana, podés saltearte el Monitor de Jaeger y construir dashboards directamente sobre las mismas métricas spanmetrics:
- Dashboard de RED metrics por servicio
- Heatmap de latencia
- Service map (con Tempo o Jaeger datasource)
Esto te da más flexibilidad pero requiere que vos diseñes los dashboards. El Monitor de Jaeger es out-of-the-box.
¿Qué viene?
Tenés instrumentación, sampling, storage y métricas. En el último capítulo cubrimos los problemas operacionales más frecuentes y las prácticas que separan a equipos que sufren el tracing de equipos que lo aprovechan.