infra : la sauvegarde PostgreSQL redevient observable, l'alerte disait faux (#172) #178

Merged
lenaic merged 2 commits from lenaic/172-sauvegarde-observable into develop 2026-09-08 09:01:44 +00:00
Owner

Ferme #172. L'alerte avait tort, et c'est le pire cas possible : une alerte critique qui se déclenche sur une absence de donnée désarme celui qui la lit.

Les vidages passaient tous les jours

/var/backups/postgresql/.dernier-etat
2026-09-08T02:30:04+00:00 OK 3 base(s) : enervision_preprod enervision_prod mlflow

enervision_prod-2026-09-08.dump    8,5 Mo   02:30
enervision_preprod-2026-09-08.dump 3,4 Mo   02:30
mlflow-2026-09-08.dump             138 ko   02:30
disque                             51 %

Deux causes, indépendantes

1. Le rôle backup n'a aucune étiquette dans site.yml.

Le déploiement continu joue --tags app,proxy. Un rôle sans étiquette n'est jamais joué. /usr/local/bin/pg-backup.sh est donc resté figé à sa version d'amorçage du 2 septembre, celle d'avant le #42, qui ne publie aucune métrique.

script installé (02/09)   0 occurrence de TEXTFILE
script du dépôt           6 occurrences

Le rôle prend l'étiquette backup et entre dans les étiquettes du déploiement. Il est idempotent et ne touche pas au socle : un répertoire, deux scripts, une ligne de cron.

2. Les fichiers de métriques sortaient en 0640 root:root.

pg-backup.sh tourne en root sous une umask restrictive et n'imposait pas de mode. node-exporter tourne sans privilège :

node_textfile_scrape_error 1
ev_ops_sauvegarde_pg.prom          0640 root:root     ← illisible
ev_ops_collecte.prom               0664 deploy:deploy ← lu, et remontait bien

D'où une panne qui ne touchait que la sauvegarde. Le chmod porte sur le temporaire, avant le renommage, donc l'écriture reste atomique. 0644 et pas 0640 : trois compteurs, aucun secret.

Vérifié sur le serveur avant d'écrire

Script réinstallé à la main, vidage rejoué :

pg-backup : 3 base(s) sauvegardée(s) dans /var/backups/postgresql
ev_ops_sauvegarde_pg_dernier_resultat 0
ev_ops_sauvegarde_pg_derniere_tentative_timestamp_seconds 1788853984
ev_ops_sauvegarde_pg_derniere_reussite_timestamp_seconds 1788853984

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

Preuve

banc de supervision   19 cas, dont la garde du mode 0644, ÉPROUVÉE EN ROUGE
ansible-lint          profil production, 50 fichiers
yamllint              propre
--syntax-check        bootstrap, site, restore
shellcheck            propre
dix bancs             verts

Lien avec le #176

Même famille : un fichier déployé une fois à la main, que le déploiement continu ne rafraîchit jamais. Le #176 traite les montages de fichiers dans les conteneurs, celui-ci un rôle Ansible sans étiquette. Les deux se fusionnent indépendamment.

Ferme #172. **L'alerte avait tort**, et c'est le pire cas possible : une alerte critique qui se déclenche sur une absence de donnée désarme celui qui la lit. ## Les vidages passaient tous les jours ``` /var/backups/postgresql/.dernier-etat 2026-09-08T02:30:04+00:00 OK 3 base(s) : enervision_preprod enervision_prod mlflow enervision_prod-2026-09-08.dump 8,5 Mo 02:30 enervision_preprod-2026-09-08.dump 3,4 Mo 02:30 mlflow-2026-09-08.dump 138 ko 02:30 disque 51 % ``` ## Deux causes, indépendantes **1. Le rôle `backup` n'a aucune étiquette dans `site.yml`.** Le déploiement continu joue `--tags app,proxy`. Un rôle sans étiquette n'est **jamais** joué. `/usr/local/bin/pg-backup.sh` est donc resté figé à sa version d'amorçage du 2 septembre, celle d'avant le #42, qui ne publie aucune métrique. ``` script installé (02/09) 0 occurrence de TEXTFILE script du dépôt 6 occurrences ``` Le rôle prend l'étiquette `backup` et entre dans les étiquettes du déploiement. Il est idempotent et ne touche pas au socle : un répertoire, deux scripts, une ligne de cron. **2. Les fichiers de métriques sortaient en `0640 root:root`.** `pg-backup.sh` tourne en root sous une umask restrictive et n'imposait pas de mode. node-exporter tourne sans privilège : ``` node_textfile_scrape_error 1 ev_ops_sauvegarde_pg.prom 0640 root:root ← illisible ev_ops_collecte.prom 0664 deploy:deploy ← lu, et remontait bien ``` D'où une panne qui ne touchait **que** la sauvegarde. Le `chmod` porte sur le temporaire, avant le renommage, donc l'écriture reste atomique. 0644 et pas 0640 : trois compteurs, aucun secret. ## Vérifié sur le serveur avant d'écrire Script réinstallé à la main, vidage rejoué : ``` pg-backup : 3 base(s) sauvegardée(s) dans /var/backups/postgresql ev_ops_sauvegarde_pg_dernier_resultat 0 ev_ops_sauvegarde_pg_derniere_tentative_timestamp_seconds 1788853984 ev_ops_sauvegarde_pg_derniere_reussite_timestamp_seconds 1788853984 ``` Cette demande fait en sorte que ça n'ait plus à être fait à la main. ## Preuve ``` banc de supervision 19 cas, dont la garde du mode 0644, ÉPROUVÉE EN ROUGE ansible-lint profil production, 50 fichiers yamllint propre --syntax-check bootstrap, site, restore shellcheck propre dix bancs verts ``` ## Lien avec le #176 Même famille : un fichier déployé une fois à la main, que le déploiement continu ne rafraîchit jamais. Le #176 traite les montages de fichiers dans les conteneurs, celui-ci un rôle Ansible sans étiquette. Les deux se fusionnent indépendamment.
lenaic self-assigned this 2026-09-08 07:55:36 +00:00
infra: la sauvegarde PostgreSQL redevient observable, l'alerte du #42 disait faux (#172)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 41s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 19s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 57s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m59s
97fdb65239
L'alerte « Sauvegarde PostgreSQL en échec » a ouvert une issue le 07/09 à
14h10, alors que les vidages passent tous les jours. Le dernier état dit
« 2026-09-08T02:30:04 OK 3 base(s) », 8,5 Mo pour enervision_prod, disque à
51 %. L'alerte se déclenchait sur une ABSENCE de donnée, ce qui est le pire
cas : une alerte critique qui a tort désarme celui qui la lit.

Deux causes, indépendantes, toutes les deux corrigées ici.

1. Le rôle backup n'a AUCUNE étiquette dans site.yml.
   Le déploiement continu joue --tags app,proxy : un rôle sans étiquette n'est
   jamais joué. /usr/local/bin/pg-backup.sh est donc resté figé à sa version
   d'amorçage du 2 septembre, celle d'avant le #42, qui ne publie aucune
   métrique. Zéro occurrence de TEXTFILE dans le script installé, six dans
   celui du dépôt. Le rôle prend l'étiquette « backup » et entre dans les
   étiquettes du déploiement. Il est idempotent et ne touche pas au socle :
   un répertoire, deux scripts, une ligne de cron.

2. Les fichiers de métriques sortaient en 0640 root:root.
   pg-backup.sh tourne en root sous une umask restrictive et n'imposait pas de
   mode. node-exporter, lui, tourne sans privilège : il rendait
   node_textfile_scrape_error 1 et n'exposait rien. Les fichiers du collecteur,
   eux, sont en 0664 et remontaient bien — d'où une panne qui ne touchait que
   la sauvegarde. Le chmod porte sur le TEMPORAIRE, avant le renommage, donc
   l'écriture reste atomique. 0644 et pas 0640 : trois compteurs, aucun secret.

Vérifié sur le serveur avant d'écrire : script réinstallé à la main, vidage
rejoué, ev_ops_sauvegarde_pg_dernier_resultat 0 et les deux horodatages
publiés. Cette demande fait en sorte que ça n'ait plus à être fait à la main.

Le banc de supervision garde le mode 0644, éprouvé en rouge sur sa suppression.

ansible-lint profil production, yamllint propre, trois playbooks valides,
shellcheck propre, dix bancs verts.
Member

Le diagnostic est juste et je l'ai vérifié de bout en bout : --collector.textfile.directory=/host/var/lib/node_exporter/textfile, répertoire créé en 0775 deploy:deploy par le rôle app, donc traversable ; des fichiers en 0640 root:root y étaient effectivement illisibles pour node-exporter, qui tourne sans privilège. chmod sur le TEMPORAIRE avant le mv garde bien l'atomicité. Le correctif est le bon, et le rôle backup sans étiquette était une vraie bombe à retardement — un script d'exploitation figé au 2 septembre que le déploiement continu ne rejouait jamais.

Un point qui me gêne, un autre plus léger.

1. Le contrôle de chaîne éprouve l'orthographe, pas le comportement — et il rend un message faux

depots=$(grep -c 'chmod 0644 "\$[fg]\.\$\$"' ".../pg-backup.sh" 2>/dev/null || echo 0)
if [ "$depots" -eq 2 ]; then

Deux problèmes, l'un après l'autre.

Le repli produit deux valeurs. grep -c imprime 0 sur sa sortie standard ET sort en 1 quand il ne trouve rien : || echo 0 ajoute donc une seconde ligne. Reproduit :

valeur brute = [0
0]
bash: [: 0
0: integer expression expected
ECHEC branche

Le verdict reste rouge, donc le banc n'est pas cassé — mais il crache une erreur bash et son message annonce « (0\n0 sur 2) ». Un rouge qui se présente mal se fait ignorer. Le || echo 0 est en trop, grep -c imprime déjà 0.

Surtout, le motif épingle une écriture, pas une propriété. umask 022 en tête de la fonction, ou install -m 0644, donneraient exactement le bon résultat et rougiraient ce banc. C'est le reproche que la #174 fait, à côté, aux bancs qui lisent du texte plutôt que d'observer un effet.

Et ici l'effet est observable presque gratuitement : le script porte déjà le crochet, TEXTFILE_DIR="${PG_BACKUP_TEXTFILE_DIR:-/var/lib/node_exporter/textfile}". Le jouer en entier demande un faux docker dans le PATH, ce qui n'est peut-être pas le prix qu'on veut payer aujourd'hui — mais alors, au minimum : retirer le || echo 0, et élargir le motif pour qu'une réécriture correcte (umask, install -m) ne fasse pas rougir du code sain.

2. Ce que --tags app,proxy,backup change vraiment

Le rôle est idempotent, Premier vidage est gardé par creates, rien à redire sur le fond. Mais à partir de cette fusion, une fusion dans develop réécrit /usr/local/bin/pg-backup.sh et /etc/cron.d/pg-backup en production. C'est voulu et c'est la bonne décision ; ça mérite juste d'être dit tel quel dans docs/runbooks/deploiement.md, à côté de la liste des rôles joués — quelqu'un qui débogue un vidage doit savoir que le script sous ses yeux peut avoir changé sous lui à la dernière fusion.

À noter aussi, si la #177 passe avec la #178 : une seule fusion recréera alors tous les conteneurs ET réécrira la sauvegarde. Ça fait beaucoup de gestes automatiques pour un git merge deux jours avant le jury. Rien ne s'y oppose techniquement, mais ça vaut d'être décidé sciemment plutôt que par empilement de deux demandes.


Le reste est propre : le commentaire de site.yml explique le pourquoi au bon endroit, et le choix de 0644 plutôt que 0640 est justifié (trois compteurs, aucun secret).

Le diagnostic est juste et je l'ai vérifié de bout en bout : `--collector.textfile.directory=/host/var/lib/node_exporter/textfile`, répertoire créé en `0775 deploy:deploy` par le rôle `app`, donc traversable ; des fichiers en `0640 root:root` y étaient effectivement illisibles pour node-exporter, qui tourne sans privilège. `chmod` sur le TEMPORAIRE avant le `mv` garde bien l'atomicité. Le correctif est le bon, et le rôle `backup` sans étiquette était une vraie bombe à retardement — un script d'exploitation figé au 2 septembre que le déploiement continu ne rejouait jamais. Un point qui me gêne, un autre plus léger. ## 1. Le contrôle de chaîne éprouve l'orthographe, pas le comportement — et il rend un message faux ```sh depots=$(grep -c 'chmod 0644 "\$[fg]\.\$\$"' ".../pg-backup.sh" 2>/dev/null || echo 0) if [ "$depots" -eq 2 ]; then ``` Deux problèmes, l'un après l'autre. **Le repli produit deux valeurs.** `grep -c` imprime `0` sur sa sortie standard ET sort en 1 quand il ne trouve rien : `|| echo 0` ajoute donc une seconde ligne. Reproduit : ``` valeur brute = [0 0] bash: [: 0 0: integer expression expected ECHEC branche ``` Le verdict reste rouge, donc le banc n'est pas cassé — mais il crache une erreur bash et son message annonce « (0\n0 sur 2) ». Un rouge qui se présente mal se fait ignorer. Le `|| echo 0` est en trop, `grep -c` imprime déjà `0`. **Surtout, le motif épingle une écriture, pas une propriété.** `umask 022` en tête de la fonction, ou `install -m 0644`, donneraient exactement le bon résultat et rougiraient ce banc. C'est le reproche que la #174 fait, à côté, aux bancs qui lisent du texte plutôt que d'observer un effet. Et ici l'effet est observable presque gratuitement : le script porte déjà le crochet, `TEXTFILE_DIR="${PG_BACKUP_TEXTFILE_DIR:-/var/lib/node_exporter/textfile}"`. Le jouer en entier demande un faux `docker` dans le `PATH`, ce qui n'est peut-être pas le prix qu'on veut payer aujourd'hui — mais alors, au minimum : retirer le `|| echo 0`, et élargir le motif pour qu'une réécriture correcte (umask, `install -m`) ne fasse pas rougir du code sain. ## 2. Ce que `--tags app,proxy,backup` change vraiment Le rôle est idempotent, `Premier vidage` est gardé par `creates`, rien à redire sur le fond. Mais à partir de cette fusion, **une fusion dans develop réécrit `/usr/local/bin/pg-backup.sh` et `/etc/cron.d/pg-backup` en production**. C'est voulu et c'est la bonne décision ; ça mérite juste d'être dit tel quel dans `docs/runbooks/deploiement.md`, à côté de la liste des rôles joués — quelqu'un qui débogue un vidage doit savoir que le script sous ses yeux peut avoir changé sous lui à la dernière fusion. À noter aussi, si la #177 passe avec la #178 : une seule fusion recréera alors tous les conteneurs ET réécrira la sauvegarde. Ça fait beaucoup de gestes automatiques pour un `git merge` deux jours avant le jury. Rien ne s'y oppose techniquement, mais ça vaut d'être décidé sciemment plutôt que par empilement de deux demandes. --- Le reste est propre : le commentaire de `site.yml` explique le pourquoi au bon endroit, et le choix de `0644` plutôt que `0640` est justifié (trois compteurs, aucun secret).
outillage: le contrôle du mode des métriques dit vrai (#172)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 39s
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 18s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m9s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m12s
6414118356
Retour de Gabriel, et deux versions successives se sont trompées dans les
deux sens avant d'arriver à celle-ci.

1. `grep -c ... || echo 0` produisait DEUX lignes. grep -c imprime déjà 0
   quand il ne trouve rien, et sort en 1 : le repli ajoutait une seconde
   ligne, le test crachait « integer expression expected » et le message
   annonçait « 0\n0 sur 2 ». Le verdict restait rouge, mais un rouge qui se
   présente mal se fait ignorer.

2. Ma première correction a élargi le motif à `umask`, et le banc est devenu
   VERT sur un script dont les deux chmod avaient disparu — le script porte un
   umask ailleurs. Un contrôle qui accepte tout ne garde rien, ce qui est
   exactement le reproche que la #174 fait aux autres, et que je viens de
   refaire en le corrigeant.

Le contrôle compte maintenant le mode LÀ OÙ IL SE POSE : sur les lignes qui
manipulent le fichier temporaire, et il en veut deux. `chmod` et
`install -m` sont acceptés, un umask global ne l'est pas — c'est justement
lui qui produisait le 0640 illisible.

Éprouvé dans les trois sens : les deux chmod retirés donnent « 0 sur 2 », un
seul retiré donne « 1 sur 2 », et un `install -m 0644` équivalent reste vert.

shellcheck propre, dix bancs verts.
gabriel approved these changes 2026-09-08 08:41:57 +00:00
lenaic merged commit 6ccf25f516 into develop 2026-09-08 09:01:44 +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!178
No description provided.