ADR 0001 — Stockage persistant réellement HA
Statut : proposé — phase d'inventaire. Date : 2026-08-24. Issue : #68. Portée de ce document : inventaire, matrice RPO/RTO et comparaison d'options. Aucune migration n'est décidée ici.
Contexte
Le plan de contrôle Kubernetes et etcd sont répartis sur trois VPS. Le stockage, lui, ne l'est pas : chaque volume persistant est un répertoire sur le disque d'un seul nœud. Répliquer les pods d'une application ne réplique pas ses données, et dans un cas le volume force au contraire tous les réplicas sur le même nœud.
Inventaire observé
Relevé du 2026-08-24 sur le contexte ovh-primary, en lecture seule.
StorageClass
Une seule classe existe, local-path (défaut) :
| Propriété | Valeur | Conséquence |
|---|---|---|
| Provisioner | rancher.io/local-path |
Répertoire local au nœud, aucune réplication |
| Modes d'accès | RWO uniquement | Un seul nœud consommateur à la fois |
reclaimPolicy |
Delete |
Le PV suit la suppression du PVC |
volumeBindingMode |
WaitForFirstConsumer |
Le nœud est fixé au premier pod, puis figé |
allowVolumeExpansion |
false |
Aucun agrandissement en place possible |
Aucune classe répliquée, aucun contrôleur de snapshot (VolumeSnapshotClass) n'est installé.
PVC actifs
Quatre PVC, tous Bound, tous RWO sur local-path.
| Namespace / PVC | Taille | Nœud | Consommateur | Réplicas désirés | Nature de la donnée |
|---|---|---|---|---|---|
monitoring/prometheus-…-prometheus-0 |
5 Gi | vps-a2cbe513 |
StatefulSet Prometheus | 1 | Reconstructible, historique perdu |
moncouple/moncouple-data |
1 Gi | vps-b49e4e6f |
Deployment moncouple |
2 | Privée, non reconstructible |
squash-tm/data-postgres-0 |
5 Gi | vps-b49e4e6f |
StatefulSet postgres |
0 | Applicative, application suspendue |
jemimah/jemimah-config-data |
100 Mi | vps-a2cbe513 |
Deployment jemimah-config |
0 | À confirmer, application suspendue |
Correction de nomenclature
L'issue #68 et l'ancienne page de sauvegarde désignaient un PVC « Atelier Jemimah ». Le volume réellement présent est jemimah/jemimah-config-data (100 Mi), voisin de jemimah-site qui, lui, n'a aucun PVC. Aucun volume ne correspond à Atelier Jemimah au sens du catalogue applicatif. De même, aucun PVC Grafana n'existe : la configuration Grafana est provisionnée, pas persistée sur volume.
Capacité des nœuds
Les trois VPS sont asymétriques, ce qui contraint fortement les options.
| Nœud | vCPU | RAM | Disque éphémère | RAM utilisée | PVC hébergés |
|---|---|---|---|---|---|
vps-a2cbe513 |
4 | 7,6 Gi | 71 Gi | 63 % | Prometheus, jemimah-config |
vps-b49e4e6f |
2 | 3,7 Gi | 37 Gi | 68 % | MonCouple, Squash TM |
vps-7a702a91 |
2 | 3,7 Gi | 37 Gi | 61 % | aucun |
Constats structurels
Trois faits comptent davantage que l'inventaire lui-même.
1. MonCouple est entièrement épinglé sur un seul nœud
Le PVC RWO moncouple-data est lié à vps-b49e4e6f. Trois pods montent ce volume et sont donc structurellement contraints sur ce nœud : les deux réplicas web et le notifier. Le CronJob moncouple-reminder ne monte aucun volume — il effectue un appel HTTP au notifier — et n'est pas contraint par le stockage, même si son pod a été observé sur le même nœud. Les deux réplicas web ne constituent pas une haute disponibilité — ils partagent le nœud et le volume. La perte de vps-b49e4e6f provoque l'indisponibilité complète de MonCouple et rend ses données inaccessibles jusqu'au retour du nœud.
C'est le seul PVC du cluster qui porte des données privées non reconstructibles. C'est donc le point le plus exposé, et il l'est deux fois : disponibilité et durabilité.
Décision du propriétaire — MonCouple est en fin de vie
MonCouple V1 doit être remplacé par NotreCouple. Aucun travail de haute disponibilité ni de migration vers un stockage répliqué n'est engagé pour cette application : l'effort ne se justifie pas sur un service appelé à disparaître, et le risque d'indisponibilité lié à l'épinglage sur vps-b49e4e6f est accepté explicitement.
La durabilité de la donnée reste, elle, exigée — et pour une raison précise. L'importeur sqlite_to_postgres.py du périmètre NotreCouple lit la base SQLite V1.5 comme source de la migration vers PostgreSQL : users, categories, inspirations, wishes, revelations, negotiations, events et les tables de révision. Le contenu de moncouple-data n'est donc pas une donnée que l'on retire avec l'application, c'est l'entrée de la bascule. Le perdre avant l'import perdrait l'historique sans possibilité de le reconstituer.
Exigence retenue, et unique : un export ponctuel de moncouple-data, dont la relecture est prouvée, avant la bascule vers NotreCouple. Ni CronJob récurrent, ni stockage répliqué, ni runbook complet — un export unique. Au 2026-08-24, PR #49 est ouverte et non fusionnée, Flux n'est pas activé sur NotreCouple : la fenêtre pendant laquelle ce volume est la seule copie est toujours ouverte.
2. Le rollout de MonCouple est structurellement fragile
Le Deployment combine replicas: 2, maxUnavailable: 0 et maxSurge: 1 sur un volume RWO. Le pod de surge doit donc être schedulé sur le même nœud que le volume. Si ce nœud est saturé ou cordonné, le rollout se bloque sans que la cause soit évidente. Par ailleurs le PDB minAvailable: 1, combiné aux trois pods contraints par le volume, rend le drain de vps-b49e4e6f coûteux : il n'existe aucun autre nœud capable d'accueillir les pods avec leur volume.
3. Aucun volume n'est extensible en place
allowVolumeExpansion=false sur l'unique StorageClass signifie qu'agrandir un volume impose de le recréer. La procédure de sauvegarde et restauration n'est donc pas une amélioration souhaitable, c'est le prérequis dur de toute évolution du stockage, y compris d'une simple augmentation de taille. Elle doit exister et être testée avant toute autre décision.
Observation connexe
Le StatefulSet Alertmanager (2 réplicas) utilise un emptyDir pour son volume -db : silences et état de notification sont perdus à chaque redémarrage de pod. Ce n'est pas un problème de stockage persistant — il n'y a pas de volume à migrer — mais cela affecte le RPO opérationnel de la chaîne d'alerte. À traiter dans le chantier monitoring, hors de cet ADR.
Matrice RPO/RTO proposée
Valeurs proposées, à arbitrer par le propriétaire : elles déterminent le coût de tout le reste.
| Application | RPO visé | RTO visé | Justification | Priorité |
|---|---|---|---|---|
| MonCouple V1 | Jusqu'à l'export de bascule | Aucun engagement | Application en fin de vie : aucune HA engagée, indisponibilité acceptée. La donnée reste la source de l'import NotreCouple | Export unique avant bascule — aucune migration de stockage |
| Prometheus | Aucun engagement | 1 h | Métriques reconstructibles ; l'historique perdu ne justifie pas un stockage répliqué | 3 |
| Squash TM (PostgreSQL) | 7 j | Non applicable tant que suspendu | Application à 0 réplica ; la donnée doit survivre à une décision de conservation | 2 pour la sauvegarde, aucune pour la HA |
jemimah-config |
Aucun engagement | Non applicable | 100 Mi, application à 0 réplica, contenu à confirmer | 4 |
Ordre qui en découle : prouver un export relisible de moncouple-data avant la bascule NotreCouple, sans autre travail sur cette application ; sauvegarder Squash TM avant de décider de son sort ; ne consommer aucun stockage répliqué pour une application suspendue ou en fin de vie.
Comparaison des options
Coûts estimés pour ce cluster précis — trois VPS dont deux à 2 vCPU / 3,7 Gi déjà à 61-68 % de RAM.
A. Conserver local-path + sauvegarde externe
- Coût : quasi nul en CPU/RAM. Un CronJob de sauvegarde et un espace objet externe.
- Apporte : durabilité (RPO tenu), pas de disponibilité.
- Ne résout pas : l'épinglage de MonCouple sur un nœud, ni le rollout fragile.
- Verdict : c'est la première étape obligatoire quelle que soit la cible, puisque la restauration conditionne tout le reste.
B. Réplication applicative (CloudNativePG pour PostgreSQL)
- Coût : une instance PostgreSQL supplémentaire par réplica, soit quelques centaines de Mi de RAM par instance.
- Apporte : disponibilité et durabilité réelles pour les charges PostgreSQL.
- Limite : ne s'applique pas à MonCouple, dont la donnée est un fichier SQLite sur volume, ni à Prometheus.
- Dépendance externe : l'opérateur CloudNativePG est déjà présent sur la plateforme et son usage applicatif est piloté par un autre périmètre (NotreCouple). Cet ADR ne conçoit rien sur ce périmètre et s'aligne sur la décision qui y sera prise.
- Verdict : pertinent pour Squash TM si et seulement si l'application est conservée. À ne pas engager avant cette décision.
C. Stockage objet externe pour les sauvegardes
- Coût : facturation au volume, quelques Gi ici. Aucun coût CPU/RAM significatif dans le cluster.
- Apporte : la seule copie réellement hors des trois VPS. Un snapshot etcd resté sur le nœud défaillant n'est pas une sauvegarde ; le même raisonnement vaut pour les volumes.
- Verdict : indispensable, complément direct de l'option A.
D. Stockage distribué dans le cluster (Longhorn / OpenEBS)
- Coût RAM — hypothèse non validée. L'ordre de grandeur couramment retenu est d'environ 1 Gi de RAM par nœud pour les composants. Cette valeur n'a été ni mesurée sur ce cluster ni rattachée à une version précise de Longhorn : elle doit être traitée comme une hypothèse de dimensionnement, à confirmer par un PoC isolé avant toute décision. Elle est distinguée ici des chiffres du tableau de capacité, qui sont, eux, relevés sur le cluster.
- Coût disque — calculé sur des faits observés. La réplication ×3 porte les 11,1 Gi actuellement provisionnés à environ 33 Gi répartis, sur des disques de 37 Gi pour deux des trois nœuds.
- Contrainte bloquante : les nœuds
vps-b49e4e6fetvps-7a702a91sont à 2 vCPU / 3,7 Gi et déjà à 61-68 % de RAM — ces valeurs sont mesurées. Même en prenant l'hypothèse RAM ci-dessus par le bas, la marge disponible sur ces machines est faible, et elles sont aussi les trois control-planes etcd, sensibles à la latence disque que Longhorn augmente. La conclusion de prudence tient donc sur les capacités relevées, indépendamment de la précision de l'hypothèse. - Verdict : non recommandé en l'état. L'option redeviendra ouverte si les VPS sont redimensionnés ou si des disques dédiés sont ajoutés, pas avant. Le noter maintenant évite d'y revenir à chaque itération.
Recommandation de la phase d'inventaire
- Exporter
moncouple-dataune fois, hors cluster, et prouver la relecture de l'export avant la bascule vers NotreCouple. C'est la seule action retenue sur cette application, et elle conditionne la migration elle-même. - Sauvegarder
data-postgres-0puis trancher le sort de Squash TM. Une application à 0 réplica qui conserve 5 Gi mérite une décision explicite. - Accepter
local-pathpour Prometheus etjemimah-config, en le documentant comme un choix et non comme un défaut subi. - Ne rien engager sur l'épinglage de MonCouple : le risque de disponibilité est accepté au vu de la fin de vie de l'application.
- Écarter le stockage distribué tant que la capacité des VPS n'a pas changé.
Inconnues restantes
Elles sont publiées comme telles plutôt que comblées par supposition.
- Espace disque réellement libre sur chaque VPS. Le relevé donne la capacité
ephemeral-storage, pas l'occupation ; la mesure demande un accès shell aux nœuds. - IOPS et latence des disques OVH, déterminants pour toute option distribuée et pour la santé d'etcd.
- Existence et fraîcheur d'une sauvegarde MonCouple actuelle. Aucune n'a été observée depuis le cluster — l'export de bascule reste donc à faire.
- Date de la bascule MonCouple → NotreCouple, qui borne la fenêtre pendant laquelle
moncouple-dataest la seule copie de l'historique. - Devenir décidé de Squash TM et de
jemimah-config: conservation, archivage ou suppression. - Contenu exact de
jemimah-config-data, qui détermine s'il faut le sauvegarder. - Empreinte RAM réelle d'un stockage distribué sur ces machines : l'hypothèse d'environ 1 Gi par nœud n'est pas mesurée et demanderait un PoC isolé pour être confirmée ou écartée.
Ce que cet ADR ne fait pas
Aucun manifest, StorageClass, PVC, Secret ou sauvegarde n'est créé ni modifié. Aucune migration, aucune suppression, aucune action de production. Les livrables restants de #68 — PoC isolé, runbook de restauration, PR GitOps par application — relèvent de lots ultérieurs, à claim séparément.