Mettre en place un cluster PostgreSQL haute disponibilité à 3 nœuds avec Patroni et etcd
25 juin 2026 · 8 min read · Read in English
Sommaire
Construire un cluster PostgreSQL haute disponibilité (Patroni + etcd + HAProxy + Keepalived)
PostgreSQL seul est un point unique de défaillance : si le serveur tombe, l'application tombe. Cet article montre comment monter un cluster à 3 nœuds qui survit à la perte d'une machine sans intervention humaine et sans changer la chaîne de connexion côté applications.
L'objectif n'est pas de copier-coller des commandes sans comprendre, mais de saisir pourquoi chaque brique existe, pour pouvoir adapter le tout à votre propre réseau.
Le résultat : une seule IP virtuelle (la VIP) sur laquelle les applications se connectent comme sur une base normale. Derrière, trois serveurs se surveillent en permanence ; en cas de panne du primaire, un réplica est promu en quelques secondes et la VIP bascule automatiquement.
Partie 1 — L'architecture et pourquoi ce stack
Le problème de la haute disponibilité
Pour qu'une base reste disponible quand un serveur tombe, il faut résoudre quatre problèmes distincts :
- Stocker la donnée plusieurs fois → réplication PostgreSQL (un primaire écrit, des réplicas suivent).
- Décider qui est le primaire, et le changer sans conflit quand il tombe → un orchestrateur (Patroni).
- Se mettre d'accord à plusieurs sur cette décision, même en cas de coupure réseau → un magasin à consensus (etcd).
- Diriger les clients vers le bon serveur sans qu'ils sachent qui est primaire → un routeur (HAProxy) + une adresse unique qui suit le service (VIP via Keepalived).
Chaque composant règle exactement un de ces problèmes. C'est pour ça qu'il y en a quatre.
Les quatre composants
| Composant | Rôle | Ce qu'il résout |
|---|---|---|
| PostgreSQL | Le moteur de base de données | Stocke et réplique la donnée |
| etcd | Magasin clé-valeur distribué (le « DCS ») | Source de vérité partagée : qui est le leader |
| Patroni | Orchestrateur au-dessus de PostgreSQL | Bootstrap, réplication, élection, failover |
| HAProxy | Proxy TCP | Route chaque connexion vers le primaire courant |
| Keepalived | Gestion d'IP virtuelle (VRRP) | Fait flotter une IP unique vers un nœud sain |
Pourquoi trois nœuds, et pas deux
etcd (comme tout système à consensus) décide à la majorité. Avec 3 nœuds, la majorité est 2 : le cluster continue de fonctionner si un nœud tombe. Avec 2 nœuds, perdre un nœud fait perdre la majorité — plus personne ne peut décider qui est primaire, et le cluster se fige par sécurité (pour éviter le split-brain, deux primaires qui divergent).
Règle simple : un nombre impair de nœuds. Trois est le minimum utile.
Le chemin d'une requête
Application
│ se connecte à la VIP (ex: 192.168.11.50:5432)
▼
Keepalived ── la VIP est montée sur le nœud sain prioritaire
▼
HAProxy ── teste les 3 nœuds, n'envoie qu'au PRIMAIRE
▼
PostgreSQL (primaire) ←── répliquent ── PostgreSQL (réplicas ×2)
▲
Patroni ── surveille, et via etcd décide qui est primaire
Point clé : HAProxy ne devine pas qui est primaire. Il interroge l'API REST de Patroni sur chaque nœud (GET /primary). Seul le nœud réellement primaire répond 200. Les réplicas répondent autre chose et sont écartés. Quand le failover se produit, c'est l'ancien réplica devenu primaire qui se met à répondre 200, et HAProxy redirige le trafic — automatiquement, sans reconfiguration.
Sécurité : TLS partout
Tout le trafic interne est chiffré et authentifié par certificats X.509 :
- etcd exige des certificats mutuels (peer-to-peer et client). Une CA privée signe un certificat par nœud.
- PostgreSQL utilise un certificat partagé dont les SAN couvrent la VIP + les 3 IP + localhost, pour que la connexion soit valide quel que soit le nœud qui sert.
- pg_hba n'autorise que des connexions
hostssl(jamais en clair), restreintes au sous-réseau du cluster.
Partie 2 — Préparation : réseau, configuration, pré-vol
Tout le déploiement tient dans deux scripts et un fichier de configuration :
cluster.env— toutes les variables (IP, ports, secrets, version PG). C'est le seul fichier à éditer.preflight.sh— vérifie que la machine est prête, n'écrit rien.deploy.sh— installe et configure tout. Idempotent : rejouable sans casser un cluster existant.
Les trois fichiers sont dans le repo des labs du blog, sous-dossier postgresql-ha-cluster. Récupérez-les :
git clone https://github.com/denisakp/dev-life-labs
cd dev-life-labs/postgresql-ha-cluster
C'est ce dossier que vous copierez ensuite sur chaque nœud (voir Partie 3).
Pré-requis réseau
Avant tout, il faut :
- 3 serveurs Debian/Ubuntu sur le même sous-réseau, avec IP fixes.
- Une IP libre dans ce sous-réseau pour la VIP — vérifiez-la :
ping -c2 192.168.11.50 # NE doit PAS répondre → la VIP est libre
- Le nom de l'interface réseau de chaque serveur (souvent
ens3,enp0s3,eth0…) :
ip link show
Si l'interface (IFACE) est mal renseignée, la VIP ne montera jamais. C'est l'erreur n°1.
Le fichier de configuration
On part du modèle :
cp cluster.env.example cluster.env && chmod 600 cluster.env
Les champs essentiels :
# Identité
CLUSTER_NAME="postgresql-cluster"
PG_VERSION="18"
ETCD_VERSION="v3.6.12"
# Réseau
SUBNET="192.168.11.0/24" # sous-réseau du cluster
VIP="192.168.11.50" # IP virtuelle (entrée des applications)
IFACE="ens3" # interface qui portera la VIP
APP_SUBNET="192.168.11.0/24" # d'où viennent les clients
# Nœuds
DB1_NAME="db1"; DB1_IP="192.168.11.45" # primaire initial, priorité VRRP 100
DB2_NAME="db2"; DB2_IP="192.168.11.46" # secours, priorité 90
DB3_NAME="db3"; DB3_IP="192.168.11.47" # secours, priorité 80
# Secrets — générer chacun avec : openssl rand -base64 24
PG_SUPERUSER_PASSWORD="..."
PG_REPLICATOR_PASSWORD="..."
PATRONI_RESTAPI_PASSWORD="..."
VRRP_AUTH_PASS="........" # ATTENTION : max 8 caractères (VRRP tronque au-delà)
Deux pièges qui reviennent toujours :
VRRP_AUTH_PASS≤ 8 caractères. Le protocole VRRP tronque silencieusement ; deux nœuds avec des mots de passe différents après troncature ne se voient pas et la VIP peut se dédoubler.cluster.envdoit être identique sur les 3 serveurs, secrets compris. Les certificats et l'authentification reposent dessus.
Les priorités VRRP
DB1=100 > DB2=90 > DB3=80. Keepalived monte la VIP sur le nœud sain de plus haute priorité. db1 l'a par défaut ; s'il tombe, db2 la récupère, puis db3. Quand db1 revient, il reprend la VIP (priorité la plus haute). Ces priorités sont indépendantes du rôle PostgreSQL : la VIP suit la santé HAProxy, pas le primaire — mais comme HAProxy ne route que vers le primaire, le résultat est cohérent de bout en bout.
Le pré-vol
À lancer sur chaque nœud avant le déploiement :
sudo ./preflight.sh db1
Il contrôle, sans rien modifier : secrets remplis (pas de CHANGE_ME), VIP dans le sous-réseau, interface présente, IP du nœud correcte, autres nœuds joignables, ports libres, RAM/disque suffisants, horloge synchronisée (NTP) — critique : une dérive d'horloge entre nœuds rend le failover instable.
[FAIL]= bloquant, à corriger avant de continuer.[WARN]= vérifiable, mais le déploiement reste possible.
Partie 3 — Déploiement, vérification, failover
L'ordre est impératif : db1 → distribution des certificats → db2 → db3. db1 génère la CA et les certificats de tout le monde ; db2 et db3 ne peuvent pas démarrer sans les recevoir.
Étape 1 — Déployer db1
sudo ./preflight.sh db1
sudo ./deploy.sh db1 --no-upgrade
--no-upgradesaute leapt upgradeglobal. Recommandé si les VM hébergent d'autres services (on ne veut pas mettre à jour tout le système au passage). Retirez-le pour un serveur dédié neuf.
Ce que fait deploy.sh sur db1, dans l'ordre : mise à jour ciblée → installation des paquets (PostgreSQL, Patroni, HAProxy, Keepalived) + binaire etcd → génération de la CA et de tous les certificats → configuration et démarrage d'etcd → configuration de Patroni, HAProxy, Keepalived → sauvegarde etcd quotidienne planifiée.
À la fin, db1 n'est pas encore primaire : etcd attend le quorum (≥ 2 nœuds). C'est normal. Le script génère deux archives certs-db2.tar.gz et certs-db3.tar.gz.
Étape 2 — Distribuer les certificats
Le déploiement ne fait aucun SSH automatique (par choix : pas de clés à propager, l'authentification reste interactive). Vous copiez vous-même les archives :
scp certs-db2.tar.gz user@192.168.11.46:/tmp/
scp certs-db3.tar.gz user@192.168.11.47:/tmp/
# et le cluster.env, identique :
scp cluster.env user@192.168.11.46:~/dev-life-labs/postgresql-ha-cluster/
scp cluster.env user@192.168.11.47:~/dev-life-labs/postgresql-ha-cluster/
Étape 3 — Déployer db2 puis db3
Sur db2 (puis, à l'identique, sur db3) — cloner le repo, puis déployer :
git clone https://github.com/denisakp/dev-life-labs
cd dev-life-labs/postgresql-ha-cluster
chmod 600 cluster.env
sudo ./preflight.sh db2
sudo ./deploy.sh db2 --no-upgrade
deploy.sh détecte l'archive dans /tmp, extrait les certificats, configure le nœud et rejoint le cluster. Dès que db2 démarre, le quorum etcd se forme (2 nœuds), Patroni élit db1 primaire et db2 se synchronise en réplica. db3 rejoint ensuite de la même façon.
Étape 4 — Vérifier
Depuis n'importe quel nœud :
# 1 Leader + 2 Replica en streaming
patronictl -c /etc/patroni/config.yml list
+ Cluster: postgresql-cluster ------+---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+---------------+---------+---------+----+-----------+
| db1 | 192.168.11.45 | Leader | running | 1 | |
| db2 | 192.168.11.46 | Replica | running | 1 | 0 |
| db3 | 192.168.11.47 | Replica | running | 1 | 0 |
+--------+---------------+---------+---------+----+-----------+
# etcd : 3 membres healthy
etcdctl --endpoints=https://192.168.11.45:2379,https://192.168.11.46:2379,https://192.168.11.47:2379 \
--cacert=/etc/etcd/ssl/ca.crt --cert=/etc/etcd/ssl/etcd-db1.crt --key=/etc/etcd/ssl/etcd-db1.key \
endpoint health
# Connexion via la VIP → doit retourner 'f' (primaire, en lecture/écriture)
PGPASSWORD='<superuser>' psql -h 192.168.11.50 -U postgres -c 'SELECT pg_is_in_recovery();'
# La VIP est bien montée sur le primaire
ip addr show ens3 | grep 192.168.11.50
pg_is_in_recovery() qui renvoie f confirme que la VIP mène bien au primaire inscriptible.
Étape 5 — Pare-feu
Sur les 3 nœuds, restreindre les ports du cluster à son sous-réseau :
sudo ufw allow from 192.168.11.0/24 to any port 2379,2380,5432,5433,8008 proto tcp
# VRRP entre les nœuds :
sudo ufw allow from 192.168.11.0/24 to any proto vrrp 2>/dev/null || \
sudo ufw allow from 192.168.11.0/24 to 224.0.0.18
| Port | Service |
|---|---|
| 2379 / 2380 | etcd (client / peer) |
| 5432 | HAProxy — le port exposé aux applications |
| 5433 | PostgreSQL interne (géré par Patroni) |
| 8008 | API REST Patroni (health check HAProxy) |
Le test qui valide tout : le failover
C'est l'intérêt même du cluster. On simule la panne du primaire :
# Repérer le leader
patronictl -c /etc/patroni/config.yml list
# Sur le nœud leader : couper Patroni
sudo systemctl stop patroni
# Quelques secondes plus tard, sur un autre nœud :
patronictl -c /etc/patroni/config.yml list # → un réplica est devenu Leader
Ce qui se passe en quelques secondes, sans intervention : Patroni perd le bail (lease) du leader dans etcd → un réplica se porte candidat et est élu → il est promu primaire → son API répond désormais 200 sur /primary → HAProxy redirige le trafic → la VIP reste sur un nœud où HAProxy est sain. Les applications, connectées à la VIP, ne voient qu'une brève coupure.
On réintègre l'ancien primaire :
sudo systemctl start patroni # il revient comme Replica et se resynchronise
Récapitulatif
| Brique | Sans elle… |
|---|---|
| PostgreSQL + réplication | pas de copie de la donnée |
| etcd (quorum 3) | pas d'accord sur qui est primaire → split-brain |
| Patroni | pas d'élection ni de failover automatiques |
| HAProxy | les clients ne savent pas qui est primaire |
| Keepalived (VIP) | la chaîne de connexion devrait changer à chaque bascule |
Pour adapter à votre environnement, vous ne touchez qu'à un seul fichier (cluster.env) : sous-réseau, IP, interface, secrets. Le reste — certificats, configuration des quatre services, planification des sauvegardes — est généré par deploy.sh, qui est idempotent : en cas d'erreur, corrigez et relancez sudo ./deploy.sh dbX --no-upgrade.
Pièges à retenir :
- Ordre obligatoire : db1 → certs → db2 → db3.
IFACEcorrect, sinon la VIP ne monte pas.VRRP_AUTH_PASS≤ 8 caractères.cluster.envidentique sur les 3 nœuds.- Horloge synchronisée (NTP) sur les trois.
- Nombre de nœuds impair (3 minimum).
S'abonner aux prochains articles
Recevez les nouveaux articles par e-mail. Pas de spam, désinscription à tout moment.
Related posts
Pourquoi j'ai construit ma propre image MongoDB pour faire tourner un replica set en production
Retour d'expérience : déployer un cluster MongoDB replica set à 3 nœuds avec Docker, et pourquoi figer la config dans une image custom au lieu de monter des volumes rend le déploiement reproductible sur des environnements qui ne se ressemblent pas.
December 13, 2025
Sauvegarder et restaurer MongoDB dans un environnement Docker
Comment créer une sauvegarde complète d'une base MongoDB qui tourne dans un conteneur Docker et restaurer la sauvegarde dans un nouveau conteneur MongoDB.
July 18, 2024