Contactar →
← Todas las entradas
Operaciones2026.08.05

Fatiga de alertas por diseño

La configuración de alerting de esta plataforma tiene una regla que suprime automáticamente una alerta de nivel de advertencia mientras ya está activa una alerta relacionada de nivel crítico para la misma condición subyacente. El on-call recibe un aviso una vez por la causa raíz, no dos veces por el mismo fallo disfrazado con dos etiquetas de severidad distintas. Suena a un detalle pequeño para dejarlo escrito como regla, pero es el tipo de detalle pequeño que decide si una rotación de on-call confía en su propio pager.

El problema que resuelve

Los umbrales de alerta suelen venir en pares: una advertencia al 80% y un crítico al 95%, o una advertencia cuando una comprobación falla una vez y un crítico cuando falla tres veces seguidas. Es una forma razonable de definir umbrales: para el tipo basado en métricas, en concreto; ese mismo emparejamiento no se traslada limpiamente a las alertas basadas en logs o en uptime, que fallan a su manera. Es una mala forma de definir notificaciones, porque cuando la condición subyacente empeora lo suficiente como para cruzar ambos umbrales, ambas alertas se disparan; y sin nada que lo corrija, el on-call recibe dos avisos por un mismo problema, escalonados por el tiempo que tarde la métrica en pasar de advertencia a crítico.

Dos avisos por un mismo incidente no cuestan solo unos segundos extra de triaje. Le enseñan a quien lleva el pager que el sistema de alerting no modela la causalidad: que trata “el disco está al 82%” y “el disco está al 97%” como dos hechos no relacionados sobre el mundo, en lugar de dos lecturas de la misma tendencia. Con suficiente repetición de eso, la respuesta natural es dejar de confiar en los avisos individuales al pie de la letra, que es el mecanismo real detrás de la fatiga de alertas: no un volumen excesivo en abstracto, sino un volumen excesivo que, al examinarlo, resulta haber sido redundante.

Inhibición, no solo enrutamiento

La solución no es una regla de enrutamiento (envía X a Slack, Y a PagerDuty): es supresión a nivel de alerta, para que la notificación redundante nunca llegue a dispararse. Prometheus Alertmanager, probablemente la herramienta más extendida para exactamente este trabajo, llama a este mecanismo una regla de inhibición (inhibition rule), y su comportamiento documentado es casi una descripción directa del problema: una regla de inhibición silencia las notificaciones de las alertas que coinciden con un conjunto de matchers de destino cuando existe otra alerta (la alerta origen) que coincide con un conjunto separado de matchers de origen, siempre que un conjunto especificado de labels tengan valores idénticos en ambas alertas (documentación de configuración de Alertmanager).

En concreto: una regla con source_matchers: [severity="critical"] y target_matchers: [severity="warning"], correlacionada mediante equal: [alertname, cluster, service], significa que una alerta crítica para una combinación dada de alertname/cluster/service suprime la alerta de advertencia para esa misma combinación exacta, no las advertencias en general, solo la que realmente describe la misma condición subyacente que ya describe la alerta crítica. Esa lista equal es la parte que mantiene honesta a la regla. Sin ella, una única alerta crítica en algún sitio podría suprimir advertencias no relacionadas en todo el entorno, lo que cambia un tipo de fatiga de alertas (avisos redundantes) por otro peor (señal descartada en silencio que no tenía nada que ver con lo que la inhibió).

Una alerta crítica activa inhibe la alerta de advertencia correspondiente a la misma condición subyacente: el on-call recibe un único aviso.

La documentación de Alertmanager también es específica sobre un caso límite que merece la pena conocer: una label ausente y una label con un valor vacío se tratan como lo mismo a efectos de coincidencia, y una alerta no se inhibe a sí misma aunque llegue a coincidir con el lado de origen y el de destino de una misma regla a la vez. Ambos son del tipo de detalle que solo importa una vez, justo hasta el momento en que una regla no se dispara silenciosamente como esperabas porque una label estaba ausente en lugar de vacía, y entonces importa mucho.

Verificar que la regla hace lo que dice

Una regla de inhibición es configuración, y la configuración incorrecta no se anuncia sola: o bien falla a la hora de suprimir algo que debería haber suprimido (el aviso redundante sigue disparándose), o bien suprime algo que no debería (una alerta genuinamente independiente se queda muda porque resultó compartir una label). Ambas direcciones de fallo son invisibles hasta el momento en que importan, que es exactamente el peor momento para descubrir un desajuste entre lo que la regla debía hacer y lo que realmente hace en cuanto empiezan a llegarle alertas reales.

La defensa razonable es verificar la regla contra payloads de alerta realistas antes de confiar en ella en producción, no limitarse a leer el YAML y confiar en que los matchers y la lista equal dicen lo que uno cree que dicen. Eso puede ser tan directo como construir un par de alertas representativas activas (una en el lado origen, otra en el lado destino, compartiendo las labels que la lista equal de la regla espera) y confirmar que la de destino queda realmente suprimida y, por separado, confirmar que una alerta de forma parecida pero que difiere en una de esas labels de equal no queda suprimida. La segunda comprobación importa tanto como la primera: una regla que suprime todo lo que apunta no resulta obviamente rota hasta que se prueba específicamente el límite en el que se supone que deja de aplicarse.

Lo que este patrón decide realmente

Hay una decisión de diseño escondida en “suprimir la advertencia cuando el crítico está activo” que merece la pena hacer explícita: decide que los niveles de severidad para una misma condición subyacente no son señales independientes, son una línea de tiempo de una única señal. Advertencia y crítico no son dos problemas distintos que resultan estar correlacionados: son el mismo problema observado en dos puntos distintos de su camino hacia empeorar. Una vez que eso es cierto, notificar en ambos es redundante por construcción, y la solución honesta es estructural (inhibición) y no conductual (pedirle al on-call que deduplique mentalmente dos avisos que llegan con cuatro minutos de diferencia).

Eso es distinto de otras técnicas de reducción de ruido que se parecen a esta desde lejos. El agrupamiento (grouping) combina varias alertas en una sola notificación porque están relacionadas pero no necesariamente conectadas causalmente: varios pods en crash-loop dentro del mismo deployment, por ejemplo. El silenciamiento (silencing) es un override manual y acotado en el tiempo para un mantenimiento conocido. La inhibición no es ninguna de las dos cosas: es una regla automática y continua que codifica “si esto ya es cierto, aquello otro no es información nueva”. Es la única de las tres que exige entender de verdad la relación causal entre dos condiciones de alerta antes de poder escribir la regla: no se puede inhibir correctamente sin antes ser honesto sobre qué alertas son en realidad solo versiones más ruidosas de la misma cosa.

Dónde se generaliza esto más allá de un archivo de configuración

Lo interesante no es la forma concreta del YAML de Alertmanager: es que este mismo razonamiento se aplica a cualquier sistema de alerting con umbrales de severidad en cascada, ya se llame al mecanismo real inhibición, supresión basada en dependencias, o de cualquier otra forma. La pregunta subyacente es siempre la misma: cuando una condición empeora lo suficiente como para cruzar dos umbrales, ¿sabe el sistema de alerting que esos dos cruces describen el mismo evento, o los trata como dos hechos separados que resultan tratar de la misma métrica?

Equivocarse en cualquiera de las dos direcciones tiene un coste. Suprimir con demasiada agresividad (una lista equal demasiado amplia, o matchers demasiado permisivos en el lado de origen) y un problema genuinamente independiente se queda callado porque resultó compartir una label con otra cosa que ya estaba activa. Suprimir con demasiada poca agresividad, o no suprimir en absoluto, y cada cruce de umbral se convierte en su propio aviso, que es exactamente cómo una rotación acaba con un pager que suena constantemente y se ignora por reflejo. El trabajo de ingeniería real aquí no es activar la inhibición; es ser preciso con las labels de equal, porque esa precisión es toda la diferencia entre “el on-call confía en cada aviso” y “el on-call ha aprendido a triajar el pager antes de triajar el incidente”.

Repasando el caso del disco lleno

Es más fácil sentir por qué importa esto con un escenario concreto (aunque ilustrativo, no una cifra real de esta plataforma) en lugar de uno abstracto: un umbral de nivel de advertencia y un umbral de nivel crítico definidos sobre la utilización de disco del mismo filesystem, ambos con alertname="DiskSpaceLow" más una label host y mountpoint.

Sin inhibición, un disco que se llena poco a poco (logs que no rotan, por ejemplo) cruza primero el umbral de advertencia y dispara un aviso. Algo más tarde cruza el umbral crítico y dispara otro aviso. El on-call tiene ahora dos notificaciones separadas para una única condición que va empeorando de forma continua, y tiene que hacer la correlación a mano: ¿es esta alerta crítica el mismo problema que aquella advertencia anterior, o es algo segundo, no relacionado, que resulta haberse disparado por las mismas fechas? En un incidente real, con varias cosas más ocurriendo a la vez, esa correlación no siempre es obvia de un vistazo, y equivocarse cuesta tiempo justo cuando el tiempo es caro.

Con una regla de inhibición (source_matchers: [alertname="DiskSpaceLow", severity="critical"], target_matchers: [alertname="DiskSpaceLow", severity="warning"], equal: [alertname, host, mountpoint]), la advertencia para ese host y ese mountpoint concretos se calla en el momento en que se dispara el crítico para ese mismo host y mountpoint. El on-call recibe exactamente una notificación, y es la más urgente de las dos, que además es la que realmente necesita respuesta. La advertencia no desaparece del sistema (Alertmanager sigue sabiendo que está activa, y se des-suprimirá automáticamente si el crítico se resuelve mientras la condición de advertencia persiste de alguna forma), simplemente deja de generar una notificación redundante mientras ya hay abierta otra más severa sobre la condición idéntica.

Mantener la regla a medida que se mueven los umbrales

Las reglas de inhibición no son un artefacto de escribir una vez, igual que tampoco lo son los umbrales de alerta de los que dependen. Si alguien más adelante reajusta el umbral de advertencia para detectar antes una fuga lenta, o divide DiskSpaceLow en variantes por tipo de filesystem, las labels de equal y los matchers necesitan revisarse contra la nueva forma de las alertas: una regla que estaba correctamente delimitada contra el conjunto de labels antiguo puede dejar de coincidir en silencio, o empezar a coincidir de forma demasiado amplia, en cuanto las alertas de debajo cambian de forma. Eso no es una razón para evitar la inhibición; es una razón para tratar la configuración de inhibición como parte de la misma revisión cada vez que se revisan las propias definiciones de alerta, en lugar de como una pieza de configuración aparte que se monta una vez durante el despliegue inicial y luego se olvida hasta que algo vuelve a avisar dos veces por sorpresa.

Fuentes:

← Todas las entradas