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.
103 lines
4.4 KiB
Markdown
103 lines
4.4 KiB
Markdown
# Incidente: Docker-in-Docker roto en el runner — por qué Semgrep corre nativo (2026-08)
|
|
|
|
| | |
|
|
|---|---|
|
|
| **Ventana del incidente** | 2026-08-15, primer intento de correr Semgrep como container aparte en `build.yaml` |
|
|
| **Servicio afectado** | Gitea Actions runner (`gitea-runner`, imagen `act_runner`) |
|
|
| **Impacto** | Cualquier step que intentara `docker run -v "$(pwd):/algo"` desde dentro de un job fallaba con `read-only file system` o rutas inexistentes |
|
|
|
|
## Docker-in-Docker vs. sibling containers, para quien recién arranca en CI/CD
|
|
|
|
Cuando un job de CI necesita usar Docker (por ejemplo, para correr una
|
|
herramienta empaquetada como imagen), hay dos formas distintas de
|
|
dárselo, y confundirlas rompe cosas de maneras difíciles de
|
|
diagnosticar:
|
|
|
|
- **Docker-in-Docker (DinD):** el contenedor del job tiene su **propio**
|
|
daemon Docker corriendo adentro, completamente aislado del daemon
|
|
del host. Cuando el job hace `docker run`, ese contenedor nuevo nace
|
|
*dentro* del contenedor del job, como una muñeca rusa. El
|
|
filesystem que ve ese daemon interno es el del contenedor del job,
|
|
no el del host real.
|
|
- **Sibling containers (contenedores hermanos):** el contenedor del
|
|
job **no** tiene su propio daemon — en cambio, monta el socket del
|
|
daemon Docker del **host** (`/var/run/docker.sock`) y habla
|
|
directamente con él. Cuando el job hace `docker run`, ese contenedor
|
|
nuevo nace como **hermano** del contenedor del job (mismo nivel,
|
|
mismo host, mismo daemon), no adentro suyo.
|
|
|
|
La imagen `act_runner` que usa este runner de Gitea Actions usa el
|
|
segundo modelo: **sibling containers**, montando el socket del daemon
|
|
del host (ver `create_gitea_runner_container()` en
|
|
`scripts/deploy-lab.sh`, que monta
|
|
`-v /var/run/docker.sock:/var/run/docker.sock`).
|
|
|
|
## Por qué eso rompe un `docker run -v` ingenuo
|
|
|
|
Con sibling containers, cuando un step dentro de un job pide
|
|
|
|
```bash
|
|
docker run -v "${{ github.workspace }}:/src" alguna-imagen
|
|
```
|
|
|
|
ese comando lo ejecuta el **daemon del host**, no el contenedor del
|
|
job. `${{ github.workspace }}` es una ruta que existe *dentro* del
|
|
contenedor del job (por ejemplo `/workspace/devops/apps-registry`) —
|
|
pero el daemon del host, al crear el contenedor hermano, intenta
|
|
montar esa misma ruta **desde el filesystem del host real**, donde
|
|
probablemente no existe (o existe otra cosa completamente distinta ahí).
|
|
|
|
## Síntoma
|
|
|
|
Al intentar correr Semgrep como container aparte (`docker run` desde
|
|
dentro del step), el error observado en el run real fue:
|
|
|
|
```text
|
|
mkdir /workspace: read-only file system
|
|
```
|
|
|
|
No un "no existe la ruta" limpio — un intento de crear el directorio
|
|
que falla porque, en el contexto real del daemon del host, esa ruta
|
|
cae en un punto de montaje de solo lectura (o simplemente no es un
|
|
lugar donde el daemon del host puede/debe escribir).
|
|
|
|
## Causa raíz
|
|
|
|
Sibling containers, no Docker-in-Docker: un `docker run -v
|
|
"${{ github.workspace }}:/src"` ejecutado desde dentro de un job
|
|
intenta montar, en el daemon del **host**, una ruta que solo tiene
|
|
sentido **dentro** del contenedor del job. El daemon del host no ve
|
|
esa ruta como el mismo directorio — la ve como una ruta arbitraria de
|
|
su propio filesystem.
|
|
|
|
## Fix aplicado
|
|
|
|
Se evita el problema de raíz: **Semgrep corre nativo**, instalado
|
|
directo en el contenedor del job (mismo criterio ya usado para
|
|
Gitleaks, Trivy, Syft y Cosign — todos se instalan como binario/paquete
|
|
dentro del job, ninguno corre como container aparte):
|
|
|
|
```bash
|
|
python3 -m venv /tmp/semgrep-venv
|
|
/tmp/semgrep-venv/bin/pip install --quiet "semgrep==1.173.0"
|
|
```
|
|
|
|
### Por qué un venv y no `pip3 install` directo
|
|
|
|
La imagen del runner ya trae paquetes de Python instalados por `apt`
|
|
(por ejemplo `PyJWT`) sin metadata compatible con `pip`. Un
|
|
`pip3 install --break-system-packages` sobre esa base falla al
|
|
intentar reemplazarlos (`RECORD file not found`) porque `pip` no
|
|
encuentra el registro de archivos que necesita para saber qué está
|
|
reemplazando. Un venv aislado evita tocar los paquetes del sistema por
|
|
completo — Semgrep y sus dependencias quedan en su propio directorio,
|
|
sin interferir con nada que `apt` ya haya instalado.
|
|
|
|
## Validación
|
|
|
|
Validado localmente contra `docker.gitea.com/runner-images:ubuntu-latest`
|
|
(la misma imagen que usa el runner real) que `python3`/`pip3` están
|
|
disponibles, y confirmado en una corrida real posterior al fix que
|
|
Semgrep corre y reporta hallazgos sin ningún error de Docker de por
|
|
medio.
|