13. SPM con Prometheus

Por: Artiko
jaegerspmprometheusspanmetricsred-metrics

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

PiezaFunción
OTel Collector con spanmetrics processorConvierte spans en métricas RED
PrometheusAlmacena las métricas
Jaeger Query con METRICS_STORAGE_TYPE=prometheusLee 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á:

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étricaTipoQué mide
traces_spanmetrics_calls_totalCounterCantidad de calls
traces_spanmetrics_duration_millisecondsHistogramDistribución de duración

Con labels:

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:

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”

Métricas existen pero el Monitor está vacío

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:

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.