Relevé en mesurant la CI de kadans les 29-30/07. Deux défauts distincts, même cause racine : la flotte de runners est sous-dimensionnée et hétérogène.
1. La famine de créneau tue des jobs, et son rouge ressemble à un bug
Gitea marque une tâche runningdès qu'il l'attribue à un runner, avant exécution. Si rien ne remonte pendant ZOMBIE_TASK_TIMEOUT — 10 minutes, le défaut, car l'app.ini de Gitea 1.25.5 n'a pas de section [actions] — il la tue :
conclusion : failure
journal : HTTP 500 … log.zst: file does not exist ← il n'a JAMAIS rien produit
durée : ~640-655 s ← la signature du délai
Constaté : trois jobs de workflows différents (gates-navigateur, build-and-push-image, build-storybook) tués au même instant (22:37:29 UTC), en 643/648/652 s, zéro journal.
⚠ Et ce n'était pas une surcharge : le journal du runner montre qu'il n'en exécutait qu'un seul à la fois (tâches 3374, 3376, 3378, espacées), capacity: 1 bien respecté — vérifié dans le conteneur. C'est l'attente qui les a tués, pas la charge.
Pourquoi ça arrive facilement
Un push sur main de kadans déclenche trois workflows (ci.yml + storybook.yml + dockerimage.yaml), soit jusqu'à 4 jobs. Avec 2 runners × capacity 1 et des jobs de 5 à 11 minutes, deux merges rapprochés suffisent à faire mourir de faim tout ce qui suit. Les runners sont partagés par toute la forge, donc le problème n'est pas propre à kadans.
2. Les deux runners ne sont pas interchangeables
Machine
act_runner
Particularité
pi1
v0.3.1
porte le control-plane k3s
pi3
v0.2.13
—
Trois versions mineures d'écart sur des machines censées être équivalentes. Effets mesurés :
le même job, sur la même image, a mis 511 s sur pi1 et 397 s sur pi3 — 114 s d'écart imputables à la machine ;
pi3 (v0.2.13) a mal lu la définition d'un job dont il dépendait : 'runs-on' key not defined, puis No steps found.
⚠ Conséquence méthodologique : sur cette flotte, un écart de moins de ~100 s n'est pas mesurable en un seul run. Toute optimisation de CI chiffrée sans répéter la mesure et noter la machine est ininterprétable. (J'en ai fait les frais : quatre annonces de gain successivement réduites par la mesure.)
Pistes, par ordre de sûreté décroissante
Un troisième runner. Le plus sûr. Il éloigne la famine sans toucher aux plafonds mémoire.
Aligner les versions d'act_runner (gitea/act_runner:latest est déjà l'image ; pi3 n'a simplement pas été recréé depuis longtemps — un passage de 03_cicd.yml suffirait, mais ⚠ il tue les jobs en vol, cf. plus bas).
Relever ZOMBIE_TASK_TIMEOUT dans app.ini (rôle deploy_gitea, app.ini.j2). Traite le symptôme, pas la cause : les jobs attendraient plus longtemps au lieu de mourir.
capacity: 2 — ⚠ déconseillé : le commentaire du playbook prévient que les hôtes (8 Go, control-plane k3s sur pi1) ne survivent pas à deux builds lourds simultanés, et l'incident du 2026-07-23 le documente (nuxt generate à 3,5 Go RSS → load 150 → ingress et API k3s morts).
⚠ Précaution pour toute intervention
03_cicd.yml recrée les conteneurs de runner (loop: [absent, present]) : il tue les jobs en vol, qui deviennent failure avec journaux perdus. Vérifier qu'aucun run n'est queued ni in_progressjuste avant — et se rappeler qu'un merge est un déclencheur, donc que « plus rien ne tourne » se vérifie après le dernier merge, pas avant.
Lié
arcodange/kadans#187 (où placer un garde-fou e2e — cette contrainte pèse sur l'arbitrage) et #198 (test flaky — à ne pas confondre avec une famine).
Doctrine consignée dans arcodange/kadansCLAUDE.md (PR #232).
Relevé en mesurant la CI de `kadans` les 29-30/07. Deux défauts distincts, même cause racine : **la flotte de runners est sous-dimensionnée et hétérogène**.
## 1. La famine de créneau tue des jobs, et son rouge ressemble à un bug
Gitea marque une tâche `running` **dès qu'il l'attribue** à un runner, *avant* exécution. Si rien ne remonte pendant **`ZOMBIE_TASK_TIMEOUT`** — **10 minutes**, le défaut, car l'`app.ini` de Gitea 1.25.5 **n'a pas de section `[actions]`** — il la tue :
```
conclusion : failure
journal : HTTP 500 … log.zst: file does not exist ← il n'a JAMAIS rien produit
durée : ~640-655 s ← la signature du délai
```
**Constaté** : trois jobs de workflows **différents** (`gates-navigateur`, `build-and-push-image`, `build-storybook`) tués **au même instant** (22:37:29 UTC), en 643/648/652 s, zéro journal.
⚠ Et ce n'était **pas** une surcharge : le journal du runner montre qu'il n'en exécutait **qu'un seul à la fois** (tâches 3374, 3376, 3378, espacées), `capacity: 1` bien respecté — vérifié *dans* le conteneur. **C'est l'attente qui les a tués, pas la charge.**
### Pourquoi ça arrive facilement
Un push sur `main` de `kadans` déclenche **trois workflows** (`ci.yml` + `storybook.yml` + `dockerimage.yaml`), soit jusqu'à **4 jobs**. Avec **2 runners × capacity 1** et des jobs de **5 à 11 minutes**, deux merges rapprochés suffisent à faire mourir de faim tout ce qui suit. Les runners sont **partagés par toute la forge**, donc le problème n'est pas propre à `kadans`.
## 2. Les deux runners ne sont pas interchangeables
| Machine | act_runner | Particularité |
|---|---|---|
| **pi1** | **v0.3.1** | porte le **control-plane k3s** |
| **pi3** | **v0.2.13** | — |
Trois versions mineures d'écart sur des machines censées être équivalentes. Effets mesurés :
- le **même** job, sur la **même** image, a mis **511 s sur pi1** et **397 s sur pi3** — **114 s d'écart imputables à la machine** ;
- pi3 (v0.2.13) a mal lu la définition d'un job dont il dépendait : `'runs-on' key not defined`, puis `No steps found`.
⚠ **Conséquence méthodologique** : sur cette flotte, **un écart de moins de ~100 s n'est pas mesurable en un seul run**. Toute optimisation de CI chiffrée sans répéter la mesure *et* noter la machine est ininterprétable. (J'en ai fait les frais : quatre annonces de gain successivement réduites par la mesure.)
## Pistes, par ordre de sûreté décroissante
1. **Un troisième runner.** Le plus sûr. Il éloigne la famine sans toucher aux plafonds mémoire.
2. **Aligner les versions d'act_runner** (`gitea/act_runner:latest` est déjà l'image ; pi3 n'a simplement pas été recréé depuis longtemps — un passage de `03_cicd.yml` suffirait, mais ⚠ il **tue les jobs en vol**, cf. plus bas).
3. **Relever `ZOMBIE_TASK_TIMEOUT`** dans `app.ini` (rôle `deploy_gitea`, `app.ini.j2`). Traite le symptôme, pas la cause : les jobs attendraient plus longtemps au lieu de mourir.
4. **`capacity: 2`** — ⚠ **déconseillé** : le commentaire du playbook prévient que les hôtes (8 Go, control-plane k3s sur pi1) **ne survivent pas à deux builds lourds simultanés**, et l'incident du 2026-07-23 le documente (`nuxt generate` à 3,5 Go RSS → load 150 → ingress et API k3s morts).
## ⚠ Précaution pour toute intervention
`03_cicd.yml` recrée les conteneurs de runner (`loop: [absent, present]`) : **il tue les jobs en vol**, qui deviennent `failure` avec journaux perdus. Vérifier qu'aucun run n'est `queued` ni `in_progress` **juste avant** — et se rappeler qu'**un merge est un déclencheur**, donc que « plus rien ne tourne » se vérifie *après* le dernier merge, pas avant.
## Lié
- `arcodange/kadans` #187 (où placer un garde-fou e2e — cette contrainte pèse sur l'arbitrage) et #198 (test flaky — à ne pas confondre avec une famine).
- Doctrine consignée dans `arcodange/kadans` `CLAUDE.md` (PR #232).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
✅ Moitié « versions divergentes » : réglée — et une hypothèse à corriger
Migration faite le 2026-07-30 (PR #51) : Gitea 1.25.5 → 1.27.1 et les deux runners act_runner v0.3.1 / v0.2.13 → gitea/runner v2.3.0.
⚠ L'image du runner avait changé de nom : gitea/act_runner est gelée à 0.6.1, le successeur officiel est gitea/runner (binaire renommé act_runner → gitea-runner). Épingler l'ancien nom nous aurait enfermés dans une image morte.
La cause de la divergence était gitea/act_runner:**latest** + pull: **missing** — un tag flottant jamais rafraîchi : chaque hôte gardait ce que « latest » voulait dire à son premier pull. La version est désormais épinglée dans inventory/group_vars/all/gitea.yml.
Chaîne validée de bout en bout : gates324 s (pi3) et gates-navigateur553 s (pi1), les deux verts, image précuite et garde anti-dérive comprises.
⚠ Mais l'écart entre machines n'était PAS dû aux versions
C'est la correction qui compte pour la suite de ce ticket. J'avais attribué les 114 s d'écart à la divergence d'act_runner. Faux — mesures de gates-navigateur, toutes avec l'image précuite :
Machine
Runner
Durée
pi3
v0.2.13
397 s
pi1
v0.3.1
511 s
pi1
v2.3.0
553 s
pi1 reste le lent, versions alignées ou non. L'explication tient à la machine : pi1 porte le control-plane k3s, et ça se paie sur chaque job.
Ce que ça change pour les pistes
Piste 2 (aligner les versions) : faite, et elle supprime une source de bruit de diagnostic — mais elle ne rééquilibre pas les machines. Ne pas en attendre de gain de durée.
Piste 1 (troisième runner) : reste la meilleure, et l'argument se précise — l'idéal serait un runner qui ne porte pas le control-plane k3s, ou de sortir la CI de pi1.
Piste 4 (capacity: 2) : toujours déconseillée, et pi1 encore moins que pi3.
La famine de créneau, elle, est intacte
ZOMBIE_TASK_TIMEOUT reste à 10 min par défaut : vérifié, app.ini n'a toujours pas de section [actions] en 1.27.1. Un push sur main de kadans déclenchait 4 jobs pour 2 créneaux — kadans#234 le ramène à 3 en filtrant Storybook, ce qui soulage sans rien couvrir de moins.
## ✅ Moitié « versions divergentes » : réglée — et une hypothèse à corriger
Migration faite le 2026-07-30 (PR #51) : **Gitea 1.25.5 → 1.27.1** et les deux runners **`act_runner` v0.3.1 / v0.2.13 → `gitea/runner` v2.3.0**.
⚠ **L'image du runner avait changé de nom** : `gitea/act_runner` est **gelée à 0.6.1**, le successeur officiel est **`gitea/runner`** (binaire renommé `act_runner` → `gitea-runner`). Épingler l'ancien nom nous aurait enfermés dans une image morte.
**La cause de la divergence** était `gitea/act_runner:**latest**` + `pull: **missing**` — un tag flottant **jamais rafraîchi** : chaque hôte gardait ce que « latest » voulait dire à son premier pull. La version est désormais **épinglée** dans `inventory/group_vars/all/gitea.yml`.
Chaîne validée de bout en bout : `gates` **324 s** (pi3) et `gates-navigateur` **553 s** (pi1), les deux verts, image précuite et garde anti-dérive comprises.
## ⚠ Mais l'écart entre machines n'était PAS dû aux versions
C'est la correction qui compte pour la suite de ce ticket. J'avais attribué les **114 s** d'écart à la divergence d'act_runner. **Faux** — mesures de `gates-navigateur`, toutes avec l'image précuite :
| Machine | Runner | Durée |
|---|---|---:|
| **pi3** | v0.2.13 | **397 s** |
| **pi1** | v0.3.1 | 511 s |
| **pi1** | **v2.3.0** | **553 s** |
**pi1 reste le lent, versions alignées ou non.** L'explication tient à la machine : **pi1 porte le control-plane k3s**, et ça se paie sur chaque job.
### Ce que ça change pour les pistes
- **Piste 2 (aligner les versions)** : faite, et elle **supprime une source de bruit de diagnostic** — mais elle **ne rééquilibre pas** les machines. Ne pas en attendre de gain de durée.
- **Piste 1 (troisième runner)** : reste la meilleure, et l'argument se précise — l'idéal serait un runner **qui ne porte pas le control-plane k3s**, ou de sortir la CI de pi1.
- **Piste 4 (`capacity: 2`)** : toujours déconseillée, et pi1 encore moins que pi3.
### La famine de créneau, elle, est intacte
`ZOMBIE_TASK_TIMEOUT` reste à **10 min** par défaut : vérifié, `app.ini` n'a **toujours pas** de section `[actions]` en 1.27.1. Un push sur `main` de `kadans` déclenchait 4 jobs pour 2 créneaux — [kadans#234](https://gitea.arcodange.lab/arcodange/kadans/pulls/234) le ramène à 3 en filtrant Storybook, ce qui soulage sans rien couvrir de moins.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Relevé en mesurant la CI de
kadansles 29-30/07. Deux défauts distincts, même cause racine : la flotte de runners est sous-dimensionnée et hétérogène.1. La famine de créneau tue des jobs, et son rouge ressemble à un bug
Gitea marque une tâche
runningdès qu'il l'attribue à un runner, avant exécution. Si rien ne remonte pendantZOMBIE_TASK_TIMEOUT— 10 minutes, le défaut, car l'app.inide Gitea 1.25.5 n'a pas de section[actions]— il la tue :Constaté : trois jobs de workflows différents (
gates-navigateur,build-and-push-image,build-storybook) tués au même instant (22:37:29 UTC), en 643/648/652 s, zéro journal.⚠ Et ce n'était pas une surcharge : le journal du runner montre qu'il n'en exécutait qu'un seul à la fois (tâches 3374, 3376, 3378, espacées),
capacity: 1bien respecté — vérifié dans le conteneur. C'est l'attente qui les a tués, pas la charge.Pourquoi ça arrive facilement
Un push sur
maindekadansdéclenche trois workflows (ci.yml+storybook.yml+dockerimage.yaml), soit jusqu'à 4 jobs. Avec 2 runners × capacity 1 et des jobs de 5 à 11 minutes, deux merges rapprochés suffisent à faire mourir de faim tout ce qui suit. Les runners sont partagés par toute la forge, donc le problème n'est pas propre àkadans.2. Les deux runners ne sont pas interchangeables
Trois versions mineures d'écart sur des machines censées être équivalentes. Effets mesurés :
'runs-on' key not defined, puisNo steps found.⚠ Conséquence méthodologique : sur cette flotte, un écart de moins de ~100 s n'est pas mesurable en un seul run. Toute optimisation de CI chiffrée sans répéter la mesure et noter la machine est ininterprétable. (J'en ai fait les frais : quatre annonces de gain successivement réduites par la mesure.)
Pistes, par ordre de sûreté décroissante
gitea/act_runner:latestest déjà l'image ; pi3 n'a simplement pas été recréé depuis longtemps — un passage de03_cicd.ymlsuffirait, mais ⚠ il tue les jobs en vol, cf. plus bas).ZOMBIE_TASK_TIMEOUTdansapp.ini(rôledeploy_gitea,app.ini.j2). Traite le symptôme, pas la cause : les jobs attendraient plus longtemps au lieu de mourir.capacity: 2— ⚠ déconseillé : le commentaire du playbook prévient que les hôtes (8 Go, control-plane k3s sur pi1) ne survivent pas à deux builds lourds simultanés, et l'incident du 2026-07-23 le documente (nuxt generateà 3,5 Go RSS → load 150 → ingress et API k3s morts).⚠ Précaution pour toute intervention
03_cicd.ymlrecrée les conteneurs de runner (loop: [absent, present]) : il tue les jobs en vol, qui deviennentfailureavec journaux perdus. Vérifier qu'aucun run n'estqueuedniin_progressjuste avant — et se rappeler qu'un merge est un déclencheur, donc que « plus rien ne tourne » se vérifie après le dernier merge, pas avant.Lié
arcodange/kadans#187 (où placer un garde-fou e2e — cette contrainte pèse sur l'arbitrage) et #198 (test flaky — à ne pas confondre avec une famine).arcodange/kadansCLAUDE.md(PR #232).🤖 Generated with Claude Code
✅ Moitié « versions divergentes » : réglée — et une hypothèse à corriger
Migration faite le 2026-07-30 (PR #51) : Gitea 1.25.5 → 1.27.1 et les deux runners
act_runnerv0.3.1 / v0.2.13 →gitea/runnerv2.3.0.⚠ L'image du runner avait changé de nom :
gitea/act_runnerest gelée à 0.6.1, le successeur officiel estgitea/runner(binaire renomméact_runner→gitea-runner). Épingler l'ancien nom nous aurait enfermés dans une image morte.La cause de la divergence était
gitea/act_runner:**latest**+pull: **missing**— un tag flottant jamais rafraîchi : chaque hôte gardait ce que « latest » voulait dire à son premier pull. La version est désormais épinglée dansinventory/group_vars/all/gitea.yml.Chaîne validée de bout en bout :
gates324 s (pi3) etgates-navigateur553 s (pi1), les deux verts, image précuite et garde anti-dérive comprises.⚠ Mais l'écart entre machines n'était PAS dû aux versions
C'est la correction qui compte pour la suite de ce ticket. J'avais attribué les 114 s d'écart à la divergence d'act_runner. Faux — mesures de
gates-navigateur, toutes avec l'image précuite :pi1 reste le lent, versions alignées ou non. L'explication tient à la machine : pi1 porte le control-plane k3s, et ça se paie sur chaque job.
Ce que ça change pour les pistes
capacity: 2) : toujours déconseillée, et pi1 encore moins que pi3.La famine de créneau, elle, est intacte
ZOMBIE_TASK_TIMEOUTreste à 10 min par défaut : vérifié,app.inin'a toujours pas de section[actions]en 1.27.1. Un push surmaindekadansdéclenchait 4 jobs pour 2 créneaux — kadans#234 le ramène à 3 en filtrant Storybook, ce qui soulage sans rien couvrir de moins.🤖 Generated with Claude Code