CreaRack-SL

Incidente: needrestart colgó un job de CI 45 min sin fallar, y crearack-ops-1 llevaba 4 días caído sin aviso

Cuándo

01-09-2026 (v1.97.2, PR #485, commit e5027651). Dos fallos independientes salieron a la luz el mismo día, ambos en la infraestructura de CI que corre en el servidor Hetzner OPS ([[concept—infra—ops-server]]).

Síntomas visibles

  1. Un job del CI estuvo 45 minutos colgado sin fallar nunca — ninguno de los 5 jobs de .github/workflows/ci.yml tenía timeout-minutes (el default de GitHub Actions son 6 horas).
  2. Al restaurar el runner caído (ver más abajo), 4 tests de subida de MIBs empezaron a fallar con PermissionError (500 en vez de 202).
  3. Retroactivamente se descubrió que crearack-ops-1 (uno de los 2 runners self-hosted, [[concept—infra—ops-server]]) llevaba caído por OOM desde el 28-08-2026, cuatro días, sin que nada lo avisara — el CI seguía en verde porque el otro runner absorbía todo el trabajo, a la mitad de velocidad.

Causa raíz

  • El job colgado: needrestart (Ubuntu 22+) se despierta con cada apt-get y lanza un systemctl restart de los servicios que usan las librerías actualizadas. El 01-09-2026 ese restart se quedó atascado 45 min arrancando cloud-init-hotplugd, bloqueando la cola de systemd y con ella el propio apt-get del paso “Install system dependencies” — sin timeout-minutes, el job nunca lo delataba.
  • El runner caído sin aviso: [[crearack-tech—admin—server-management|UptimeRobot]] solo mira si el host crearack-ops responde por HTTP/TCP desde fuera — no ve el estado interno de los 2 runners que corren dentro. Con un runner OOM y el otro sano, el host seguía respondiendo y el CI seguía verde: ninguna alarma existente podía detectar el problema. Mismo punto ciego que motivó [[feature—observability—alerting-prod-s95]] para PROD, nunca replicado para OPS.
  • El PermissionError de los MIBs: consecuencia directa de restaurar crearack-ops-1. Los 2 runners corren con usuarios Linux aislados (runner / runner2); config/settings/test.py apuntaba a un directorio temporal común (/tmp/crearack_test_mib_cache), y el primer runner en crearlo dejaba al otro fuera por permisos. Llevaba latente desde que se fijó ese tmpdir, pero no se veía porque todo corría bajo un único usuario mientras crearack-ops-1 estuvo caído — salió a la luz al restaurarlo. Mismo patrón que el /opt/hostedtoolcache compartido documentado en [[concept—infra—ops-server]].

Fix aplicado

Commit e5027651 (PR #485):

  • timeout-minutes en los 5 jobs de ci.yml (10/20/30/40 min según el job) — un job colgado ahora falla en vez de consumir 6 horas en silencio.
  • El paso de dependencias del sistema neutraliza needrestart (NEEDRESTART_MODE=l + NEEDRESTART_SUSPEND=1 + DEBIAN_FRONTEND=noninteractive) con su propio timeout-minutes: 5, y comprueba con dpkg -s antes de invocar apt (OPS es máquina compartida: también aloja crons y Forgejo).
  • Los runners pasan a estar vigilados (script en el repo workspace, fuera del alcance de este commit): simulacro verificado en el servidor real — caída → correo rojo → restaurado → correo verde.
  • config/settings/test.py: MIB_CACHE_DIR ahora incluye el usuario del sistema (crearack_test_mib_cache_<user>), así los 2 runners aislados ya no compiten por el mismo directorio.

Lecciones

  • Un CI “verde” no prueba que la infraestructura esté sana — solo prueba que el trabajo se completó con los recursos disponibles en ese momento. Con 2 runners y una carga que uno solo puede absorber, perder el 50% de la capacidad es invisible desde fuera.
  • Un monitor externo tipo UptimeRobot certifica que el HOST responde, no que los SERVICIOS de dentro (los runners) estén sanos — el mismo punto ciego ya identificado para PROD en [[feature—observability—alerting-prod-s95]] existía sin réplica para OPS.
  • Ningún job debería confiar en el timeout por defecto de la plataforma (6h en GitHub Actions): en la práctica equivale a “sin límite” para el propósito de detectar cuelgues.

Preventivos futuros

  • Los timeouts por job quedan como red de seguridad permanente contra cualquier futuro cuelgue, no solo el de needrestart.
  • La vigilancia de los runners (mencionada en el commit, implementada en el repo workspace) es el preventivo real del punto ciego de 4 días — pendiente de página propia si se documenta ese lado del cambio.

Véase también

  • [[concept—infra—ops-server]]
  • [[decision—20260516—fase-c-crons-stage-ligera]]
  • [[crearack-tech—admin—server-management]]
  • [[feature—observability—alerting-prod-s95]]
  • [[entity—public—forgejo-runner]]