Prometheus deja de funcionar como lo montaste el primer día

La cardinalidad está detrás de casi todos los problemas que vas a tener con Prometheus, y suele ser una etiqueta añadida por un ingeniero con buena intención. Cómo encontrarla, cómo sobrevivir a la retención y la alta disponibilidad, y cuándo se justifica Thanos o Mimir.

El fallo tiene siempre la misma forma. Prometheus lleva dieciocho meses funcionando bien en un pod de cuatro gigabytes. Alguien añade una etiqueta con el identificador de usuario a un contador de peticiones porque vendría bien para depurar. Dos días después el pod muere por falta de memoria cada veinte minutos, las consultas expiran y quien está de guardia se queda sin monitorización durante el incidente que provocó el cambio en la monitorización.

Prometheus es magnífico a la escala en la que empiezas e implacable con la forma en que creces. Casi todo lo que sale mal se remonta a un solo número.

La cardinalidad es la causa, y se mide directamente

Una serie temporal de Prometheus es una combinación única de nombre de métrica y valores de etiqueta. Cada combinación distinta es una serie separada que se mantiene en memoria con su propio bloque. Un contador con tres etiquetas de diez valores cada una son mil series. Añade una cuarta etiqueta con diez mil valores y son diez millones, y el proceso muere.

No hace falta adivinar dónde está. Prometheus lo expone:

  • topk(20, count by (__name__)({__name__=~".+"})) te da los veinte nombres de métrica con más series. Es la consulta que hay que ejecutar primero, siempre.
  • prometheus_tsdb_head_series es tu total de series activas, el número que hay que graficar y sobre el que hay que alertar.
  • count(count by (nombre_etiqueta) (tu_metrica)) te dice cuántos valores distintos tiene en la práctica una etiqueta concreta, que casi siempre son más de los que supuso quien la programó.
  • La página de estado del almacén de series en la interfaz de Prometheus muestra las series principales por métrica y por par de etiquetas sin escribir ninguna consulta. Manda ahí a la gente antes de que añada una etiqueta.

Pon una alerta dura sobre el total de series activas con un umbral para el que de verdad hayas planificado capacidad, y una segunda sobre la tasa de crecimiento. Los problemas de cardinalidad son muchísimo más baratos de cazar con un quince por ciento más en una hora que cuando el proceso muere.

Etiquetas que jamás pueden llevar un identificador

La regla es simple y merece escribirse en una comprobación automática: el valor de una etiqueta tiene que venir de un conjunto pequeño, acotado y que cambie despacio.

Nunca metas en una etiqueta: identificador de usuario, de cliente, de petición, de traza o de sesión, dirección de correo, ruta de URL completa con identificadores dentro, mensaje de error en crudo, marca de tiempo, identificador de contenedor, IP de pod ni ningún identificador único universal. Todos son ilimitados, y ilimitado significa que el número de series crece con tu tráfico en vez de con tu arquitectura.

La que pilla a los equipos en concreto es la ruta HTTP. Una ruta con el número de pedido dentro es una serie por pedido. Instrumenta la plantilla de la ruta, con el identificador como marcador, no la URL resuelta. Casi todos los marcos te dan la plantilla; a la instrumentación automática solo hay que configurarla para que la use.

Si de verdad necesitas detalle por usuario o por petición, para eso están las trazas y los logs. Pon el identificador de alta cardinalidad en un atributo de tramo, que es su sitio, y usa la traza para ir de una anomalía en una métrica a la petición concreta. Ese traspaso es el argumento entero de depurar con trazas distribuidas.

Aplícalo también en el punto de recogida. La configuración de reetiquetado de métricas en la recolección puede descartar una etiqueta o una métrica entera antes de que llegue a almacenarse, lo que significa que puedes frenar un despliegue malo sin esperar a un arreglo en el código.

Retención y escritura remota son dos problemas distintos

La retención local de Prometheus está limitada por disco y suele fijarse entre quince y noventa días. Eso vale para operar y no vale para nada en planificación de capacidad, tendencias trimestrales ni nada que un informe de objetivos de nivel de servicio necesite a lo largo de un año.

La respuesta es la escritura remota a almacenamiento de largo plazo, pero ten claro qué estás comprando. La escritura remota envía cada muestra por la red de forma continua, así que añade CPU, memoria para la cola del registro de escritura anticipada y salida de datos. Ajusta el tamaño máximo de envío y la capacidad de la cola, y vigila el contador de muestras fallidas y el número de fragmentos de la cola, porque un almacén de destino que se ralentiza aplica contrapresión directamente sobre tu Prometheus.

Reduce lo que envías. Casi nunca necesitas resolución cruda de un segundo de todas las series en almacenamiento frío. Filtra en el reetiquetado de escritura para enviar las series que de verdad consultas históricamente y deja que el resto caduque en local.

Ejecutar dos Prometheus no es alta disponibilidad

El patrón habitual son dos instancias idénticas recolectando los mismos objetivos. Te da supervivencia ante el fallo de un nodo, y eso es todo lo que te da. No te da:

  • Una vista consistente. Las dos instancias recolectan en instantes ligeramente distintos, así que sus muestras difieren. Consulta una y luego la otra durante un incidente y los números no cuadran, lo que erosiona la confianza justo cuando la necesitas.
  • Alertas deduplicadas. Necesitas Alertmanager en modo clúster, con todas las instancias comunicándose, o cada alerta se dispara dos veces. Alertmanager lo hace bien, pero es otra cosa que hay que configurar.
  • Una vista global entre clústeres. Dos réplicas del Prometheus de un clúster no te dicen nada de los otros cuatro.

Ese último hueco es el motivo honesto para buscar algo más grande.

Thanos o Mimir, y cuándo hace falta de verdad

No despliegues ninguno porque lo dijo una charla. Necesitas uno cuando se cumple al menos una de estas: tienes que consultar varias instancias o clústeres en un mismo panel; necesitas retención medida en años con un rendimiento de consulta razonable; o un solo Prometheus ya no cabe en una máquina que estés dispuesto a operar.

Thanos se acopla a los servidores Prometheus que ya tienes. Un sidecar sube los bloques al almacenamiento de objetos, un consultor reparte las consultas entre sidecars y pasarelas de almacén, y un compactador reduce la resolución de los bloques viejos. Es incremental, que es su virtud principal: conservas los Prometheus que tienes.

Mimir es un backend escalable horizontalmente y multiinquilino al que escribes de forma remota. Prometheus se queda como un recolector y reenviador fino. Encaja mejor cuando tienes multiinquilinato real, muchos equipos, y quieres un sistema operado en lugar de una flota de servidores más una capa de consulta.

Guía aproximada: Thanos si estás añadiendo almacenamiento a largo plazo y vista global a un conjunto de Prometheus por clúster con los que estás contento. Mimir si estás consolidando una dispersión de ellos en una plataforma. Los dos necesitan almacenamiento de objetos, los dos necesitan atención operativa real y los dos son un salto de complejidad. El coste del almacenamiento de objetos no es la preocupación; los recursos de compactación y de consulta sí.

El operador y los ServiceMonitor son lo que mantiene esto sostenible en Kubernetes

Editar a mano el fichero de configuración dentro de un ConfigMap no sobrevive al contacto con un clúster de varios equipos. El operador de Prometheus lo sustituye por objetos de Kubernetes: uno para el servidor, ServiceMonitor y PodMonitor para los objetivos de recolección seleccionados por etiqueta, PrometheusRule para reglas de registro y de alerta, y otro para Alertmanager.

El efecto práctico es que cada equipo publica su propio ServiceMonitor en su propio chart, y el equipo de plataforma no vuelve a tocar una configuración de recolección. Dos cosas que hay que acertar: los selectores de etiqueta del objeto del servidor determinan qué ServiceMonitor se recogen en los distintos namespaces, y equivocarse ahí es la razón habitual de que un servicio nuevo no se recolecte en silencio. Y los objetos PrometheusRule deberían validarse en integración continua con la comprobación de reglas de promtool, porque un error de sintaxis en un fichero puede impedir que cargue el grupo entero.

Reglas de registro para las consultas que tus paneles lanzan cada treinta segundos

Un panel con una tasa sobre una ventana de cinco minutos a lo largo de cincuenta mil series, refrescado cada treinta segundos por doce personas, es una denegación de servicio autoinfligida. Las reglas de registro precalculan la expresión de forma programada y guardan el resultado como una serie nueva, así que el panel lee una serie barata en vez de agregar cincuenta mil.

Encuentra las candidatas igual que encuentras la cardinalidad: mira la duración de las consultas del motor y los paneles más lentos de Grafana. Todo lo que agregue sobre un conjunto grande de etiquetas, todo lo que se use en más de tres paneles y todo lo que evalúe una alerta con frecuencia es una regla de registro. Nómbralas con la convención de nivel, métrica y operación, para que en un panel quede claro que estás leyendo una serie precalculada.

Esto además hace tus reglas de alerta más rápidas y estables, lo que importa porque una evaluación lenta retrasa las alertas precisamente cuando el clúster está bajo carga.

Lo que se olvida

  • El intervalo de recolección multiplica el almacenamiento, no el número de series. Partirlo por la mitad duplica muestras y disco, pero no cambia las series activas. La gente confunde ambas cosas y ajusta la palanca equivocada.
  • Las métricas del estado del clúster y las del contenedor son las que más series producen en casi todos los clústeres. Antes de culpar a las métricas de aplicación, cuéntalas. Descartar las métricas de contenedor que nunca consultas suele ser la mayor victoria individual.
  • Obsolescencia y ausencia. Una alerta sobre una métrica que desaparece del todo no se disparará nunca, porque no hay serie que evaluar. Usa absent() o up para las condiciones de "esto dejó de reportar".
  • La federación no es una estrategia de escalado. Se diseñó para traer un conjunto pequeño de series agregadas entre servidores. Usarla para traerlo todo de diez clústeres a uno produce exactamente la sobrecarga que intentabas evitar.
  • El registro de escritura anticipada se reproduce al arrancar. Un bloque en memoria muy grande implica un arranque lento, y un Prometheus que tarda ocho minutos en volver son ocho minutos de ceguera después de cada reinicio.

Dimensionar bien la pila de monitorización suele ir en paralelo a dimensionar bien el clúster que vigila, y por eso aparece en la misma conversación que por qué un clúster de EKS cuesta el doble de lo que debería. Trabajamos las dos cosas en un proyecto cloud.

Qué hacer esta semana

Abre la página de estado del almacén de series de tu Prometheus de producción, o ejecuta topk(20, count by (__name__)({__name__=~".+"})), y mira los cinco primeros nombres de métrica. Después ejecuta el recuento de valores distintos sobre la peor de ellas para cada una de sus etiquetas. En menos de una hora sabrás exactamente qué etiqueta te está costando más memoria, y en casi todos los entornos es una que puedes descartar hoy con una regla de reetiquetado.

ConsultorIA

¿Quieres esto en tu nube?

El diagnóstico de diez días en solo lectura es gratuito, y con Skyline puedes ver tu entorno en un mapa antes de escribirnos.

Artículos relacionados

Módulos que la gente reutiliza en vez de copiar

Los dos fracasos son un módulo que envuelve un recurso y no aporta nada, y un módulo que lo hace todo y que nadie se atreve a cambiar. Una interfaz mínima, valores por defecto seguros y un versionado honesto son lo que los separa.