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
- Un job del CI estuvo 45 minutos colgado sin fallar nunca — ninguno de los 5 jobs de
.github/workflows/ci.ymlteníatimeout-minutes(el default de GitHub Actions son 6 horas). - 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). - 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 cadaapt-gety lanza unsystemctl restartde los servicios que usan las librerías actualizadas. El 01-09-2026 ese restart se quedó atascado 45 min arrancandocloud-init-hotplugd, bloqueando la cola desystemdy con ella el propioapt-getdel paso “Install system dependencies” — sintimeout-minutes, el job nunca lo delataba. - El runner caído sin aviso: [[crearack-tech—admin—server-management|UptimeRobot]] solo mira si el host
crearack-opsresponde 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
PermissionErrorde los MIBs: consecuencia directa de restaurarcrearack-ops-1. Los 2 runners corren con usuarios Linux aislados (runner/runner2);config/settings/test.pyapuntaba 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ó esetmpdir, pero no se veía porque todo corría bajo un único usuario mientrascrearack-ops-1estuvo caído — salió a la luz al restaurarlo. Mismo patrón que el/opt/hostedtoolcachecompartido documentado en [[concept—infra—ops-server]].
Fix aplicado
Commit e5027651 (PR #485):
timeout-minutesen los 5 jobs deci.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 propiotimeout-minutes: 5, y comprueba condpkg -santes de invocarapt(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_DIRahora 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]]