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
129 lines
6.3 KiB
Markdown
129 lines
6.3 KiB
Markdown
# 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](incidente-504-gitea-tunnel.md): 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:
|
|
|
|
```text
|
|
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=0` y `RestartCount=0` en los dos — **no fue un crash ni un
|
|
OOM-kill** (eso dejaría un `ExitCode` distinto de 0 o
|
|
`OOMKilled=true`, y el conteo de reinicios de la política de Docker
|
|
subiría). El `StartedAt` cambió 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.log` es
|
|
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](incidente-sqlite-modo-delete-2026-08.md),
|
|
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](incidente-docker-in-docker-semgrep-2026-08.md)).
|
|
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)
|
|
|
|
1. **`scripts/deploy-lab.sh`** (mergeado a `main`): agrega
|
|
`--cpu-shares 512 --memory 8g --memory-swap 10g` a la creación de
|
|
`gitea-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.
|
|
2. **`docker-compose.yml` de Gitea** (fuera de este repo, en el host
|
|
ZimaOS — `/DATA/.casaos/apps/gitea/docker-compose.yml`, acceso
|
|
root-only): `cpu_shares` de `gitea` subido de 90 a **1024**
|
|
(el default de Docker), aplicado en caliente con
|
|
`docker compose up -d` y validado con `docker inspect`.
|
|
3. **Mover `gitea-runner` a 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.
|