Ressources
PostgreSQL sur Kubernetes en haute disponibilité : topologies, bascule, RPO et tests de panne
Kubernetes PostgreSQL en haute disponibilité : service managé, CloudNativePG ou Patroni, RPO et RTO réels, déroulé de bascule et tests de panne à mener.
Par Corentin Mas, publié le · 24 min de lecture
Base d'expérience : Méthode de conception et de validation de clusters PostgreSQL haute disponibilité sur Kubernetes, établie sur la documentation officielle de PostgreSQL, de CloudNativePG et de Patroni, sans donnée ni rattachement client.
Relecture factuelle : Claude Code (revue indépendante déléguée par Corentin Mas, 28/09/2026), le
Trois pods PostgreSQL prêts derrière un service ne disent rien de ce qui se passera quand le nœud du primaire disparaîtra. La haute disponibilité se joue dans le mode de réplication, qui décide ce qu'une bascule peut perdre, dans la règle qui choisit la réplique promue, dans le délai que met Kubernetes à constater la panne et dans ce qui empêche l'ancien primaire d'écrire encore. Les documentations d'opérateurs expliquent l'installation ; elles disent rarement comment choisir, ni comment prouver que la bascule tient.
Cet article traite trois questions : service managé ou opérateur, et lequel (CloudNativePG, ou les opérateurs de Zalando et de Crunchy Data fondés sur Patroni) ; ce que valent le RPO et le RTO selon le mode de réplication ; quels tests de panne mener avant de déclarer un cluster hautement disponible. Cette lecture est établie sur PostgreSQL 18.6, CloudNativePG 1.30.1, Patroni 4.1.5, l'opérateur Zalando 2.0.2 et la série 6.0 de Crunchy Postgres for Kubernetes ; les sources citées ont été vérifiées le .
Ce que la haute disponibilité doit prouver : RPO, RTO et périmètre
La haute disponibilité décrit un comportement face à des pannes nommées, pas un nombre d'instances. Pour chaque scénario, elle fixe deux valeurs. Le RPO (objectif de point de reprise) répond à la question : quelles transactions dont l'application a reçu la confirmation du COMMIT peuvent manquer après la bascule ? Le RTO (objectif de temps de reprise) est la durée pendant laquelle les écritures échouent, de la panne à la première écriture réussie, reconnexion comprise. Un taux de disponibilité affiché ne dit rien de ces deux valeurs lors d'un incident donné.
Trois mécanismes à ne pas confondre
La réplication en continu sert la bascule. Elle ne protège pas d'une erreur logique : un DELETE sans clause WHERE est rejoué sur toutes les répliques. Seule une restauration à un instant donné (PITR, Point In Time Recovery), depuis une sauvegarde de base et l'archive du journal de transactions (WAL, Write Ahead Log), ramène la base avant l'erreur ; la documentation CloudNativePG rappelle qu'une archive de WAL sans sauvegarde de base ne permet aucune restauration. Les répliques en lecture, enfin, absorbent de la charge sans rien garantir sur l'écriture.
Chaque mécanisme a son RPO. CloudNativePG fixe par défaut archive_timeout à 5 minutes, ce qui force la fermeture et l'archivage d'un segment de WAL au moins toutes les 5 minutes, même sous faible charge : sa documentation y voit une valeur de RPO déterministe, fondée sur le temps, pour la reprise depuis l'archive. Elle est distincte du RPO d'une bascule.
Décomposer le RTO et borner le RPO
Une formule de contrôle, sans valeur imposée, oblige à nommer chaque terme :
RTO_mesuré = T_détection + T_décision + T_promotion + T_reroutage + T_reconnexion
RPO_asynchrone <= retard de la réplique promue au moment de la panne
RPO_synchrone = 0 pour les commits acquittés, si la réplique promue
fait partie des répliques synchrones à jour- Détection. Un conteneur qui plante est repéré par ses sondes. Un nœud injoignable est plus lent : le contrôleur de nœuds attend
--node-monitor-grace-period(50 secondes par défaut depuis Kubernetes 1.32), plus au plus une période de surveillance de 5 secondes, avant de déclarer ses pods non prêts : l'opérateur constate la perte 50 à 55 secondes après, selon la documentation CloudNativePG (40 à 55 secondes en comptant Kubernetes 1.29 à 1.31). Chez Patroni, la clé de leader expire aprèsttl, 30 secondes par défaut. - Décision. Délai volontaire (
.spec.failoverDelay, 0 par défaut chez CloudNativePG), contrôle de quorum, expiration du bail du primaire. - Promotion. La réplique rejoue d'abord le WAL reçu et non appliqué.
- Reroutage. Service
-rw(CloudNativePG), étiquette de rôle (Patroni), enregistrement DNS (services managés). - Reconnexion. Pools, pilotes, cache DNS : souvent le terme le plus long, et le seul que l'opérateur ne maîtrise pas.
Le périmètre à couvrir
Du volume au pooler, du plan de contrôle Kubernetes à l'application, chaque composant peut causer une indisponibilité ou une perte. La méthode décrite ici relie chaque objectif à une preuve ; aucune mesure citée ne provient d'un environnement client.
| Objectif | Ce qui le fixe | Comment le prouver |
|---|---|---|
| RPO de bascule | Mode de réplication, règle de promotion, synchronous_commit de chaque transaction | Identifiants acquittés absents après la bascule |
| RTO de bascule | Détection, bail, reroutage, reconnexion | Écart entre la dernière et la première écriture acquittée |
| RPO de restauration | Sauvegardes de base, archive_timeout, retard d'archivage | Restauration à un instant connu |
| RTO de restauration | Volume, débit du stockage objet, WAL à rejouer | Restauration chronométrée en environnement isolé |
Topologies et réplication : asynchrone, synchrone, quorum
La topologie de référence reste celle de PostgreSQL : un primaire et des répliques en lecture seule qui consomment son flux de réplication en continu. Kubernetes fournit le réseau, les volumes et le plan de contrôle, sans changer cette mécanique.
Placement : trois zones, rien de partagé
CloudNativePG s'appuie sur la réplication de PostgreSQL et déconseille la réplication au niveau du stockage. Sa documentation recommande une architecture sans partage : des instances sur des nœuds distincts, qui ne partagent que le réseau, et dans des zones de disponibilité distinctes d'un même cluster Kubernetes, qui doit en compter au moins trois. L'article du blog de la Cloud Native Computing Foundation (CNCF) sur les architectures recommandées décrit la cible : un nœud dédié par instance, trois zones, au moins une réplique synchrone, dans un seul cluster Kubernetes.
Attention au défaut : l'anti-affinité générée par CloudNativePG est preferred sur kubernetes.io/hostname. Elle répartit sur des nœuds, pas sur des zones. topologyKey: topology.kubernetes.io/zone et podAntiAffinityType: required garantissent une instance par zone, au risque d'un pod en attente faute de nœud éligible. Chaque cluster reçoit deux PodDisruptionBudget, et le drainage du nœud du primaire déclenche d'abord une bascule planifiée.
Ce que le commit attend
Le paramètre synchronous_commit fixe ce qu'attend un COMMIT. Il n'a d'effet de réplication que si synchronous_standby_names est renseigné, ce qui n'est pas le cas par défaut.
| Valeur | Le commit attend | Ce qui peut encore être perdu |
|---|---|---|
off | Rien, pas même le vidage local | Transactions récentes au crash, sans incohérence |
local | L'écriture durable sur le primaire | Tout ce qu'aucune réplique n'a reçu |
remote_write | L'écriture par le système de fichiers des répliques synchrones | Données non vidées si le système de la réplique plante |
on (défaut) | Le vidage sur disque des répliques synchrones | Rien, sauf corruption du stockage du primaire et de toutes les répliques synchrones |
remote_apply | Le rejeu sur les répliques synchrones | Comme on, avec lecture cohérente sur la réplique |
Ce paramètre se change jusqu'à la transaction (SET LOCAL synchronous_commit TO off) : une transaction commitée en local ou off n'a aucune garantie de survie à une bascule.
synchronous_standby_names accepte deux méthodes. FIRST 2 (s1, s2, s3), ou 2 (s1, s2, s3) puisque le mot-clé est facultatif, attend les deux répliques de plus haute priorité. ANY 1 (s1, s2) attend un accusé de n'importe laquelle : c'est le quorum. Avec trois instances et ANY 1, le cluster tolère la perte d'une réplique, et chaque commit acquitté existe sur au moins deux instances.
Les limites honnêtes du synchrone
- Blocage. Si une réplique synchrone requise tombe, les commits peuvent ne jamais se terminer.
- Redémarrage du primaire. Des commits en attente d'accusé sont marqués validés au redémarrage du primaire, sans certitude qu'une réplique les ait reçus. La seule garantie : l'application n'a pas reçu de confirmation avant la réception du WAL par les répliques synchrones.
- Attente annulée. La documentation Patroni le rappelle : si l'attente est annulée (délai côté client, processus arrêté), les modifications deviennent visibles sans être répliquées, et peuvent disparaître à la promotion.
- Double panne. Avec
ANY 1sur deux répliques, si le primaire et la réplique qui a acquitté tombent ensemble, la réplique restante peut être en retard ; la promouvoir perd des commits acquittés. C'est ce qu'empêchent le contrôle de quorum de CloudNativePG et le mode synchrone de Patroni.
Réglage dans CloudNativePG et dans Patroni
CloudNativePG calcule lui-même synchronous_standby_names depuis .spec.postgresql.synchronous (method et number obligatoires) et interdit de le fixer directement. dataDurability: required, la valeur par défaut, suspend les écritures tant que les répliques synchrones manquent ; preferred réduit le nombre attendu aux répliques disponibles, avec un risque de perte documenté si toutes tombent. failoverQuorum, stable depuis la 1.28.0, n'autorise une promotion que si R + W > N (R répliques promouvables, W accusés exigés, N répliques potentiellement synchrones) ; sinon l'opérateur attend, et kubectl cnpg promote ne force la promotion qu'en dernier recours.
# Référence synthétique CloudNativePG 1.30, paramètres entre chevrons à décider
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: <NOM_CLUSTER>
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:18.6-system-trixie
postgresql:
synchronous:
method: any
number: 1
dataDurability: required
failoverQuorum: true
affinity:
enablePodAntiAffinity: true
topologyKey: topology.kubernetes.io/zone
podAntiAffinityType: required
storage:
storageClass: <CLASSE_DE_STOCKAGE>
size: <TAILLE>
plugins:
- name: barman-cloud.cloudnative-pg.io
isWALArchiver: true
parameters:
barmanObjectName: <OBJECTSTORE>Patroni réplique en asynchrone par défaut. synchronous_mode (on ou quorum) réserve la promotion aux répliques synchrones ; sans synchronous_mode_strict, le primaire continue d'écrire sans garantie de réplication quand aucune réplique synchrone n'est disponible. En asynchrone, maximum_lag_on_failover (1 048 576 octets par défaut dans le code de Patroni 4.1.5) écarte les répliques trop en retard ; la perte reste bornée, au pire, par cette valeur plus ce qui a été écrit pendant les ttl dernières secondes. Chez Crunchy PGO, ces réglages passent par spec.patroni.dynamicConfiguration.
| Mode | CloudNativePG | Patroni | Perte possible à la bascule | Écritures si les répliques synchrones manquent |
|---|---|---|---|---|
| Asynchrone (défaut) | Pas de section synchronous | synchronous_mode: off | Commits non reçus par la réplique promue | Continuent |
| Synchrone, disponibilité d'abord | dataDurability: preferred | on ou quorum, sans mode strict | Nulle tant qu'une réplique synchrone existe | Continuent, après une pause pouvant atteindre ttl (Patroni), sans garantie de réplication |
| Synchrone, durabilité d'abord | required et failoverQuorum: true | on ou quorum, avec mode strict | Nulle pour les commits acquittés, hors attentes annulées et local ou off | Bloquées |
Service managé, CloudNativePG ou opérateur Patroni
La question utile n'est pas « Kubernetes ou pas », mais : qui détecte, qui décide la promotion, qui empêche l'ancien primaire d'écrire, qui sauvegarde, et qui prouve que tout cela fonctionne.
Ce que documentent les services managés
Durées typiques publiées par les fournisseurs :
- RDS, instance Multi-AZ. Réserve dans une autre zone ; bascule typique de 60 à 120 secondes par changement de l'enregistrement DNS. AWS recommande de limiter à 60 secondes le cache DNS des machines virtuelles Java (JVM).
- RDS, cluster Multi-AZ. Un écrivain et deux lecteurs dans trois zones, en réplication semi-synchrone (accusé d'au moins un lecteur) ; bascule typique de moins de 35 secondes.
- Aurora PostgreSQL. Chaque segment du volume est répliqué six fois sur trois zones ; avec une réplique, service rétabli typiquement en moins de 60 secondes, souvent moins de 30 ; sans réplique, en moins de 10 minutes.
- Cloud SQL. Instance régionale sur deux zones, écritures répliquées de façon synchrone sur les disques des deux zones avant la validation ; à la bascule, environ 60 secondes pour rétablir les connexions au primaire.
CloudNativePG
CloudNativePG 1.30.1 date du 23 septembre 2026. Sans outil de bascule externe, il s'appuie sur l'API Kubernetes comme seule source de vérité de l'état du cluster ; un gestionnaire d'instance pilote PostgreSQL dans chaque pod, et les services -rw, -ro et -r exposent le primaire, les répliques ou toutes les instances. Point d'architecture : c'est le contrôleur de l'opérateur qui déclenche la bascule, dans sa boucle de réconciliation. Il s'en déduit qu'un opérateur arrêté retarde la bascule jusqu'à son retour ; l'essai 8 du plan de tests le vérifie.
Les protections se sont durcies depuis 2025 : contrôle d'isolement du primaire actif par défaut depuis la 1.27.0, bascule sous quorum stable depuis la 1.28.0, bail Kubernetes (Lease) qui sérialise la promotion depuis la 1.30.0. Les versions 1.28.3 et 1.29.1 du 8 mai 2026 ont corrigé deux défauts : l'absence de bascule quand le nœud du primaire devenait injoignable, et un ancien primaire qui, gardant son étiquette, recevait à nouveau le trafic -rw puis perdait des écritures validées au pg_rewind. Un test limité à la suppression d'un pod n'aurait révélé ni l'un ni l'autre.
Les sauvegardes passent par le plugin Barman Cloud ou par des instantanés de volumes ; l'intégration Barman native, dépréciée depuis la 1.26, doit disparaître en 1.31.0. Les clusters répliques étendent la topologie à d'autres clusters Kubernetes, mais l'opérateur ne fonctionne qu'à l'intérieur d'un cluster : une bascule entre clusters reste une décision coordonnée hors de lui. La 1.30 prend en charge Kubernetes 1.34 à 1.36 et PostgreSQL 14 à 18 ; la 1.29 atteint sa fin de support le 29 septembre 2026.
Opérateurs fondés sur Patroni : Zalando et Crunchy PGO
Patroni (4.1.5, publié le 12 août 2026) s'exécute à côté de chaque instance et s'appuie sur un magasin de configuration distribué : etcd, Consul, ZooKeeper ou l'API Kubernetes. Une instance ne reste primaire que si elle parvient à mettre à jour la clé de leader ; sinon elle se rétrograde. Défauts : ttl 30 secondes, loop_wait 10, retry_timeout 10, avec la règle loop_wait + 2 × retry_timeout ≤ ttl. Si PostgreSQL plante alors que Patroni tourne, primary_start_timeout (300 secondes par défaut) laisse au primaire le temps de se rétablir avant la bascule ; à 0, la bascule suit le plantage, au prix de transactions perdues en asynchrone.
L'opérateur Zalando (2.0.2, PostgreSQL 14 à 18) déploie l'image Spilo, qui embarque Patroni et WAL-G. Quand le magasin est l'API Kubernetes, il stocke le leader dans des ConfigMap par défaut ; passer d'un mode à l'autre exige de réduire d'abord le cluster à un seul pod primaire, sous peine de split brain. Crunchy PGO (série 6.0, images PostgreSQL 14 à 18) combine Patroni et pgBackRest ; sa ressource PostgresCluster expose la durée du bail de leader (leaderLeaseDurationSeconds, 30 secondes par défaut) et la configuration dynamique de Patroni. Dans ces opérateurs, la décision de bascule appartient à Patroni, dans les pods, et non au contrôleur de l'opérateur : c'est la différence d'architecture la plus nette avec CloudNativePG, et elle se vérifie elle aussi par un essai opérateur arrêté.
Tableau de décision
| Critère | Service managé (RDS, Aurora, Cloud SQL) | CloudNativePG | Opérateur Patroni (Zalando, Crunchy PGO) |
|---|---|---|---|
| Qui décide la bascule | L'automate du fournisseur | Le contrôleur de l'opérateur, avec un bail Kubernetes | Patroni dans chaque pod, via la clé de leader |
| Bascule opérateur arrêté | Sans objet | Non, elle attend le retour du contrôleur | Décidée par Patroni, à confirmer par l'essai |
| RPO nul | Réplication synchrone ou semi-synchrone selon l'offre | required et failoverQuorum | synchronous_mode et mode strict |
| Durée de bascule publiée | Moins de 35 s à 120 s selon l'offre | Aucune ; 50 à 55 s de détection d'un nœud perdu | Aucune ; clé de leader de 30 s |
| Sauvegarde et PITR | Intégrées | Plugin Barman Cloud, instantanés | WAL-G (Zalando), pgBackRest (Crunchy) |
| À la charge de l'équipe | Paramétrage, reconnexion, tests | Opérateur, stockage, sauvegardes, versions, tests | Idem, plus le magasin de consensus s'il est externe |
Un service managé convient quand l'équipe ne veut pas exploiter de base, que la plateforme vit chez un seul fournisseur et que ses extensions y sont proposées. CloudNativePG convient à une plateforme Kubernetes déclarative, portable entre fournisseurs ou sur site, qui accepte l'opérateur comme composant critique. Un opérateur Patroni se justifie avec un parc Patroni existant ou l'exigence d'une bascule indépendante de l'opérateur. Dans tous les cas, la portabilité annoncée se vérifie par des essais sur chaque fournisseur visé.
Versions : PostgreSQL 14 en fin de vie le 12 novembre 2026
À la date du , le projet PostgreSQL maintient les versions 14 à 18, chacune cinq ans après sa sortie ; les dernières mineures, dont 18.6 et 14.24, datent du 13 août 2026. PostgreSQL 14 cesse de recevoir des correctifs le 12 novembre 2026, et PostgreSQL 19 n'est qu'en bêta 4 (24 septembre 2026). Les services managés décalent l'échéance : sur Amazon RDS for PostgreSQL, la version 14 reste en support standard jusqu'au 28 février 2027, puis passe en support étendu payant ; sur Cloud SQL, son support étendu commence le 1er février 2027.
La montée majeure est une opération de disponibilité. CloudNativePG propose trois méthodes : export et import logiques (hors ligne), réplication logique vers un nouveau cluster (en ligne), ou pg_upgrade en place (hors ligne), pendant lequel tout le cluster, répliques comprises, est indisponible. Depuis PostgreSQL 18, initdb active par défaut les sommes de contrôle des pages, et pg_upgrade exige le même réglage des deux côtés : un cluster 14 créé sans elles doit en tenir compte, l'option --no-data-checksums d'initdb existant pour ce cas.
Déroulé d'une bascule et protections contre le split brain
Le cas exigeant n'est pas la suppression d'un pod, qui laisse PostgreSQL s'arrêter proprement, mais la perte du nœud du primaire, ici avec CloudNativePG 1.30 et ses réglages par défaut.
Bascule CloudNativePG après la perte du nœud du primaire
Détection par Kubernetes, isolement de l'ancien primaire, contrôle du quorum, bail, promotion, reroutage du service rw et retour de l'ancien primaire comme réplique.
Schéma défilable horizontalement ; sa version textuelle complète suit.
Lire le schéma sous forme textuelle
- Le nœud du primaire devient injoignable (machine, kubelet ou réseau).
- Après la période de grâce du contrôleur de nœuds (50 secondes par défaut), Kubernetes déclare le pod primaire non prêt : l'opérateur le constate 50 à 55 secondes après la perte du nœud.
- Si l'ancien primaire tourne encore sans joindre ni l'API ni aucune autre instance, son contrôle d'isolement fait échouer la sonde de vivacité : le kubelet le redémarre en une trentaine de secondes par un arrêt « smart », qui laisse les sessions ouvertes valider jusqu'à
smartShutdownTimeout(180 secondes par défaut). - Après
failoverDelay(0 par défaut), l'opérateur engage la bascule, arrête les récepteurs de WAL et, sifailoverQuorumest actif, vérifie qu'une réplique joignable détient tous les commits synchrones, faute de quoi il ne promeut personne. - La réplique élue acquiert le bail du primaire ; après une perte brutale, elle doit l'avoir observé inchangé pendant
leaseDurationSeconds(15 secondes par défaut). - Elle se promeut et ouvre une nouvelle timeline.
- L'opérateur dirige le service
-rwvers elle. - L'application se reconnecte et reprend ses écritures : le RTO s'arrête là.
- Au retour de son nœud, l'ancien primaire rejoint le cluster comme réplique, après
pg_rewindsi les timelines ont divergé.
Ce qui empêche deux primaires
- CloudNativePG. Bascule en deux temps, bail, contrôle d'isolement, étiquette
unhealthyposée sur l'ancien primaire pendant la bascule. Limite reconnue par la documentation : l'arrêt « smart » réduit la fenêtre d'écriture d'un primaire isolé sans la fermer ;.spec.smartShutdownTimeout: 0passe directement à un arrêt rapide. En synchronerequired, un primaire isolé qui ne joint plus aucune réplique synchrone ne peut plus rien acquitter.kubectl cnpg fencing onarrête PostgreSQL en gardant le pod pour l'analyse. - Patroni. Une instance qui ne parvient plus à mettre à jour la clé de leader se rétrograde. Un chien de garde (watchdog) Linux réinitialise la machine si Patroni se bloque ; il expire par défaut 5 secondes avant le
ttl.failsafe_mode, désactivé par défaut, maintient le primaire pendant une panne du magasin de consensus, seulement s'il joint tous les membres connus. - Service managé. La protection est celle du fournisseur ; restent à l'équipe le changement DNS et la reconnexion.
switchoverDelay (3 600 secondes par défaut) borne l'arrêt rapide pendant lequel l'ancien primaire, s'il répond encore, archive son WAL. Le réduire favorise le RTO au détriment du RPO, dit la documentation. S'il ne répond plus, l'arrêt immédiat est direct, et le WAL resté en attente n'est archivé qu'au redémarrage de l'instance.
Côté application et retour de l'ancien primaire
L'application se connecte au service qui suit le primaire, jamais à un pod ; hors Kubernetes, libpq accepte plusieurs hôtes et target_session_attrs=read-write. Les pools valident leurs connexions. Seules les opérations idempotentes se rejouent automatiquement : un commit dont l'accusé s'est perdu est incertain, pas perdu.
pg_rewind resynchronise l'ancien primaire sur la nouvelle timeline. Il exige wal_log_hints ou des sommes de contrôle, et le WAL depuis la divergence (option -c pour le lire dans l'archive). En asynchrone, les transactions présentes seulement sur l'ancien primaire sont effacées ; si pg_rewind échoue en cours de route, le répertoire cible est probablement irrécupérable et une nouvelle copie complète s'impose. Chez Patroni, pg_rewind n'est utilisé que si use_pg_rewind est activé : vérifier ce que pose l'image ou l'opérateur.
Plan de tests de panne : primaire, nœud, zone, partition, restauration
Un cluster n'est hautement disponible que pour les scénarios exercés. La méthode vaut pour tout service à état, Keycloak compris : une injection, un comportement attendu, une condition d'échec, des preuves, dans un environnement qui reproduit la production (zones, stockage, versions, volume).
Instrumenter avant de casser
Une sonde d'écriture minimale insère un identifiant croissant, une connexion par écriture, et ne consigne « ack » que si le COMMIT est confirmé.
CREATE TABLE ha_probe (
id bigint PRIMARY KEY,
ts timestamptz NOT NULL DEFAULT clock_timestamp()
);# Sonde d'écriture synthétique, via le service qui suit le primaire
DSN_RW='postgresql://<UTILISATEUR>@<NOM_CLUSTER>-rw.<ESPACE>.svc:5432/<BASE>?connect_timeout=2'
i=0
while true; do
i=$((i + 1))
if psql "$DSN_RW" -qAt -c "INSERT INTO ha_probe (id) VALUES ($i)" >/dev/null 2>&1; then
echo "$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ) ack $i" >> ack.log
else
echo "$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ) err $i" >> ack.log
fi
sleep 0.2
done
# Après la bascule : identifiants acquittés absents de la base = perte de données
grep ' ack ' ack.log | awk '{print $3}' | sort > ack_ids.txt
psql "$DSN_RW" -qAt -c "SELECT id FROM ha_probe" | sort > db_ids.txt
comm -23 ack_ids.txt db_ids.txtEn synchrone, la sortie de comm doit être vide ; en asynchrone, elle donne la perte réelle. Le RTO mesuré va du dernier « ack » avant la première erreur au premier « ack » après ; un identifiant en « err » présent en base signale un commit incertain. Après chaque essai, un seul pod doit répondre f à SELECT pg_is_in_recovery();, et kubectl get lease <NOM_CLUSTER> doit désigner le nouveau primaire.
Les scénarios minimaux
| # | Scénario | Injection | Attendu | Échec du test |
|---|---|---|---|---|
| 1 | Bascule planifiée | kubectl cnpg promote ou patronictl switchover | Aucun commit acquitté perdu | Identifiant acquitté manquant |
| 2 | Perte du pod primaire | Arrêt brutal de PostgreSQL ou du pod | Promotion, -rw basculé | Deux primaires, perte en synchrone |
| 3 | Perte du nœud primaire | Arrêt de la machine virtuelle | Détection en 50 à 55 s, promotion | Pas de bascule, ancien primaire à nouveau routé |
| 4 | Perte d'une zone | Arrêt des nœuds d'une zone | Quorum respecté | Réplique non synchrone promue |
| 5 | Partition du primaire | Isolement réseau de son nœud | Un seul côté écrit | Écritures acquittées des deux côtés |
| 6 | Perte des répliques synchrones | Arrêt des répliques | Blocage ou poursuite, selon la décision | Comportement contraire à la décision |
| 7 | Perte de l'API ou du magasin de consensus | Coupure de l'accès des nœuds de données | Patroni sans failsafe_mode : rétrogradation ; CloudNativePG : à constater | Écriture sans protection active |
| 8 | Opérateur arrêté | Opérateur à zéro, puis scénario 2 | CloudNativePG : bascule différée ; opérateur Patroni : bascule par Patroni | Écart absent du plan de reprise |
| 9 | Restauration PITR | Nouveau cluster isolé depuis l'archive | Données conformes, durée mesurée | Archive incomplète |
| 10 | Retour de l'ancien primaire | Remise en service du nœud | Réplique, pg_rewind réussi | Ancien primaire qui écrit |
Le scénario 3 compte plus que le 2 : c'est le défaut corrigé par CloudNativePG 1.28.3 et 1.29.1. Il se rejoue à chaque montée de version de l'opérateur, de Kubernetes ou de PostgreSQL ; CloudNativePG ne maintenant chaque mineure que trois mois après la sortie de la suivante, ce rythme dicte celui des tests.
Le test de restauration
La restauration se teste dans un espace de noms isolé, sous un nouveau nom, en lisant l'archive sans jamais y écrire : un ObjectStore dédié, avec des identifiants en lecture seule. Le manifeste ne déclare aucun archivage ; s'il en déclarait un vers une archive non vide, CloudNativePG bloquerait le cluster à l'état « Setting up primary » avec l'erreur « Expected empty archive », garde-fou à ne pas contourner.
# Référence synthétique : restauration à un instant donné, CloudNativePG 1.30
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: <NOM_CLUSTER>-essai-restauration
namespace: <ESPACE_ISOLE>
spec:
instances: 1
storage:
size: <TAILLE>
bootstrap:
recovery:
source: origine
recoveryTarget:
targetTime: "<HORODATAGE_RFC3339>"
externalClusters:
- name: origine
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: <OBJECTSTORE_LECTURE_SEULE>
serverName: <NOM_CLUSTER>Sans backupID, l'opérateur part de la dernière sauvegarde terminée avant la cible. Le contrôle porte sur les dernières données attendues au point visé (la table de sonde s'y prête) et sur des parcours applicatifs ; la durée jusqu'au premier parcours réussi est le RTO de restauration réel. Ces chiffres mesurés alimentent le plan de reprise d'activité.
Le registre de preuves
Chaque essai consigne les versions (opérateur, PostgreSQL, Kubernetes), les heures d'injection, de détection, de promotion et de première écriture réussie, l'instance promue et sa timeline, le détenteur du bail, les identifiants acquittés manquants, le comportement de l'ancien primaire et les écarts avec l'attendu, sans aucune donnée personnelle. Dans une mise en production Kubernetes par étapes, ce registre sert de critère d'ouverture : une base est déclarée prête une fois ses scénarios passés et mesurés.
Sources officielles et limites de lecture
Faits datés
Les sources citées ont été vérifiées le ; leurs délais par défaut ont changé d'une version à l'autre et sont à recontrôler avant toute autre version.
Vérifié le
Les durées de bascule des services managés sont des valeurs typiques publiées, pas des engagements ni des mesures de l'environnement du lecteur ; les distributions Kubernetes managées peuvent aussi modifier les délais du contrôleur de nœuds. Les pages consultées décrivent l'isolement d'un primaire CloudNativePG qui ne joint ni l'API ni les autres instances ; une perte prolongée de l'API seule, instances joignables entre elles, se constate par un essai. L'article ne couvre ni les poolers en détail, ni les topologies multi-régions actives, que PostgreSQL ne propose pas nativement. Il ne recommande aucun opérateur dans l'absolu et ne fixe aucun RPO ni RTO : ces valeurs se décident avec les métiers, puis se prouvent par les essais.
Sources
- Versioning Policy, PostgreSQL Global Development Group, https://www.postgresql.org/support/versioning/, consulté le 28/09/2026.
- PostgreSQL 18.6, 17.11, 16.15, 15.19, 14.24 and 19 Beta 3 Released!, PostgreSQL Global Development Group, https://www.postgresql.org/about/news/postgresql-186-1711-1615-1519-1424-and-19-beta-3-released-3365/, consulté le 28/09/2026.
- PostgreSQL 19 Beta 4 Released!, PostgreSQL Global Development Group, https://www.postgresql.org/about/news/postgresql-19-beta-4-released-3386/, consulté le 28/09/2026.
- Write Ahead Log (synchronous_commit), documentation PostgreSQL 18, https://www.postgresql.org/docs/18/runtime-config-wal.html, consulté le 28/09/2026.
- Replication (synchronous_standby_names), documentation PostgreSQL 18, https://www.postgresql.org/docs/18/runtime-config-replication.html, consulté le 28/09/2026.
- Log-Shipping Standby Servers (synchronous replication), documentation PostgreSQL 18, https://www.postgresql.org/docs/18/warm-standby.html, consulté le 28/09/2026.
- pg_rewind, documentation PostgreSQL 18, https://www.postgresql.org/docs/18/app-pgrewind.html, consulté le 28/09/2026.
- Release 18 (sommes de contrôle par défaut et pg_upgrade), documentation PostgreSQL 18, https://www.postgresql.org/docs/18/release-18.html, consulté le 28/09/2026.
- Database Connection Control Functions (target_session_attrs), documentation PostgreSQL 18, https://www.postgresql.org/docs/18/libpq-connect.html, consulté le 28/09/2026.
- Automated failover, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/failover.md, consulté le 28/09/2026.
- Architecture, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/architecture.md, consulté le 28/09/2026.
- Replication, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/replication.md, consulté le 28/09/2026.
- Postgres instance manager, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/instance_manager.md, consulté le 28/09/2026.
- Backup et WAL archiving, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/backup.md et https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/wal_archiving.md, consulté le 28/09/2026.
- Recovery, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/recovery.md, consulté le 28/09/2026.
- Scheduling, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/scheduling.md, consulté le 28/09/2026.
- Kubernetes Upgrade and Maintenance, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/kubernetes_upgrade.md, consulté le 28/09/2026.
- PostgreSQL upgrades, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/postgres_upgrades.md, consulté le 28/09/2026.
- Fencing et kubectl plugin, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/fencing.md et https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/kubectl-plugin.md, consulté le 28/09/2026.
- Supported releases, documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/supported_releases.md, consulté le 28/09/2026.
- Release notes 1.30, 1.29, 1.28, 1.27 et 1.26, CloudNativePG, https://github.com/cloudnative-pg/cloudnative-pg/tree/v1.30.1/docs/src/release_notes, consulté le 28/09/2026.
- FAQ (API Kubernetes comme source de vérité, pg_rewind), documentation CloudNativePG 1.30.1, https://github.com/cloudnative-pg/cloudnative-pg/blob/v1.30.1/docs/src/faq.md, consulté le 28/09/2026.
- Barman Cloud Plugin, Concepts, CloudNativePG, https://github.com/cloudnative-pg/plugin-barman-cloud/blob/v0.15.0/web/docs/concepts.md, consulté le 28/09/2026.
- Recommended architectures for PostgreSQL in Kubernetes, blog de la CNCF, 29/09/2023, https://www.cncf.io/blog/2023/09/29/recommended-architectures-for-postgresql-in-kubernetes/, consulté le 28/09/2026.
- Replication modes, documentation Patroni 4.1.5, https://github.com/patroni/patroni/blob/v4.1.5/docs/replication_modes.rst, consulté le 28/09/2026.
- Dynamic Configuration Settings, documentation Patroni 4.1.5, https://github.com/patroni/patroni/blob/v4.1.5/docs/dynamic_configuration.rst, consulté le 28/09/2026.
- Watchdog support et DCS Failsafe Mode, documentation Patroni 4.1.5, https://github.com/patroni/patroni/blob/v4.1.5/docs/watchdog.rst et https://github.com/patroni/patroni/blob/v4.1.5/docs/dcs_failsafe_mode.rst, consulté le 28/09/2026.
- README et Using Patroni with Kubernetes, documentation Patroni 4.1.5, https://github.com/patroni/patroni/blob/v4.1.5/README.rst et https://github.com/patroni/patroni/blob/v4.1.5/docs/kubernetes.rst, consulté le 28/09/2026.
- Release notes (version 4.1.5) et code source (global_config.py, rewind.py), Patroni, https://github.com/patroni/patroni/blob/v4.1.5/docs/releases.rst, https://github.com/patroni/patroni/blob/v4.1.5/patroni/global_config.py et https://github.com/patroni/patroni/blob/v4.1.5/patroni/postgresql/rewind.py, consulté le 28/09/2026.
- Postgres Operator 2.0.2, README et paramètres de l'opérateur, Zalando, https://github.com/zalando/postgres-operator/blob/v2.0.2/README.md et https://github.com/zalando/postgres-operator/blob/v2.0.2/docs/reference/operator_parameters.md, consulté le 28/09/2026.
- PGO 6.0.3, README et définition de la ressource PostgresCluster, Crunchy Data, https://github.com/CrunchyData/postgres-operator/blob/v6.0.3/README.md et https://github.com/CrunchyData/postgres-operator/blob/v6.0.3/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml, consulté le 28/09/2026.
- High Availability et notes de version 6.0.x, documentation Crunchy Postgres for Kubernetes, https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/high-availability et https://access.crunchydata.com/documentation/postgres-operator/latest/releases/6.0.x, consulté le 28/09/2026.
- Multi-AZ DB instance deployments et Failing over a Multi-AZ DB instance, Amazon RDS User Guide, https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZSingleStandby.html et https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.Failover.html, consulté le 28/09/2026.
- Multi-AZ DB cluster deployments et Failing over a Multi-AZ DB cluster, Amazon RDS User Guide, https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/multi-az-db-clusters-concepts.html et https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/multi-az-db-clusters-concepts-failover.html, consulté le 28/09/2026.
- High availability for Amazon Aurora, Amazon Aurora User Guide, https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Concepts.AuroraHighAvailability.html, consulté le 28/09/2026.
- About high availability, documentation Cloud SQL pour PostgreSQL, https://docs.cloud.google.com/sql/docs/postgres/high-availability, consulté le 28/09/2026.
- Release calendars for Amazon RDS for PostgreSQL, AWS, https://docs.aws.amazon.com/AmazonRDS/latest/PostgreSQLReleaseNotes/postgresql-release-calendar.html, consulté le 28/09/2026.
- Database versions and version policies, documentation Cloud SQL pour PostgreSQL, https://docs.cloud.google.com/sql/docs/postgres/db-versions, consulté le 28/09/2026.
- kube-controller-manager (options --node-monitor-grace-period et --node-monitor-period), documentation Kubernetes, https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/, consulté le 28/09/2026.