Volver al blog
AIOpsNOCMonitoringSREAlertmanager

AIOps en la práctica: cómo reducir la fatiga de alertas, de Alertmanager al machine learning

Mide qué alertas saltan y se ignoran, usa la agrupación y la inhibición de Alertmanager, arregla las etiquetas y añade machine learning solo donde hace falta.

P
Davi Nunes
February 25, 2026Actualizado October 30, 202611 min de lectura

Un pager que suena docenas de veces cada noche no hace que un equipo sea más cuidadoso. Le enseña a reconocer primero y leer después, hasta que la alerta que importaba recibe el mismo trato que todas las demás.

AIOps se vende como la cura: pon machine learning sobre el flujo de alertas y deja que separe la señal del ruido. Puede ayudar, pero un modelo entrenado con alertas sin etiquetas, sin dueño y sin registro de cuáles se atendieron aprende tu ruido con mucha precisión. El orden que funciona: mide la avalancha, aprovecha lo que Alertmanager ya hace, arregla etiquetas y dependencias, y solo entonces pon machine learning donde una regla estática no puede hacer el trabajo.

Este artículo trata de la avalancha que ya tienes. Para decidir qué alertas crear en primer lugar, consulta nuestra guía sobre alertas de Kubernetes que siguen siendo accionables.

Mide la avalancha antes de comprar nada

No puedes reducir un ruido que no has contado, y a menudo el ranking está muy desequilibrado: un puñado de reglas produce la mayoría de los avisos.

Qué alertas saltan más

Prometheus escribe una serie ALERTS por cada alerta que producen sus reglas, con las etiquetas alertname, alertstate (pending o firing) y las propias etiquetas de la alerta. Dos consultas sobre ella ordenan tus reglas:

promql
# Time spent firing: rule evaluations in which each alert was firing,
# summed over all its label sets (pods, instances, ...)
topk(20, sum by (alertname) (
  count_over_time(ALERTS{alertstate="firing"}[7d])
))

# Breadth: how many distinct label sets each alert fired for this week
topk(20, count by (alertname) (
  count_over_time(ALERTS{alertstate="firing"}[7d])
))

La primera consulta cuenta muestras, no disparos: multiplica por el intervalo de evaluación de tus reglas para obtener el tiempo en disparo. Una alerta que sigue activa durante días sin que nadie la arregle o no hace falta o se está ignorando; ambos casos exigen una decisión. La segunda saca a la luz las alertas que se disparan por separado para cada pod o instancia de una flota, candidatas a agregarse o agruparse.

Para encontrar reglas que oscilan (flapping), cuenta cuántas veces empieza a dispararse cada alerta:

promql
# Roughly how many times each alert went from not firing to firing in the last day
topk(20, sum by (alertname) (
  count_over_time(
    (ALERTS{alertstate="firing"} unless ALERTS{alertstate="firing"} offset 1m)[1d:1m]
  )
))

Una regla que se activa muchas veces al día y dura unos minutos cada vez necesita una duración for: más larga, o keep_firing_for en las versiones recientes de Prometheus, no a una persona.

Dos límites: ALERTS solo cubre las reglas que evalúa Prometheus e ignora los silencios y la inhibición, así que muestra lo que se disparó, no lo que llegó a una persona. Además, un rango de 7 días necesita al menos una semana de retención.

Qué alertas se reconocen y se ignoran

Alertmanager no tiene el concepto de reconocimiento (acknowledgment). Ese dato vive en tu herramienta de on-call, así que exporta los avisos de los últimos 30 días y, para cada alertname, anota cuántas veces avisó, cuántas veces alguien actuó (un fix, un rollback, un ticket, un paso del runbook) y cuántas se reconoció y cerró sin hacer nada o se resolvió sola antes de que nadie la mirara.

Si la exportación no muestra si hubo acción, pide a quien esté de on-call que clasifique cada aviso en una revisión semanal como atendido, solo reconocido o duplicado. Lista también los silencios de larga duración con amtool silence query: una alerta silenciada durante meses es una alerta en la que nadie confía.

Cada regla recibe entonces un veredicto: eliminarla, degradarla a ticket o dashboard, corregirla (umbral, duración, etiquetas) o mantenerla como aviso.

Lo que Alertmanager ya hace antes de cualquier machine learning

Buena parte de la correlación que anuncian las plataformas de AIOps empieza con cuatro mecanismos que Alertmanager tiene desde hace años:

  • Agrupación. group_by decide qué alertas comparten una notificación. group_wait retrasa la primera notificación de un grupo nuevo para que las alertas relacionadas, y cualquier alerta que deba inhibirlas, lleguen juntas: es la correlación temporal en su forma más simple. group_interval marca el ritmo de las notificaciones sobre alertas añadidas a un grupo existente, y repeat_interval fija cuándo se reenvían las alertas que siguen activas.
  • Enrutamiento. Un árbol de rutas basadas en etiquetas envía las alertas críticas al pager y el resto a las colas de cada equipo. Gana la primera ruta que coincide, salvo que tenga continue: true.
  • Inhibición. Mientras una alerta de origen está activa, las alertas de destino que coinciden quedan silenciadas si las etiquetas listadas en equal coinciden en ambas.
  • Silencios. Silenciamientos con caducidad para trabajos planificados.

Un punto de partida realista que pasa amtool check-config:

yaml
route:
  receiver: team-tickets
  group_by: ['env', 'system']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - receiver: oncall-pager
      matchers:
        - severity="critical"
      group_wait: 10s
      repeat_interval: 1h
    - receiver: payments-queue
      matchers:
        - team="payments"
    - receiver: dashboard-only
      matchers:
        - severity="info"

inhibit_rules:
  # A node that is down explains every other alert on that node
  - source_matchers:
      - alertname="KubeNodeNotReady"
    target_matchers:
      - alertname!="KubeNodeNotReady"
    equal: ['cluster', 'node']
  # A database that is down explains the errors of the apps that use it
  - source_matchers:
      - alertname="PostgresDown"
      - system!=""
    target_matchers:
      - tier="app"
    equal: ['env', 'system']

receivers:
  - name: oncall-pager
    webhook_configs:
      - url: 'http://pager-bridge.monitoring.svc:8080/alerts'
  - name: payments-queue
    webhook_configs:
      - url: 'http://ticket-bridge.monitoring.svc:8080/payments'
  - name: team-tickets
    webhook_configs:
      - url: 'http://ticket-bridge.monitoring.svc:8080/default'
  - name: dashboard-only

system y tier son etiquetas tuyas (más abajo hablamos de ellas), y PostgresDown representa tu alerta de base de datos. Un receiver sin configuración descarta lo que le llega, lo que encaja con las alertas informativas.

La regla del nodo esconde una trampa. Las alertas de pods basadas en kube-state-metrics llevan namespace y pod pero no la etiqueta node, y su etiqueta instance apunta al propio kube-state-metrics. Hasta que añadas node, esa regla de inhibición no hace nada y no te avisa. Añádela en la expresión de la regla del pod con un join:

promql
(
  max by (cluster, namespace, pod) (
    increase(kube_pod_container_status_restarts_total[15m])
  ) > 3
)
* on (cluster, namespace, pod) group_left(node)
  max by (cluster, namespace, pod, node) (kube_pod_info)

Para los trabajos planificados, haz que el pipeline cree un silencio acotado y con caducidad, en lugar de que alguien silencie un canal entero:

bash
amtool silence add env=prod system=orders \
  --duration=2h \
  --comment="Planned PostgreSQL failover, change 4812" \
  --alertmanager.url=http://alertmanager.monitoring.svc:9093

¿Quieres el inventario sin construirlo tú? Nuestra evaluación gratuita del ruido de alertas ejecuta estas consultas sobre tu Prometheus, revisa tus reglas de enrutamiento e inhibición y te entrega una lista priorizada de reglas que eliminar, degradar, agrupar o corregir, antes de que gastes nada en herramientas.

La correlación empieza por las etiquetas y un mapa de dependencias

La agrupación y la inhibición solo funcionan con etiquetas, igual que cualquier motor de correlación, con o sin machine learning. Acuerda con tu equipo un conjunto que toda alerta deba llevar:

  • service: lo que está roto, con el nombre que le da el equipo responsable
  • team: quién recibe la notificación; una alerta sin equipo no tiene dueño
  • env y cluster (o región): dónde está pasando
  • severity: critical, warning o info, cada una con un único significado acordado

Pon cluster y env en global.external_labels de Prometheus, que Prometheus añade a cada alerta que envía a Alertmanager. Pon team y severity en el bloque labels: de cada regla, y haz que la CI falle con cualquier regla a la que le falte alguna: promtool check rules cubre la sintaxis y un script corto cubre las etiquetas. Hacer esto coherente entre clusters es la mayor parte del trabajo de una limpieza de observabilidad.

Después, escribe las dependencias, que es lo que necesita la correlación topológica. Para empezar basta con un fichero en Git que diga a qué llama cada servicio y sobre qué corre, y de él derivas una etiqueta system, tan obligatoria como las demás, que une una base de datos con las aplicaciones que la usan.

Un ejemplo práctico. Supón que orders-db, un primario de PostgreSQL, hace failover. En un par de minutos podrías recibir la alerta de la base de datos, dos alertas de retraso de réplica, tres alertas (tasa de errores, latencia, readiness probe fallida) de cada uno de los seis servicios que la usan, cuatro alertas de cola acumulada, dos alertas de burn rate del SLO y una comprobación sintética del checkout fallida. Son 28 notificaciones para una sola causa.

Con el mapa convertido en etiquetas, el mismo evento produce un único aviso. Todo lo que pertenece al sistema de pedidos lleva system="orders", así que la ruta lo agrupa en una sola notificación, y la regla de inhibición de la base de datos silencia las alertas de aplicación mientras PostgresDown esté activa. Da a tus alertas de SLO su propio tier, por ejemplo tier="slo", para que esa regla nunca las silencie: te dicen que los clientes están afectados, algo que la alerta de la base de datos no puede decirte.

Dónde ayuda el machine learning y dónde no

Cuando las alertas están etiquetadas, tienen dueño y están agrupadas, queda un conjunto más pequeño de problemas en los que un modelo entrenado supera a una regla fija.

Dónde ayuda

Métricas sin un buen umbral estático. El volumen de pedidos o la profundidad de una cola pueden ser normales a las 03:00 y alarmantes en el mismo nivel a las 11:00. Prueba una comparación estacional antes de recurrir a un modelo (orders_total representa cualquier contador de negocio que exportes):

promql
sum(rate(orders_total[30m]))
  < 0.5 * sum(rate(orders_total[30m] offset 1w))

Salta cuando la última media hora va a menos de la mitad del ritmo de la misma media hora una semana antes. Falla en festivos y después de grandes lanzamientos, y ahí es donde un modelo que aprende varios patrones estacionales se gana su sitio. Lo mismo vale para el propio flujo de alertas: un servicio que normalmente genera un par de alertas al día y de repente genera cincuenta te está diciendo algo, aunque cada una sea solo un warning.

Correlacionar alertas con cambios. La primera pregunta en cualquier llamada de incidente es qué ha cambiado. Haz que tu pipeline registre deploys, cambios de configuración, cambios de feature flags y rotaciones de certificados como eventos con las mismas etiquetas service y env que tus alertas, y muestra en cada incidente los cambios recientes del servicio afectado y de sus dependencias. Un modelo puede entonces aprender qué cambios suelen preceder a qué alertas y ordenar a los sospechosos.

Patrones recurrentes. Las mismas alertas cada domingo a las 02:00 mientras corre el backup. Agrupar incidentes pasados por la huella de sus alertas permite al sistema decir "esto se parece al incidente del mes pasado, y esto es lo que lo arregló". Eso requiere un histórico con etiquetas coherentes y resultados registrados, que es justo lo que el paso de medición empieza a construir.

Dónde no ayuda

  • No puede correlacionar alertas que no comparten etiquetas. Si service se escribe de tres formas, el modelo ve tres servicios.
  • No puede enrutar una alerta que no tiene dueño, ni aprender de un feedback que nadie da.
  • No puede arreglar una mala alerta. Una regla de CPU por encima de 80 que nunca importó acaba aprendida como ruido y suprimida, igual que ya hacen tus ingenieros a mano. Elimínala.
  • No debería ser una caja negra. Si quien está de on-call no ve por qué se agruparon o suprimieron las alertas, deja de confiar en la agrupación y vuelve a leerlas todas.

Las alertas de SLO basadas en síntomas son la solución a largo plazo

Todo lo anterior reduce el ruido de las alertas basadas en causas. La solución duradera es avisar por menos de ellas: avisa cuando los usuarios están afectados, medido contra un objetivo de nivel de servicio (SLO), y envía las causas como CPU, memoria y reinicios de pods a tickets y dashboards.

Las alertas de burn rate multiventana lo hacen práctico. Con un objetivo de disponibilidad del 99,9% en 30 días, tu presupuesto de errores es el 0,1% de las peticiones. Un burn rate de 1 gasta ese presupuesto en exactamente 30 días, es decir, 720 horas. Quemar a 14,4 veces ese ritmo durante una hora gasta 14,4 / 720 = 2% del presupuesto del mes, lo que justifica despertar a alguien:

promql
(
  sum(rate(http_requests_total{service="checkout", code=~"5.."}[1h]))
  /
  sum(rate(http_requests_total{service="checkout"}[1h]))
) > (14.4 * 0.001)
and
(
  sum(rate(http_requests_total{service="checkout", code=~"5.."}[5m]))
  /
  sum(rate(http_requests_total{service="checkout"}[5m]))
) > (14.4 * 0.001)

La ventana de una hora demuestra que el consumo es sostenido; la de cinco minutos hace que la alerta se desactive poco después de la recuperación. Un par más lento, como un burn rate de 6 en seis horas (6 × 6 / 720 = 5% del presupuesto), detecta fugas lentas y puede abrir un ticket en lugar de avisar. Adapta los nombres de las métricas a lo que exportan tus servicios, y fija los objetivos con las personas responsables del servicio, no por ellas; esa conversación es el núcleo de nuestro trabajo de consultoría SRE.

Automatiza los runbooks, con salvaguardas

Algunas alertas tienen una respuesta conocida y segura. Automatizarla elimina un aviso; automatizar lo que no toca provoca una caída a velocidad de máquina.

Se pueden automatizar acciones idempotentes y reversibles sobre un único objetivo con una causa bien entendida, como reiniciar un worker con una fuga de memoria conocida y con ticket abierto, escalar dentro de un máximo predefinido, rotar logs en un volumen que se está llenando, renovar un certificado o hacer cordon de un nodo que reporta errores de hardware.

Deja en manos humanas cualquier cosa que borre datos, la promoción o el failover de una base de datos, los rollbacks de esquema, los cambios en reglas de firewall o políticas de acceso, las acciones que abarcan más de un dominio de fallo y cualquier caso en que el diagnóstico sea ambiguo.

Las salvaguardas importan más que los scripts:

  • Primero en dry run. Durante las primeras semanas, la automatización solo publica lo que habría hecho, para compararlo con lo que hizo el ingeniero.
  • Límites de frecuencia. Limita las ejecuciones por objetivo, por ejemplo tres por hora, y a partir de ahí para y avisa. Una corrección automática que se ejecuta cada día esconde un bug, así que cada ejecución abre también un ticket.
  • Auditoría e interruptor de emergencia. Cada acción queda registrada junto a su alerta, y un solo flag desactiva toda la automatización.
  • Etiquetas como filtro. La automatización solo actúa sobre alertas que llevan service, team y una anotación runbook_url.

Conéctalo como un receiver webhook de Alertmanager: Alertmanager envía un JSON con las etiquetas y anotaciones de cada alerta, y un pequeño servicio asocia los valores conocidos de alertname con acciones aprobadas.

Un plan de 90 días

Empieza por tu servicio o equipo más ruidoso, demuéstralo ahí y luego extiéndelo.

Mes 1 (días 1-30): medir y limpiar

  • Un inventario de alertas: cada regla con su dueño, severidad, enlace al runbook, tiempo en disparo y tasa de acción
  • El estándar de etiquetas por escrito y aplicado en la CI
  • Las reglas sin dueño o que nunca llevaron a una acción, eliminadas o degradadas con el acuerdo del equipo responsable
  • Las reglas que oscilan, corregidas con duraciones for: y keep_firing_for

Mes 2 (días 31-60): agrupar, inhibir, enrutar

  • Un árbol de enrutamiento por equipo y severidad, agrupado por sistema
  • Reglas de inhibición para tus tres mayores cascadas, como de nodo a pods y de base de datos a aplicaciones
  • Silencios creados por el pipeline, no a mano
  • El mapa de dependencias en Git para los servicios cubiertos
  • Alertas de burn rate para dos o tres recorridos de cliente, con las alertas de causa correspondientes degradadas
  • Las consultas del primer mes repetidas y comparadas con la línea base, junto con los avisos por turno de on-call

Mes 3 (días 61-90): añadir inteligencia donde compensa

  • Líneas base estacionales en las métricas que no tienen un umbral estático útil
  • Eventos de cambio del pipeline asociados a cada incidente
  • De uno a tres runbooks automatizados, en modo dry run antes de que actúen
  • Una decisión por escrito de comprar, construir o descartar una capa de correlación con machine learning, basada en lo que sigue haciendo ruido y en el histórico etiquetado que ahora tienes

Por dónde empezar

La agrupación, la inhibición, las etiquetas y las alertas de SLO no son glamurosas, pero funcionan con las herramientas que ya usas, y deciden si cualquier modelo que añadas después tendrá algo útil de lo que aprender. Nuestro equipo de operaciones NOC hace el triaje de alertas las 24 horas y monta esta configuración con los ingenieros responsables de los servicios.

La evaluación gratuita del ruido de alertas revisa tus alertas y avisos de los últimos 30 días, tus reglas de enrutamiento e inhibición de Alertmanager y la coherencia de tus etiquetas entre clusters, y te devuelve el inventario del primer mes junto con las tres cascadas que más merece la pena arreglar primero.