Prometheus 3.0 y OpenTelemetry: Soporte Nativo OTLP Explicado

Prometheus 3.0 y OpenTelemetry: Soporte Nativo OTLP Explicado

Siete años es mucho tiempo en el mundo de la observabilidad. Desde que Prometheus 2.0 apareció en 2017, el ecosistema ha sido transformado por la adopción cloud-native, el auge del distributed tracing y la consolidación de OpenTelemetry como estándar de facto para la instrumentación. Prometheus 3.0, publicado en noviembre de 2024, es la respuesta del proyecto a esa transformación — y su cambio más significativo es la capacidad nativa de ingerir métricas OpenTelemetry directamente, sin ningún componente intermediario de por medio.

Este artículo entra en detalle en lo que Prometheus 3.0 cambia realmente para ingenieros de plataforma y arquitectos cloud que gestionan — o planean gestionar — cargas de trabajo instrumentadas con OTel junto a stacks de monitorización basados en Prometheus. Cubriremos el endpoint nativo de ingesta OTLP, el soporte de nombres de métricas UTF-8, Remote Write 2.0, las consideraciones de migración y los patrones arquitectónicos que siguen teniendo sentido incluso cuando OTLP nativo está disponible.

Qué Cambió en Prometheus 3.0: Lo Relevante para OTel

Prometheus 3.0 incluye un conjunto sustancial de cambios. No todos son igualmente relevantes para la integración con OpenTelemetry, así que veamos qué mueve realmente la aguja para los usuarios de OTel antes de entrar en detalle en cada área.

Ingesta Nativa OTLP

La funcionalidad estrella: Prometheus 3.0 incorpora un receptor OTLP integrado que expone un endpoint HTTP que acepta métricas en formato OpenTelemetry Protocol. Las aplicaciones instrumentadas con cualquier SDK OTel pueden ahora enviar métricas directamente a Prometheus sin pasar por un OpenTelemetry Collector. No es un sidecar, no es un plugin, no es un adaptador externo — es un endpoint de primera clase en el propio binario de Prometheus.

Nombres de Métricas UTF-8

Prometheus restringía históricamente los nombres de métricas a [a-zA-Z_:][a-zA-Z0-9_:]*. OpenTelemetry utiliza puntos y barras en los nombres de métricas por convención — http.server.request.duration es un nombre canónico de métrica OTel. Prometheus 3.0 elimina esta restricción y admite caracteres UTF-8 arbitrarios en nombres de métricas y etiquetas, que es el cambio de compatibilidad más importante para la interoperabilidad con OTel.

Remote Write 2.0

Remote Write 2.0 sustituye el protocolo original con una codificación más eficiente basada en protobuf, añade soporte nativo de histogramas en el formato de wire y reduce significativamente el consumo de ancho de banda en despliegues a gran escala. Si estáis federando métricas hacia Thanos, Mimir o Cortex, esto tiene impacto en el coste operativo.

Nueva Interfaz Web

La interfaz web de Prometheus ha sido completamente reescrita. La nueva UI usa React, soporta exploración de metadata de métricas y proporciona una experiencia de construcción de queries notablemente mejorada. Es una mejora de calidad de vida más que un cambio arquitectónico, pero reduce la dependencia de herramientas externas como Grafana para la investigación ad-hoc.

Resumen de Cambios Incompatibles

Prometheus 3.0 elimina varias funcionalidades que estaban deprecadas en 2.x. Las más significativas operacionalmente son: eliminación de la ruta de flag deprecated --web.enable-admin-api, eliminación de ciertas opciones de formato de almacenamiento legacy, cambios en los timeouts de scrape por defecto, y validación más estricta de configuraciones que anteriormente se aceptaban en silencio. Más adelante en este artículo incluimos un checklist de migración.

El Receptor OTLP: Cómo Funciona y Qué Acepta

El receptor OTLP en Prometheus 3.0 está implementado como una funcionalidad opcional que debe activarse explícitamente. Una vez habilitado, expone un endpoint HTTP en /api/v1/otlp/v1/metrics que acepta payloads OTLP ExportMetricsServiceRequest codificados en protobuf — el mismo formato de wire que utiliza el exportador OTLP del OpenTelemetry Collector.

Qué Acepta (y Qué No)

Esto es crítico entenderlo antes de diseñar vuestra arquitectura en torno a la ingesta OTLP nativa: el soporte OTLP de Prometheus 3.0 es exclusivamente para métricas. No acepta trazas ni logs. OTLP es un protocolo unificado que cubre las tres señales, pero Prometheus es un almacén de métricas — el receptor gestiona únicamente la parte de métricas de la especificación OTLP.

Tipos de métricas soportados en el receptor OTLP:

  • Gauge — se mapea directamente a un Gauge de Prometheus
  • Sum (monotónico) — se mapea a un Counter de Prometheus
  • Sum (no monotónico) — se mapea a un Gauge de Prometheus
  • Histogram (explicit bucket) — se mapea a un Histogram de Prometheus
  • ExponentialHistogram — se mapea a Prometheus Native Histograms (funcionalidad de 3.0)
  • Summary — se mapea a un Summary de Prometheus

Los resource attributes del payload OTLP — cosas como service.name, k8s.pod.name, cloud.region — se convierten en labels de Prometheus. Esta conversión es configurable, y por defecto Prometheus aplica una estrategia de promoción que convierte los resource attributes más comunes a labels descartando los que crearían cardinalidad extremadamente alta.

Activar el Receptor OTLP

Activar la ingesta OTLP nativa requiere dos cosas: un feature flag y un bloque de configuración en prometheus.yml.

Arrancad el binario de Prometheus con el feature flag:

prometheus \
  --config.file=/etc/prometheus/prometheus.yml \
  --enable-feature=otlp-write-receiver

Luego añadid la configuración del receptor OTLP a vuestro prometheus.yml:

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

otlp:
  # Promover estos resource attributes de OTLP a labels de Prometheus
  promote_resource_attributes:
    - service.name
    - service.namespace
    - service.instance.id
    - k8s.namespace.name
    - k8s.pod.name
    - k8s.node.name
    - cloud.region
    - deployment.environment

Con esta configuración, Prometheus escuchará en el puerto 9090 (por defecto) y aceptará métricas OTLP en http://<prometheus-host>:9090/api/v1/otlp/v1/metrics.

Estrategia de Promoción de Resource Attributes

La lista promote_resource_attributes merece una reflexión cuidadosa. OTLP transporta un contexto de nivel de recurso muy rico — cada payload de métricas incluye un objeto ResourceMetrics con atributos que describen el origen: nombre del servicio, versión, entorno, pod de Kubernetes, nodo, cluster, detalles del proveedor cloud y más. Las labels de Prometheus son pares clave-valor planos en cada serie temporal. Promover demasiados resource attributes dispara la cardinalidad; promover demasiado pocos pierde contexto importante.

Una lista pragmática de partida para despliegues en Kubernetes:

otlp:
  promote_resource_attributes:
    - service.name          # Crítico: identifica el servicio
    - service.namespace     # Agrupación lógica
    - deployment.environment  # prod/staging/dev
    - k8s.namespace.name    # Namespace de Kubernetes
    - k8s.pod.name          # Cardinalidad a nivel de pod — valorad omitirlo a gran escala
    - k8s.node.name         # Útil para correlación con infraestructura

Evitad promover k8s.pod.name indiscriminadamente a escala — en un cluster con miles de pods de vida corta, esto genera una presión de cardinalidad significativa. Prefered service.name y service.namespace para la mayoría de los casos de uso de alertas, reservando las labels a nivel de pod para dashboards de depuración.

Nombres de Métricas UTF-8: El Cambio que Realmente Importa

Para apreciar por qué el soporte de nombres de métricas UTF-8 importa tanto, necesitáis entender la fricción que elimina. Las convenciones semánticas de OpenTelemetry definen nombres de métricas usando puntos como separadores de namespace. La métrica canónica de duración de servidor HTTP es http.server.request.duration. La duración canónica de consulta a base de datos es db.client.operation.duration. Estos nombres están estandarizados entre lenguajes y frameworks — vuestro servicio en Go, vuestro servicio en Java y vuestro servicio en Python emiten el mismo nombre de métrica cuando se instrumentan con OTel.

Prometheus 2.x no podía almacenar estos nombres. Los puntos son caracteres ilegales en la nomenclatura de métricas de Prometheus. Todos los puentes OTel-a-Prometheus — el exportador Prometheus del OpenTelemetry Collector, las capas de compatibilidad de prom-client, el antiguo exportador prometheusremotewrite — tenían que traducir estos nombres, típicamente reemplazando los puntos por guiones bajos: http_server_request_duration.

Esta traducción es imprecisa y crea múltiples problemas:

  • Colisiones de nombres: http.server.request_duration y http.server.request.duration se convierten ambas en http_server_request_duration
  • Rotura de dashboards: Los dashboards de Grafana construidos contra las convenciones semánticas de OTel no funcionan contra métricas Prometheus traducidas sin modificación
  • Correlación entre señales: Los atributos de trazas usan notación de puntos; cuando los nombres de métricas difieren, las herramientas de correlación automatizada pierden el hilo
  • Presión hacia el vendor lock-in: Los equipos acaban con convenciones de nomenclatura separadas para «métricas Prometheus» y «métricas OTel» y mantienen ambas

Prometheus 3.0 con soporte UTF-8 almacena http.server.request.duration de forma nativa. Sin traducción. Sin colisiones. El nombre de métrica con el que instrumentáis es el nombre de métrica que consultáis.

Activar Nombres de Métricas UTF-8

Los nombres de métricas UTF-8 requieren el feature flag utf8-names:

prometheus \
  --config.file=/etc/prometheus/prometheus.yml \
  --enable-feature=utf8-names \
  --enable-feature=otlp-write-receiver

Una vez habilitado, las queries PromQL deben usar nombres de métrica entrecomillados cuando el nombre contiene caracteres fuera del conjunto de caracteres legacy:

# Nombre de métrica legacy — sin comillas funciona bien
http_server_requests_total

# Nombre de métrica OTel con puntos — requiere entrecomillado en PromQL
{"__name__"="http.server.request.duration"}

# O usando la nueva sintaxis PromQL de Prometheus 3.0
http.server.request.duration{service_name="api-gateway"}

El parser de PromQL en Prometheus 3.0 se ha actualizado para gestionar nombres de métricas entrecomillados como construcción de primera clase. El motor PromQL de Grafana también se ha actualizado para gestionar esta sintaxis — verificad vuestra versión de Grafana (10.3+ tiene soporte completo) antes de desplegar.

Del SDK OTel Directamente a Prometheus 3.0: Sin Collector

Para equipos que solo necesitan introducir métricas de aplicación en Prometheus, la ingesta OTLP nativa permite una arquitectura dramáticamente más simple. Así es como se ve con diferentes SDKs OTel.

Go (OpenTelemetry SDK)

package main

import (
    "context"
    "time"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
    "go.opentelemetry.io/otel/sdk/metric"
    "go.opentelemetry.io/otel/sdk/resource"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

func initMetrics(ctx context.Context) (*metric.MeterProvider, error) {
    res, err := resource.New(ctx,
        resource.WithAttributes(
            semconv.ServiceName("mi-api"),
            semconv.ServiceNamespace("plataforma"),
            semconv.DeploymentEnvironment("produccion"),
        ),
    )
    if err != nil {
        return nil, err
    }

    // Apuntar directamente al endpoint OTLP de Prometheus 3.0
    exporter, err := otlpmetrichttp.New(ctx,
        otlpmetrichttp.WithEndpoint("prometheus:9090"),
        otlpmetrichttp.WithURLPath("/api/v1/otlp/v1/metrics"),
        otlpmetrichttp.WithInsecure(), // Usar WithTLSClientConfig en producción
    )
    if err != nil {
        return nil, err
    }

    provider := metric.NewMeterProvider(
        metric.WithResource(res),
        metric.WithReader(
            metric.NewPeriodicReader(exporter,
                metric.WithInterval(30*time.Second),
            ),
        ),
    )

    otel.SetMeterProvider(provider)
    return provider, nil
}

Python (OpenTelemetry SDK)

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk.resources import Resource, SERVICE_NAME, SERVICE_NAMESPACE

resource = Resource.create({
    SERVICE_NAME: "mi-api",
    SERVICE_NAMESPACE: "plataforma",
    "deployment.environment": "produccion",
})

exporter = OTLPMetricExporter(
    endpoint="http://prometheus:9090/api/v1/otlp/v1/metrics",
)

reader = PeriodicExportingMetricReader(
    exporter,
    export_interval_millis=30_000,
)

provider = MeterProvider(resource=resource, metric_readers=[reader])
metrics.set_meter_provider(provider)

# Usar el meter
meter = metrics.get_meter("mi-api")
request_counter = meter.create_counter(
    name="http.server.request.count",
    description="Total de peticiones HTTP al servidor",
    unit="1",
)
request_duration = meter.create_histogram(
    name="http.server.request.duration",
    description="Duración de peticiones HTTP al servidor",
    unit="s",
)

Java (OpenTelemetry SDK con Spring Boot)

# application.properties (Spring Boot con auto-instrumentación OTel)
otel.service.name=mi-api
otel.resource.attributes=service.namespace=plataforma,deployment.environment=produccion

# Configurar el exportador OTLP para enviar directamente a Prometheus
otel.metrics.exporter=otlp
otel.exporter.otlp.metrics.endpoint=http://prometheus:9090/api/v1/otlp/v1/metrics
otel.exporter.otlp.metrics.protocol=http/protobuf

# Intervalo de exportación
otel.metric.export.interval=30000

Con Spring Boot y el agente Java de OTel, no se necesitan cambios de código más allá de la configuración — el agente instrumenta vuestro servidor HTTP, clientes de base de datos y sistemas de mensajería automáticamente y envía métricas usando los nombres definidos en las convenciones semánticas de OTel.

OTel Collector hacia Prometheus 3.0: Cuándo Necesitáis el Intermediario

La ingesta OTLP nativa es atractiva, pero el OpenTelemetry Collector sigue siendo relevante para un conjunto significativo de casos de uso. Entender cuándo es apropiado cada patrón es la decisión arquitectónica central que afrontaréis al adoptar Prometheus 3.0 en un entorno OTel.

Patrón 1: OTel Collector como Gateway de Fan-Out

Cuando necesitáis enviar métricas a múltiples backends simultáneamente — Prometheus para alertas, un almacén de larga duración como Thanos para análisis histórico, y una plataforma de observabilidad comercial para correlación full-stack — el OTel Collector gestiona el fan-out eficientemente. Las aplicaciones envían una sola vez al Collector; el Collector distribuye a todos los backends.

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 10s
    send_batch_size: 1000
  memory_limiter:
    check_interval: 1s
    limit_mib: 512

exporters:
  # Enviar a Prometheus 3.0 vía OTLP
  otlphttp/prometheus:
    endpoint: http://prometheus:9090/api/v1/otlp
    tls:
      insecure: true

  # Fan-out a Thanos vía remote_write
  prometheusremotewrite/thanos:
    endpoint: http://thanos-receive:10908/api/v1/receive
    resource_to_telemetry_conversion:
      enabled: true

  # Fan-out a backend comercial
  otlp/datadog:
    endpoint: https://otel-intake.datadoghq.com
    headers:
      DD-API-KEY: "${DD_API_KEY}"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp/prometheus, prometheusremotewrite/thanos, otlp/datadog]

Patrón 2: Collector para Transformación de Métricas

Los procesadores transform y metricstransform del OTel Collector permiten remodelar las métricas antes de que lleguen a Prometheus: renombrar labels, añadir atributos estáticos, filtrar series de alta cardinalidad, agregar métricas para reducir el coste de almacenamiento, o aplicar conversiones de unidades. Estas operaciones no están disponibles en el receptor OTLP nativo de Prometheus.

processors:
  transform/metrics:
    metric_statements:
      - context: metric
        statements:
          # Descartar métricas internas de depuración
          - delete_matching_keys(attributes, "internal.*")
          # Normalizar valores del label de entorno
          - set(attributes["deployment.environment"], "prod")
            where attributes["deployment.environment"] == "production"

  filter/drop_debug:
    metrics:
      exclude:
        match_type: regexp
        metric_names:
          - ".*\.debug\..*"
          - "runtime\.go\.internal\..*"

  metricstransform:
    transforms:
      # Renombrar una métrica para ajustarse a vuestra convención de nomenclatura Prometheus
      - include: http.server.request.duration
        action: update
        new_name: http_server_request_duration_seconds

Patrón 3: Collector para Trazas y Logs (Siempre Necesario)

Si vuestra arquitectura incluye trazas y logs junto a métricas — y en 2025 casi con toda seguridad lo hace — necesitáis un OTel Collector independientemente de lo que hagáis con las métricas. Prometheus no acepta trazas ni logs. Jaeger, Tempo y Loki tienen sus propios protocolos de ingesta. El Collector es la capa de enrutamiento universal para los tres pilares de la observabilidad.

En esta arquitectura, suele ser más sencillo enrutar las tres señales a través del Collector y dejar que envíe las métricas a Prometheus vía OTLP o remote_write, en lugar de dividir las métricas para que vayan directamente y todo lo demás a través del Collector.

Cuándo Usar OTLP Nativo vs. OTel Collector: Marco de Decisión

EscenarioOTLP NativoOTel Collector
Un solo backend de métricas (solo Prometheus)PreferidoInnecesariamente complejo
Múltiples backends de métricasInsuficienteNecesario
Trazas + Logs en scopeNo aplicableNecesario
Transformación/filtrado de métricas necesarioNo soportadoNecesario
Despliegue Kubernetes simplePreferidoComplejidad adicional
Entornos air-gapped / restringidosPreferido (menos componentes)Valorar con cuidado
Mezcla de targets OTel + Prometheus legacyFunciona junto al scrapingPuede normalizar nomenclatura
Alto volumen, necesidad de bufferingControl limitadoPreferido

La recomendación pragmática para la mayoría de los equipos de ingeniería de plataforma: si ya estáis ejecutando el OTel Collector (y deberíais si las trazas están en scope), continuad enrutando las métricas a través de él. Utilizad el exportador otlphttp del Collector para enviar a Prometheus 3.0. Reservad el patrón de SDK directo a Prometheus para servicios simples donde el Collector sería la única razón para añadir complejidad.

Remote Write 2.0: Qué Cambia para Vuestros Setups Existentes

Remote Write 2.0 es una actualización significativa del protocolo con implicaciones operativas reales para equipos que usan Prometheus como fuente de métricas para sistemas de almacenamiento a largo plazo como Thanos, Mimir, VictoriaMetrics o Cortex.

Cambios Clave en el Protocolo

  • Codificación protobuf con compresión snappy — sustituye al formato anterior basado en texto. Típicamente supone una reducción del 50-70% en el tamaño del wire para grandes lotes de métricas
  • Soporte nativo de histogramas en el formato de wire — los histogramas exponenciales pueden ahora enviarse sin convertirlos a histogramas clásicos, preservando la resolución completa
  • Reenvío de metadata — la información de tipo y unidad de métricas se transmite ahora junto a las muestras, habilitando un procesamiento downstream mejor
  • Timestamps de creación — el timestamp en que se creó un counter se reenvía, habilitando cálculos de tasa más precisos tras reinicios

Configurar Remote Write 2.0

# prometheus.yml
remote_write:
  - url: "http://thanos-receive:10908/api/v1/receive"
    # Remote Write 2.0 se negocia automáticamente con receptores compatibles
    # Forzar RW2.0 explícitamente si es necesario:
    send_native_histograms: true
    metadata_config:
      send: true
      send_interval: 1m
    queue_config:
      capacity: 10000
      max_shards: 200
      max_samples_per_send: 2000
      batch_send_deadline: 5s

Remote Write 2.0 usa negociación de protocolo por content — Prometheus 3.0 intentará RW2.0 primero y volverá a RW1.0 si el receptor no lo soporta. Esto significa que las actualizaciones son generalmente retrocompatibles. Verificad que vuestro sistema receptor (Thanos Receive 0.35+, Mimir 2.12+, VictoriaMetrics 1.98+) soporta RW2.0 antes de esperar los beneficios.

Migración desde Prometheus 2.x: Checklist de Cambios Incompatibles

La actualización de Prometheus 2.x a 3.0 requiere atención a varios cambios incompatibles. Este checklist cubre los más significativos operacionalmente para equipos que ejecutan despliegues Prometheus en producción.

Cambios de Configuración

  • Eliminado: cambio del valor por defecto de query.lookback-delta — el valor por defecto cambió de 5 minutos a coincidir con el scrape interval. Las queries que dependían del valor por defecto de 5m pueden devolver resultados diferentes. Auditad las reglas de alerta que usen instant queries sobre counters.
  • Eliminadas: opciones remote_write deprecadas — la semántica de remote_write[].queue_config.capacity cambió. Revisad y actualizad las configuraciones de cola.
  • Eliminado: flag storage.tsdb.allow-overlapping-blocks — el manejo de bloques solapados ahora es automático. Eliminad este flag de vuestros scripts de arranque.
  • Cambio en protocolos de scrape por defecto — Prometheus 3.0 usa por defecto el formato OpenMetrics para el scraping cuando los targets lo soportan. Esto habilita histogramas nativos pero puede revelar diferencias de parsing. Probad el comportamiento si dependíais del comportamiento anterior.
  • Cambios en Agent mode — si usáis Prometheus en modo Agent, revisad las opciones de configuración actualizadas para la gestión del WAL.

Cambios en PromQL

  • Parsing más estricto — algunas expresiones PromQL anteriormente aceptadas pero técnicamente inválidas ahora fallan. Pasad vuestras reglas de alerta por promtool check rules contra un binario de Prometheus 3.0 antes del cutover.
  • Funciones de histograma nativo — nuevas funciones como histogram_fraction() y histogram_quantile() tienen comportamiento actualizado con histogramas nativos. Las queries existentes de dashboards que usen histogram_quantile() sobre histogramas clásicos siguen funcionando sin cambios.

Compatibilidad de Almacenamiento

Prometheus 3.0 puede leer datos TSDB existentes de 2.x. La ruta de actualización no requiere migración de datos. Sin embargo, Prometheus 2.x no puede leer bloques de datos escritos por 3.0 (el downgrade no está soportado sin pérdida de datos una vez que se han producido escrituras). Tomad un snapshot antes de actualizar si necesitáis capacidad de rollback:

# Tomar un snapshot de TSDB antes de actualizar
curl -X POST http://prometheus:9090/api/v1/admin/tsdb/snapshot

# Verificar que el snapshot existe
ls /prometheus/snapshots/

Pasos de Validación Previos a la Actualización

# 1. Validar la configuración contra Prometheus 3.0
docker run --rm -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus:v3.0.0 \
  promtool check config /etc/prometheus/prometheus.yml

# 2. Validar las reglas de alerta
docker run --rm -v $(pwd)/rules:/etc/prometheus/rules \
  prom/prometheus:v3.0.0 \
  promtool check rules /etc/prometheus/rules/*.yml

# 3. Ejecutar en paralelo (modo shadow) antes del cutover completo
# Desplegar Prometheus 3.0 junto a 2.x, scrapeando los mismos targets
# Comparar resultados de queries entre versiones usando promtool query range

Despliegue Práctico en Kubernetes

A continuación se muestra un despliegue Kubernetes de Prometheus 3.0 preparado para producción con ingesta OTLP habilitada, adecuado como punto de partida para equipos de ingeniería de plataforma.

ConfigMap de Prometheus 3.0

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitoring
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      evaluation_interval: 15s
      external_labels:
        cluster: produccion
        region: eu-west-1

    otlp:
      promote_resource_attributes:
        - service.name
        - service.namespace
        - deployment.environment
        - k8s.namespace.name
        - k8s.pod.name

    rule_files:
      - /etc/prometheus/rules/*.yml

    scrape_configs:
      - job_name: kubernetes-pods
        kubernetes_sd_configs:
          - role: pod
        relabel_configs:
          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
            action: keep
            regex: "true"
          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
            action: replace
            target_label: __metrics_path__
            regex: (.+)

    remote_write:
      - url: http://thanos-receive.monitoring.svc.cluster.local:10908/api/v1/receive
        send_native_histograms: true
        metadata_config:
          send: true

Fijaos en el uso de region: eu-west-1 en external_labels. Para equipos en Europa, esta etiqueta es útil no solo para correlación operativa sino también para trazabilidad de datos: saber en qué región reside vuestra telemetría facilita la demostración de cumplimiento del Reglamento General de Protección de Datos (RGPD). En organizaciones bajo supervisión de la Agencia Española de Protección de Datos (AEPD) o cualquier otra autoridad de control europea, poder demostrar que los datos de monitorización no salen de la UE es un requisito real. Etiquetas de región en todas vuestras métricas os dan esa trazabilidad sin esfuerzo adicional.

Deployment de Prometheus 3.0

apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      serviceAccountName: prometheus
      containers:
        - name: prometheus
          image: prom/prometheus:v3.0.0
          args:
            - --config.file=/etc/prometheus/prometheus.yml
            - --storage.tsdb.path=/prometheus/data
            - --storage.tsdb.retention.time=15d
            - --web.enable-lifecycle
            - --web.enable-admin-api
            - --enable-feature=otlp-write-receiver
            - --enable-feature=utf8-names
            - --enable-feature=native-histograms
          ports:
            - name: http
              containerPort: 9090
              protocol: TCP
          volumeMounts:
            - name: config
              mountPath: /etc/prometheus
            - name: data
              mountPath: /prometheus/data
          resources:
            requests:
              cpu: 500m
              memory: 2Gi
            limits:
              cpu: 2000m
              memory: 8Gi
          livenessProbe:
            httpGet:
              path: /-/healthy
              port: http
            initialDelaySeconds: 30
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /-/ready
              port: http
            initialDelaySeconds: 5
            periodSeconds: 5
      volumes:
        - name: config
          configMap:
            name: prometheus-config
        - name: data
          persistentVolumeClaim:
            claimName: prometheus-data
---
apiVersion: v1
kind: Service
metadata:
  name: prometheus
  namespace: monitoring
spec:
  selector:
    app: prometheus
  ports:
    - name: http
      port: 9090
      targetPort: http
  type: ClusterIP

Configurar las Aplicaciones para Enviar OTLP

Con este despliegue, cualquier aplicación en el cluster puede enviar métricas OTLP configurando las siguientes variables de entorno (funciona con cualquier SDK OTel que soporte OTLP HTTP):

env:
  - name: OTEL_SERVICE_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.labels['app']
  - name: OTEL_SERVICE_NAMESPACE
    valueFrom:
      fieldRef:
        fieldPath: metadata.namespace
  - name: OTEL_METRICS_EXPORTER
    value: "otlp"
  - name: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
    value: "http://prometheus.monitoring.svc.cluster.local:9090/api/v1/otlp/v1/metrics"
  - name: OTEL_EXPORTER_OTLP_METRICS_PROTOCOL
    value: "http/protobuf"
  - name: OTEL_METRIC_EXPORT_INTERVAL
    value: "30000"
  - name: OTEL_RESOURCE_ATTRIBUTES
    value: "deployment.environment=produccion,k8s.namespace.name=$(NAMESPACE)"

Este enfoque funciona especialmente bien en entornos que usan el OTel Operator para Kubernetes, donde el CRD Instrumentation puede inyectar estas variables de entorno automáticamente en los pods basándose en selectores de namespace o de etiquetas de pod — instrumentación zero-touch con almacenamiento nativo en Prometheus.

Prometheus 3.0 y la Soberanía de Datos en Europa

Un aspecto que suele pasarse por alto en los análisis técnicos de Prometheus 3.0, pero que resulta especialmente relevante para empresas europeas, es cómo la arquitectura de observabilidad interactúa con los requisitos del RGPD.

Cuando las métricas contienen datos que pueden considerarse personales — identificadores de sesión en labels, IPs de usuarios en atributos de recursos, nombres de usuario en trazas correlacionadas — la elección del backend de almacenamiento y su ubicación geográfica tiene implicaciones legales directas. En España y en la UE en general, el RGPD exige que los datos personales no se transfieran a países terceros sin las salvaguardas adecuadas.

Prometheus, al ser autoalojado, ofrece control total sobre dónde residen los datos. Para equipos que trabajan con proveedores cloud europeos — Hetzner, OVHcloud, IONOS, Exoscale — o que gestionan infraestructura propia como un homelab o un cluster on-premises, Prometheus 3.0 con almacenamiento local garantiza que los datos de observabilidad no salen de la jurisdicción europea.

Algunas recomendaciones prácticas para entornos con requisitos de cumplimiento:

  • Usad external_labels con region: eu-west-X en todas las instancias de Prometheus para tener trazabilidad geográfica completa
  • Evitad promover resource attributes que contengan datos personales (IPs de usuario, identificadores de sesión) mediante la lista promote_resource_attributes — filtradlos en el OTel Collector antes de que lleguen a Prometheus
  • Si usáis Thanos o Mimir para almacenamiento a largo plazo, verificad que los buckets de object storage (S3-compatible) están en regiones europeas
  • Documentad vuestros flujos de datos de telemetría en el registro de actividades de tratamiento que exige el RGPD

Preguntas Frecuentes

¿Puedo usar la ingesta OTLP de Prometheus 3.0 para trazas y logs?

No. El receptor OTLP de Prometheus 3.0 gestiona únicamente métricas. Prometheus es un almacén de métricas — no tiene modelo de datos para trazas ni logs. Para trazas necesitáis un backend como Jaeger o Grafana Tempo. Para logs necesitáis Loki, Elasticsearch o un sistema similar. El OTel Collector es la capa de enrutamiento apropiada cuando necesitáis enviar las tres señales a sus respectivos backends desde un único endpoint de push en el lado de la aplicación.

¿El chart Helm de kube-prometheus-stack soporta Prometheus 3.0?

Sí, con matices. El chart kube-prometheus-stack actualizó su imagen de Prometheus a 3.0 a partir de la versión 66.0.0 del chart. Sin embargo, algunas recording rules y alerting rules incluidas pueden necesitar ajuste por los cambios en PromQL y las diferencias de comportamiento por defecto. El propio Prometheus Operator (versión 0.78+) se ha actualizado para soportar las nuevas opciones de configuración incluyendo el bloque de configuración otlp. Si gestionáis Prometheus vía Operator, configuraréis los ajustes OTLP a través de spec.additionalArgs del CRD Prometheus y un recurso PrometheusConfiguration personalizado.

¿Qué ocurre con los nombres de métricas de Prometheus 2.x existentes cuando activo el soporte UTF-8?

Las métricas existentes con nombres basados en guiones bajos siguen funcionando exactamente igual que antes. Activar el soporte UTF-8 es puramente aditivo — permite el almacenamiento y consulta de nombres de métricas que contienen puntos y otros caracteres UTF-8, pero no renombra ni modifica las métricas existentes. Vuestros dashboards, reglas de alerta y recording rules existentes siguen funcionando sin modificación. Solo las métricas ingeridas vía OTLP (o expuestas por exportadores que usen convenciones de nomenclatura OTel) usarán nombres separados por puntos.

¿Cómo afecta la ingesta OTLP nativa al modelo pull de Prometheus?

Coexiste con él. Prometheus 3.0 sigue scrapeando targets vía el modelo pull en el mismo puerto 9090. El endpoint OTLP es una ruta de ingesta adicional, no un reemplazo del scraping. Podéis tener una instancia de Prometheus scrapeando simultáneamente pods de Kubernetes vía service discovery y recibiendo métricas OTLP push de aplicaciones — ambas se almacenan en el mismo TSDB y son consultables vía la misma interfaz PromQL. Este enfoque híbrido es habitual durante las migraciones, donde los componentes legacy se scrapean y los nuevos servicios instrumentados con OTel envían vía OTLP.

¿Es el receptor OTLP de Prometheus 3.0 adecuado para cargas de trabajo de producción de alto volumen?

Para volúmenes moderados, sí. El receptor OTLP es síncrono — la petición HTTP se completa solo después de que las muestras se escriben en el WAL. Bajo tasas de ingesta muy altas (cientos de miles de muestras por segundo), esto puede crear back-pressure que afecta a la latencia de las aplicaciones. El OTel Collector gestiona esto mejor mediante buffering interno, colas de reintentos y procesamiento por lotes. Para escenarios de alto volumen, el patrón recomendado es: las aplicaciones envían al OTel Collector (que confirma inmediatamente y hace buffer), el Collector envía a Prometheus vía OTLP o remote_write en lotes optimizados. Para la mayoría de las cargas de trabajo en Kubernetes — decenas o centenares de servicios con cardinalidad típica de métricas — el receptor OTLP nativo rinde bien sin intermediario.

¿Funciona esto con el Prometheus que viene incluido en kube-prometheus-stack?

Sí. Si ya tenéis kube-prometheus-stack desplegado y queréis añadir ingesta OTLP, la forma más limpia es actualizar el chart a la versión 66.0.0 o posterior y añadir los additionalArgs correspondientes al CRD Prometheus:

# values.yaml para kube-prometheus-stack
prometheus:
  prometheusSpec:
    additionalArgs:
      - name: enable-feature
        value: otlp-write-receiver
      - name: enable-feature
        value: utf8-names
    additionalScrapeConfigs: []

Combinad esto con la configuración otlp.promote_resource_attributes en un PrometheusConfiguration adicional o directamente en el additionalConfig del spec.

¿Cómo migrar de la configuración basada en OpenTelemetry Collector Prometheus exporter a OTLP nativo?

La migración es incremental. Podéis ejecutar ambas configuraciones en paralelo durante la transición:

  1. Habilitad el receptor OTLP en Prometheus 3.0
  2. Configurad vuestras aplicaciones para enviar a ambos endpoints simultáneamente (Collector y Prometheus directo)
  3. Verificad que las métricas aparecen correctamente en Prometheus con los nombres UTF-8 originales
  4. Comparad dashboards y alertas entre ambas fuentes
  5. Una vez validado, eliminad el exportador Prometheus del Collector y simplificad la configuración

La ventaja de esta migración gradual es que podéis verificar que las queries PromQL existentes funcionan con los nuevos nombres (o adaptarlas si fuera necesario) antes de eliminar el path anterior.