docs : les deux manuels disent quoi faire quand ça échoue (#44) #190

Merged
lenaic merged 3 commits from lenaic/44-manuels-echec into develop 2026-09-08 12:03:54 +00:00
Owner

Ferme la première partie du #44. Gabarit repris de mlflow.md §8-§9, comme demandé.

reprise.md : une section d'échec, elle n'en avait aucune

Les trois frictions de l'exercice du 02/09 remontent du tableau de constat vers une procédure : pg_restore sans shared_preload_libraries=timescaledb, forgejo.db rejoué par-dessus le vidage, l'app.ini qui écoute sur 127.0.0.1.

Plus quatre cas rencontrés depuis : coffre qui refuse, vidage illisible, migrations plus récentes que le vidage, pile api sautée faute de clé de signature.

deploiement.md : huit symptômes

Les quatre demandés, coffre, UFW qui coupe SSH, crontab qui garde une tâche retirée du dépôt, retour arrière du front. Plus le cas le plus traître, failed=0 mais rien de neuf sur le serveur, qui est arrivé deux fois cette semaine et qu'on a mis vingt minutes à comprendre chaque fois.

Le retour arrière du front est écrit pour de vrai : app.ancien existe, une génération de profondeur, et Caddy sert le répertoire donc il n'y a rien à recharger.

Un défaut trouvé en écrivant, et c'est la raison d'être de l'exercice

restore.yml prend un « filet » avant d'écraser, en appelant pg-backup.sh. Ce script nomme ses fichiers <base>-<date du jour>.dump, dans le même répertoire.

Restaurer la sauvegarde du jour faisait donc écraser la source par le filet, juste avant de la lire. La reprise repartait de l'état cassé, et le dispositif censé protéger détruisait ce qu'il protégeait. Personne ne l'aurait vu avant d'en avoir besoin.

Le correctif tient en deux lignes : pg-backup.sh accepte PG_BACKUP_DEST, et restore.yml écrit son filet dans backup_dir/filet/.

Pour demain matin

Marvin rejoue reprise.md. Je me tais pendant. Chaque question qu'il me pose est un défaut du manuel : je la note, je ne réponds pas, et je corrige après. C'est le critère central du ticket, et y répondre le perdrait.

ansible-lint profil production, yamllint propre, trois playbooks valides, shellcheck propre, 199 liens markdown vérifiés.

Ferme la première partie du #44. Gabarit repris de `mlflow.md` §8-§9, comme demandé. ## `reprise.md` : une section d'échec, elle n'en avait aucune Les trois frictions de l'exercice du 02/09 remontent du tableau de constat vers une **procédure** : `pg_restore` sans `shared_preload_libraries=timescaledb`, `forgejo.db` rejoué par-dessus le vidage, l'`app.ini` qui écoute sur `127.0.0.1`. Plus quatre cas rencontrés depuis : coffre qui refuse, vidage illisible, migrations plus récentes que le vidage, pile `api` sautée faute de clé de signature. ## `deploiement.md` : huit symptômes Les quatre demandés, coffre, UFW qui coupe SSH, crontab qui garde une tâche retirée du dépôt, retour arrière du front. Plus le cas le plus traître, **`failed=0` mais rien de neuf sur le serveur**, qui est arrivé deux fois cette semaine et qu'on a mis vingt minutes à comprendre chaque fois. Le retour arrière du front est écrit pour de vrai : `app.ancien` existe, une génération de profondeur, et Caddy sert le répertoire donc il n'y a rien à recharger. ## Un défaut trouvé en écrivant, et c'est la raison d'être de l'exercice `restore.yml` prend un « filet » avant d'écraser, en appelant `pg-backup.sh`. Ce script nomme ses fichiers `<base>-<date du jour>.dump`, **dans le même répertoire**. Restaurer la sauvegarde **du jour** faisait donc écraser la source par le filet, juste avant de la lire. La reprise repartait de l'état cassé, et le dispositif censé protéger détruisait ce qu'il protégeait. Personne ne l'aurait vu avant d'en avoir besoin. Le correctif tient en deux lignes : `pg-backup.sh` accepte `PG_BACKUP_DEST`, et `restore.yml` écrit son filet dans `backup_dir/filet/`. ## Pour demain matin Marvin rejoue `reprise.md`. **Je me tais pendant.** Chaque question qu'il me pose est un défaut du manuel : je la note, je ne réponds pas, et je corrige après. C'est le critère central du ticket, et y répondre le perdrait. ansible-lint profil production, yamllint propre, trois playbooks valides, shellcheck propre, 199 liens markdown vérifiés.
lenaic self-assigned this 2026-09-08 09:59:18 +00:00
docs: les deux manuels disent quoi faire quand ça échoue, et le filet ne détruit plus sa source (#44)
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 7s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 19s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 2m21s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m49s
c858c27d3a
Demandé par Gabriel : reprise.md n'avait AUCUNE section d'échec, deploiement.md
n'avait qu'un garde-fou préventif. Gabarit repris de mlflow.md §8-§9.

reprise.md — les trois frictions de l'exercice du 02/09 remontent du tableau
de constat vers une procédure : pg_restore sans shared_preload_libraries,
forgejo.db rejoué par-dessus le vidage, l'app.ini qui écoute sur 127.0.0.1.
Plus les quatre cas rencontrés depuis : coffre qui refuse, vidage illisible,
migrations plus récentes que le vidage, pile api sautée faute de clé.

deploiement.md — huit symptômes, dont les quatre demandés : coffre qui refuse,
UFW qui coupe SSH, crontab qui garde une tâche retirée du dépôt, retour arrière
du front. Plus le cas le plus traître, « failed=0 mais rien de neuf », qui est
arrivé deux fois cette semaine.

ET UN DÉFAUT TROUVÉ EN ÉCRIVANT, qui est la raison d'être de l'exercice.

restore.yml prend un « filet » avant d'écraser, en appelant pg-backup.sh. Ce
script nomme ses fichiers <base>-<date du jour>.dump, dans le même répertoire.
Restaurer la sauvegarde DU JOUR faisait donc écraser la source par le filet,
juste avant de la lire : la reprise repartait de l'état cassé, et le dispositif
censé protéger détruisait ce qu'il protégeait.

Le correctif tient en deux lignes. pg-backup.sh accepte PG_BACKUP_DEST, et
restore.yml écrit son filet dans backup_dir/filet/. Le script crée son
répertoire au besoin, sans quoi la redirection échouerait sur un message
inutile.

ansible-lint profil production, yamllint propre, trois playbooks valides,
shellcheck propre, liens markdown vérifiés.
lenaic requested review from gabriel 2026-09-08 09:59:19 +00:00
Merge branch 'develop' into lenaic/44-manuels-echec
All checks were successful
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 22s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 38s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m51s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m34s
d140996bcf
gabriel left a comment

Relu, et tout vérifié sur ml-stagiaire-02 avant de poster — les commandes des deux manuels ont été rejouées, pas seulement lues.

Le correctif du filet est bon et bien trouvé : simulé avec le script de cette branche, PG_BACKUP_DEST + mkdir -p font exactement ce qui est annoncé, et le vidage du jour à la racine reste intact.

Trois blocages en commentaires de ligne : la commande ➀ de reprise.md (impossible à exécuter, trois raisons distinctes), le retour arrière du filet (Permission denied), et l'alerte #42 faussée par le filet. Deux points de plus ici.


Le même bug reste ouvert sur le chemin « machine neuve »

Hors diff, mais c'est la classe de bug que cette PR ferme, et elle est dans le périmètre de reprise.md.

Le rôle backup finit par « Premier vidage, à la main comme le fait cron », gardé par creates: {{ backup_dir }}/.dernier-etat — un fichier caché (confirmé sur le serveur). En reprise sur machine neuve, la doc dit que {{ backup_dir }} a été « copié à la main » : si on recopie les *.dump sans le point-fichier, site.yml vide les bases neuves par-dessus le vidage du jour, à la racine de backup_dir.

Et rien ne l'attrape ensuite :

$ pg_dump -Fc d'une base VIDE  →  2578 octets
$ pg_restore -l dessus         →  succès, 0 objet

verifier-sauvegardes.sh le déclare « lisible », le contrôle d'entrée de restore.yml voit le fichier présent, et la restauration ne restaure rien. La phrase « si vous voyez un filet à la racine de /var/backups/postgresql/, c'est une machine qui n'a pas le correctif » ne couvre pas ce cas-là.

Hors PR, mais en cours en ce moment : l'alerte sauvegarde est aveugle

$ curl -s http://127.0.0.1:9100/metrics | grep -E '^node_textfile_scrape_error|^ev_ops_sauvegarde'
node_textfile_scrape_error 1
(aucune métrique ev_ops_sauvegarde_pg_* exposée)

Les .prom datent du vidage de 07:53 et sont en 0640 root:root ; pg-backup.sh n'a reçu le chmod 0644 du #172 qu'au déploiement de 09:40. Ça se répare tout seul au vidage de 02:30 — donc pas avant le jury. Pour les rendre visibles tout de suite : sudo chmod 0644 /var/lib/node_exporter/textfile/ev_ops_sauvegarde_pg*.prom.

Ce qui tient

Le retour arrière du front est juste, y compris sur le point qui aurait pu piéger : g2-forge-proxy monte le parent (/opt/g2-forge/www -> /srv/www), donc le mv est bien vu par Caddy et il n'y a effectivement rien à recharger. app et app.ancien existent tous les deux. Les commandes git log et crontab -l -u deploy de deploiement.md répondent correctement.

Relu, et **tout vérifié sur `ml-stagiaire-02`** avant de poster — les commandes des deux manuels ont été rejouées, pas seulement lues. Le correctif du filet est bon et bien trouvé : simulé avec le script de cette branche, `PG_BACKUP_DEST` + `mkdir -p` font exactement ce qui est annoncé, et le vidage du jour à la racine reste intact. Trois blocages en commentaires de ligne : la commande ➀ de `reprise.md` (impossible à exécuter, trois raisons distinctes), le retour arrière du filet (`Permission denied`), et l'alerte #42 faussée par le filet. Deux points de plus ici. --- ### Le même bug reste ouvert sur le chemin « machine neuve » Hors diff, mais c'est la classe de bug que cette PR ferme, et elle est dans le périmètre de `reprise.md`. Le rôle `backup` finit par « Premier vidage, à la main comme le fait cron », gardé par `creates: {{ backup_dir }}/.dernier-etat` — un fichier **caché** (confirmé sur le serveur). En reprise sur machine neuve, la doc dit que `{{ backup_dir }}` a été « copié à la main » : si on recopie les `*.dump` sans le point-fichier, `site.yml` vide les bases neuves **par-dessus le vidage du jour**, à la racine de `backup_dir`. Et rien ne l'attrape ensuite : ``` $ pg_dump -Fc d'une base VIDE → 2578 octets $ pg_restore -l dessus → succès, 0 objet ``` `verifier-sauvegardes.sh` le déclare « lisible », le contrôle d'entrée de `restore.yml` voit le fichier présent, et la restauration ne restaure rien. La phrase « si vous voyez un filet à la racine de `/var/backups/postgresql/`, c'est une machine qui n'a pas le correctif » ne couvre pas ce cas-là. ### Hors PR, mais en cours en ce moment : l'alerte sauvegarde est aveugle ``` $ curl -s http://127.0.0.1:9100/metrics | grep -E '^node_textfile_scrape_error|^ev_ops_sauvegarde' node_textfile_scrape_error 1 (aucune métrique ev_ops_sauvegarde_pg_* exposée) ``` Les `.prom` datent du vidage de 07:53 et sont en `0640 root:root` ; `pg-backup.sh` n'a reçu le `chmod 0644` du #172 qu'au déploiement de 09:40. Ça se répare tout seul au vidage de 02:30 — donc pas avant le jury. Pour les rendre visibles tout de suite : `sudo chmod 0644 /var/lib/node_exporter/textfile/ev_ops_sauvegarde_pg*.prom`. ### Ce qui tient Le retour arrière du front est juste, y compris sur le point qui aurait pu piéger : `g2-forge-proxy` monte le **parent** (`/opt/g2-forge/www -> /srv/www`), donc le `mv` est bien vu par Caddy et il n'y a effectivement rien à recharger. `app` et `app.ancien` existent tous les deux. Les commandes `git log` et `crontab -l -u deploy` de `deploiement.md` répondent correctement.
@ -48,0 +63,4 @@
```bash
# ➀ La restauration avec l'extension chargée, cas rencontré le 02/09
sudo docker exec -e PGOPTIONS="-c shared_preload_libraries=timescaledb" \
Member

Cette commande ne peut pas s'exécuter. Trois échecs, tous reproduits sur ml-stagiaire-02 :

1. shared_preload_libraries est un paramètre POSTMASTER. Il ne se change pas par session : PGOPTIONS fait refuser la connexion.

$ docker exec -e PGPORT=5433 -e PGOPTIONS="-c shared_preload_libraries=timescaledb" \
    -u postgres ev-postgres psql -U postgres -tAc "select 1"
FATAL: le paramètre « shared_preload_libraries » ne peut pas être modifié sans redémarrer le serveur

Le geste correct pour restaurer une base TimescaleDB est SELECT timescaledb_pre_restore(); avant, timescaledb_post_restore(); après.

2. Le conteneur ne voit pas /var/backups.

$ docker exec ev-postgres ls -la /var/backups
total 8   (répertoire vide de l'image)
$ docker inspect ev-postgres --format '{{range .Mounts}}...'
tls (ro), initdb (ro), conf (ro), volume donnees   → aucun montage de /var/backups

Le chemin passé à pg_restore est un chemin hôte, exécuté dans le conteneur → could not open input file. C'est exactement la raison d'être du détour de verifier-sauvegardes.sh (son commentaire n°1).

3. Ni -u postgres ni PGPORT=5433.

$ docker exec -e PGPORT=5433 ev-postgres psql -U postgres    → FATAL: authentification peer échouée
$ docker exec -u postgres ev-postgres psql -U postgres       → socket .s.PGSQL.5432 : aucun fichier

Le second est le piège que pg-backup.sh documente lui-même comme « celui qui fait perdre le plus de temps sur cette base ».

C'est le remède de la panne n°1 du tableau, celle déjà rencontrée le 02/09 : quelqu'un qui la suit en reprise réelle enchaîne trois erreurs. La ligne 56 du tableau porte le même conseil et tombe donc aussi.

**Cette commande ne peut pas s'exécuter.** Trois échecs, tous reproduits sur `ml-stagiaire-02` : **1. `shared_preload_libraries` est un paramètre `POSTMASTER`.** Il ne se change pas par session : `PGOPTIONS` fait refuser la connexion. ``` $ docker exec -e PGPORT=5433 -e PGOPTIONS="-c shared_preload_libraries=timescaledb" \ -u postgres ev-postgres psql -U postgres -tAc "select 1" FATAL: le paramètre « shared_preload_libraries » ne peut pas être modifié sans redémarrer le serveur ``` Le geste correct pour restaurer une base TimescaleDB est `SELECT timescaledb_pre_restore();` avant, `timescaledb_post_restore();` après. **2. Le conteneur ne voit pas `/var/backups`.** ``` $ docker exec ev-postgres ls -la /var/backups total 8 (répertoire vide de l'image) $ docker inspect ev-postgres --format '{{range .Mounts}}...' tls (ro), initdb (ro), conf (ro), volume donnees → aucun montage de /var/backups ``` Le chemin passé à `pg_restore` est un chemin **hôte**, exécuté **dans** le conteneur → `could not open input file`. C'est exactement la raison d'être du détour de `verifier-sauvegardes.sh` (son commentaire n°1). **3. Ni `-u postgres` ni `PGPORT=5433`.** ``` $ docker exec -e PGPORT=5433 ev-postgres psql -U postgres → FATAL: authentification peer échouée $ docker exec -u postgres ev-postgres psql -U postgres → socket .s.PGSQL.5432 : aucun fichier ``` Le second est le piège que `pg-backup.sh` documente lui-même comme « celui qui fait perdre le plus de temps sur cette base ». C'est le remède de la panne n°1 du tableau, celle déjà rencontrée le 02/09 : quelqu'un qui la suit en reprise réelle enchaîne trois erreurs. La ligne 56 du tableau porte le même conseil et tombe donc aussi.
@ -48,0 +92,4 @@
sudo docker exec -i -e PGPORT=5433 -u postgres ev-postgres \
pg_restore -U postgres -d enervision_prod --clean --if-exists \
--no-owner --no-privileges --exit-on-error \
< /var/backups/postgresql/filet/enervision_prod-<AAAA-MM-JJ>.dump
Member

Ces deux lignes échouent en Permission denied, et c'est le seul chemin de retour arrière du manuel. Reproduit en tant que deploy sur ml-stagiaire-02 :

$ ls -lt /var/backups/postgresql/filet/
ls: cannot access '/var/backups/postgresql/filet/': Permission denied

$ sudo docker exec -i -e PGPORT=5433 -u postgres ev-postgres true \
    < /var/backups/postgresql/globals.sql.gz
bash: /var/backups/postgresql/globals.sql.gz: Permission denied

Le répertoire est drwxr-x--- root root (rôle backup, mode: "0750") et les vidages sont en 0640 root:root (umask 027 du script). La redirection < est ouverte par le shell appelant, pas par sudo : le pg_restore échoue avant même de démarrer. restore.yml s'en sort parce qu'il redirige sous become: true ; ici non.

Il faut passer la redirection à root, par exemple :

sudo ls -lt /var/backups/postgresql/filet/
sudo sh -c 'docker exec -i -e PGPORT=5433 -u postgres ev-postgres \
  pg_restore -U postgres -d enervision_prod --clean --if-exists \
  --no-owner --no-privileges --exit-on-error \
  < /var/backups/postgresql/filet/enervision_prod-<AAAA-MM-JJ>.dump'
**Ces deux lignes échouent en `Permission denied`**, et c'est le seul chemin de retour arrière du manuel. Reproduit en tant que `deploy` sur `ml-stagiaire-02` : ``` $ ls -lt /var/backups/postgresql/filet/ ls: cannot access '/var/backups/postgresql/filet/': Permission denied $ sudo docker exec -i -e PGPORT=5433 -u postgres ev-postgres true \ < /var/backups/postgresql/globals.sql.gz bash: /var/backups/postgresql/globals.sql.gz: Permission denied ``` Le répertoire est `drwxr-x--- root root` (rôle `backup`, `mode: "0750"`) et les vidages sont en `0640 root:root` (umask 027 du script). **La redirection `<` est ouverte par le shell appelant, pas par `sudo`** : le `pg_restore` échoue avant même de démarrer. `restore.yml` s'en sort parce qu'il redirige sous `become: true` ; ici non. Il faut passer la redirection à root, par exemple : ```bash sudo ls -lt /var/backups/postgresql/filet/ sudo sh -c 'docker exec -i -e PGPORT=5433 -u postgres ev-postgres \ pg_restore -U postgres -d enervision_prod --clean --if-exists \ --no-owner --no-privileges --exit-on-error \ < /var/backups/postgresql/filet/enervision_prod-<AAAA-MM-JJ>.dump' ```
@ -77,2 +83,4 @@
ansible.builtin.command:
cmd: /usr/local/bin/pg-backup.sh
environment:
PG_BACKUP_DEST: "{{ backup_dir }}/filet"
Member

Il manque PG_BACKUP_TEXTFILE_DIR ici, et ça fausse l'alerte #42.

pg-backup.sh appelle publier_metrique dans tous les cas, et cette fonction n'est pas pilotée par PG_BACKUP_DEST : elle écrit dans PG_BACKUP_TEXTFILE_DIR, resté à sa valeur de production. Reproduit avec le script de la branche (répertoire textfile détourné vers /tmp pour ne pas polluer la prod) :

$ sudo env PG_BACKUP_DEST=/tmp/essai-filet PG_BACKUP_TEXTFILE_DIR=/tmp/essai-textfile \
    /tmp/pg-backup-pr190.sh
pg-backup : 3 base(s) sauvegardée(s) dans /tmp/essai-filet

$ grep -v '^#' /tmp/essai-textfile/*.prom
ev_ops_sauvegarde_pg_dernier_resultat 0
ev_ops_sauvegarde_pg_derniere_reussite_timestamp_seconds 1788867765   ← « maintenant »

Sans la surcharge, ces lignes partent dans /var/lib/node_exporter/textfile (drwxrwxr-x, le script tourne en root, donc il écrit dedans). Conséquences :

  • filet réussi → le compteur de succès repart à zéro, et l'alerte « aucun vidage réussi depuis 26 h » reste éteinte 26 h — alors que le vidage quotidien vers /var/backups/postgresql est très souvent la raison même de la restauration ;
  • filet en échec → une issue s'ouvre toute seule dans la forge en pleine reprise.

Correctif, une ligne dans ce même bloc :

      environment:
        PG_BACKUP_DEST: "{{ backup_dir }}/filet"
        PG_BACKUP_TEXTFILE_DIR: /nonexistent

publier_metrique et la purge des .prom.$$ sortent proprement quand le répertoire est absent. Le .dernier-etat, lui, suit bien $DEST — le contrôle du matin n'est pas touché.

(Vérifié au passage : le correctif du filet lui-même fonctionne. Simulé avec la version de cette branche, mkdir -p crée le répertoire, les trois vidages + globals.sql.gz y atterrissent, et le vidage du jour à la racine n'est pas touché.)

**Il manque `PG_BACKUP_TEXTFILE_DIR` ici, et ça fausse l'alerte #42.** `pg-backup.sh` appelle `publier_metrique` dans tous les cas, et cette fonction n'est pas pilotée par `PG_BACKUP_DEST` : elle écrit dans `PG_BACKUP_TEXTFILE_DIR`, resté à sa valeur de production. Reproduit avec le script de la branche (répertoire textfile détourné vers `/tmp` pour ne pas polluer la prod) : ``` $ sudo env PG_BACKUP_DEST=/tmp/essai-filet PG_BACKUP_TEXTFILE_DIR=/tmp/essai-textfile \ /tmp/pg-backup-pr190.sh pg-backup : 3 base(s) sauvegardée(s) dans /tmp/essai-filet $ grep -v '^#' /tmp/essai-textfile/*.prom ev_ops_sauvegarde_pg_dernier_resultat 0 ev_ops_sauvegarde_pg_derniere_reussite_timestamp_seconds 1788867765 ← « maintenant » ``` Sans la surcharge, ces lignes partent dans `/var/lib/node_exporter/textfile` (`drwxrwxr-x`, le script tourne en root, donc il écrit dedans). Conséquences : - **filet réussi** → le compteur de succès repart à zéro, et l'alerte « aucun vidage réussi depuis 26 h » reste éteinte 26 h — alors que le vidage quotidien vers `/var/backups/postgresql` est très souvent *la raison même* de la restauration ; - **filet en échec** → une issue s'ouvre toute seule dans la forge en pleine reprise. Correctif, une ligne dans ce même bloc : ```yaml environment: PG_BACKUP_DEST: "{{ backup_dir }}/filet" PG_BACKUP_TEXTFILE_DIR: /nonexistent ``` `publier_metrique` et la purge des `.prom.$$` sortent proprement quand le répertoire est absent. Le `.dernier-etat`, lui, suit bien `$DEST` — le contrôle du matin n'est pas touché. (Vérifié au passage : le correctif du filet lui-même fonctionne. Simulé avec la version de cette branche, `mkdir -p` crée le répertoire, les trois vidages + `globals.sql.gz` y atterrissent, et le vidage du jour à la racine n'est pas touché.)
reprise : les commandes du manuel d'échec s'exécutent vraiment (#44)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 40s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 18s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m13s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m53s
6453f5d31e
Les trois gestes documentés ont été rejoués sur ml-stagiaire-02. Aucun ne
passait.

`PGOPTIONS="-c shared_preload_libraries=timescaledb"` fait refuser la
connexion : le paramètre est de contexte POSTMASTER. Il est de toute façon
déjà posé au niveau du serveur. Ce qui manquait à la restauration du 02/09
n'était pas le chargement de l'extension mais le mode « restoring », d'où
timescaledb_pre_restore() / post_restore() encadrant le pg_restore, le second
à jouer même en cas d'échec du premier.

Le conteneur ne monte pas /var/backups : un chemin de vidage passé en argument
à pg_restore n'existe pas de son point de vue. Le vidage entre par stdin.

`sudo cmd < fichier` ouvre le fichier avec les droits de l'appelant, et le
répertoire est en 0750 root:root : les deux commandes de retour arrière
échouaient en « Permission denied » avant de joindre la base. La redirection
passe dans un sh -c, et le `ls` prend son sudo.

restore.yml : le filet détourne aussi PG_BACKUP_TEXTFILE_DIR. pg-backup.sh
publie sa métrique dans tous les cas, et publier_metrique ne suit pas
PG_BACKUP_DEST : un filet réussi éteignait 26 h l'alerte du #42, alors qu'un
vidage quotidien cassé est souvent la raison même de la restauration.

rôle backup : le premier vidage ne se déclenche plus que si aucun vidage n'est
présent. `.dernier-etat` est un point-fichier, qu'un `cp *.dump` de reprise sur
machine neuve n'emporte pas ; le vidage partait alors sur des bases vides et
écrasait celui qu'on venait de recopier. Un vidage vide passe `pg_restore -l`,
donc ni verifier-sauvegardes.sh ni le contrôle d'entrée de restore.yml ne
l'attrapaient.
Member

Correctifs poussés sur la branche : 6453f5d. Les quatre points sont traités, et chaque commande réécrite a été rejouée sur ml-stagiaire-02 avant d'être commitée.

➀ La restauration TimescaleDB

En cherchant le bon geste, la cause supposée s'est révélée fausse : l'extension est déjà chargée au niveau du serveur.

$ docker exec -e PGPORT=5433 -u postgres ev-postgres psql -U postgres -tAc "show shared_preload_libraries"
timescaledb

Ce qui manquait le 02/09 n'était donc pas le chargement, mais le mode « restoring » : timescaledb_pre_restore() avant, timescaledb_post_restore() après. Les deux fonctions existent bien en 2.29.2, et la séquence a été jouée de bout en bout à travers l'exact docker exec du manuel, sur une base d'essai jetable créée puis supprimée — jamais sur enervision_prod.

Le post_restore() est signalé à jouer même si le pg_restore a échoué : une base laissée en mode restoring a ses déclencheurs TimescaleDB désarmés, elle accepte les écritures sans les ranger. C'est le genre d'état qu'on ne remarque qu'une semaine plus tard.

➁ Le retour arrière, et le ls

Redirection passée dans un sh -c, sudo ajouté au ls. Vérifié en tant que deploy :

$ sudo sh -c "docker exec -i -e PGPORT=5433 -u postgres ev-postgres cat > /dev/null \
    < /var/backups/postgresql/enervision_prod-2026-09-08.dump"
OK : le vidage entre bien par stdin

PG_BACKUP_TEXTFILE_DIR: /nonexistent dans le filet

Une ligne, avec le pourquoi en commentaire au-dessus.

➃ Le premier vidage du rôle backup

creates: seul ne suffisait pas — .dernier-etat est un point-fichier. J'ai ajouté un find sur *.dump et un when: matched == 0. Sur la machine actuelle : 27 vidages à la racine, donc la tâche est sautée, aucune régression ; sur une machine réellement vierge elle joue comme avant.

ansible-playbook --syntax-check passe sur restore.yml et site.yml. Je n'ai pas pu faire tourner de --check réel, il me manque le mot de passe du coffre — c'est le pipeline qui tranchera pour ansible-lint / yamllint.


Un point que je n'ai PAS corrigé, et qui mérite ton avis

restore.yml restaure enervision_prod avec un pg_restore nu, sans timescaledb_pre_restore(). C'est exactement ce qui a fait échouer la restauration à la main du 02/09. Si le diagnostic ci-dessus est le bon, le playbook porte le même défaut, sur le chemin qui compte le plus.

Je ne l'ai pas touché parce que ça ne se corrige pas à l'aveugle : mlflow n'a pas l'extension, donc l'appel doit être conditionnel par base, et je n'ai aucun moyen de tester une vraie restauration aujourd'hui. Ça vaut un ticket à part plutôt qu'un correctif non testé la veille du jury.

Hors PR, et toujours en cours

node_textfile_scrape_error 1 : les .prom de sauvegarde datent du vidage de 07:53 et sont en 0640 root:root, pg-backup.sh n'ayant reçu le chmod 0644 du #172 qu'au déploiement de 09:40. Aucune métrique ev_ops_sauvegarde_pg_* n'est exposée en ce moment. Ça se répare seul au vidage de 02:30, donc pas avant le jury — sudo chmod 0644 /var/lib/node_exporter/textfile/ev_ops_sauvegarde_pg*.prom si tu veux les voir tout de suite.

Correctifs poussés sur la branche : `6453f5d`. Les quatre points sont traités, et chaque commande réécrite a été rejouée sur `ml-stagiaire-02` avant d'être commitée. ### ➀ La restauration TimescaleDB En cherchant le bon geste, la cause supposée s'est révélée fausse : l'extension **est déjà chargée** au niveau du serveur. ``` $ docker exec -e PGPORT=5433 -u postgres ev-postgres psql -U postgres -tAc "show shared_preload_libraries" timescaledb ``` Ce qui manquait le 02/09 n'était donc pas le chargement, mais le **mode « restoring »** : `timescaledb_pre_restore()` avant, `timescaledb_post_restore()` après. Les deux fonctions existent bien en 2.29.2, et la séquence a été jouée de bout en bout à travers l'exact `docker exec` du manuel, sur une base d'essai jetable créée puis supprimée — jamais sur `enervision_prod`. Le `post_restore()` est signalé **à jouer même si le `pg_restore` a échoué** : une base laissée en mode restoring a ses déclencheurs TimescaleDB désarmés, elle accepte les écritures sans les ranger. C'est le genre d'état qu'on ne remarque qu'une semaine plus tard. ### ➁ Le retour arrière, et le `ls` Redirection passée dans un `sh -c`, `sudo` ajouté au `ls`. Vérifié en tant que `deploy` : ``` $ sudo sh -c "docker exec -i -e PGPORT=5433 -u postgres ev-postgres cat > /dev/null \ < /var/backups/postgresql/enervision_prod-2026-09-08.dump" OK : le vidage entre bien par stdin ``` ### ➂ `PG_BACKUP_TEXTFILE_DIR: /nonexistent` dans le filet Une ligne, avec le pourquoi en commentaire au-dessus. ### ➃ Le premier vidage du rôle `backup` `creates:` seul ne suffisait pas — `.dernier-etat` est un point-fichier. J'ai ajouté un `find` sur `*.dump` et un `when: matched == 0`. Sur la machine actuelle : 27 vidages à la racine, donc la tâche est sautée, aucune régression ; sur une machine réellement vierge elle joue comme avant. `ansible-playbook --syntax-check` passe sur `restore.yml` et `site.yml`. Je n'ai **pas** pu faire tourner de `--check` réel, il me manque le mot de passe du coffre — c'est le pipeline qui tranchera pour `ansible-lint` / `yamllint`. --- ### Un point que je n'ai PAS corrigé, et qui mérite ton avis `restore.yml` restaure `enervision_prod` avec un `pg_restore` nu, **sans** `timescaledb_pre_restore()`. C'est exactement ce qui a fait échouer la restauration à la main du 02/09. Si le diagnostic ci-dessus est le bon, le playbook porte le même défaut, sur le chemin qui compte le plus. Je ne l'ai pas touché parce que ça ne se corrige pas à l'aveugle : `mlflow` n'a pas l'extension, donc l'appel doit être conditionnel par base, et je n'ai aucun moyen de tester une vraie restauration aujourd'hui. Ça vaut un ticket à part plutôt qu'un correctif non testé la veille du jury. ### Hors PR, et toujours en cours `node_textfile_scrape_error 1` : les `.prom` de sauvegarde datent du vidage de 07:53 et sont en `0640 root:root`, `pg-backup.sh` n'ayant reçu le `chmod 0644` du #172 qu'au déploiement de 09:40. Aucune métrique `ev_ops_sauvegarde_pg_*` n'est exposée en ce moment. Ça se répare seul au vidage de 02:30, donc pas avant le jury — `sudo chmod 0644 /var/lib/node_exporter/textfile/ev_ops_sauvegarde_pg*.prom` si tu veux les voir tout de suite.
gabriel approved these changes 2026-09-08 12:02:59 +00:00
lenaic merged commit c79d2de1a8 into develop 2026-09-08 12:03:54 +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!190
No description provided.