5 playbooks nuevos en docs/playbooks/ del portal: - SQLite en modo DELETE causando SQLITE_BUSY, por que WAL lo resuelve. - Anchors YAML no soportados por el parser de Gitea Actions, incluido el diagnostico equivocado inicial (indentacion) que se corrigio despues -- documentado con honestidad como leccion de proceso. - Docker-in-Docker vs sibling containers, y por que Semgrep corre nativo en un venv en vez de como container aparte. - El SNI/realm HTTP del tunel rompiendo especificamente a Cosign (docker push funcionaba, cosign sign no), conectado con el incidente ya documentado del 504 -- misma capa de ingress, dos fallas distintas. - Contencion de recursos entre Gitea y su runner: causa raiz real confirmada en vivo durante esta misma sesion (ambos contenedores reiniciados a mitad de un build, sin limites de CPU/memoria reales), con fix propuesto (ver rama fix/gitea-runner-resource-limits en scripts/) pendiente de aplicacion manual. Se actualiza el playbook del 504 para linkear hacia el nuevo hallazgo de contencion, cerrando el loop que habia quedado abierto ahi. cosign.md: nueva seccion "Limitaciones conocidas y mejoras futuras" con los dos TODO reales -- firma por tag en vez de digest (con el warning real de Cosign visto en los logs) y transparency log de Rekor deshabilitado, explicado en simple. index.md: tabla resumen de los 3 pipelines (imagen + adaptacion de Semgrep por stack), diagrama generalizado para reflejar que es el mismo patron en los 3 workflows, y "que falta" actualizado. Validado con mkdocs build --strict (0 errores, 0 warnings) antes de commitear.
6.4 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 propuesto (documentado, aplicación manual pendiente)
-
scripts/deploy-lab.sh(ramafix/gitea-runner-resource-limits, pendiente de mergear): agrega--cpu-shares 512 --memory 8g --memory-swap 10ga la creación degitea-runner. Solo afecta a la próxima vez que se cree el contenedor (la función es idempotente), no al que ya está corriendo. -
Fix inmediato en caliente (sin recrear contenedores), a aplicar manualmente por fuera de este repo:
docker update --cpu-shares 1024 gitea docker update --cpu-shares 512 --memory 8g --memory-swap 10g gitea-runner -
docker-compose.ymlde Gitea (fuera de este repo, acceso root-only): subircpu_sharesdegiteade 90 a 1024 — pendiente de aplicar manualmente, no se implementó todavía. -
Mover
gitea-runnera k3d como job/pod separado: evaluado como posible mejora de fondo (aislamiento real vía Kubernetes en vez de límites de Docker sueltos), pero queda solo como recomendación documentada — no implementado en esta vuelta.
Validación
Con gitea y gitea-runner estables (sin reinicios) después del
incidente, se reintentó el mismo pipeline y corrió de punta a punta
sin interrupciones — pero eso confirma que el host se estabilizó
después del pico de carga, no que el fix de límites de recursos ya
esté aplicado (sigue pendiente, ver arriba). La correlación completa
carga-pesada → reinicio ya se había visto antes, en dos fechas
distintas (2026-07-30 y 2026-08-11) además de esta — no es un evento
aislado, es un patrón recurrente que ahora tiene una causa raíz
concreta y una propuesta de fix.