Arquitectura Self-Healing: Del Monitoreo Pasivo a la Auto-Reparación Inteligente
Share
Arquitectura Self-Healing: Del Monitoreo Pasivo a la Auto-Reparación Inteligente
El Problema: Demasiados Vigilantes, Ningún Doctor
Después de meses construyendo herramientas de monitoreo, nos encontramos con una paradoja: teníamos 10 monitores en Uptime Kuma reportando verde, 5 watchdogs corriendo en systemd, y aún así los servicios críticos fallaban silenciosamente hasta que un cliente reportaba el problema.
La razón era simple: detectar != reparar. Teníamos ojos por todas partes, pero ningún cerebro coordinando las respuestas.
La Evolución de Nuestro Sistema
Fase 1: Vigilancia Reactiva (Nov 2025)
Empezamos con lo básico: un MCP Orchestrator que funcionaba como hub de comunicación entre agentes de IA (Claude, Gemini, Aider). Su trabajo era mantener vivo el canal de mensajes usando WebSocket con auto-reconnect de 5 reintentos y backoff exponencial.
Fragmento simplificado del orchestrator
class MCPOrchestrator:
def __init__(self):
self.max_retries = 5
self.backoff_factor = 2
async def connect_with_retry(self, agent):
for attempt in range(self.max_retries):
try:
await agent.connect()
return True
except ConnectionError:
await asyncio.sleep(self.backoff_factor ** attempt)
return False
Funcionaba, pero solo para comunicación entre agentes. Los servicios externos seguían siendo puntos ciegos.
Fase 2: Protección Agresiva (Ene 2026)
Cuando Claude CLI empezó a consumir 150% CPU y congelar el sistema, creamos un watchdog que mataba procesos sin piedad. Efectivo pero brutal: perdíamos trabajo en progreso y generaba falsos positivos.
La lección: el contexto importa. Un proceso usando 200% CPU durante análisis de datos es normal; el mismo consumo durante idle es un leak de memoria.
Fase 3: Límites Kernel + Observación Inteligente
En lugar de matar procesos, implementamos cgroups v2 con límites duros:
[Slice]
CPUQuota=360%
MemoryMax=12G
MemorySwapMax=2G
TasksMax=512
El kernel se encarga de la contención, el watchdog V2 solo observa y alerta. Separación de responsabilidades: el kernel protege, el watchdog diagnostica.
Los Gaps que Descubrimos
Al auditar nuestro stack, encontramos patrones preocupantes:
1. Silos de Información
- Uptime Kuma sabe si un endpoint responde
- Fail2ban sabe si hay ataques SSH
- System Health Check sabe si el disco está lleno
- Ninguno habla entre sí
2. Acción sin Contexto
Cada watchdog tiene su propia lógica de restart, sin coordinar con los demás. Resultado: cuando PostgreSQL se reinicia, 3 servicios intentan reconectar simultáneamente, saturando el pool de conexiones.
3. Estado Fantasma
Systemd reporta `active (running)`, pero el servicio está en deadlock esperando un lock de base de datos que nunca se libera. Los health checks HTTP fallan, pero systemd no reinicia porque el proceso sigue vivo.
El Diseño del Orquestador Maestro
Arquitectura de 3 Capas
Capa 1: Colectores (Stateless)
- Uptime Kuma → endpoints HTTP/TCP
- Fail2ban → logs de seguridad
- System Health → métricas de hardware
- Systemd Journal → eventos de servicios
Cada colector envía eventos estructurados al orquestador vía API REST local.
Capa 2: Orquestador (Stateful)
- SQLite con WAL mode para estado persistente
- Event correlation engine: "PostgreSQL reinició hace 30s, 3 servicios fallaron health check justo después"
- Dependency graph: "antes de reiniciar n8n, verificar que PostgreSQL esté estable"
Capa 3: Executors (Idempotentes)
- Restart Service: verifica que no se reinició en los últimos 5min
- Clear Cache: solo si el uso de disco >80%
- Send Alert: agrupa alertas similares en ventanas de 10min
Flujo de Auto-Reparación
Uptime Kuma detecta n8n down
↓
Orquestador verifica dependency graph
↓
¿PostgreSQL está up? → NO
↓
Inicia secuencia: PostgreSQL → wait 10s → n8n
↓
Verifica health checks post-restart
↓
Éxito: log + alerta informativa
Fallo: escala a Telegram + deshabilita auto-restart
Principios de Diseño Aplicados
1. Fail-Safe, Not Fail-Proof
No buscamos eliminar fallos, sino contenerlos. Si un servicio falla 3 veces en 10 minutos, el orquestador lo marca como "requires human intervention" y deja de reiniciarlo.
2. Observabilidad Primero
Cada decisión del orquestador se loggea con contexto completo: qué detectó, qué decidió hacer, qué esperaba que pasara, qué pasó realmente.
3. Graceful Degradation
Si el orquestador mismo falla, los servicios individuales siguen teniendo sus systemd restart policies. El orquestador es un layer de inteligencia, no un single point of failure.
Lecciones para Implementar en Producción
Empieza con Read-Only Mode
Primera semana: el orquestador solo observa y loggea qué haría, sin ejecutar acciones. Usa esos logs para ajustar umbrales antes de darle permisos de ejecución.
Dependency Graph como Código
No hardcodees las dependencias. Usa un archivo declarativo que puedas versionar:
services:
n8n:
depends_on: [postgresql]
health_check: "curl -f http://localhost:5678"
max_restart_attempts: 3
restart_window: 600 # segundos
Alertas con Gradación
- INFO: servicio reiniciado exitosamente (solo log)
- WARNING: segundo restart en 10min (Telegram)
- CRITICAL: tercer fallo consecutivo (Telegram + email + deshabilitar auto-restart)
Conclusión
La diferencia entre monitoreo y self-healing no es técnica, es arquitectónica. No se trata de agregar más checks o hacer los watchdogs más agresivos. Se trata de:
- Correlación de eventos: entender que fallos aislados son síntomas de causas raíz compartidas
- Estado persistente: recordar qué se intentó antes para evitar loops infinitos
- Acción contextual: reiniciar un servicio solo cuando tiene sentido, no como reflejo automático
Nuestro stack pasó de "muchos vigilantes gritando" a "un médico diagnosticando y tratando". Y lo más importante: ahora los clientes no son los primeros en reportar fallos.