Problema
En entornos de producción con contenedores Docker, los fallos inesperados (por ejemplo, procesos que se detienen, puertos que dejan de responder o configuraciones que quedan corruptas) pueden pasar desapercibidos hasta que un cliente experimenta un error. Cuando el orquestador no está configurado para reiniciar automáticamente (por política, por falta de restart: always o porque el contenedor forma parte de un sandbox de pruebas), el estado fallido persiste y el servicio queda inoperativo.
Este patrón se repite en muchos setups: un contenedor crítico se detiene, el equipo de operaciones recibe una alerta tardía y debe intervenir manualmente para volver a ponerlo en marcha, validar que el servicio está saludable y registrar la acción. La intervención manual introduce tiempo de inactividad, errores humanos y una carga operativa que escala con el número de microservicios.
Causa
Los orígenes de estos fallos pueden agruparse en tres categorías habituales:
- Fallos internos del proceso – errores de aplicación, excepciones no capturadas o consumo excesivo de recursos que hacen que el proceso principal termine. Docker solo marca el contenedor como “exited”, pero no lo reinicia si la política lo impide.
- Problemas de infraestructura – pérdida de conectividad de red, discos saturados o reinicios del host que dejan contenedores en estado “dead”. En estos casos Docker tampoco interviene automáticamente.
- Acciones de prueba o caos – inyección deliberada de fallos (por ejemplo, con herramientas como
chaos-mesho scripts personalizados) para validar la resiliencia. Si el entorno de pruebas no permite reinicios automáticos, el contenedor queda detenido a la espera de una reparación manual.
En los tres casos la raíz del problema es la falta de un mecanismo que detecte el estado no deseado, ejecute una acción pre‑aprobada y verifique que el contenedor volvió a estar operativo antes de considerar el incidente resuelto.
Solución
Un agente de auto‑curación para Docker aborda el ciclo completo: monitorización, decisión, ejecución y auditoría. La arquitectura típica consta de los siguientes componentes:
- Watcher – proceso ligero que consulta el API de Docker (
docker ps -a --filter "status=exited"o la API HTTP) a intervalos definidos. Su única responsabilidad es identificar contenedores que no estén en el estado deseado. - Policy Engine – tabla de correspondencia entre estado detectado y acción permitida. Cada regla incluye:
- Identificador del contenedor (nombre, etiqueta o patrón de imagen).
- Acción permitida (
restart,run script,scale up). - Condiciones de verificación (puertos abiertos, endpoint
/healthz, logs recientes).
- Executor – módulo que, bajo control estricto, ejecuta la acción definida. No permite shells arbitrarios; solo llama a la API de Docker o a scripts pre‑aprobados almacenados en un directorio de solo‑lectura.
- Verifier – después de la acción, vuelve a inspeccionar el contenedor y realiza pruebas de salud (por ejemplo,
curl -f http://localhost:80/health). Si la verificación falla, el agente registra el intento y aborta, evitando bucles infinitos. - Logger/Auditor – escribe cada evento en un log estructurado (JSON) o en un sistema de observabilidad (ELK, Loki). Los logs incluyen timestamp, contenedor, acción, resultado y duración.
Implementación práctica
A continuación se muestra un esquema de cómo montar este agente con Bash + Docker CLI y un pequeño daemon en Python (solo como ejemplo; cualquier lenguaje que pueda consumir la API de Docker sirve).
- Definir la política en un archivo YAML (
policy.yaml):
containers:
- name: sandbox-demo-api
action: restart
health_check:
url: http://localhost:80/health
timeout: 5
- name: another-service
action: run_script
script: /opt/recovery/fix_another.sh
health_check:
url: http://localhost:8080/status
timeout: 10
- Watcher en Bash (ejecutado cada 30 s mediante
systemdocron):
#!/usr/bin/env bash
docker ps -a --filter "status=exited" --format "{{.Names}}" | while read cname; do
if grep -q "$cname" /opt/selfheal/active_containers.txt; then
# Ya está bajo proceso de reparación, saltar
continue
fi
echo "$cname" >> /opt/selfheal/active_containers.txt
/opt/selfheal/engine.py "$cname" &
done
- Engine en Python (simplificado):
#!/usr/bin/env python3
import sys, yaml, subprocess, time, requests, json, os
def load_policy():
with open('/opt/selfheal/policy.yaml') as f:
return yaml.safe_load(f)
def find_rule(name, policy):
for c in policy['containers']:
if c['name'] == name:
return c
return None
def restart_container(name):
subprocess.check_call(['docker', 'start', name])
def run_script(path):
subprocess.check_call([path])
def verify(url, timeout):
try:
r = requests.get(url, timeout=timeout)
return r.status_code == 200
except Exception:
return False
def main():
cname = sys.argv[1]
policy = load_policy()
rule = find_rule(cname, policy)
if not rule:
print(f'No policy for {cname}', file=sys.stderr)
return
start = time.time()
if rule['action'] == 'restart':
restart_container(cname)
elif rule['action'] == 'run_script':
run_script(rule['script'])
else:
print('Unsupported action', file=sys.stderr)
return
ok = verify(rule['health_check']['url'], rule['health_check']['timeout'])
duration = time.time() - start
log = {
"container": cname,
"action": rule['action'],
"verified": ok,
"duration_s": round(duration, 2),
"timestamp": time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
}
print(json.dumps(log))
# Cleanup
os.remove('/opt/selfheal/active_containers.txt')
if __name__ == '__main__':
main()
Este ejemplo muestra la lógica esencial: detección, acción permitida, verificación y registro estructurado. En producción se recomienda:
- Ejecutar el engine dentro de un contenedor con permisos limitados (
--cap-drop ALLy solodocker.sockmontado). - Usar un sistema de colas (Redis, RabbitMQ) para evitar concurrencia.
- Añadir reintentos con back‑off exponencial y límite de intentos.
Cuándo aplicar esta solución
Señales de que vale la pena:
- Contenedores críticos que no usan políticas de reinicio automático por razones de seguridad o pruebas.
- Entornos donde la tolerancia a fallos es alta y el tiempo de inactividad debe mantenerse en segundos.
- Equipos que ya cuentan con un pipeline de CI/CD y pueden versionar políticas de reparación como código.
Escenarios donde no es necesario:
- Clústeres Kubernetes con
livenessProbeyrestartPolicyconfigurados; la propia plataforma ya gestiona la auto‑curación. - Servicios sin requisitos de alta disponibilidad (por ejemplo, jobs batch que pueden fallar y volver a lanzar).
- Infraestructuras donde la política de “no reinicio automático” es deliberada y la intervención humana es parte del proceso de auditoría.
Código
# Instalar dependencias (Docker CLI, python3, pip, requests, pyyaml)
apt-get update && apt-get install -y docker.io python3-pip
pip3 install pyyaml requests
# Crear directorios
mkdir -p /opt/selfheal/{active_containers.txt,policy.yaml}
# Guardar los scripts mostrados arriba en /opt/selfheal/
chmod +x /opt/selfheal/engine.py /opt/selfheal/watcher.sh
# Configurar systemd service para el watcher
cat > /etc/systemd/system/selfheal-watcher.service <<'EOF'
[Unit]
Description=Docker Self-Healing Watcher
After=network.target docker.service
[Service]
ExecStart=/opt/selfheal/watcher.sh
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now selfheal-watcher.service
Verificación
- Inyección de fallo – detén manualmente un contenedor incluido en la política:
docker stop sandbox-demo-api. - Observa el log – el engine imprimirá un JSON con
verified: truey la duración. En un entorno con Loki, buscacontainer="sandbox-demo-api"y verifica que la marca de tiempo coincida con la reparación. - Prueba de salud – abre el endpoint definido (
curl -f http://localhost:80/health). Debería responder 200 después de la reparación. - Escenarios de error – modifica la política para que la URL de health check sea incorrecta y confirma que el agente registra
verified: falsey no entra en bucle infinito.
Notas adicionales
- Seguridad: montar solo
docker.socksin acceso a/var/run/docker.sockde forma global reduce la superficie de ataque. Usa usuarios sin privilegios de root dentro del contenedor del agente. - Escalabilidad: si gestionas cientos de contenedores, agrupa la monitorización por etiquetas (
docker ps -a --filter "label=autoheal"). El motor de políticas puede cargar reglas dinámicamente desde un repositorio Git. - Persistencia de estado: guarda los IDs de contenedores en proceso de reparación en una base de datos ligera (SQLite) para evitar duplicados tras reinicios del agente.
- Integración con alertas: envía eventos a Slack, PagerDuty o Prometheus Alertmanager para que el equipo tenga visibilidad incluso cuando la reparación sea exitosa.
Con este enfoque, la auto‑curación pasa de ser una prueba de concepto aislada a una práctica reutilizable que se adapta a cualquier stack Docker que requiera disponibilidad continua sin depender de orquestadores más complejos.