infra : les conteneurs relisent les fichiers du dépôt à chaque déploiement (#176) #177

Merged
lenaic merged 3 commits from lenaic/176-montages-fichiers into develop 2026-09-08 09:07:16 +00:00
Owner

Ferme #176, sauf le tableau de bord Grafana, qui reste à faire et que je dis à la fin.

Le défaut

Un montage de fichier unique suit l'inode, pas le chemin. Git n'édite jamais un fichier en place : il en écrit un neuf et le renomme. Après un déploiement, le conteneur garde donc l'ancien contenu, et rien ne le signale.

Deux pannes en deux jours, même cause :

  • Prometheus a tourné quatre jours avec la configuration du 3 septembre. Le job blackbox était dans le fichier côté hôte, absent côté conteneur, et la cinquième alerte du #42 n'avait aucune donnée sur laquelle se déclencher.
  • Le tableau de bord a rendu 404 après le déploiement de la #173 : le Caddy de la forge ne voyait pas le montage du front.

Ce que ça change

1. La boucle des piles passe recreate: always. Les piles à build: true étaient déjà recréées par effet de bord de la reconstruction ; le réglage rend la règle explicite et uniforme. Après un déploiement, chaque conteneur exécute les fichiers qui sont dans le dépôt, sans exception.

Coût mesuré : quelques secondes d'interruption par pile. Les volumes nommés survivent, le TSDB comme les données PostgreSQL.

2. Notre propre API entre dans les cibles blackbox. Elle en était absente parce que prometheus.yml est antérieur à la pile api, alors que c'est la latence que l'ENF-04 chiffre à 400 ms p95. Sondée sur son écoute directe et non par Caddy, pour mesurer le service et non le proxy.

3. Le manuel dit comment reconnaître le cas en deux commandes : le compte diffère entre le fichier de l'hôte et celui que le conteneur lit.

Preuve

Le correctif manuel a déjà été appliqué hier soir pour rétablir la sonde, et l'effet est mesuré :

                              avant   après
blackbox vu du conteneur        0       4
cibles Prometheus               4       7
probe_success                 aucune   3 séries

Cette demande fait en sorte que ça n'ait plus à être fait à la main.

banc du rôle app        16 contrôles, 0 échec
ansible-lint            profil production, 50 fichiers
yamllint                propre
--syntax-check          bootstrap, site, restore
dix bancs de la chaîne  verts

Le banc du rôle garde le réglage, et il est éprouvé en rouge : retirer la ligne recreate: always le fait échouer sur l'assertion visée.

Ce que ça ne fait pas

Le troisième critère du #176, un tableau de bord Grafana montrant disponibilité et latence des sondes, n'est pas ici. Les séries existent maintenant, probe_success et probe_duration_seconds ; il reste à les afficher. C'est du provisioning Grafana, donc plutôt le terrain de Gabriel, et ça se fait sans toucher à cette demande.

Ferme #176, sauf le tableau de bord Grafana, qui reste à faire et que je dis à la fin. ## Le défaut Un montage de fichier unique suit l'**inode**, pas le chemin. Git n'édite jamais un fichier en place : il en écrit un neuf et le renomme. Après un déploiement, le conteneur garde donc l'ancien contenu, et **rien ne le signale**. Deux pannes en deux jours, même cause : - **Prometheus a tourné quatre jours** avec la configuration du 3 septembre. Le job `blackbox` était dans le fichier côté hôte, absent côté conteneur, et la cinquième alerte du #42 n'avait aucune donnée sur laquelle se déclencher. - **Le tableau de bord a rendu 404** après le déploiement de la #173 : le Caddy de la forge ne voyait pas le montage du front. ## Ce que ça change **1. La boucle des piles passe `recreate: always`.** Les piles à `build: true` étaient déjà recréées par effet de bord de la reconstruction ; le réglage rend la règle explicite et uniforme. Après un déploiement, chaque conteneur exécute les fichiers qui sont dans le dépôt, sans exception. Coût mesuré : quelques secondes d'interruption par pile. Les volumes nommés survivent, le TSDB comme les données PostgreSQL. **2. Notre propre API entre dans les cibles blackbox.** Elle en était absente parce que `prometheus.yml` est antérieur à la pile `api`, alors que c'est la latence que l'ENF-04 chiffre à 400 ms p95. Sondée sur son écoute directe et non par Caddy, pour mesurer le service et non le proxy. **3. Le manuel dit comment reconnaître le cas** en deux commandes : le compte diffère entre le fichier de l'hôte et celui que le conteneur lit. ## Preuve Le correctif manuel a déjà été appliqué hier soir pour rétablir la sonde, et l'effet est mesuré : ``` avant après blackbox vu du conteneur 0 4 cibles Prometheus 4 7 probe_success aucune 3 séries ``` Cette demande fait en sorte que ça n'ait plus à être fait à la main. ``` banc du rôle app 16 contrôles, 0 échec ansible-lint profil production, 50 fichiers yamllint propre --syntax-check bootstrap, site, restore dix bancs de la chaîne verts ``` Le banc du rôle garde le réglage, et il est **éprouvé en rouge** : retirer la ligne `recreate: always` le fait échouer sur l'assertion visée. ## Ce que ça ne fait pas Le troisième critère du #176, un tableau de bord Grafana montrant disponibilité et latence des sondes, n'est pas ici. Les séries existent maintenant, `probe_success` et `probe_duration_seconds` ; il reste à les afficher. C'est du provisioning Grafana, donc plutôt le terrain de Gabriel, et ça se fait sans toucher à cette demande.
lenaic self-assigned this 2026-09-08 07:47:24 +00:00
infra: les conteneurs relisent les fichiers du dépôt à chaque déploiement (#176)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 9s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 28s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 39s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m8s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m12s
801017a43e
Un montage de fichier unique suit l'INODE, pas le chemin. Git n'édite jamais
un fichier en place : il en écrit un neuf et le renomme. Après un déploiement,
le conteneur garde donc l'ancien contenu, et RIEN ne le signale.

Deux pannes en deux jours, même cause :

- Prometheus a tourné QUATRE JOURS avec la configuration du 3 septembre. Le
  job blackbox était dans le fichier côté hôte, absent côté conteneur, et la
  cinquième alerte du #42 n'avait aucune donnée sur laquelle se déclencher.
- Le tableau de bord a rendu 404 après le déploiement de la #173 : le Caddy de
  la forge ne voyait pas le montage du front.

Trois choses ici.

1. La boucle des piles passe recreate: always. Les piles à build: true étaient
   déjà recréées par effet de bord de la reconstruction ; le réglage rend la
   règle explicite et uniforme. Coût mesuré : quelques secondes par pile, les
   volumes nommés survivent.

2. Notre propre API entre dans les cibles blackbox. Elle en était absente parce
   que prometheus.yml est antérieur à la pile api, alors que c'est la latence
   que l'ENF-04 chiffre à 400 ms p95. Sondée sur son écoute directe, pas par
   Caddy, pour mesurer le service et non le proxy.

3. Le manuel de supervision dit comment reconnaître le cas en deux commandes :
   le compte diffère entre le fichier de l'hôte et celui que le conteneur lit.

Le banc du rôle garde le réglage, éprouvé en rouge sur sa suppression.

ansible-lint profil production, yamllint propre, trois playbooks valides,
banc du rôle 16 contrôles, dix bancs verts.
Member

Le diagnostic est bon : ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro est bien un montage de FICHIER, git renomme au lieu d'éditer en place, le conteneur garde l'ancien inode. Et recreate: always répare au passage quelque chose que la demande ne mentionne pas et qui est plus grave que la configuration : la pile api monte ../../../services/api:/opt/api:ro et lance uvicorn sans rechargement, donc jusqu'ici un déploiement ne mettait jamais le nouveau code d'API en service. Ça, c'est un vrai gain.

Mais le réglage est posé sur les cinq piles d'un coup, et c'est là que ça coince.

1. Chaque fusion dans develop recrée le conteneur PostgreSQL de production

app_stacks vaut postgres, minio, mlflow, api, supervision. Aucune des trois premières n'a le défaut qu'on répare :

infra/compose/postgres/docker-compose.yml
  - donnees:/var/lib/postgresql/data     volume nommé
  - ./conf:/etc/enervision:ro            RÉPERTOIRE
  - ./tls:/etc/postgresql/tls:ro         RÉPERTOIRE

et son commentaire dit déjà, en capitales : « UN SEUL MONTAGE, ET DE RÉPERTOIRE — jamais quatre montages de fichiers. Un montage de FICHIER ne suit pas un remplacement par renommage ». Il donne même les deux inodes mesurés sur la machine. Le projet connaissait donc la cause ET le remède avant cette demande ; la base est déjà à l'abri, et elle sera pourtant redémarrée à chaque fusion.

Le coût n'est pas nul : la relève tourne à la minute, la zone argent à :00, la zone or à :17 et :27, les recommandations à :37. Un déploiement tombe forcément au milieu d'une passe. « Quelques secondes d'interruption par pile » n'est pas faux, mais c'est cinq piles, dont la base, à chaque fusion, pour un défaut qui n'en concerne que deux.

app_stacks porte déjà build et env par pile. Un recreate au même endroit — vrai pour api et supervision, faux pour postgres — garde tout le bénéfice sans bouger la base.

2. Le remède d'un mot est dans le dépôt et n'est pas appliqué

Le répertoire infra/compose/supervision/prometheus/ ne contient qu'un seul fichier, prometheus.yml. Donc :

- ./prometheus:/etc/prometheus:ro

ferme la cause pour de bon, sans recréer quoi que ce soit, et c'est exactement ce que la pile postgres recommande en capitales depuis avant cette demande.

3. Sans ça, une promesse du fichier reste fausse

La composition porte toujours :

- --web.enable-lifecycle
  # Permet `curl -X POST 127.0.0.1:9090/-/reload` sans redémarrer le
  # conteneur quand prometheus.yml change.

Ce commentaire n'a jamais été vrai avec un montage de fichier — le rechargement relit l'inode monté, c'est-à-dire l'ancien — et il reste faux après cette demande. Le nouveau runbook envoie d'ailleurs l'exploitant sur --force-recreate, ce qui en est l'aveu. Soit on monte le répertoire et le commentaire redevient vrai, soit on retire le drapeau et le commentaire.

4. recreate: always rejoue l'amorçage MinIO à chaque déploiement

infra/compose/docker-compose.yml monte ./minio/init-buckets.sh:/init/init-buckets.sh:ro (montage de fichier, donc concerné) sur un conteneur restart: "no". Le recréer à chaque passage le relance à chaque déploiement. C'est probablement idempotent, mais c'est un changement de comportement que la demande ne dit pas.

5. Le contrôle porte sur le texte, pas sur le comportement

- "'recreate: always' in code_piles" rougirait sur recreate: "always", qui est le même réglage. C'est la même famille que ce que la #174 corrige à côté.


Sur le reste : la cible blackbox http://127.0.0.1:8000/health est juste — les conteneurs de supervision sont en network_mode: host et l'API écoute bien sur 127.0.0.1:8000 de la même machine. Le runbook est clair et les deux commandes de diagnostic sont les bonnes.

Ce que je demanderais avant fusion : le recreate par pile (point 1) et le montage de répertoire pour Prometheus (point 2). Les deux tiennent en trois lignes et retirent la seule chose qui me gêne, une base de production redémarrée par une fusion.

Le diagnostic est bon : `./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro` est bien un montage de FICHIER, git renomme au lieu d'éditer en place, le conteneur garde l'ancien inode. Et `recreate: always` répare au passage quelque chose que la demande ne mentionne pas et qui est plus grave que la configuration : la pile `api` monte `../../../services/api:/opt/api:ro` et lance uvicorn sans rechargement, donc **jusqu'ici un déploiement ne mettait jamais le nouveau code d'API en service**. Ça, c'est un vrai gain. Mais le réglage est posé sur les cinq piles d'un coup, et c'est là que ça coince. ## 1. Chaque fusion dans develop recrée le conteneur PostgreSQL de production `app_stacks` vaut `postgres, minio, mlflow, api, supervision`. Aucune des trois premières n'a le défaut qu'on répare : ``` infra/compose/postgres/docker-compose.yml - donnees:/var/lib/postgresql/data volume nommé - ./conf:/etc/enervision:ro RÉPERTOIRE - ./tls:/etc/postgresql/tls:ro RÉPERTOIRE ``` et son commentaire dit déjà, en capitales : « UN SEUL MONTAGE, ET DE RÉPERTOIRE — jamais quatre montages de fichiers. Un montage de FICHIER ne suit pas un remplacement par renommage ». Il donne même les deux inodes mesurés sur la machine. Le projet connaissait donc la cause ET le remède avant cette demande ; la base est déjà à l'abri, et elle sera pourtant redémarrée à chaque fusion. Le coût n'est pas nul : la relève tourne **à la minute**, la zone argent à `:00`, la zone or à `:17` et `:27`, les recommandations à `:37`. Un déploiement tombe forcément au milieu d'une passe. « Quelques secondes d'interruption par pile » n'est pas faux, mais c'est cinq piles, dont la base, à chaque fusion, pour un défaut qui n'en concerne que deux. `app_stacks` porte déjà `build` et `env` par pile. Un `recreate` au même endroit — vrai pour `api` et `supervision`, faux pour `postgres` — garde tout le bénéfice sans bouger la base. ## 2. Le remède d'un mot est dans le dépôt et n'est pas appliqué Le répertoire `infra/compose/supervision/prometheus/` ne contient **qu'un seul fichier**, `prometheus.yml`. Donc : ```yaml - ./prometheus:/etc/prometheus:ro ``` ferme la cause pour de bon, sans recréer quoi que ce soit, et c'est exactement ce que la pile `postgres` recommande en capitales depuis avant cette demande. ## 3. Sans ça, une promesse du fichier reste fausse La composition porte toujours : ```yaml - --web.enable-lifecycle # Permet `curl -X POST 127.0.0.1:9090/-/reload` sans redémarrer le # conteneur quand prometheus.yml change. ``` Ce commentaire n'a jamais été vrai avec un montage de fichier — le rechargement relit l'inode monté, c'est-à-dire l'ancien — et il reste faux après cette demande. Le nouveau runbook envoie d'ailleurs l'exploitant sur `--force-recreate`, ce qui en est l'aveu. Soit on monte le répertoire et le commentaire redevient vrai, soit on retire le drapeau et le commentaire. ## 4. `recreate: always` rejoue l'amorçage MinIO à chaque déploiement `infra/compose/docker-compose.yml` monte `./minio/init-buckets.sh:/init/init-buckets.sh:ro` (montage de fichier, donc concerné) sur un conteneur `restart: "no"`. Le recréer à chaque passage le **relance** à chaque déploiement. C'est probablement idempotent, mais c'est un changement de comportement que la demande ne dit pas. ## 5. Le contrôle porte sur le texte, pas sur le comportement `- "'recreate: always' in code_piles"` rougirait sur `recreate: "always"`, qui est le même réglage. C'est la même famille que ce que la #174 corrige à côté. --- Sur le reste : la cible blackbox `http://127.0.0.1:8000/health` est juste — les conteneurs de supervision sont en `network_mode: host` et l'API écoute bien sur `127.0.0.1:8000` de la même machine. Le runbook est clair et les deux commandes de diagnostic sont les bonnes. Ce que je demanderais avant fusion : le `recreate` par pile (point 1) et le montage de répertoire pour Prometheus (point 2). Les deux tiennent en trois lignes et retirent la seule chose qui me gêne, une base de production redémarrée par une fusion.
infra: la recréation se décide pile par pile, pas globalement (#176)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 37s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 20s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m2s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m33s
06d0d6f7f7
Retour de Gabriel en relecture, et il a raison sur le point qui compte.

recreate: always sur les cinq piles recréait le conteneur PostgreSQL DE
PRODUCTION à chaque fusion. Or postgres ne porte que des montages de
répertoire et de volume : sa composition le dit déjà en capitales, avec les
deux inodes mesurés. Il n'a pas le défaut qu'on répare, et le couper pendant
que la relève tourne à la minute, la zone argent à :00, la zone or à :17
et :27 et les recommandations à :37, c'est tomber au milieu d'une passe à
chaque déploiement.

Le drapeau vit maintenant dans app_stacks, à côté de build et env :

  api           vrai  monte services/api en lecture seule et lance uvicorn
                      sans rechargement : sans recréation, un déploiement ne
                      mettait jamais le nouveau code d'API en service
  supervision   vrai  monte le fichier de règles d'alerte de Grafana
  postgres      faux  montages de répertoire, déjà à l'abri
  minio         faux  monte init-buckets.sh sur un conteneur restart: no ;
                      le recréer le RELANCE à chaque déploiement
  mlflow        faux  reconstruite de toute façon par build: true

Et le remède d'un mot, que Gabriel a relevé et qui était dans le dépôt depuis
avant cette demande : prometheus/ et blackbox/ sont montés en RÉPERTOIRES.
Chacun ne contient qu'un fichier, le montage est strictement équivalent, la
cause disparaît sans recréer quoi que ce soit. --web.enable-lifecycle redevient
vrai : son commentaire promettait un rechargement à chaud qui relisait
l'ancien inode.

Le contrôle du banc porte sur le drapeau et non sur un littéral : il cherchait
« recreate: always » et aurait rougi sur « recreate: "always" », qui est le
même réglage. C'est la faute même que la #174 corrige à côté.

Éprouvé en rouge sur trois défauts réintroduits un par un : postgres remis en
recréation, l'API privée de la sienne, et la tâche qui ignore le drapeau.

ansible-lint profil production, yamllint propre, trois playbooks valides,
banc du rôle 16 contrôles, dix bancs verts.
Author
Owner

Tu as raison sur le point 1, et c'est le seul qui comptait. Corrigé dans 06d0d6f, avec le 2 qui va avec.

1. Le drapeau vit maintenant dans app_stacks

api           vrai   monte services/api en lecture seule, uvicorn sans rechargement
supervision   vrai   monte le fichier de règles d'alerte de Grafana
postgres      faux   montages de répertoire, déjà à l'abri
minio         faux   init-buckets.sh sur un conteneur restart: no
mlflow        faux   reconstruite de toute façon par build: true

Ton argument sur la cadence est le bon : relève à la minute, argent à :00, or à :17 et :27, recommandations à :37. Un déploiement tombe forcément au milieu d'une passe, et couper la base pour un défaut qu'elle n'a pas était une bêtise. Tes points 4 et 5 tombent avec.

Tu m'apprends au passage que la pile api monte services/api en lecture seule sans rechargement : je n'avais pas vu que le déploiement ne mettait jamais le nouveau code d'API en service. C'est le vrai gain de cette demande, et il est maintenant écrit dedans.

2. Le montage de répertoire, et j'ai étendu à blackbox

- ./prometheus:/etc/prometheus:ro
- ./blackbox:/etc/blackbox:ro

Chaque répertoire ne contient qu'un fichier, le montage est strictement équivalent, et la cause disparaît sans recréer quoi que ce soit. blackbox.yml avait exactement le même défaut, juste au-dessus dans le fichier.

Ton point 3 tombe avec : --web.enable-lifecycle redevient vrai, un rechargement à chaud relit enfin le bon fichier. Le runbook ne renvoie plus sur --force-recreate comme remède permanent.

5. Le contrôle porte sur le drapeau, plus sur un littéral

'item.item.recreate' in code_piles, plus les trois piles vérifiées dans app_stacks. Tu as raison que chercher recreate: always aurait rougi sur recreate: "always" : c'est la faute même que la #174 corrige à côté, je l'ai refaite en la corrigeant ailleurs.

Éprouvé en rouge sur trois défauts réintroduits un par un : postgres remis en recréation, l'API privée de la sienne, la tâche qui ignore le drapeau.

Ce que je ne traite pas

La pile postgres connaissait la cause et le remède avant cette demande, et son commentaire le dit mieux que le mien. Je n'y touche pas.

ansible-lint profil production, yamllint propre, banc du rôle à 16 contrôles, dix bancs verts.

Tu as raison sur le point 1, et c'est le seul qui comptait. Corrigé dans `06d0d6f`, avec le 2 qui va avec. ## 1. Le drapeau vit maintenant dans `app_stacks` ``` api vrai monte services/api en lecture seule, uvicorn sans rechargement supervision vrai monte le fichier de règles d'alerte de Grafana postgres faux montages de répertoire, déjà à l'abri minio faux init-buckets.sh sur un conteneur restart: no mlflow faux reconstruite de toute façon par build: true ``` Ton argument sur la cadence est le bon : relève à la minute, argent à `:00`, or à `:17` et `:27`, recommandations à `:37`. Un déploiement tombe forcément au milieu d'une passe, et couper la base pour un défaut qu'elle n'a pas était une bêtise. Tes points 4 et 5 tombent avec. Tu m'apprends au passage que la pile `api` monte `services/api` en lecture seule sans rechargement : je n'avais pas vu que le déploiement ne mettait jamais le nouveau code d'API en service. C'est le vrai gain de cette demande, et il est maintenant écrit dedans. ## 2. Le montage de répertoire, et j'ai étendu à blackbox ```yaml - ./prometheus:/etc/prometheus:ro - ./blackbox:/etc/blackbox:ro ``` Chaque répertoire ne contient qu'un fichier, le montage est strictement équivalent, et la cause disparaît sans recréer quoi que ce soit. `blackbox.yml` avait exactement le même défaut, juste au-dessus dans le fichier. Ton point 3 tombe avec : `--web.enable-lifecycle` redevient vrai, un rechargement à chaud relit enfin le bon fichier. Le runbook ne renvoie plus sur `--force-recreate` comme remède permanent. ## 5. Le contrôle porte sur le drapeau, plus sur un littéral `'item.item.recreate' in code_piles`, plus les trois piles vérifiées dans `app_stacks`. Tu as raison que chercher `recreate: always` aurait rougi sur `recreate: "always"` : c'est la faute même que la #174 corrige à côté, je l'ai refaite en la corrigeant ailleurs. Éprouvé en rouge sur trois défauts réintroduits un par un : postgres remis en recréation, l'API privée de la sienne, la tâche qui ignore le drapeau. ## Ce que je ne traite pas La pile `postgres` connaissait la cause et le remède avant cette demande, et son commentaire le dit mieux que le mien. Je n'y touche pas. ansible-lint profil production, yamllint propre, banc du rôle à 16 contrôles, dix bancs verts.
gabriel approved these changes 2026-09-08 08:42:16 +00:00
Dismissed
Merge branch 'develop' into lenaic/176-montages-fichiers
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 7s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 35s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 34s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 2m58s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m26s
6d09cdbd4e
lenaic dismissed gabriel's review 2026-09-08 08:56:08 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

gabriel approved these changes 2026-09-08 09:03:05 +00:00
lenaic merged commit 94e8f9b4fa into develop 2026-09-08 09:07:16 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
g2/enervision!177
No description provided.