4.0 KiB
CI-Runner auf node1 + CI-Pipeline-Overhaul (2026-06-28)
Operativer Record der CI-Umstellung. Runner-Host-Themen gehören mittelfristig auch nach
infra/LESSONS.md(Infra-Repo, Akeshan-Domain) — hier dokumentiert, weil die.gitea/workflows/-Änderungen in diesem Repo liegen.
Warum: Homeserver OOMte unter paralleler CI
Der geteilte Homeserver (32 GB RAM, neben anderen Diensten ~17 GB belegt) ist bei paralleler CI wiederholt abgestürzt: parallele Jobs spikten den RAM auf 30+/32 GB, CPU auf 100%, der Linux-OOM-Killer riss den größten Job → Crash-Recover-Schleife (Runner grabbte sofort den nächsten Job). Parallelisierung auf der geteilten Box war der falsche Hebel.
Lösung: node1 als dedizierter CI-Runner
node1 (176.9.111.179, eigener Hetzner-Dedi mit self-hosted Sentry, 20 Kerne / 62 GB)
ist Mesh-Peer und erreicht Gitea über 10.42.0.2. Gitea-Server bleibt, wo er ist — nur die
Runner-Last zieht um.
- Compose:
/opt/stacks/gitea-ci-runner/docker-compose.yml(network_mode: host,--add-host git.tool-ctrl.de:10.42.0.2,CONFIG_FILE=/data/config.yaml). - Config:
/srv/data/gitea-ci-runner/config.yaml(capacity, labels, cache.dir). - Homeserver-Runner steht auf
restart=no(aus, kein Auto-Restart, keine OOM-Schleife).
act_runner-Footguns (alle real getroffen)
| Symptom | Ursache | Fix |
|---|---|---|
runner.capacity wird ignoriert |
run.sh überschreibt den compose-command, läuft ohne --config |
env CONFIG_FILE=/data/config.yaml setzen |
| "no matching runner online with label toolctrl-ci" | leere runner.labels → KEIN Fallback auf .runner |
Labels EXPLIZIT in config.yaml |
| Daemon "token is empty" trotz erfolgreicher Registrierung | act_runner register schrieb .runner ins Container-CWD → bei --rm weg |
docker run -w /data ... beim Register |
| Register "instance address is empty" | run.sh ignoriert CLI-Args, nutzt leeres GITEA_INSTANCE_URL |
--entrypoint act_runner umgeht run.sh |
| pnpm-Store-Cache greift nie (Cache-Dir 12K) | cache.dir default ephemer im Container, Restarts wischen ihn |
cache.dir: /data/actcache (gemountet) |
Capacity-Findings (empirisch gemessen)
| Setup | Wallclock | RAM-Peak frei | Notiz |
|---|---|---|---|
| cap 4, docker-smoke auf Tail | ~10m | 35 GB | |
| cap 6 | ~8m | 23-25 GB | |
| cap 6 + docker-smoke parallel | ~7m35s | 23 GB | |
| cap 8 + docker-smoke parallel | ~6m53s | 25 GB | Sweet-Spot |
cap 8 ist schneller als cap 6 trotz mehr Contention: Install/Netzwerk/Docker sind I/O-bound, die 20 Kerne tragen 8 Jobs; die Queueing-Reduktion schlägt die Mehr-Contention. cap >8 bringt nichts (Kerne saturiert). Reise gesamt: ~32m → ~6m53s.
Pipeline-Änderungen (.gitea/workflows/)
- docker-smoke
needsentfernt → läuft parallel zur Quality-Phase (baut Images aus Source, keine Cross-Job-Artefakt-Abhängigkeit), raus aus dem kritischen Tail. - Sentry-Release-Jobs aus ci.yml entfernt (ci.yml läuft nur auf
pull_request; ein PR-Build wird nirgends deployed → die Release/Sourcemaps mappen auf keine Deployment). Release passiert indeploy.yml(push→main / promote).
Install-Diagnose (warum KEIN Deps-Image)
Gemessen: Install = ~43s Download + ~15s Link (warmer Store). Die ~2-min-Annahme war zu
hoch. Ein Deps-Image würde nur die 15s Link sparen → verworfen. Der Hebel war der kalte
Store (Cache persistierte nicht) → mit cache.dir gefixt.
Offene Punkte
- Sourcemaps im Deploy: web nutzt
@sentry/vite-plugin(lädt Sourcemaps beim Build hoch, wennSENTRY_AUTH_TOKENgesetzt). To-do: Token als BuildKit---secretan denweb-build-Dockerfile-Stage durchreichen (NICHT build-arg → kein Leak) +SENTRY_PROJECT=webexplizit (Default ist alter Slughivegrid-web). Nur auf echtem Deploy testbar → bis Sentry-Go-Live aufgeschoben. - Persistenter Store-Cache:
cache.dirgesetzt + befüllt (2,9 GB), aber Hit-Gewinn bei cap 8 noch ungemessen (Per-Job-Restore-I/O könnte Download-Gewinn auffressen → ggf. auf geteilten gemounteten Store umstellen statt actions/cache).