De la alerta al arreglo (con freno). Cuando Prometheus dispara DiskPressure o un aluvión de pods Failed, Telegram no es remediación. Es un aviso. Si quieres triage y, en casos seguros, arreglo automático, hace falta un gate local y una allowlist corta: no un agente en la nube que, por sí solo, redacte un informe bonito sin kubectl, Docker ni SSH a tu LAN.
Este post resume una decisión de diseño y una implementación real en homelab con Kubernetes (o k3s), kube-prometheus-stack (Prometheus + Alertmanager), un orquestador de webhooks (n8n u otro) y Docker en el nodo. También quieres un solo aviso, no tres mensajes diciendo lo mismo.
Encaja con el SOC proactivo en homelab (Alertmanager → n8n como inbox) y con la idea de que un contenedor no es frontera de seguridad: la limpieza de disco del host no se hace desde un Job cuyo /tmp no es el del nodo.
No hace falta Cursor Automations ni un agente remoto. Hace falta código en el nodo y un freno de mano.
El problema que intentábamos resolver
Tras un incidente clásico de homelab (disco lleno por temporales de escáner, taint DiskPressure, miles de pods Failed, servicios en Pending), quedaron tres huecos:
- Alertas incompletas — el stack por defecto avisa de CrashLoop / NotReady, pero no siempre de “el nodo se va a ahogar”.
- Investigación manual — había un script de diagnóstico, pero nadie lo lanzaba al dispararse la alerta.
- Ruido y duplicados — Alertmanager y el webhook (n8n) podían avisar al mismo canal; el template incluso decía “investigación automática” sin hacer nada.
La tentación: enchufar un agente LLM a cada firing. La realidad: un agente en la nube, por sí solo, no tiene kubectl, Docker ni acceso SSH a tu LAN. Puede redactar un informe; no puede borrar temporales del host ni liberar el scheduler. Para eso hace falta código en el nodo.
Arquitectura: dos capas, empezando por la 0
Prometheus ──► Alertmanager
│
├─► Telegram directo (solo alertas NO remediables)
│
└─► Webhook (n8n u otro)
│
├─► ¿Allowlist + firing?
│ │
│ └─► HTTP local :PUERTO /remediate
│ │
│ └─► script en el HOST
│ · investiga
│ · aplica allowlist
│ · un informe a Telegram
│
└─► Telegram inbox (solo lo que AM no cubre,
o “RESUELTO” de allowlist)
Capa 0 (esta): investigación + remediación determinista con allowlist.
Capa 1 (opcional, más adelante): un agente local (por ejemplo con cwd en el repo) solo si la capa 0 no cierra el incidente.
No recomendado como ejecutor: agentes cloud o automatizaciones remotas para arreglar el nodo.
Principios de la allowlist de remediación
- Allowlist, no “haz lo que creas”. Solo acciones acotadas, reproducibles y de bajo impacto.
- La remediación de disco corre en el host, no dentro de un Job de Kubernetes cuyo
/tmpno es el del nodo. - Un canal de aviso por familia de alerta. Si Alertmanager ya telegea CrashLoop, el webhook no vuelve a telear lo mismo. Si la allowlist remedia DiskPressure, el único mensaje útil es el informe post-acción (más un “RESUELTO” corto si quieres).
- Cooldown por
groupKey/ fingerprint — evita bucles cadagroupInterval. - Un solo lock (
flock) — el cooldown evita re-lanzar la misma alerta; el lock evita que dos alertas distintas limpien a la vez. - Verificar después de actuar (
df, condiciónDiskPressure, conteo de Failed). No declarar victoria por haber lanzado unrm.
Allowlist de ejemplo (ajusta a tu entorno)
| Alerta | Permitido | Nunca automático |
|---|---|---|
DiskPressure / disco alto |
Temporales conocidos (p. ej. /tmp/fanal-*, /tmp/analyzer-fs-*); journalctl --vacuum-time=7d; docker builder prune -af; borrar Failed si superan umbral |
Volúmenes, namespaces, docker rm -v |
PodsMassFailed |
Solo kubectl delete pods --field-selector=status.phase=Failed |
Pods Running / Pending; tocar disco |
NodeTaint / Pending por taint |
Investigar + las acciones de disco si el taint es DiskPressure |
Quitar taints arbitrariamente |
docker builder prune -af no es reversible: borra caché reconstruible. Entra en allowlist porque es acotado y de bajo impacto, no porque se pueda deshacer.
También humano (nunca automático): crontab, daemons de seguridad, firewall, subir limits a ciegas.
Umbral razonable de Failed en un nodo pequeño: > 20 (ajusta; en incidentes graves verás cientos o miles).
Piezas que necesitas
1. Reglas Prometheus que midan el fallo de verdad
Además de las reglas del chart, conviene tener expresiones para:
- uso de disco raíz por encima de un umbral de aviso
kube_node_status_condition{condition="DiskPressure",status="true"}- taints
NoSchedule count(kube_pod_status_phase{phase="Failed"})por encima de umbrales- Pending largo sin programar
Etiqueta severity y annotations con summary / description útiles. Un runbook_url ayuda a humanos; la allowlist no depende de él.
2. Alertmanager: dos destinos, sin solaparse
- Receiver Telegram — alertnames de workloads no remediables (CrashLoop, NotReady, JobFailed, OOM informativo…).
- Receiver webhook —
warning|criticalhacia tu orquestador (http://ORQUESTADOR:5678/webhook/...o similar).
Las alertas de la allowlist no deberían ir al receiver Telegram de Alertmanager si el script ya va a mandar el informe. Si no, tendrás doble (o triple) notificación.
Plantillas HTML/Markdown válidas para tu versión de Alertmanager: evita helpers que no existan o acabarás con message text is empty.
3. Script de investigación + remediación en el host
Un bash (o Python) que:
- Reciba un tipo (
DiskPressure,PodsMassFailed,auto, …) y flags--remediate/--dry-run/--no-telegram. - Recoja estado:
df, condiciones del nodo, conteos de pods, restos del patrón de temporales. - Si
--remediatey el tipo está en allowlist, ejecute solo esas acciones, bajo un lock:
flock -n /run/soc-remediation.lock \
/ruta/investigate.sh DiskPressure --remediate
- Re-mida y envíe un resumen al canal (causa, antes/después, qué se hizo, qué se saltó).
Importante: si lo lanzas como Job de Kubernetes, monta el script por hostPath solo para investigar. La limpieza de /tmp del host y docker del host deben ejecutarse en un proceso del nodo (systemd user/system, o el mismo usuario que opera el lab).
4. Mini-webhook HTTP en el host
Un servicio mínimo (stdlib Python basta) en 0.0.0.0:PUERTO:
GET /healthPOST /remediatecon el JSON de Alertmanager- ignora
resolved - mapea
alertname→ tipo del script - cooldown en ficheros bajo
/tmp/... - responde
202y ejecuta el script en background (para no tumbar el timeout del orquestador)
- ignora
El webhook no debe quedar expuesto a Internet. Limítalo a la LAN/VLAN del lab y, preferiblemente, valida un token compartido o una firma antes de aceptar /remediate. Escuchar en 0.0.0.0 solo es aceptable si el firewall ya filtra quién puede hablar con ese puerto.
Ejemplo de mapeo:
NodeDiskPressureActive, NodeHighDiskUsage → DiskPressure
NodeTaintNoSchedule, PodPendingTaint → NodeTaint
PodsMassFailedCritical / Warning → PodsMassFailed*
5. Orquestador (n8n u otro)
Al recibir el webhook de Alertmanager:
- Parsear
status,commonLabels.alertname,alerts[]. - Dedupe de Telegram:
- allowlist +
firing→ no mandar mensaje corto (el script informará). - allowlist +
resolved→ un “RESUELTO” corto (opcional). - alertnames que ya telegea Alertmanager → no repetir.
- resto → inbox.
- allowlist +
- Si allowlist +
firing→POST http://HOST_LAN:PUERTO/remediatecon el body original.
El contenedor del orquestador debe poder alcanzar la IP LAN del host (no 127.0.0.1 del contenedor). El mismo aviso de red que en el paso Alertmanager → n8n del SOC: el pod tiene que llegar al webhook, y el orquestador a la IP del nodo.
6. Servicio que sobreviva reinicios
systemd --user (con linger) o unit de sistema:
[Service]
ExecStart=/usr/bin/python3 /ruta/soc-remediate-webhook.py
Environment=KUBECONFIG=/ruta/.kube/config
Restart=on-failure
Implementación paso a paso
-
Escribe y prueba el script a mano
bash investigate.sh DiskPressure --dry-run --no-telegramLuego sin--dry-runen un entorno de prueba. -
Levanta el webhook y comprueba
curl -s http://HOST:PUERTO/healthcurl -s -X POST http://HOST:PUERTO/remediate?dry_run=1 -H 'Content-Type: application/json' -H 'Authorization: Bearer TOKEN' -d @alerta-ejemplo.json -
Separa receivers en AlertmanagerConfig / config Allowlist fuera del Telegram directo; webhook para severidades altas.
-
Cablea el orquestador con las dos ramas (Telegram único + remediate).
-
Fuerza una alerta de prueba (o un POST sintético al path del webhook) y confirma:
- un solo mensaje de informe
- acciones en el log del script
- cooldown: un segundo POST no relanza en N horas
- lock: un segundo tipo de alerta no arranca otra limpieza en paralelo
-
Documenta la allowlist en el repo del lab (qué sí / qué no). Quien herede el cluster tiene que poder leerlo sin adivinar.
JSON de prueba (sin secretos)
{
"status": "firing",
"groupKey": "test:{alertname=\"NodeHighDiskUsage\"}",
"commonLabels": {
"alertname": "NodeHighDiskUsage",
"severity": "warning"
},
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "NodeHighDiskUsage",
"severity": "warning"
},
"annotations": {
"summary": "Disco raíz por encima del umbral de aviso"
}
}
]
}
Errores que conviene no repetir
- Decir en la plantilla de Telegram “iniciando investigación automática” sin cablear el script.
- Remediar desde un contenedor sin montar el
/tmp(o el socket Docker) del host y creer que limpiaste el nodo. - Dejar
set -o pipefail+ls /tmp/patron-*sin matches:lssale con código 2 y tumba el script. Usafindoshopt -s nullglob. - Multiplicar agentes cloud por cada
KubePodNotReadyflapping: caro y poco útil sin datos de host. - Declarar el incidente resuelto sin comprobar runtime (réplica
1/1, HTTP del servicio,DiskPressure=False).
Qué ganas
- Tiempo de respuesta medido en minutos, no en “cuando alguien mire Telegram”.
- Acciones predecibles y auditables (log de allowlist + informe).
- Menos ruido: un mensaje bueno > tres mensajes mediocres.
- Base limpia si más adelante quieres un agente local: solo cuando la allowlist no baste.
Qué no es este post
No es un sustituto de backups, de capacidad de disco bien dimensionada, ni de higiene en los propios jobs (el escáner debería limpiar temporales al salir; el cron/allowlist es red de seguridad). Tampoco es “IA que administra el cluster”: es un runbook ejecutable con freno de mano.
Si tu lab ya tiene Prometheus, Alertmanager y un webhook, puedes montar la capa 0 en una tarde. El valor no está en el modelo de lenguaje; está en no mentir en el template y en tocar solo lo que está en la lista.
Siguiente paso
Cuando la allowlist no pueda cerrar el incidente, el siguiente paso no será darle acceso total a un LLM, sino escalar el diagnóstico a un agente local con herramientas y permisos igualmente acotados.