Guilgo Blog

Notas de mi quehacer diario con las técnologias.

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:

  1. Alertas incompletas — el stack por defecto avisa de CrashLoop / NotReady, pero no siempre de “el nodo se va a ahogar”.
  2. Investigación manual — había un script de diagnóstico, pero nadie lo lanzaba al dispararse la alerta.
  3. 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

  1. Allowlist, no “haz lo que creas”. Solo acciones acotadas, reproducibles y de bajo impacto.
  2. La remediación de disco corre en el host, no dentro de un Job de Kubernetes cuyo /tmp no es el del nodo.
  3. 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).
  4. Cooldown por groupKey / fingerprint — evita bucles cada groupInterval.
  5. Un solo lock (flock) — el cooldown evita re-lanzar la misma alerta; el lock evita que dos alertas distintas limpien a la vez.
  6. Verificar después de actuar (df, condición DiskPressure, conteo de Failed). No declarar victoria por haber lanzado un rm.

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 webhookwarning|critical hacia 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:

  1. Reciba un tipo (DiskPressure, PodsMassFailed, auto, …) y flags --remediate / --dry-run / --no-telegram.
  2. Recoja estado: df, condiciones del nodo, conteos de pods, restos del patrón de temporales.
  3. Si --remediate y el tipo está en allowlist, ejecute solo esas acciones, bajo un lock:
flock -n /run/soc-remediation.lock \
  /ruta/investigate.sh DiskPressure --remediate
  1. 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 /health
  • POST /remediate con el JSON de Alertmanager
    • ignora resolved
    • mapea alertname → tipo del script
    • cooldown en ficheros bajo /tmp/...
    • responde 202 y ejecuta el script en background (para no tumbar el timeout del orquestador)

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:

  1. Parsear status, commonLabels.alertname, alerts[].
  2. 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.
  3. Si allowlist + firingPOST http://HOST_LAN:PUERTO/remediate con 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

  1. Escribe y prueba el script a mano bash investigate.sh DiskPressure --dry-run --no-telegram Luego sin --dry-run en un entorno de prueba.

  2. Levanta el webhook y comprueba curl -s http://HOST:PUERTO/health curl -s -X POST http://HOST:PUERTO/remediate?dry_run=1 -H 'Content-Type: application/json' -H 'Authorization: Bearer TOKEN' -d @alerta-ejemplo.json

  3. Separa receivers en AlertmanagerConfig / config Allowlist fuera del Telegram directo; webhook para severidades altas.

  4. Cablea el orquestador con las dos ramas (Telegram único + remediate).

  5. 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
  6. 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: ls sale con código 2 y tumba el script. Usa find o shopt -s nullglob.
  • Multiplicar agentes cloud por cada KubePodNotReady flapping: 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.