Le 2026-09-20 à 15:00:50, Traefik a redémarré et n'a pas réussi à télécharger le plugin bouncer CrowdSec :
unable to install plugin crowdsec-bouncer: ... Get
"https://plugins.traefik.io/.../v1.3.3": context deadline exceeded
Il ne s'arrête pas pour autant. Il journalise Plugins are disabled because an error has occurred. en ERR puis sert quand même — sans plugin. Le middleware crowdsec devient alors invalid middleware type or middleware does not exist, et Traefik jette tous les routeurs qui le référencent, c'est-à-dire presque tout l'ingress : kadans, cms, webapp, url-shortener, minio, telegram-gateway, prospection, grafana, gitea, plausible.
Mesuré au diagnostic :
Constat
Valeur
www.arcodange.lab, cms-rec.arcodange.lab
404 pendant 3 h 18
Dernier pull du bouncer
14:30:50, soit ~3 h 45 avant
Erreurs invalid middleware dans les logs Traefik
en continu, sur chaque routeur
Latence de plugins.traefik.io depuis un pod de pi1
214 ms
Le registre répondait normalement au moment du diagnostic : ce n'était pas une panne, juste un hoquet de moins de 10 s. Ça a suffi à ouvrir l'edge et à couper les sites. Un kubectl rollout restart a tout rétabli (404 → 200) — c'est la remise en service immédiate, faite avant ce correctif.
Pourquoi c'est arrivé
1. Le réseau était dans le chemin de démarrage
experimental.plugins fait passer Traefik par RegistryDownloader.Download(), qui appelle plugins.traefik.ioà chaque démarrage, avec 10 s de timeout.
⚠ La correction évidente — persister /plugins-storage, aujourd'hui un emptyDir — est fausse. Vérifié dans pkg/plugins/downloader.go (v3.6.2) : l'appel HTTP est fait inconditionnellement, le cache ne sert qu'à envoyer un hash pour obtenir un 304. Avoir l'archive sur disque n'aurait rien évité.
2. L'échec était silencieux
Servir l'ingress sans bouncer ni captcha, en ne le signalant que dans les logs, n'est pas un mode dégradé acceptable pour un composant de sécurité.
Ce que fait cette PR
experimental.localPlugins — un autre chemin de code (SetupLocalPlugins), qui ne fait aucune I/O réseau : Yaegi lit les sources sous /plugins-local/src/<module>. Elles sont posées sur un PVC par un Job Ansible, à la version déclarée. Le réseau ne sert plus qu'au provisionnement, où l'on peut réessayer (5 tentatives) sans que l'ingress soit en jeu.
abortOnPluginFailure: true — Traefik ≥ 3.3 sait refuser de démarrer plutôt que de servir sans plugin. Maintenant qu'il n'y a plus de réseau au boot, ce drapeau ne peut se déclencher que sur un PVC vide ou corrompu : cas où l'on veut précisément qu'il refuse de démarrer.
Même famille de fragilité que 93e7abf (résolution de la forge, 2026-08-26) : on ne fait pas dépendre l'edge d'un service joignable à la seconde près.
⚠ Monter la version du plugin passe désormais par Ansible (--tags traefik-plugins) et non plus par un champ que Traefik irait chercher. Le Job est idempotent (marqueur .vendored-version), tourne sur le nœud du control-plane et monte le PVC RWO en parallèle de Traefik — il peut donc repeupler sans couper l'ingress, la nouvelle version prenant effet au redémarrage suivant.
Vérification
Appliqué en prod (ansible-playbook playbooks/system/k3s_config.yml), puis mesuré :
Contrôle
Avant
Après
Chargement du plugin
Loading plugins… 15:00:40 → échec 15:00:50 (10 s de timeout)
Loading plugins… → Plugins loaded.dans la même seconde
Erreurs invalid middleware
en continu
0
www.arcodange.lab / cms-rec.arcodange.lab
404 / 404
200 / 200
grafana / gitea
302 / 200
302 / 200
Pull du bouncer
figé à 14:30
18:37:38, en continu
Montage /plugins-storage
emptyDir
supprimé
Test de non-régression : rollout restart de Traefik — le scénario exact qui cassait. Pod neuf, plugin chargé dans la seconde, 0 middleware invalide, routes toujours à 200.
PVC : v1.3.3, 107 fichiers, 1 Mo, .traefik.yml + vendor/ + go.mod présents, lisibles par l'UID 65532 de Traefik.
Rendu : helm template du chart v37.4.0 avec les valeurs réelles du playbook, avant application.
Pas de dérive collatérale : --tags longhorn --check --diff → ok, le playbook ne touche que Traefik.
Ce que je n'ai pas exercé : un blocage réel par le bouncer. clientTrustedIPs couvre 192.168.1.0/24 et 10.42.0.0/16, donc toute requête que je peux émettre (LAN ou pod) est whitelistée par construction. La preuve du chemin d'application reste le pull des décisions depuis la LAPI, qui tourne.
Hors périmètre, constaté au passage
594 machines fantômes dans la base de la LAPI : chaque redémarrage du pod LAPI enregistre une machine de plus (depuis 2025-12) et rien ne les purge.
La LAPI avait crashé plus tôt sur FATAL: remaining connection slots are reserved for roles with the SUPERUSER attribute — saturation de max_connections (100) sur le Postgres de pi2. Deux identifiants dynamiques Vault de plausible détenaient à eux seuls 20 connexions idle : pgbouncer ouvre un pool par couple (user, base), et chaque rotation Vault en crée un nouveau sans fermer l'ancien.
Ces deux points sont réels mais distincts de la panne d'ingress, et leur cause racine est dans le dépôt tools, pas ici.
## Ce qui s'est passé
Le 2026-09-20 à 15:00:50, Traefik a redémarré et n'a pas réussi à télécharger le plugin bouncer CrowdSec :
```
unable to install plugin crowdsec-bouncer: ... Get
"https://plugins.traefik.io/.../v1.3.3": context deadline exceeded
```
**Il ne s'arrête pas pour autant.** Il journalise `Plugins are disabled because an error has occurred.` en ERR puis sert quand même — sans plugin. Le middleware `crowdsec` devient alors `invalid middleware type or middleware does not exist`, et Traefik **jette tous les routeurs qui le référencent**, c'est-à-dire presque tout l'ingress : kadans, cms, webapp, url-shortener, minio, telegram-gateway, prospection, grafana, gitea, plausible.
Mesuré au diagnostic :
| Constat | Valeur |
|---|---|
| `www.arcodange.lab`, `cms-rec.arcodange.lab` | **404** pendant 3 h 18 |
| Dernier pull du bouncer | 14:30:50, soit ~3 h 45 avant |
| Erreurs `invalid middleware` dans les logs Traefik | en continu, sur chaque routeur |
| Latence de `plugins.traefik.io` depuis un pod de pi1 | **214 ms** |
Le registre répondait normalement au moment du diagnostic : ce n'était pas une panne, juste un hoquet de moins de 10 s. Ça a suffi à ouvrir l'edge et à couper les sites. Un `kubectl rollout restart` a tout rétabli (404 → 200) — c'est la remise en service immédiate, faite avant ce correctif.
## Pourquoi c'est arrivé
### 1. Le réseau était dans le chemin de démarrage
`experimental.plugins` fait passer Traefik par `RegistryDownloader.Download()`, qui appelle `plugins.traefik.io` **à chaque démarrage**, avec 10 s de timeout.
> ⚠ La correction évidente — persister `/plugins-storage`, aujourd'hui un `emptyDir` — **est fausse**. Vérifié dans `pkg/plugins/downloader.go` (v3.6.2) : l'appel HTTP est fait *inconditionnellement*, le cache ne sert qu'à envoyer un hash pour obtenir un 304. Avoir l'archive sur disque n'aurait rien évité.
### 2. L'échec était silencieux
Servir l'ingress sans bouncer ni captcha, en ne le signalant que dans les logs, n'est pas un mode dégradé acceptable pour un composant de sécurité.
## Ce que fait cette PR
**`experimental.localPlugins`** — un autre chemin de code (`SetupLocalPlugins`), qui ne fait **aucune I/O réseau** : Yaegi lit les sources sous `/plugins-local/src/<module>`. Elles sont posées sur un PVC par un Job Ansible, à la version déclarée. Le réseau ne sert plus qu'au provisionnement, où l'on peut réessayer (5 tentatives) sans que l'ingress soit en jeu.
**`abortOnPluginFailure: true`** — Traefik ≥ 3.3 sait refuser de démarrer plutôt que de servir sans plugin. Maintenant qu'il n'y a plus de réseau au boot, ce drapeau ne peut se déclencher que sur un PVC vide ou corrompu : cas où l'on veut précisément qu'il refuse de démarrer.
Même famille de fragilité que 93e7abf (résolution de la forge, 2026-08-26) : on ne fait pas dépendre l'edge d'un service joignable à la seconde près.
> ⚠ **Monter la version du plugin passe désormais par Ansible** (`--tags traefik-plugins`) et non plus par un champ que Traefik irait chercher. Le Job est idempotent (marqueur `.vendored-version`), tourne sur le nœud du control-plane et monte le PVC RWO en parallèle de Traefik — il peut donc repeupler **sans couper l'ingress**, la nouvelle version prenant effet au redémarrage suivant.
## Vérification
Appliqué en prod (`ansible-playbook playbooks/system/k3s_config.yml`), puis mesuré :
| Contrôle | Avant | Après |
|---|---|---|
| Chargement du plugin | `Loading plugins…` 15:00:40 → **échec 15:00:50** (10 s de timeout) | `Loading plugins…` → `Plugins loaded.` **dans la même seconde** |
| Erreurs `invalid middleware` | en continu | **0** |
| `www.arcodange.lab` / `cms-rec.arcodange.lab` | 404 / 404 | **200 / 200** |
| `grafana` / `gitea` | 302 / 200 | 302 / 200 |
| Pull du bouncer | figé à 14:30 | **18:37:38**, en continu |
| Montage `/plugins-storage` | `emptyDir` | **supprimé** |
- **Test de non-régression** : `rollout restart` de Traefik — le scénario exact qui cassait. Pod neuf, plugin chargé dans la seconde, 0 middleware invalide, routes toujours à 200.
- **PVC** : `v1.3.3`, 107 fichiers, 1 Mo, `.traefik.yml` + `vendor/` + `go.mod` présents, lisibles par l'UID 65532 de Traefik.
- **Rendu** : `helm template` du chart v37.4.0 avec les valeurs réelles du playbook, avant application.
- **Pas de dérive collatérale** : `--tags longhorn --check --diff` → `ok`, le playbook ne touche que Traefik.
Ce que je **n'ai pas** exercé : un blocage réel par le bouncer. `clientTrustedIPs` couvre `192.168.1.0/24` et `10.42.0.0/16`, donc toute requête que je peux émettre (LAN ou pod) est whitelistée par construction. La preuve du chemin d'application reste le pull des décisions depuis la LAPI, qui tourne.
## Hors périmètre, constaté au passage
- **594 machines fantômes** dans la base de la LAPI : chaque redémarrage du pod LAPI enregistre une machine de plus (depuis 2025-12) et rien ne les purge.
- **La LAPI avait crashé plus tôt** sur `FATAL: remaining connection slots are reserved for roles with the SUPERUSER attribute` — saturation de `max_connections` (100) sur le Postgres de pi2. Deux identifiants dynamiques Vault de `plausible` détenaient à eux seuls 20 connexions *idle* : pgbouncer ouvre un pool par couple (user, base), et chaque rotation Vault en crée un nouveau sans fermer l'ancien.
Ces deux points sont réels mais distincts de la panne d'ingress, et leur cause racine est dans le dépôt `tools`, pas ici.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Le 2026-09-20 à 15:00:50, Traefik a redémarré et n'a pas réussi à télécharger
le plugin bouncer CrowdSec :
unable to install plugin crowdsec-bouncer: ... Get
"https://plugins.traefik.io/.../v1.3.3": context deadline exceeded
Il ne s'arrête pas pour autant. Il journalise « Plugins are disabled because an
error has occurred. » et sert quand même — sans plugin. Le middleware `crowdsec`
devient alors « invalid middleware type or middleware does not exist » et Traefik
JETTE tous les routeurs qui le référencent, c'est-à-dire presque tout l'ingress.
Mesuré : www.arcodange.lab et cms-rec.arcodange.lab en 404 pendant 3 h 18, et
zéro pull du bouncer depuis 14:30. Un `kubectl rollout restart` a tout rétabli
(404 → 200), ce qui confirme le caractère transitoire : le registre répondait en
214 ms depuis un pod de pi1 au moment du diagnostic. Un hoquet de moins de 10 s
a suffi à ouvrir l'edge et à couper les sites.
Deux causes, deux correctifs.
1. Le réseau était dans le chemin de démarrage (experimental.localPlugins)
`experimental.plugins` fait passer Traefik par RegistryDownloader.Download(),
qui appelle plugins.traefik.io à CHAQUE démarrage, avec 10 s de timeout.
⚠ Avoir déjà l'archive en cache NE DISPENSE PAS de l'appel : vérifié dans
pkg/plugins/downloader.go (v3.6.2), le cache ne sert qu'à envoyer un hash pour
obtenir un 304. Persister /plugins-storage (aujourd'hui un emptyDir) n'aurait
donc rien évité — c'était la correction évidente, elle est fausse.
`localPlugins` emprunte un autre chemin de code, SetupLocalPlugins, qui ne fait
aucune I/O réseau : Yaegi lit les sources sous /plugins-local/src/<module>. Les
sources sont posées sur un PVC par un Job Ansible, à la version déclarée.
Le réseau ne sert plus qu'au provisionnement, où l'on peut réessayer sans que
l'ingress soit en jeu (5 tentatives).
Même famille de fragilité que 2026-08-26 (résolution de la forge) : on ne fait
pas dépendre l'edge d'un service joignable à la seconde près.
2. L'échec était silencieux (abortOnPluginFailure)
Servir l'ingress sans bouncer ni captcha, en ne le disant qu'en INFO dans les
logs, n'est pas un mode dégradé acceptable pour un composant de sécurité.
Traefik >= 3.3 sait refuser de démarrer : on le lui demande. Maintenant qu'il
n'y a plus de réseau au boot, ce drapeau ne peut se déclencher que sur un PVC
vide ou corrompu — cas où l'on veut précisément qu'il refuse de démarrer.
⚠ Monter la version du plugin passe désormais par Ansible
(`--tags traefik-plugins`) et non plus par un champ que Traefik irait chercher.
Le Job est idempotent (marqueur .vendored-version) et tourne sur le nœud du
control-plane, donc il peut repeupler le PVC sans couper l'ingress.
Vérifié : `helm template` du chart v37.4.0 avec les valeurs réelles du playbook
rend bien --experimental.localPlugins.crowdsec-bouncer.moduleName et
--experimental.abortonpluginfailure=true, le montage en subPath, et plus aucun
emptyDir `plugins`.
Co-Authored-By: Claude Opus 5 <[email protected]>
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.
Ce qui s'est passé
Le 2026-09-20 à 15:00:50, Traefik a redémarré et n'a pas réussi à télécharger le plugin bouncer CrowdSec :
Il ne s'arrête pas pour autant. Il journalise
Plugins are disabled because an error has occurred.en ERR puis sert quand même — sans plugin. Le middlewarecrowdsecdevient alorsinvalid middleware type or middleware does not exist, et Traefik jette tous les routeurs qui le référencent, c'est-à-dire presque tout l'ingress : kadans, cms, webapp, url-shortener, minio, telegram-gateway, prospection, grafana, gitea, plausible.Mesuré au diagnostic :
www.arcodange.lab,cms-rec.arcodange.labinvalid middlewaredans les logs Traefikplugins.traefik.iodepuis un pod de pi1Le registre répondait normalement au moment du diagnostic : ce n'était pas une panne, juste un hoquet de moins de 10 s. Ça a suffi à ouvrir l'edge et à couper les sites. Un
kubectl rollout restarta tout rétabli (404 → 200) — c'est la remise en service immédiate, faite avant ce correctif.Pourquoi c'est arrivé
1. Le réseau était dans le chemin de démarrage
experimental.pluginsfait passer Traefik parRegistryDownloader.Download(), qui appelleplugins.traefik.ioà chaque démarrage, avec 10 s de timeout.2. L'échec était silencieux
Servir l'ingress sans bouncer ni captcha, en ne le signalant que dans les logs, n'est pas un mode dégradé acceptable pour un composant de sécurité.
Ce que fait cette PR
experimental.localPlugins— un autre chemin de code (SetupLocalPlugins), qui ne fait aucune I/O réseau : Yaegi lit les sources sous/plugins-local/src/<module>. Elles sont posées sur un PVC par un Job Ansible, à la version déclarée. Le réseau ne sert plus qu'au provisionnement, où l'on peut réessayer (5 tentatives) sans que l'ingress soit en jeu.abortOnPluginFailure: true— Traefik ≥ 3.3 sait refuser de démarrer plutôt que de servir sans plugin. Maintenant qu'il n'y a plus de réseau au boot, ce drapeau ne peut se déclencher que sur un PVC vide ou corrompu : cas où l'on veut précisément qu'il refuse de démarrer.Même famille de fragilité que
93e7abf(résolution de la forge, 2026-08-26) : on ne fait pas dépendre l'edge d'un service joignable à la seconde près.Vérification
Appliqué en prod (
ansible-playbook playbooks/system/k3s_config.yml), puis mesuré :Loading plugins…15:00:40 → échec 15:00:50 (10 s de timeout)Loading plugins…→Plugins loaded.dans la même secondeinvalid middlewarewww.arcodange.lab/cms-rec.arcodange.labgrafana/gitea/plugins-storageemptyDirrollout restartde Traefik — le scénario exact qui cassait. Pod neuf, plugin chargé dans la seconde, 0 middleware invalide, routes toujours à 200.v1.3.3, 107 fichiers, 1 Mo,.traefik.yml+vendor/+go.modprésents, lisibles par l'UID 65532 de Traefik.helm templatedu chart v37.4.0 avec les valeurs réelles du playbook, avant application.--tags longhorn --check --diff→ok, le playbook ne touche que Traefik.Ce que je n'ai pas exercé : un blocage réel par le bouncer.
clientTrustedIPscouvre192.168.1.0/24et10.42.0.0/16, donc toute requête que je peux émettre (LAN ou pod) est whitelistée par construction. La preuve du chemin d'application reste le pull des décisions depuis la LAPI, qui tourne.Hors périmètre, constaté au passage
FATAL: remaining connection slots are reserved for roles with the SUPERUSER attribute— saturation demax_connections(100) sur le Postgres de pi2. Deux identifiants dynamiques Vault deplausibledétenaient à eux seuls 20 connexions idle : pgbouncer ouvre un pool par couple (user, base), et chaque rotation Vault en crée un nouveau sans fermer l'ancien.Ces deux points sont réels mais distincts de la panne d'ingress, et leur cause racine est dans le dépôt
tools, pas ici.🤖 Generated with Claude Code