Nuevo playbook del blackhole MTU cross-node (diagnostico + fix + validacion). Actualiza el playbook de contencion de recursos Gitea/runner de 'pendiente' a 'aplicado'. Actualiza cosign.md: firma por digest ya implementada, Rekor como decision tomada (no TODO). Corrige 2 links que quedaron rotos al renombrar la seccion de limitaciones (validado con mkdocs build --strict). Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_011FqYP3Wf1W63Qmh7qgXAAT
6.3 KiB
Incidente: contención de recursos entre Gitea y su runner (2026-08)
| Ventana del incidente | 2026-08-15, durante el primer intento real de correr el pipeline de seguridad completo en commerce-backend |
| Servicio afectado | gitea y gitea-runner (mismo host ZimaOS) |
| Impacto | Un job de CI en curso (build pesado de Medusa + Trivy) fue cancelado a mitad de camino porque ambos contenedores se reiniciaron solos |
Contexto
Este hallazgo estaba anotado como pendiente en el playbook del
504 del túnel: una ventana de latencia
alta y algunos 502/530 habían coincidido, en su momento, con una
ejecución pesada de Gitea Actions — anotado como "contención de
recursos local, no relacionado al fix del túnel, vigilar si es
recurrente". Al triplicar la carga agregando el mismo pipeline de
seguridad a commerce-backend y docs-portal, se volvió a ver en
vivo, esta vez con evidencia suficiente para confirmar la causa.
Síntoma
Un run de build-medusa.yaml (con Gitleaks ya en verde, build de
Docker en curso) quedó cancelado a mitad de camino. El log del runner
mostró, en la misma ventana de un par de minutos:
level=error msg="failed to fetch task" error="unavailable: 502 Bad Gateway"
level=info msg="runner: ... shutdown initiated, waiting 0s for running jobs to complete before shutting down"
level=warning msg="runner: ... cancelled in progress jobs during shutdown"
level=info msg="Starting runner daemon"
El mismo patrón se repitió una segunda vez pocos minutos después. En
paralelo, docker ps mostró que el contenedor gitea también se
había reiniciado casi al mismo tiempo.
Diagnóstico
docker inspect sobre ambos contenedores en el momento del incidente
mostró:
ExitCode=0yRestartCount=0en los dos — no fue un crash ni un OOM-kill (eso dejaría unExitCodedistinto de 0 oOOMKilled=true, y el conteo de reinicios de la política de Docker subiría). ElStartedAtcambió igual, lo que indica un reinicio limpio disparado por algo externo al propio proceso — muy probablemente el supervisor de apps de CasaOS (zimaos-app-management), aunque no se pudo confirmar por logs (/var/log/casaos/log.loges de acceso root-only, sin sudo sin password disponible en esta sesión).- Memoria del host en el momento del incidente: ~1.3 GB libres de 15 GB totales, con ~2 GB de swap en uso — el host estaba bajo presión real de memoria, no solo de CPU.
- Ningún límite de recursos real en ninguno de los dos contenedores (ver detalle abajo).
Causa raíz
Ni gitea ni gitea-runner tienen aislamiento de recursos real
frente al resto del host:
gitea |
gitea-runner |
|
|---|---|---|
| Límite de memoria | ~15.5 GB (≈ toda la RAM del host, no es un límite real) | ninguno |
| Límite de CPU (cuota dura) | ninguno | ninguno |
Peso relativo de CPU (cpu-shares) |
90 (muy por debajo del default de Docker, 1024) | 0 → default Docker (1024) |
Con gitea en 90 y gitea-runner en el default de 1024, bajo
contención de CPU el runner tiene ~11x más prioridad que el propio
servidor Gitea — lo opuesto a lo deseable, ya que Gitea es el
servicio que necesita seguir respondiendo peticiones HTTP (incluidas
las del propio runner haciendo polling) mientras un build pesado corre.
Sumado a que gitea-runner no tiene techo de memoria: un job pesado
(build de Docker de una imagen con node_modules grande + escaneo de
Trivy con vuln + secret scanning) puede consumir memoria sin límite,
empujando al host entero a swap y dejando lento/no-responsivo a todo
lo demás — incluida la propia base de datos SQLite de Gitea (ver
incidente de SQLite en modo DELETE,
que probablemente reaparezca con más frecuencia bajo este mismo tipo
de presión, aunque WAL reduce el impacto).
gitea-runner tampoco corre gestionado por el mismo docker-compose.yml
de Gitea — se crea con un docker run suelto desde
scripts/deploy-lab.sh (create_gitea_runner_container()), montando
/var/run/docker.sock directo (sibling containers, ver
incidente de Docker-in-Docker).
Esto no es la causa del incidente de recursos, pero significa que
cualquier ajuste de límites tiene que aplicarse en dos lugares
distintos: el docker-compose.yml de gitea y el script que crea
gitea-runner.
!!! info "La concurrencia del runner ya estaba bien"
capacity: 1 en el config.yaml del runner ya limita la
ejecución a un solo job a la vez — no es un problema de
"demasiados jobs en paralelo". El problema es que incluso un solo
job pesado, sin ningún límite de recursos, puede acaparar
suficiente CPU/memoria como para dejar sin aire al resto del host.
Fix aplicado (2026-08-16)
scripts/deploy-lab.sh(mergeado amain): agrega--cpu-shares 512 --memory 8g --memory-swap 10ga la creación degitea-runner. Solo afecta a la próxima vez que se recree el contenedor (la función es idempotente) — el runner queda con menos prioridad relativa que Gitea a propósito.docker-compose.ymlde Gitea (fuera de este repo, en el host ZimaOS —/DATA/.casaos/apps/gitea/docker-compose.yml, acceso root-only):cpu_sharesdegiteasubido de 90 a 1024 (el default de Docker), aplicado en caliente condocker compose up -dy validado condocker inspect.- Mover
gitea-runnera k3d como job/pod separado: sigue quedando solo como recomendación documentada, no implementado — la contención real ya se resolvió con los límites de arriba, así que esto pasa a ser una mejora de aislamiento a futuro, no una urgencia.
Validación
Con el fix aplicado: docker inspect gitea --format '{{.HostConfig.CpuShares}}' confirma 1024, el contenedor se recreó
limpio (Started, sin errores) y siguió sirviendo tráfico normal
(200 OK en http://127.0.0.1:3002/ inmediatamente después). La
correlación completa carga-pesada → reinicio se había visto en tres
fechas distintas (2026-07-30, 2026-08-11 y el propio 2026-08-15 de este
incidente) antes de este fix — no era un evento aislado. Queda por
confirmar con el próximo pipeline pesado real que la contención no
vuelva a aparecer, pero la causa raíz (pesos de CPU desbalanceados y sin
techo de memoria en el runner) ya no está presente.