Aller au contenu

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-b49e4e6f et vps-7a702a91 sont à 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

  1. Exporter moncouple-data une 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.
  2. Sauvegarder data-postgres-0 puis trancher le sort de Squash TM. Une application à 0 réplica qui conserve 5 Gi mérite une décision explicite.
  3. Accepter local-path pour Prometheus et jemimah-config, en le documentant comme un choix et non comme un défaut subi.
  4. Ne rien engager sur l'épinglage de MonCouple : le risque de disponibilité est accepté au vu de la fin de vie de l'application.
  5. É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-data est 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.