Haute disponibilité de la plateforme (addendum 20.4)
Modèle
Chaque cluster et chaque service portent ownership_mode (PLATFORM_MANAGED, CLIENT_MANAGED) et deployment_mode
(SINGLE_HOST, NODE_GROUP, K8S_EXISTING, K8S_MANAGED, MULTI_CLUSTER, EXTERNAL_MANAGED), migration
server/sql/V050__availability.sql. Un groupe HA (nm.ha_groups) déclare desired_replicas, fault_domains, ha_mode,
failover_policy, un profil standalone, ha (2 réplicas, 2 domaines) ou critical-ha (3 réplicas, 3 domaines) et des
cibles RTO et RPO fournies par le client (vides par défaut).
Statut ha_validation_status : HA_CONFIGURED tant qu'aucun test de bascule enregistré (nm.ha_test_runs, statut
passed) n'est postérieur à la dernière modification du groupe ; HA_VALIDATED uniquement après un tel test. Un membre
sans heartbeat récent (90 s) est unknown, jamais sain : le groupe passe DEGRADED. MULTI_CLUSTER est marqué non pris
en charge (aucun second site ici).
Routes
GET /api/v1/ha-modes, POST /api/v1/ha-groups, GET /api/v1/ha-groups, GET /api/v1/ha-groups/{id},
POST /api/v1/ha-groups/{id}/test-failover, GET /api/v1/ha-groups/{id}/operations. Authentifiées, autorisées
(cluster:manage, services:operate, cluster:read), auditées. Une action destructive sur CLIENT_MANAGED ou
EXTERNAL_MANAGED est refusée (409 ha_client_managed_refused, audit denied). Base ou piste d'audit indisponible :
refus (503 ha_fail_closed).
Opérations exclusives
nm.exclusive_leases : un bail par portée (install, upgrade, failover, restore), pris par INSERT ... ON CONFLICT
atomique, jeton de fencing croissant (nm.fencing_token_seq). Le résultat d'un test est refusé si son jeton n'est plus
valide. Testé avec deux processus concurrents.
Preuves locales mesurées (binaires officiels, sans Docker)
| Composant | Scénario | RTO mesuré | Perte mesurée |
|---|---|---|---|
| PostgreSQL 16.2 | primaire tué (-m immediate), réplica promu par pg_ctl promote, ancien primaire clôturé (pg_rewind, standby, écriture refusée), réintégration | 0,215 s | 0 ligne sur 2000 |
| Patroni 4.0.6 + etcd 3.5.21 | leader tué (Patroni et PostgreSQL), élection, ancien leader réintégré en lecture seule | 19,878 s (ttl 10 s) | 0 ligne sur 500 |
| Redis 7.0.15 Sentinel | 1 primaire, 2 réplicas, 3 sentinelles, primaire tué, client résolu via SENTINEL get-master-addr-by-name | 2,223 s | 0 clé sur 2000 |
| NATS 2.15.2 JetStream | flux R3, leader tué, publication et consommation reprises, doublon refusé (Nats-Msg-Id), 10 relivraisons dédupliquées | 5,569 s | 0 message sur 100 |
La réplication PostgreSQL et Redis est asynchrone : une perte nulle n'est pas garantie, seulement mesurée nulle dans ces exécutions. Ces chiffres valent pour ce poste local et ne remplacent pas les cibles du client.
Kubernetes (configuré seulement)
deploy/helm/enterprise-ai/values-ha.yaml : 3 réplicas minimum, PDB minAvailable: 2, répartition sur hostname et zone,
anti-affinité requise par nœud, sondes, arrêt gracieux 60 s. Le manifeste CloudNativePG est validé hors ligne par
kubeconform contre le schéma CRD épinglé (tools/ha/fetch.sh, sha256). Aucune bascule n'a été exécutée sur un cluster :
BLOQUÉ faute de cluster de test (le contexte disponible est la production). Temporal est modélisé HA_CONFIGURED
seulement. Perte d'une salle LiveKit : interruption de session puis reconnexion du client, aucun transfert transparent.
Services du catalogue
Ce que chaque service du catalogue sait fournir en haute disponibilité (cluster, bascule, quorum, prérequis,
documentation officielle, topologie par mode) est déclaré dans son bloc high_availability, servi par
GET /api/v1/catalog/high-availability et résumé dans la matrice générée
fr/high-availability.md.
Contrôles d'installation et supervision
Avant une installation (POST /clusters/{cid}/services, POST /clusters/{cid}/blueprint/apply) et avant un
changement de topologie d'un service installé (POST .../services/{s}/components ajoute des instances,
DELETE .../services/{s}/components/{c}/hosts/{h} en retire une), server/src/navin_manager/services/ha_checks.py
contrôle le mode choisi sur les hôtes choisis : ha.distinct_hosts, ha.fault_domains (label zone de l'hôte,
sinon sa baie, sinon inconnu), ha.quorum, ha.prerequisites (un prérequis ha: true lié à un service du catalogue
en mode autonome échoue, lié à une connexion externe demande de confirmer sa HA), ha.entrypoint (un point d'entrée
répartiteur ou VIP exige la paire HAProxy avec Keepalived du cluster, ou un répartiteur externe déclaré),
ha.peer_tls, ha.readiness, et ha.not_ha (info) pour un mode autonome. POST .../services/validate et les
réponses d'installation renvoient ces vues sous ha.
- Un
failrefuse la demande : 409ha_requirements_not_met,details.checks. - Un
warnexigeha_acknowledged: truedans le corps (paramètre de requête sur le DELETE), sinon 409ha_acknowledgement_required; l'acquittement est inscrit dans l'événement d'audit. Les corps sans avertissement restent valides tels quels. Un changement de topologie ne compte que les contrôles qui se dégradent par rapport au placement actuel. - Les modes d'exploitation (
POST .../services/{s}/mode: drainage, arrêt d'urgence, suspension de sécurité...) ne sont jamais refusés pour des raisons HA ; leur réponse porte un impacthainformatif. - Le tableau de bord d'un service renvoie
ha_status(niveau, instances saines, état du quorumok,at_risk,lost, étathealthy,degraded,quorum_lostoustandalone, docs, commande d'état de la grappe). La console l'affiche sur la fiche du service avec un bouton qui lance la commandehealthcheck.cluster_statusdéclarée. La page Services regroupe les groupes HA, le quorum, les tests de bascule et la capacité du catalogue de chaque service dans son action de ligne « Haute disponibilité (HA) » ; la matrice du catalogue et l'architecture cible sont dans fr/high-availability.md (les pages/availabilityet/high-availabilityredirigent vers Services).
Tests
server/tests/test_availability_api.py, test_availability_local_ha.py, test_availability_helm.py,
ui/src/test/services-consoles-ha.test.tsx. Mesures écrites dans /tmp/ha-evidence/*.json.