La garde de scripts/coffre-luks.sh refuse un conteneur LXC ou l'absence de device-mapper. L'ADR 0020, 10-infra.md et la vue d'ensemble disent ce qui est en place (SSE-C des archives) et ce qui relève de l'hôte Proxmox (chiffrement du disque du conteneur).
6.8 KiB
0019 - Stockage objet Garage par environnement, et cycle de vie des mesures : export puis suppression
- Statut : accepté
- Date : 2026-09-24
Contexte
Les issues #24 « Déployer MinIO » et #36 « Politique de rétention + export vers MinIO » datent du
cadrage du 14/09. Au 24/09, la hypertable reading grossit d'une lecture par site et par heure
sans qu'aucune politique ne la borne, et docs/architecture/40-data.md classe rétention et
compression parmi les cibles non faites. Aucun stockage objet ne tourne.
La PR 164 a posé une amorce : un projet Compose à part dans garage/, l'image dxflrs/garage:v1.0.1,
des secrets dans un garage.toml gitignoré et des tests de fumée boto3 que rien ne jouait. Rien
n'était branché sur les trois environnements de la VM (ADR 0009,
ADR 0017), ni sur la CI, ni sur la supervision.
Contrainte propre au projet : le jeu historique s'arrête au 31/12/2024 et make demo-data s'y
ancre. Une rétention sous vingt-et-un mois effacerait la démonstration.
Décision
Garage plutôt que MinIO, en v2.4.1. Un binaire statique de quelques dizaines de Mo, une
API S3 suffisante pour boto3, des métriques Prometheus natives, et depuis la v2.3.0 un mode
--single-node --default-bucket qui crée layout, clé et bucket au premier démarrage à partir de
trois variables d'environnement : aucun conteneur d'initialisation, aucune séquence CLI à rejouer.
Un Garage par projet Compose. Le service garage vit dans docker-compose.yml, comme db
et mailpit. Chaque environnement a le sien, ses volumes garage_meta et garage_data, ses
secrets et ses ports sur 127.0.0.1 : S3 3900, 3910, 3920 et admin 3903, 3913, 3923
pour prod, recette et dev. Le RPC n'est pas publié. Rien ne passe par le proxy.
infra/garage/garage.toml est versionné sans secret. GARAGE_RPC_SECRET (32 octets
hexadécimaux), GARAGE_ADMIN_TOKEN et GARAGE_METRICS_TOKEN arrivent par l'environnement, comme
les autres secrets du .env, générés par scripts/provision-host.sh. L'image est FROM scratch,
sans shell : la garde sur les secrets vit dans le Makefile (garage-garde, appelée par
services-up et stack-up), et le healthcheck est garage health -q.
Nœud unique assumé. replication_factor = 1 et moteur sqlite, avec un instantané des
métadonnées toutes les six heures. La documentation de Garage réserve ce facteur aux
déploiements de test : ici la machine est unique, la redondance n'existe pour aucun autre service,
et le chiffrement au repos est traité à part (ADR 0020). LMDB, le moteur par défaut, se corrompt à l'arrêt brutal et rien ne le reconstruirait.
La rétention de reading est un traitement du backend, ordonnancé par Airflow. Le DAG
retention lance chaque nuit app.etl.reading_retention (ADR 0008),
qui, pour chaque chunk entièrement plus vieux que READING_RETENTION_DAYS (1095 jours par défaut) :
- lit ses lignes par la hypertable (
WHERE timestamp >= range_start AND timestamp < range_end) ; - les sérialise en CSV gzip reproductible, les colonnes
jsonbettext[]en JSON ; - les dépose sur Garage sous
reading/<annee>/reading_<debut>_<fin>.csv.gz, chiffrées par SSE-C, avec le sha256 et le nombre de lignes en métadonnées ; un objet déjà présent avec le même sha n'est pas réécrit ; - relit l'objet et compare son sha256 ;
- supprime ce seul chunk par
drop_chunks(older_than => range_end, newer_than => range_start), dans une transaction dédiée et courte.
add_retention_policy de TimescaleDB est écartée : son travail de fond supprimerait sans avoir
exporté. db/migrations/ reste vide pour la même raison.
Supervision. Prometheus scrute garage:3903/metrics avec GARAGE_METRICS_TOKEN passé en
secret Compose. CibleInjoignable couvre son indisponibilité, aucune règle nouvelle.
CI. Le job « Validation des fichiers Compose et de la supervision » démarre le vrai conteneur
avec des secrets générés, attend son healthcheck et joue tests/garage/test_smoke.py : bucket
présent, aller-retour, suppression effective, et lecture refusée sans clé SSE-C.
Alternatives écartées
| Écartée | Raison |
|---|---|
| MinIO | Plus lourd, licence AGPL, orientation vers l'offre commerciale ; l'équipe préfère un composant qu'elle peut lire en entier. Le titre des issues date du cadrage, la décision a changé depuis. |
| Un Garage partagé entre les trois environnements | Un troisième projet Compose et des réseaux externes à déclarer, le couplage que l'ADR 0009 évite. |
add_retention_policy TimescaleDB, plus un export séparé |
Deux horloges indépendantes : un export en retard d'une semaine perd les données que la politique a déjà supprimées. |
| Export Parquet | Une dépendance binaire de plus (pyarrow) dans l'image Airflow et le backend, pour un gain nul sur 120 000 lignes ; le CSV gzip est le format d'origine du jeu historique. |
| Commande de restauration | Hors périmètre du J6. La procédure manuelle tient en trois commandes : get_object avec la clé SSE-C, gunzip, COPY reading FROM STDIN CSV HEADER ; uq_reading_source refuse les doublons. |
| Compression TimescaleDB des chunks chauds | Autre chantier, sans lien avec l'export. |
Conséquences
- Premier passage en prod (24/09/2026, borne à trois ans) : les chunks de janvier à septembre 2023 sont archivés puis supprimés, environ quarante objets. La démonstration ancrée fin 2024 et l'entraînement du modèle (quinze mois d'historique plus 2026) ne sont pas touchés.
drop_chunksverrouillesiteetdataseten exclusif jusqu'au COMMIT : le DAG tourne à 03h20, entrealertes(:15) etderive(05h30), et chaque suppression est une transaction propre.- Secrets.
.env.examplegagneGARAGE_RPC_SECRET,GARAGE_ADMIN_TOKEN,GARAGE_METRICS_TOKEN,GARAGE_ACCESS_KEY,GARAGE_SECRET_KEY,GARAGE_BUCKET,GARAGE_S3_PORT,GARAGE_ADMIN_PORT,GARAGE_SSE_KEYetREADING_RETENTION_DAYS.provision-host.shles génère et réaligne les.envde la VM : il doit être rejoué avant le premier déploiement qui suit ce changement, sinonmake stack-ups'arrête sur la garde. - Rotation.
GARAGE_SECRET_KEYne se change pas sur un volume peuplé : Garage refuse de démarrer. Passer pargarage keyen CLI, ou recréer le volume d'un environnement jetable. - Perte de
GARAGE_SSE_KEY= archives illisibles. La clé est sauvegardée hors de la VM. - Postes de développement.
make devexige désormais les clésGARAGE_*dans le.env, comme il exigeait déjà les clés Airflow. - Métriques Garage visibles dans Prometheus ; aucun tableau Grafana dédié pour l'instant.