Arquitectura Self-Healing: Del Monitoreo Pasivo a la Auto-Reparación Inteligente

Arquitectura Self-Healing: Del Monitoreo Pasivo a la Auto-Reparación Inteligente

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:

  1. Correlación de eventos: entender que fallos aislados son síntomas de causas raíz compartidas
  2. Estado persistente: recordar qué se intentó antes para evitar loops infinitos
  3. 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.

Regresar al blog

Deja un comentario