Cuatro alertas que no podían dispararse nunca y cómo las encontraron
Una auditoría interna de una semana encontró cuatro reglas capaces de no dar nunca la alarma en el escenario exacto para el que se escribieron. Los paneles siguieron en verde todo el tiempo.

Un equipo dedicó una semana a auditar su propio sistema de alertas y cambió la pregunta habitual por otra: no si algo avisa, sino si esa alerta se vería distinta en el caso de que aquello que vigila hubiera fallado. En cuatro casos la respuesta fue no. No es que dispararan tarde ni que el umbral fuera flojo: no podían producir una señal roja en la circunstancia exacta para la que se escribieron. Los paneles estuvieron en verde todo el tiempo.
La regla que solo dispara con el sistema sano
Buscaban avisar de que una tarea de mantenimiento programada había dejado de llegar. La expresión natural era increase(celery_tasks_total{task="..."}[2h]) < 1. Se lee bien y evalúa sin errores, pero en PromQL una comparación entre vector y escalar es un filtro, no un booleano: X < 1 no devuelve verdadero o falso, devuelve el valor de X en las series que cumplen la condición y descarta el resto. Con un umbral de Grafana gt [0], el caso que debe cazar —la tarea parada del todo, con increase(...) == 0— pasa el filtro y luego evalúa 0 > 0, falso. Solo está viva en el intervalo abierto (0,1): detecta la tarea parada a medias y es ciega a la que se ha parado del todo. Se arregla con bool.
Lo revelador es cómo salió. El autor había escrito bool a propósito y mutó su código a la versión ingenua para comprobar si el linter lo habría cazado. No lo cazó, y las 196 pruebas pasaron en verde. Otras cuatro mutaciones del mismo tipo sí se pusieron rojas.
El exporter que se raspaba y no recibía nada
Había un contador de fallos de tareas con su alerta, y nunca había saltado. Tampoco había habido un fallo que debiera cazar, así que nadie lo miró. Fallaban dos cosas, y cada una por sí sola es letal.
El contador se incrementaba en el worker de Celery y se servía desde la ruta /metrics del proceso web. Dos registries sin estado compartido: lo que mantiene el worker no viaja en la carga que sirve el web, y nunca viajó. Encima, Prometheus no raspaba el worker: siete targets configurados y ninguno era ese. La consulta devolvía cero series. Como las reglas corren con noDataState: OK —correcto para filtros, porque un sistema sano no produce filas—, "la métrica no existe" y "no pasa nada" llegan al motor como la misma señal.
El arreglo fue dejar de pedir a la aplicación que se reporte a sí misma y leer el flujo del broker con un exporter dedicado y el worker arrancado con -E. Eso trae su propia trampa: un exporter apuntado a un worker sin -E sigue emitiendo métricas de liveness y cero de tareas, indistinguible de un worker que aún no ha ejecutado nada.
curl -sf sale con cero sobre una página de error HTML
El más barato de reproducir y probablemente el más extendido. Un runbook que bloqueaba un cambio de precios tenía este paso:
curl -sf https://app.example.com/metrics | grep -E "bytes_processed|bytes_quota"
Medido: código 200, 5.106 bytes, content_type=text/html y un <!DOCTYPE html>. La ruta /metrics no tenía entrada en el proxy inverso, que manda lo que no reconoce a la aplicación de una sola página: una ruta de backend sin enrutar no da 404, resuelve al frontend y devuelve el shell con un 200. curl -f falla con códigos a partir de 400, y un 200 con HTML no es un error de estado, así que no se activa y curl sale con 0. grep recibe cinco kilobytes de HTML, no encuentra nada y el operador ve una salida vacía.
Los cuatro casos comparten una forma: un control que, en el escenario para el que existe, produce silencio en lugar de alarma. Lo que los caza no es revisar que las alertas estén, sino mutar la regla a su versión rota y ver si algo se pone rojo.

