Haute disponibilité PostgreSQL avec Patroni, etcd, HAProxy et keepalived

PostgreSQL dispose d'une pile haute disponibilité mature et prête pour la production qui ne coûte rien en licence et est simple à exploiter une fois qu'elle est configurée.

Ce laboratoire crée un cluster HA à six nœuds à l'aide de quatre composants open source : Patroni pour la gestion de cluster et le basculement automatique, etcd en tant que magasin de consensus distribué, HAProxy pour l'équilibrage de charge et le routage de connexion, et keepalived pour une adresse IP virtuelle flottante qui survit aux pannes des nœuds HAProxy.

Le résultat est un cluster où une défaillance primaire est détectée et un nouveau primaire est élu en moins de 30 secondes, sans aucune intervention manuelle requise.

Les basculements sont nets et sans perte de données.

L'ensemble de la pile est géré via une seule CLIpatronictlqui rend les opérations quotidiennes — basculement, reprise après incident, réinitialisation, modifications de configuration — des commandes simples plutôt que des procédures en plusieurs étapes.

Ce laboratoire couvre tout de A à Z : génération de certificats TLS, formation de cluster etcd, configuration de Patroni, configuration de HAProxy, configuration VIP keepalived, et vérification complète des basculements et des reprises sur incident.

Chaque étape est expliquée avec la sortie attendue et le diagnostic de défaillance afin que vous sachiez exactement à quoi ressemble le succès à chaque étape.


Table des matières

Alta disponibilidad de PostgreSQL con Patroni

Couvre l'architecture de Patroni, la configuration TLS, le basculement (failover), le changement de rôle (switchover) et les opérations quotidiennes.

Environnement : Six serveurs au total. etcd s'exécute sur les mêmes nœuds que PostgreSQL — pas de serveurs etcd dédiés.

RôleNom d'hôteCI
Nœud HAProxy 1haproxy-01192.168.0.200
Nœud HAProxy 2haproxy-02192.168.0.201
Nœud HAProxy 3haproxy-03192.168.0.202
PostgreSQL + etcd + Patroni 1postgres-01192.168.0.203
PostgreSQL + etcd + Patroni 2postgres-02192.168.0.204
PostgreSQL + etcd + Patroni 3postgres-03192.168.0.205
Adresse IP virtuelle (VIP)192.168.0.210

1. Architecture

          +----------+ +----------+ +----------+
          | etcd | | etcd | | etcd | | etcd | | etcd
          | Patroni | | Patroni | | Patroni | | Patroni | | Patroni
          | +PG | | +PG | | +PG | | +PG |
          | | node1 | | node2 | | node3 |
          | PRIMAIRE | | STANDBY | | STANDBY |
          +----------+ +----------+ +----------+
                \ | /
                 
          +----------+ +----------+ +----------+
          HAProxy | | HAProxy | | HAProxy | HAProxy | HAProxy | HAProxy | HAProxy | HAProxy | HAProxy
          | node1 | | node2 | | node3 |
          | (MAÎTRE) | | (SAUVEGARDE) | (SAUVEGARDE) | (MAÎTRE) | (MAÎTRE) | (MAÎTRE) | (MAÎTRE) | (MAÎTRE) | (MAÎTRE) | (MAÎTRE)
          +----------+ +----------+ +----------+
                \ | /
                 \ | /
              [keepalived VIP : 192.168.0.210:5432]
                              
                        Applications
  • etcd: magasin clé-valeur distribué co-localisé sur chaque nœud PostgreSQL. Maintient l'état du cluster (leader actuel, liste des membres). Nécessite un nombre impair de nœuds pour le quorum — 3 nœuds tolèrent 1 panne, 5 nœuds tolèrent 2.
  • Patroni: démon sur chaque nœud PostgreSQL. Gère la réplication, surveille la santé et coordonne le basculement via etcd.
  • HAProxy: trois nœuds dédiés acheminent les connexions de l'application vers le principal actuel en consultant l'API REST de Patroni.
  • keepalived: gère le VIP à l'aide de VRRP. Un nœud HAProxy détient le VIP à la fois. Si ce nœud tombe en panne, le VIP est automatiquement transféré au nœud HAProxy suivant.
  • Toute communication est chiffrée TLS: trafic de pairs etcd, trafic client etcd, API REST Patroni et connexions PostgreSQL.

2. Prérequis

Étape 1 — Réglez le fuseau horaire correct sur tous les nœuds

Exécutez sur les 6 serveurs (postgres-01/02/03 et haproxy-01/02/03).

Les serveurs sont réglés sur UTC par défaut — définissez votre fuseau horaire local avant toute autre chose.

Des horodatages incorrects ou discordants provoquent de la confusion dans les journaux et la validation des certificats.

sudo timedatectl set-timezone Europe/Madrid
timedatectl
# Attendu : Fuseau horaire : Europe/Madrid (CET/CEST, +0100/+0200)
# Service NTP devrait indiquer : actif
# Horloge système synchronisée : oui

Étape 2 — Confirmer que les ports requis sont ouverts

Avant de commencer, confirmez que les ports suivants sont ouverts :

SourceDestinationPortObjectif
Nœuds PostgreSQLNœuds PostgreSQL2379Client etcd (Patroni → etcd)
Nœuds PostgreSQLNœuds PostgreSQL2380communication entre pairs etcd
Nœuds PostgreSQLNœuds PostgreSQL5432Réplication PostgreSQL
Nœuds PostgreSQLNœuds PostgreSQL8008API REST Patroni
Nœuds HAProxyNœuds PostgreSQL8008Vérification de l'état de santé HAProxy
Nœuds HAProxyNœuds HAProxy112/VRRPÉlection VIP keepalived
ApplicationsVIP5432Connexions clients

3. Installation de PostgreSQL

Étape 1 — Installer PostgreSQL sur les 3 nœuds PostgreSQL

# Sur postgres-01, postgres-02, postgres-03
sudo apt update
sudo apt install -y postgresql-common
# postgresql-common : fournit le script de configuration du dépôt PGDG

sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
# Ce script ajoute le dépôt apt officiel de PostgreSQL (postgresql.org)
# et importe sa clé GPG — garantit que vous obtenez la dernière version de PostgreSQL,
# et non la version plus ancienne intégrée à Ubuntu

sudo apt update
sudo apt install -y postgresql-18 postgresql-contrib-18
# Installe la version 18 explicitement — le méta-paquet générique "postgresql" installe la version
# par défaut intégrée à Ubuntu (16) en plus de la version PGDG, laissant deux versions installées
# Spécifiez toujours le numéro de version pour éviter cela
# postgresql-contrib-18 : modules supplémentaires, y compris pg_rewind, que Patroni utilise
# pour resynchroniser l'ancien primaire après un basculement sans sauvegarde de base complète

Étape 2 — Arrêtez et désactivez le service PostgreSQL

# Sur postgres-01, postgres-02, postgres-03
sudo systemctl stop postgresql
# Patroni gère entièrement le démarrage de PostgreSQL
# Si PostgreSQL est déjà en cours d'exécution lorsque Patroni démarre, Patroni échouera avec :
# " postmaster est déjà en cours d'exécution "

sudo systemctl disable postgresql
# Empêche PostgreSQL de démarrer automatiquement au démarrage
# Le propre service systemd de Patroni démarre PostgreSQL lorsque le nœud rejoint le cluster

4. Installation d'etcd

Étape 1 — Installer etcd sur les 3 nœuds PostgreSQL

# Sur postgres-01, postgres-02, postgres-03
sudo apt-get install -y wget curl

wget https://github.com/etcd-io/etcd/releases/download/v3.6.10/etcd-v3.6.10-linux-amd64.tar.gz
# Télécharger le binaire etcd directement depuis les releases GitHub
# Le paquet apt est souvent obsolète — toujours installer depuis les releases officielles
# Vérifier sur https://github.com/etcd-io/etcd/releases la dernière version stable

tar xvf etcd-v3.6.10-linux-amd64.tar.gz
# xvf : extraire (x), verbose (v), from file (f)

sudo mv etcd-v3.6.10-linux-amd64/etcd /usr/local/bin/
sudo mv etcd-v3.6.10-linux-amd64/etcdctl /usr/local/bin/
# etcd : le binaire serveur etcd
# etcdctl : l'interface en ligne de commande client etcd — utilisée pour les vérifications d'intégrité et l'inspection de l'état du cluster

# Vérifier l'installation
etcd --version
# Attendu : etcd Version : 3.6.10
# Si "etcd: command not found" : /usr/local/bin n'est pas dans le PATH — exécuter : export PATH=$PATH:/usr/local/bin

etcdctl version
# Attendu : etcdctl version : 3.6.10

Étape 2 — Créer l'utilisateur système etcd

# Sur postgres-01, postgres-02, postgres-03
sudo useradd --system --home /var/lib/etcd --shell /bin/false etcd

# --system : crée un compte système sans shell de connexion par défaut
# --home /var/lib/etcd : etcd y stocke ses données
# --shell /bin/false : empêche la connexion interactive — etcd s'exécute uniquement en tant que démon

5. Génération de certificats TLS

Tous les certificats sont générés une seule fois sur postgres-01 puis distribués aux autres nœuds.

La clé privée du certificatLa clé privée du CAca.clé) reste sur postgres-01 après la fin de la distribution — ne le copiez pas sur d'autres nœuds.

Étape 1 — Créer le répertoire de travail

# Sur postgres-01
mkdir ~/certs && cd ~/certs
# Tous les fichiers de certificat sont créés ici avant d'être copiés sur chaque nœud

Étape 2 — Générer l'autorité de certification

# Sur postgres-01
openssl genrsa -out ca.key 2048
# genrsa: génère une clé privée RSA ; 2048 : longueur de la clé en bits

openssl req -x509 -new -nodes -key ca.key -subj "/CN=etcd-ca" -days 7300 -out ca.crt
# req -x509 : crée un certificat auto-signé (pas une demande de signature)
# -new -nodes : nouveau certificat, pas de phrase de passe sur la clé privée
# -subj "/CN=etcd-ca" : le nom commun (Common Name) du certificat — l'identifie comme le CA du cluster
# -days 7300 : valide pendant 20 ans
# ca.crt : distribué à chaque nœud comme racine de confiance

Étape 3 — Générer les certificats etcd par nœud

Chaque nœud obtient son propre certificat avec son adresse IP comme nom alternatif du sujet (SAN). La vérification du nom d'hôte TLS exige que l'adresse IP du serveur apparaisse dans le SAN – sans cela, les connexions échoueront.

# On postgres-01 — generate all three node certificates here, then distribute

# Certificate for postgres-01 (192.168.0.203)
openssl genrsa -out etcd-node1.key 2048

cat > temp.cnf <<EOF
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 192.168.0.203
IP.2 = 127.0.0.1
EOF

# temp.cnf: OpenSSL config that adds the node IP as a SAN
# IP.2 = 127.0.0.1: allows etcdctl to connect locally without specifying a remote address

openssl req -new -key etcd-node1.key -out etcd-node1.csr \
  -subj "/CN=etcd-node1" \
  -config temp.cnf
# req -new: generate a certificate signing request (CSR)
# -subj: the certificate's identity — CN identifies the node in logs

openssl x509 -req -in etcd-node1.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out etcd-node1.crt -days 7300 \
  -sha256 -extensions v3_req -extfile temp.cnf
# x509 -req: sign the CSR with the CA to produce a certificate
# -CAcreateserial: creates ca.srl to track serial numbers across certificates
# -extensions v3_req -extfile temp.cnf: embed the SANs into the signed certificate

openssl x509 -in etcd-node1.crt -text -noout | grep -A1 "Subject Alternative Name"
# Verify the SAN was embedded — Expected: IP Address:192.168.0.203, IP Address:127.0.0.1
# If the SAN is missing: the -extensions and -extfile flags were not applied correctly

rm temp.cnf

# Certificate for postgres-02 (192.168.0.204)
openssl genrsa -out etcd-node2.key 2048

cat > temp.cnf <<EOF
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 192.168.0.204
IP.2 = 127.0.0.1
EOF

openssl req -new -key etcd-node2.key -out etcd-node2.csr \
  -subj "/CN=etcd-node2" -config temp.cnf

openssl x509 -req -in etcd-node2.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out etcd-node2.crt -days 7300 \
  -sha256 -extensions v3_req -extfile temp.cnf

openssl x509 -in etcd-node2.crt -text -noout | grep -A1 "Subject Alternative Name"
# Expected: IP Address:192.168.0.204, IP Address:127.0.0.1
rm temp.cnf


# Certificate for postgres-03 (192.168.0.205)
openssl genrsa -out etcd-node3.key 2048

cat > temp.cnf <<EOF
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 192.168.0.205
IP.2 = 127.0.0.1
EOF

openssl req -new -key etcd-node3.key -out etcd-node3.csr \
  -subj "/CN=etcd-node3" -config temp.cnf

openssl x509 -req -in etcd-node3.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out etcd-node3.crt -days 7300 \
  -sha256 -extensions v3_req -extfile temp.cnf

openssl x509 -in etcd-node3.crt -text -noout | grep -A1 "Subject Alternative Name"
# Expected: IP Address:192.168.0.205, IP Address:127.0.0.1
rm temp.cnf

Étape 4 — Générer le certificat du serveur PostgreSQL

Un certificat partagé couvre tous les nœuds PostgreSQL.

Il est utilisé à la fois pour les connexions PostgreSQL et pour l'API REST de Patroni.

# Sur postgres-01
openssl genrsa -out server.key 2048

openssl req -new -key server.key -out server.req
# Il vous sera demandé les détails du certificat — le nom commun n'est pas critique
# car les connexions sont vérifiées par IP SAN, pas par CN
# L'avertissement "No -copy_extensions given" est inoffensif — le certificat du serveur n'a pas besoin de SAN
openssl req -x509 -key server.key -in server.req -out server.crt -days 7300
# Certificat serveur auto-signé — signé directement avec server.key, pas avec l'AC
# Patroni et PostgreSQL utilisent ce certificat pour s'identifier auprès des clients

Étape 5 — Distribuer les certificats à postgres-02 et postgres-03

postgres-01 garde ses propres certificats dans ~/certs — aucun scp n'est nécessaire pour lui.

# Sur postgres-01
scp ~/certs/ca.crt ~/certs/etcd-node2.crt ~/certs/etcd-node2.key \
  ~/certs/server.crt ~/certs/server.key fernando@192.168.0.204:/tmp/

scp ~/certs/ca.crt ~/certs/etcd-node3.crt ~/certs/etcd-node3.key \
  ~/certs/server.crt ~/certs/server.key fernando@192.168.0.205:/tmp/

Étape 6 — Installer les certificats sur chaque nœud PostgreSQL

Tous les certificats résident dans /etc/etcd/certs/ sur chaque nœud.

Le répertoire appartient à etcd:etcd alors le démon etcd peut lire ses certificats.

Le PostgreSQL l'utilisateur obtient un accès en lecture via ACL afin que Patroni puisse se connecter à etcd.

Important : définir les permissions de fichier avant de verrouiller le répertoire.

Après chmod 700 le shell ne peut pas développer les jokers à l'intérieur du répertoire en tant qu'utilisateur non root — utilisez des noms de fichiers explicites.

# Sur postgres-01, postgres-02, postgres-03
sudo mkdir -p /etc/etcd/certs
sudo apt-get install -y acl
# acl : fournit setfacl — nécessaire pour accorder l'accès à l'utilisateur postgres sans modifier la propriété

Sur postgres-01 — copier depuis ~/certs (les fichiers n'ont jamais été dans /tmp sur ce nœud) :

sudo cp ~/certs/ca.crt /etc/etcd/certs/
sudo cp ~/certs/etcd-node1.crt /etc/etcd/certs/
sudo cp ~/certs/etcd-node1.key /etc/etcd/certs/
sudo cp ~/certs/server.crt /etc/etcd/certs/
sudo cp ~/certs/server.key /etc/etcd/certs/

Sur postgres-02 — déplacer depuis /tmp :

sudo mv /tmp/ca.crt /etc/etcd/certs/
sudo mv /tmp/etcd-node2.crt /etc/etcd/certs/
sudo mv /tmp/etcd-node2.key /etc/etcd/certs/
sudo mv /tmp/server.crt /etc/etcd/certs/
sudo mv /tmp/server.key /etc/etcd/certs/

Sur postgres-03 — déplacer depuis /tmp :

sudo mv /tmp/ca.crt /etc/etcd/certs/
sudo mv /tmp/etcd-node3.crt /etc/etcd/certs/
sudo mv /tmp/etcd-node3.key /etc/etcd/certs/
sudo mv /tmp/server.crt /etc/etcd/certs/
sudo mv /tmp/server.key /etc/etcd/certs/

Sur les trois nœuds — définissez les permissions puis verrouillez le répertoire :

les certificats etcd doivent appartenir à etcd — le démon etcd s'exécute en tant que cet utilisateur.

Les certificats de serveur doivent appartenir à PostgreSQL — PostgreSQL s'assure que sa clé privée SSL appartient à l'utilisateur de la base de données ou à root.

Utilisation etcd La propriété entraînera le refus de démarrage de PostgreSQL avec le message : “ le fichier de clé privée doit appartenir à l'utilisateur de la base de données ou à root ”.

# Définir d'abord les permissions des fichiers - doit se faire avant chmod 700 sur le répertoire
# Après chmod 700, le shell ne peut pas développer les globs en tant qu'utilisateur non-root

# etcd certs : owned by etcd
# Sur postgres-01 :
sudo chown etcd:etcd /etc/etcd/certs/etcd-node1.crt /etc/etcd/certs/etcd-node1.key /etc/etcd/certs/ca.crt
sudo chmod 600 /etc/etcd/certs/etcd-node1.key
sudo chmod 644 /etc/etcd/certs/etcd-node1.crt /etc/etcd/certs/ca.crt

# Sur postgres-02 :
sudo chown etcd:etcd /etc/etcd/certs/etcd-node2.crt /etc/etcd/certs/etcd-node2.key /etc/etcd/certs/ca.crt
sudo chmod 600 /etc/etcd/certs/etcd-node2.key
sudo chmod 644 /etc/etcd/certs/etcd-node2.crt /etc/etcd/certs/ca.crt

# Sur postgres-03 :
sudo chown etcd:etcd /etc/etcd/certs/etcd-node3.crt /etc/etcd/certs/etcd-node3.key /etc/etcd/certs/ca.crt
sudo chmod 600 /etc/etcd/certs/etcd-node3.key
sudo chmod 644 /etc/etcd/certs/etcd-node3.crt /etc/etcd/certs/ca.crt

# Server certs : owned by postgres (all three nodes - same files on each)
sudo chown postgres:postgres /etc/etcd/certs/server.crt /etc/etcd/certs/server.key
sudo chmod 600 /etc/etcd/certs/server.key
sudo chmod 644 /etc/etcd/certs/server.crt

# Verrouillez le répertoire - exécutez cette opération en dernier, après avoir défini toutes les autorisations de fichiers
sudo chown etcd:etcd /etc/etcd/certs
sudo chmod 700 /etc/etcd/certs

# Accorder à l'utilisateur postgres l'accès en lecture au répertoire et à tous les fichiers qu'il contient
# Patroni doit lire les certificats etcd pour se connecter avec TLS
sudo setfacl -R -m u:postgres:rX /etc/etcd/certs
# -R : appliquer récursivement à tous les fichiers ; rX : lire + exécuter sur les répertoires (à traverser)

Étape 7 — Créer le fichier PEM combiné pour Patroni

Patroni's restapi.certfile attend un fichier unique contenant à la fois le certificat et la clé privée.

# Sur postgres-01, postgres-02, postgres-03
sudo sh -c 'cat /etc/etcd/certs/server.crt /etc/etcd/certs/server.key \N- > /etc/etcd/certs/server.pem'".
  > /etc/etcd/certs/server.pem"
# Concaténation du certificat et de la clé en un seul fichier
sudo chown postgres:postgres /etc/etcd/certs/server.pem
sudo chmod 600 /etc/etcd/certs/server.pem
# server.pem contient la clé privée - PostgreSQL nécessite 0600 et la propriété de postgres

# Vérifier que le fichier PEM est valide
sudo openssl x509 -in /etc/etcd/certs/server.pem -text -noout
# Attendu : détails du certificat, y compris les dates de validité et le sujet
# Si "unable to load certificate" : le fichier PEM est malformé - recréez-le.

# Vérifier les permissions finales sur tous les fichiers cert.
sudo ls -la /etc/etcd/certs/
# Résultats attendus (postgres-01 affiché - le numéro de nœud diffère sur 02/03) :
# drwx------+ 2 etcd etcd ca.crt etcd-node1.crt etcd-node1.key server.crt server.key server.pem
# -rw-r--r--+ 1 etcd etcd ca.crt
# -rw-r--r--+ 1 etcd etcd etcd-node1.crt
# -rw-------+ 1 etcd etcd etcd-node1.key
# -rw-r--r--+ 1 postgres postgres server.crt
# -rw-------+ 1 postgres postgres server.key ← doit être 0600, propriété de postgres
# -rw-------+ 1 postgres postgres server.pem ← doit être 0600, propriété de postgres
# PostgreSQL refusera de démarrer si server.key ou server.pem est lisible par le groupe ou le monde.

6. Configuration d'etcd

Étape 1 — Créer le répertoire de données etcd

Sur postgres-01, postgres-02, postgres-03
sudo mkdir -p /var/lib/etcd
sudo chown -R etcd:etcd /var/lib/etcd
# etcd y stocke ses données WAL et snapshot — doit être la propriété de l'utilisateur etcd

Étape 2 — Créez le fichier d'environnement etcd sur chaque nœud

etcd est configuré via des variables d'environnement chargées par le service systemd.

Seules les valeurs spécifiques aux nœuds diffèrent entre les nœuds.

# /etc/etcd/etcd.env - postgres-01 (192.168.0.203)

ETCD_NAME="postgresql-01"
# ETCD_NAME : identifiant unique de ce membre au sein du cluster

ETCD_DATA_DIR="/var/lib/etcd"
# ETCD_DATA_DIR : endroit où etcd stocke ses WAL et ses instantanés.

ETCD_INITIAL_CLUSTER="postgresql-01=https://192.168.0.203:2380,postgresql-02=https://192.168.0.204:2380,postgresql-03=https://192.168.0.205:2380"
# ETCD_INITIAL_CLUSTER : tous les membres au moment du démarrage - doit être identique sur les trois nœuds

ETCD_INITIAL_CLUSTER_STATE="new"
# new : il s'agit d'un nouveau démarrage de cluster
# Important : passer à "existing" après le démarrage de la grappe (voir section 9, étape 3).

ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
# ETCD_INITIAL_CLUSTER_TOKEN : empêche les nœuds de rejoindre accidentellement le mauvais cluster

ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.0.203:2380"
# ETCD_INITIAL_ADVERTISE_PEER_URLS : adresse que ce nœud annonce aux autres membres du système etcd pour le trafic entre pairs.

ETCD_LISTEN_PEER_URLS="https://0.0.0.0:2380"
# ETCD_LISTEN_PEER_URLS : adresse sur laquelle etcd écoute les connexions de pairs provenant d'autres membres etcd

ETCD_LISTEN_CLIENT_URLS="https://0.0.0.0:2379"
# ETCD_LISTEN_CLIENT_URLS : adresse sur laquelle etcd écoute les connexions des clients (Patroni se connecte ici)

ETCD_ADVERTISE_CLIENT_URLS="https://192.168.0.203:2379"
# ETCD_ADVERTISE_CLIENT_URLS : adresse que ce noeud annonce aux clients - doit être accessible depuis Patroni

# TLS pour les connexions clients (Patroni → etcd)
ETCD_CLIENT_CERT_AUTH="true"
# ETCD_CLIENT_CERT_AUTH : demande aux clients de présenter un certificat valide (TLS mutuel)
ETCD_TRUSTED_CA_FILE="/etc/etcd/certs/ca.crt"
# ETCD_TRUSTED_CA_FILE : Certificat de l'autorité de certification utilisé pour vérifier les certificats des clients
ETCD_CERT_FILE="/etc/etcd/certs/etcd-node1.crt"
# ETCD_CERT_FILE : certificat présenté aux clients se connectant à ce nœud
ETCD_KEY_FILE="/etc/etcd/certs/etcd-node1.key"
# ETCD_KEY_FILE : clé privée pour le certificat ci-dessus

# TLS pour les connexions entre pairs (nœud etcd ↔ nœud etcd)
ETCD_PEER_CLIENT_CERT_AUTH="true"
# ETCD_PEER_CLIENT_CERT_AUTH : demande aux nœuds homologues de présenter un certificat valide
ETCD_PEER_TRUSTED_CA_FILE="/etc/etcd/certs/ca.crt"
ETCD_PEER_CERT_FILE="/etc/etcd/certs/etcd-node1.crt"
ETCD_PEER_KEY_FILE="/etc/etcd/certs/etcd-node1.key"

Pour postgres-02 (192.168.0.204) : changer ETCD_NAME à postgresql-02, les deux adresses IP à 192.168.0.204, et les noms de fichiers de certificat/clé à etcd-node2.crt / etcd-node2.clé.

Pour postgres-03 (192.168.0.205) : changer ETCD_NAME à postgresql-03, les deux adresses IP à 192.168.0.205, et les noms de fichiers de certificat/clé à etcd-node3.crt / etcd-node3.clé.

Étape 3 — Créer le fichier de service systemd pour etcd

# Sur postgres-01, postgres-02, postgres-03
# Créez /etc/systemd/system/etcd.service avec le contenu suivant :
[Unit]
Description=Magasin clé-valeur etcd
Documentation=https://github.com/etcd-io/etcd
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
# Type=notify : systemd attend qu'etcd envoie un signal de préparation avant de le marquer comme démarré
WorkingDirectory=/var/lib/etcd
EnvironmentFile=/etc/etcd/etcd.env
# EnvironmentFile : charge toutes les variables ETCD_* du fichier créé à l'étape 2
ExecStart=/usr/local/bin/etcd
Restart=always
# Restart=always : systemd redémarre etcd s'il se termine pour une raison quelconque
RestartSec=10s
LimitNOFILE=40000
# LimitNOFILE : augmente la limite des descripteurs de fichiers ouverts — etcd ouvre de nombreux fichiers en cas de forte charge
User=etcd
Group=etcd

[Install]
WantedBy=multi-user.target

Étape 4 — Démarrer etcd sur les 3 nœuds

# Sur postgres-01, postgres-02, postgres-03
sudo systemctl daemon-reload
# daemon-reload : requis après la création ou la modification d'un fichier d'unité systemd

sudo systemctl enable etcd
# enable : démarre etcd automatiquement au démarrage (boot)

sudo systemctl start etcd
# start : démarre le service etcd maintenant

sudo systemctl status etcd
# Attendu : Active : active (running)
# Si "Active : failed" : vérifiez journalctl -xeu etcd.service pour plus de détails
# Causes courantes :
#   - certificat non trouvé : vérifiez que les chemins dans etcd.env correspondent aux fichiers dans /etc/etcd/certs/
#   - permission refusée sur la clé : exécutez "sudo chown etcd:etcd /etc/etcd/certs/*.key"
#   - port utilisé : exécutez "ss -tlnp | grep 237" pour trouver ce qui utilise les ports 2379/2380

Étape 5 — Vérifiez l'intégrité du cluster etcd

# Sur postgres-01
etcdctl endpoint health
# Attendu :
# 127.0.0.1:2379 est sain : proposition validée avec succès : durée = 2.3ms
# Ceci ne vérifie que le noeud local - la vérification complète du cluster est ci-dessous

# Vérification complète de l'état de santé de la grappe avec les informations d'identification TLS
sudo etcdctl \N- --endpoints=
  --endpoints=https://192.168.0.203:2379,https://192.168.0.204:2379,https://192.168.0.205:2379 \
  --cacert=/etc/etcd/certs/ca.crt \N- --cert=/etc/etcd/certs/ca.crt
  --cert=/etc/etcd/certs/etcd-node1.crt \N-key=/etc/etcd/certs/ca.crt
  --key=/etc/etcd/certs/etcd-node1.key \N--cert=/etc/etcd/certs/etcd-node1.crt
  santé du point d'extrémité
# --cacert : Certificat CA pour vérifier les certificats du serveur
# --cert / --key : certificat et clé du client pour TLS mutuel
# Attendu :
# https://192.168.0.203:2379 est sain : proposition engagée avec succès : durée = 2.3ms
# https://192.168.0.204:2379 est sain : proposition engagée avec succès : durée = 2.1ms
# https://192.168.0.205:2379 est en bonne santé : proposition validée avec succès : délai = 2.4ms
# Si un nœud est en mauvaise santé : vérifier journalctl -u etcd sur ce nœud
# Si tous les nœuds sont en mauvaise santé : vérifier le pare-feu sur le port 2380 entre les nœuds

# Vérifier l'élection du leader - un nœud doit être le leader
sudo etcdctl \N
  --endpoints=https://192.168.0.203:2379,https://192.168.0.204:2379,https://192.168.0.205:2379 \
  --cacert=/etc/etcd/certs/ca.crt \N- --cert=/etc/etcd/certs/ca.crt
  --cert=/etc/etcd/certs/etcd-node1.crt \N-key=/etc/etcd/certs/ca.crt
  --key=/etc/etcd/certs/etcd-node1.key \N--CE
  endpoint status --write-out=table
# Attendu : une table avec un nœud montrant IS LEADER = true
# Si pas de leader : le quorum n'est pas établi - vérifier que les trois nœuds fonctionnent.

7. Installation et configuration de Patroni

Étape 1 — Installer Patroni

# Sur postgres-01, postgres-02, postgres-03
sudo apt install -y patroni
# Sur Ubuntu 22.04+, le paquet apt inclut la bibliothèque cliente etcd v3
# Si le paquet de votre distribution ne l'inclut pas : pip install patroni[etcd3]
# [etcd3] sélectionne le backend de l'API etcd v3 — requis pour etcd 3.5+
# L'ancien drapeau [etcd] utilise l'API HTTP dépréciée v2

sudo mkdir -p /etc/patroni/
# Patroni lit sa configuration à partir d'un fichier YAML dans ce répertoire

Étape 2 — Créer patroni.yml sur chaque nœud

Seulement nom, restapi.adresse_de_connexion, postgresql.connect_address, et les chemins des certificats/clés etcd diffèrent entre les nœuds.

# /etc/patroni/config.yml - postgres-01 (192.168.0.203)

scope : postgresql-cluster
# scope : cluster name - doit être identique sur tous les nœuds Patroni
# Utilisé comme préfixe dans etcd pour séparer plusieurs clusters Patroni

espace de noms : /service/
# namespace : etcd key prefix - tout l'état du cluster est stocké sous /service/postgresql-cluster/

nom : postgresql-01
# name : nom unique pour ce nœud au sein du cluster

etcd3 :
  hosts : 192.168.0.203:2379,192.168.0.204:2379,192.168.0.205:2379
  # etcd3 : utiliser l'API etcd v3 (gRPC) - requis pour etcd 3.5+
  # L'ancienne clé "etcd :" utilise l'API HTTP v2 qui est obsolète
  protocole : https
  Protocole # : https - Patroni se connecte à etcd via TLS
  cacert : /etc/etcd/certs/ca.crt
  # cacert : Certificat CA pour vérifier les certificats du serveur etcd
  cert : /etc/etcd/certs/etcd-node1.crt
  # cert : certificat client présenté à etcd pour TLS mutuel
  mutuel : /etc/etcd/certs/etcd-node1.key
  # key : clé privée du certificat client

restapi :
  listen : 0.0.0.0:8008
  # listen : L'API REST de Patroni écoute sur toutes les interfaces sur le port 8008
  # HAProxy interroge cette API pour déterminer quel nœud est le nœud primaire actuel
  connect_address : 192.168.0.203:8008
  # connect_address : l'adresse que les autres nœuds utilisent pour atteindre l'API REST de ce nœud - doit être l'IP réelle
  certfile : /etc/etcd/certs/server.pem
  # certfile : fichier PEM combiné cert+key - active TLS sur l'API REST
  # HAProxy utilise check-ssl lors de l'interrogation de /primary - l'API doit servir un certificat

bootstrap :
  dcs :
    ttl : 30
    # ttl : durée du bail du leader en secondes
    # Si le primaire n'est pas renouvelé dans les secondes ttl, il est considéré comme défaillant.
    # et un standby est promu. Plus bas = basculement plus rapide ; plus haut = plus de tolérance pour les réseaux lents.
    loop_wait : 10
    # loop_wait : fréquence à laquelle Patroni vérifie la santé du cluster (secondes)
    retry_timeout : 10
    # retry_timeout : combien de temps Patroni retente une opération etcd ou PostgreSQL qui a échoué avant d'abandonner.
    maximum_lag_on_failover : 1048576
    # maximum_lag_on_failover : les serveurs en attente ayant plus de 1 Mo de retard sur le serveur primaire ne seront pas promus.
    # Empêche la promotion d'un standby très défectueux qui causerait une perte de données importante.
    postgresql :
      parameters :
        ssl : 'on'
        # ssl : active TLS sur les connexions PostgreSQL
        ssl_cert_file : /etc/etcd/certs/server.crt
        ssl_key_file : /etc/etcd/certs/server.key
      pg_hba :
        # pg_hba : Patroni écrit ce pg_hba.conf au bootstrap
        # hostssl : TLS est requis - les connexions simples sont rejetées
        - hostssl replication replicator 127.0.0.1/32 md5
        - hostssl replication replicator 192.168.0.203/32 md5
        - hostssl replication replicator 192.168.0.204/32 md5
        - hostssl replication replicator 192.168.0.205/32 md5
        # Connexions de réplication depuis les trois nœuds PostgreSQL
        - hostssl all all 127.0.0.1/32 md5
        - hostssl all all 0.0.0.0/0 md5
        # Connexions d'application - TLS requis, authentification par mot de passe

  initdb :
    - encodage : UTF8
    - données de contrôle (data-checksums)
    # data-checksums : active les sommes de contrôle au niveau de la page - requis pour pg_rewind
    # détecte les blocs corrompus ; léger surcoût d'écriture (typiquement inférieur à 2%)

postgresql :
  listen : 0.0.0.0:5432
  connect_address : 192.168.0.203:5432
  # connect_address : l'adresse IP réelle de ce nœud - utilisée par les serveurs pour se connecter à la réplication
  data_dir : /var/lib/postgresql/data
  # data_dir : Répertoire de données PostgreSQL - Patroni gère entièrement ce répertoire
  bin_dir : /usr/lib/postgresql/18/bin
  # bin_dir : répertoire contenant pg_ctl, pg_basebackup, pg_rewind, initdb
  # Ajustez le numéro de version pour qu'il corresponde à votre installation PostgreSQL
  authentification :
    superuser :
      nom d'utilisateur : postgres
      mot de passe : strongpassword
      # Patroni utilise ces identifiants pour les connexions de gestion interne
      # Modifier avant l'utilisation en production
    réplication :
      nom d'utilisateur : replicator
      mot de passe : replpassword
      # Patroni crée ce rôle automatiquement pendant le bootstrap
      # Modifier avant la mise en production
  paramètres :
    max_connections : 100
    shared_buffers : 256MB
    # tampons_partagés : Cache mémoire principal de PostgreSQL - typiquement 25% de RAM totale
    # 256MB est une valeur conservatrice par défaut ; augmenter en fonction de la mémoire disponible

tags :
  nofailover : false
  # nofailover : set to true on a node you never want automatically promoted (e.g. a DR standby)
  noloadbalance : false
  # noloadbalance : valeur vraie pour exclure ce nœud de l'acheminement des répliques en lecture
  clonefrom : false
  nosync : false

ctl :
  insecure : true
  # insecure : sauter la vérification du certificat TLS lorsque patronictl appelle l'API REST de Patroni
  # Requis pour les commandes de basculement et de basculement - sans lui, patronictl échoue avec une erreur SSL
  # Les commandes en lecture seule (patronictl list) fonctionnent sans cela car elles utilisent etcd, et non l'API REST.
  # N'ajoutez PAS cacert ou certfile ici - un certfile serveur provoque un mauvais handshake TLS

Pour postgres-02 (192.168.0.204) : changer nom à postgresql-02, les deux adresse_connexion valeurs à 192.168.0.204, et clé/certifikat etcd à etcd-node2.crt / etcd-node2.clé.

Pour postgres-03 (192.168.0.205) : changer nom à postgresql-03, les deux adresse_connexion valeurs à 192.168.0.205, et clé/certifikat etcd à etcd-node3.crt / etcd-node3.clé.


8. Démarrage du Cluster

Étape 1 — Démarrer Patroni sur postgres-01 en premier

Le primaire visé doit commencer en premier.

Si un standby démarre avant qu'un primaire n'existe dans etcd, il attend — cela ne provoque pas d'erreur. Démarrer d'abord le primaire évite une course d'élection de leader à trois.

# Sur postgres-01
sudo systemctl enable patroni
# activer : démarrer Patroni automatiquement au démarrage
sudo systemctl restart patroni
# Patroni initialise le répertoire de données PostgreSQL (initdb), démarre PostgreSQL,
# acquiert le bail de leader dans etcd, et se configure en tant que primaire

journalctl -u patroni -f
# -f : suivre — afficher les nouvelles lignes de log au fur et à mesure de leur apparition
# Rechercher : " promu soi-même au rôle de leader " — confirme que postgres-01 est le primaire
# Appuyer sur Ctrl+C pour arrêter le suivi une fois la confirmation obtenue
# Si " impossible de se connecter à etcd " : vérifier que etcd est en cours d'exécution et que les certificats TLS sont corrects

Étape 2 — Vérifier que postgres-01 est le leader

# Sur postgres-01
# patronictl lit ca.crt depuis /etc/etcd/certs/ — ce répertoire est en mode 700.
# Exécutez avec sudo, sinon cela échouera avec une erreur de chargement de certificat SSL.
sudo patronictl -c /etc/patroni/config.yml list
# Attendu :
# + Cluster : postgresql-cluster +----+-----------+
# | Membre | Hôte | Rôle | État | TL | Lag in MB |
# +---------------+-------------------+--------+---------+----+-----------+
# | postgresql-01 | 192.168.0.203 | Leader | running | 1 | |
# +---------------+-------------------+--------+---------+----+-----------+
# Si l'état est "start failed" : vérifiez journalctl -u patroni pour l'erreur PostgreSQL
# Si aucun Leader après 30 secondes : vérification de l'état d'etcd (section 7 étape 5)

Étape 3 — Démarrer Patroni sur postgres-02 et postgres-03

# Sur postgres-02 et postgres-03
sudo systemctl enable patroni && sudo systemctl restart patroni
# Patroni détecte le primaire existant dans etcd, exécute pg_basebackup à partir de postgres-01,
# et démarre PostgreSQL en tant que standby en streaming

# Vérifier les trois noeuds
sudo patronictl -c /etc/patroni/config.yml list
# Attendu :
# + Cluster : postgresql-cluster -----+----+-----------+
# | Membre | Hôte | Rôle | Etat | TL | Lag en MB |
# +---------------+----------------+---------+---------+----+-----------+
# | postgresql-01 | 192.168.0.203 | Leader | running | 1 | | |
# | postgresql-02 | 192.168.0.204 | Réplique | en cours d'exécution | 1 | 0 |
# | postgresql-03 | 192.168.0.205 | Réplique | en cours d'exécution | 1 | 0 |
# +---------------+----------------+---------+---------+----+-----------+
# Lag in MB = 0 : standbys are fully caught up with the primary
# Si un nœud indique "stopped" : vérifier journalctl -u patroni sur ce nœud
# Si pg_basebackup a échoué : vérifier que le port 5432 est ouvert entre les nœuds

Étape 4 — Changer initial-cluster-state en existing sur tous les nœuds

Après un amorçage réussi, état-initial-du-cluster doit être changé de nouveau à existant.

Ceci empêche un nœud d'amorcer accidentellement un nouveau cluster s'il est redémarré ultérieurement en isolation.

# Sur postgres-01, postgres-02, postgres-03
# Modifiez /etc/etcd/etcd.env et changez :
#   ETCD_INITIAL_CLUSTER_STATE="new"
# en :
#   ETCD_INITIAL_CLUSTER_STATE="existing"

sudo systemctl restart etcd
# Redémarrez pour appliquer le changement
# Attendu : etcd rejoint le cluster existant proprement
# Vérifiez : exécutez la commande de santé des points d'extrémité de la section 7, étape 5

9. Configuration HAProxy

HAProxy achemine toutes les connexions d'application vers le primaire actuel en interrogeant l'API REST de Patroni.

Seuls les appels principaux renvoient HTTP 200 /principal — Les sauvegardes renvoient le HTTP 503.

Trois nœuds HAProxy assurent la redondance ; keepalived (section 11) déplace le VIP entre eux.

Étape 1 — Installer HAProxy

# Sur haproxy-01, haproxy-02, haproxy-03
sudo apt install -y haproxy

haproxy -v
# Attendu : HAProxy version 2.x.x
# Si une version plus ancienne : ajoutez le dépôt apt HAProxy pour la dernière version

Étape 2 — Configurer HAProxy

La configuration est identique sur les trois nœuds HAProxy.

# /etc/haproxy/haproxy.cfg

frontend postgres_frontend
    bind *:5432
    mode tcp
    # mode tcp : HAProxy transmet du TCP brut - PostgreSQL n'est pas un protocole HTTP
    timeout client 30s
    Le client timeout # n'a sa place que dans le frontend - HAProxy l'avertira et l'ignorera dans un backend.
    default_backend postgres_backend

backend postgres_backend
    mode tcp
    option tcp-check
    option httpchk GET /primary
    # httpchk : HAProxy interroge ce point d'extrémité sur l'API REST Patroni de chaque nœud.
    # Seul le nœud principal actuel renvoie HTTP 200 sur /primary ; les nœuds secondaires renvoient 503
    # C'est ainsi que HAProxy sait vers quel nœud acheminer le trafic d'écriture
    http-check expect status 200
    timeout connect 5s
    # timeout connect et timeout server appartiennent au backend uniquement
    timeout server 30s
    server postgresql-01 192.168.0.203:5432 port 8008 check check-ssl verify none
    server postgresql-02 192.168.0.204:5432 port 8008 check check-ssl verify none
    server postgresql-03 192.168.0.205:5432 port 8008 check check-ssl verify none
    # port 8008 : HAProxy vérifie le port de l'API REST de Patroni, pas celui de PostgreSQL.
    # check-ssl : utiliser TLS lors de l'interrogation de l'API REST (l'API REST de Patroni a TLS activé)
    # verify none : ne pas vérifier le certificat CN - acceptable dans un cluster privé
    # où les nœuds sont identifiés par IP

Étape 3 — Valider la configuration et recharger HAProxy

Toujours valider la configuration avant de la recharger.

HAProxy refusera de recharger si la configuration contient des erreurs.

# Sur haproxy-01, haproxy-02, haproxy-03
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# -c : vérifier uniquement la configuration, ne pas démarrer
# -f : chemin vers le fichier de configuration
# Attendu : Le fichier de configuration est valide
# Si vous voyez des avertissements « timeout client/server in the wrong section »,
# vérifiez que « timeout client » est dans le frontend et « timeout connect/server » sont dans le backend
# Sur haproxy-01, haproxy-02, haproxy-03
sudo systemctl reload haproxy
# reload : applique la nouvelle configuration sans interrompre les connexions existantes
# Attendu : aucune sortie sur le terminal ; confirmer le succès dans les journaux ci-dessous
sudo journalctl -u haproxy --since "1 minute ago"
# Lignes attendues (les horodatages différeront) :
#   systemd[1]: Rechargement de haproxy.service - HAProxy Load Balancer...
#   systemd[1]: Rechargé haproxy.service - HAProxy Load Balancer.
# Si vous voyez "Échec" : vérifiez d'abord la sortie de sudo haproxy -c
sudo tail -f /var/log/syslog | grep haproxy
# Surveiller les logs HAProxy pour confirmer qu'il vérifie les nœuds PostgreSQL
# Attendu : lignes répétées de contrôle de santé ; un nœud doit afficher "UP" (le primaire)
# Si tous les nœuds affichent "DOWN" : HAProxy ne peut pas atteindre le port 8008 — vérifier le pare-feu

10. Configuration de Keepalived

keepalived gère la VIP à l'aide de VRRP.

Un nœud HAProxy détient la VIP (le MAÎTRE).

Si la vérification de l'état du MASTER échoue ou si le nœud tombe en panne, keepalived sur un nœud BACKUP revendique la VIP.

Les applications se connectent toujours au VIP — les défaillances des nœuds HAProxy sont transparentes.

Étape 1 — Installer keepalived

# Sur haproxy-01, haproxy-02, haproxy-03
sudo apt update && sudo apt install -y keepalived

Étape 2 — Créer le script de vérification de l'état d'HAProxy

keepalived exécute ce script toutes les 2 secondes.

Un code de sortie non nul indique à keepalived que ce nœud ne doit pas détenir la VIP.

# Sur haproxy-01, haproxy-02, haproxy-03
# Créez le fichier /etc/keepalived/check_haproxy.sh avec le contenu suivant :
#!/bin/bash

PORT=5432

if ! pidof haproxy > /dev/null; then
    # pidof haproxy : vérifie si le processus HAProxy est en cours d'exécution
    echo "HAProxy n'est pas en cours d'exécution"
    exit 1
    # exit 1 : un code non nul indique à keepalived que ce nœud n'est pas sain — la VIP est déplacée vers un nœud BACKUP
fi

if ! ss -ltn | grep -q ":${PORT}"; then
    # ss -ltn : liste les sockets TCP en écoute ; grep vérifie si le port 5432 est lié
    echo "HAProxy n'écoute pas sur le port ${PORT}"
    exit 2
fi

exit 0
# exit 0 : indique à keepalived que ce nœud est sain et doit conserver (ou recevoir) la VIP
# Sur haproxy-01, haproxy-02, haproxy-03
sudo useradd -r -s /bin/false keepalived_script
# -r : compte système ; -s /bin/false : pas de connexion interactive
# keepalived exécute les scripts de vérification de l'état en tant que cet utilisateur lorsque enable_script_security est activé

sudo chmod +x /etc/keepalived/check_haproxy.sh
sudo chown keepalived_script:keepalived_script /etc/keepalived/check_haproxy.sh
sudo chmod 700 /etc/keepalived/check_haproxy.sh
# 700 : propriétaire exécutable uniquement — keepalived exige que le script ne soit pas modifiable par d'autres utilisateurs
# lorsque enable_script_security est activé (configuré ci-dessous dans keepalived.conf)

Étape 3 — Configurer keepalived

Chaque nœud est différent état et priorité.

Le MAÎTRE (priorité 100) détient initialement le VIP.

S'il échoue, la SAUVEGARDE avec la priorité la plus élevée suivante (90) prend le témoin VIP.

# /etc/keepalived/keepalived.conf - haproxy-01 (MASTER)

global_defs {
    enable_script_security
    # enable_script_security : empêche keepalived d'exécuter des scripts appartenant à root
    # ou accessibles en écriture par n'importe qui - empêche l'escalade des privilèges par le biais des scripts de contrôle de santé
    script_user keepalived_script
    # script_user : l'utilisateur du système d'exploitation que keepalived utilise pour exécuter les scripts de contrôle de santé
}

vrrp_script check_haproxy {
    script "/etc/keepalived/check_haproxy.sh"
    intervalle 2
    # interval : exécuter le contrôle de santé toutes les 2 secondes
    fall 3
    # fall : marque ce noeud comme défaillant après 3 vérifications consécutives (6 secondes au total)
    rise 2
    # rise : marque ce nœud comme rétabli après 2 vérifications consécutives (4 secondes)
}

vrrp_instance VI_1 {
    state MASTER
    # MASTER : ce nœud détient le VIP initialement au démarrage
    interface enp0s3
    # interface : l'interface réseau à laquelle le VIP est attribué
    # Exécutez "ip link show" pour trouver le nom de votre interface - il peut s'agir de ens3, enp0s3, eth0, etc.
    # Dans ce laboratoire, l'interface est enp0s3 (valeur par défaut de VirtualBox).
    virtual_router_id 51
    # virtual_router_id : identifie ce groupe VRRP - doit être identique sur les trois nœuds
    priority 100
    # priority : le nœud ayant la priorité la plus élevée remporte l'élection VIP
    # haproxy-01 gagne par défaut (100 > 90 > 80)
    advert_int 1
    # advert_int : envoi d'annonces VRRP toutes les 1 seconde
    authentification {
        auth_type PASS
        auth_pass changeme123
        # auth_pass : mot de passe partagé entre tous les nœuds keepalived - changer avant l'utilisation en production
    }
    virtual_ipaddress {
        192.168.0.210
        # Le VIP - les applications se connectent à cette adresse sur le port 5432
    }
    track_script {
        check_haproxy
        # track_script : lier cette instance VRRP au contrôle de santé de HAProxy
        # Si le script sort avec une valeur non nulle, la priorité effective de ce nœud tombe
        # en dessous des BACKUPs et des mouvements VIP
    }
}
# /etc/keepalived/keepalived.conf — haproxy-02 (BACKUP, seconde priorité)
# Identique à haproxy-01 sauf :
#   state BACKUP
#   priority 90
# /etc/keepalived/keepalived.conf — haproxy-03 (BACKUP, priorité la plus basse)
# Identique à haproxy-01 sauf :
#   state BACKUP
#   priority 80

Étape 4 — Démarrer keepalived

# Sur haproxy-01, haproxy-02, haproxy-03
sudo systemctl enable --now keepalived
sudo journalctl -u keepalived -f
# Observer les journaux de keepalived — on s'attend à voir des transitions d'état VRRP
# haproxy-01 devrait afficher : "(VI_1) Entering BACKUP STATE" puis "(VI_1) Entering MASTER STATE"
# (entre brièvement en mode BACKUP au démarrage, remporte l'élection après environ 4 secondes en raison de la priorité 100)
# haproxy-02 et haproxy-03 devraient afficher : "(VI_1) Entering BACKUP STATE"
# Appuyez sur Ctrl+C pour arrêter de suivre
#
# REMARQUE : "Truncating auth_pass to 8 characters" est un avertissement, pas une erreur.
# keepalived n'utilise que les 8 premiers caractères de auth_pass, quelle que soit la valeur que vous définissez.
# C'est bon tant que tous les nœuds utilisent la même chaîne de mots de passe — ils seront tous tronqués de manière identique.
# Vérifier que la VIP est active sur haproxy-01
ping -c 3 192.168.0.210
# Attendu : 3 paquets transmis, 3 reçus
# Si 0 reçu : La VIP n'est attribuée à aucun nœud — vérifier les journaux de keepalived sur les trois nœuds HAProxy

11. Définir le mot de passe du superutilisateur PostgreSQL

Patroni gère son propre pg_hba.conf à /var/lib/postgresql/data/pg_hba.conf — pas l'emplacement du package par défaut à /etc/postgresql/18/main/pg_hba.conf.

Le fichier de Patroni contient uniquement hôte ssl entrées et aucune entrée de socket Unix locale.

Cela signifie sudo -u postgres psql échouera jusqu'à ce qu'une entrée locale soit ajoutée.

# Sur postgres-01 — vérifiez pg_hba.conf de Patroni
sudo cat /var/lib/postgresql/data/pg_hba.conf
# Attendu : " Ne modifiez pas ce fichier manuellement ! Il sera écrasé par Patroni ! "
# suivi d'entrées hostssl uniquement — aucune entrée de socket local

Ajoutez une entrée locale temporaire pour permettre à l'utilisateur PostgreSQL de se connecter via socket :

# Sur postgres-01
echo "local all postgres peer" | sudo tee -a /var/lib/postgresql/data/pg_hba.conf

Recharger la configuration à l'aide de pg_ctl — pg_reload_conf() ne peut pas être utilisé car il n'y a pas encore de connexion socket :

# Sur postgres-01
sudo -u postgres /usr/lib/postgresql/18/bin/pg_ctl reload -D /var/lib/postgresql/data
# Attendu : serveur signalé

Définir le mot de passe :

# Sur postgres-01
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'motdepassefort';"
# Attendu : ALTER ROLE
# IMPORTANT : ce mot de passe doit correspondre à postgresql.authentication.superuser.password dans config.yml
# S'ils diffèrent, Patroni ne pourra pas se connecter à son instance PostgreSQL locale et signalera
# LSN inconnu — le nœud semblera bloqué après un basculement ou un redémarrage
# Le mot de passe est maintenant stocké dans la base de données — il survivra à l'écrasement de pg_hba.conf par Patroni

Patroni écrasera pg_hba.conf lors de son prochain cycle et de supprimer l'entrée locale — cela est attendu et normal.

Le mot de passe persiste quoi qu'il arrive dans le catalogue de la base de données.


12. Vérifier la pile complète

Exécutez ces vérifications dans l'ordre, une fois toutes les étapes de configuration terminées.

# Check 1 : etcd cluster is healthy (run on postgres-01)
sudo etcdctl \N
  --endpoints=https://192.168.0.203:2379,https://192.168.0.204:2379,https://192.168.0.205:2379 \
  --cacert=/etc/etcd/certs/ca.crt \N- --cert=/etc/etcd/etcd/etcd/etcd/ca.crt
  --cert=/etc/etcd/certs/etcd-node1.crt \N-key=/etc/etcd/certs/ca.crt
  --key=/etc/etcd/certs/etcd-node1.key \N--cert=/etc/etcd/certs/etcd-node1.crt
  santé du point d'extrémité
# Attendu : les trois nœuds signalent "est sain".

# Vérification 2 : Le cluster Patroni a un leader et deux répliques (exécuté sur n'importe quel noeud PostgreSQL)
sudo patronictl -c /etc/patroni/config.yml list
# Attendu : un leader, deux répliques, tous State = running, Lag = 0

# Vérification 3 : VIP répond
ping -c 3 192.168.0.210
# Attendu : 3 paquets reçus
# Si 0 reçu : VIP non assigné - vérifier keepalived sur tous les nœuds HAProxy

# Vérification 4 : PostgreSQL est accessible via le VIP
psql -h 192.168.0.210 -U postgres -c "SELECT inet_server_addr(), pg_is_in_recovery() ;"
# Attendu : inet_server_addr = 192.168.0.203 (le primaire), pg_is_in_recovery = f
# pg_is_in_recovery = f : confirme qu'il s'agit bien de l'ordinateur primaire (la méthode standard renvoie t)
# Si la connexion est refusée : HAProxy n'est pas en train de router - vérifier l'état de haproxy et le point de terminaison /primary

# Vérification 5 : la réplication est en cours
psql -h 192.168.0.210 -U postgres -c "SELECT client_addr, replay_lag FROM pg_stat_replication ;"
# Attendu : deux lignes (une par standby), replay_lag = 00:00:00 ou NULL (rattrapé)
# Si pas de lignes : les standbys ne sont pas en streaming - vérifier les logs Patroni sur les noeuds standby

# Vérification 6 : insérer des données sur le nœud primaire, les lire sur les deux nœuds en attente
psql -h 192.168.0.210 -U postgres -c "CREATE TABLE test (id serial, val text) ;"
psql -h 192.168.0.210 -U postgres -c "INSERT INTO test (val) VALUES ('replication works') ;"
psql -h 192.168.0.204 -U postgres -c "SELECT * FROM test ;"
# Attendu : une ligne avec val = 'replication works'
# Se connecte directement à postgres-02 (un standby) pour confirmer les données répliquées
# Si "relation does not exist" : la réplication ne fonctionne pas - vérifier pg_stat_replication
psql -h 192.168.0.205 -U postgres -c "SELECT * FROM test ;"
# Attendu : même ligne - confirme que postgres-03 est également répliqué
psql -h 192.168.0.210 -U postgres -c "DROP TABLE test ;"
# Nettoyer la table test

13. Basculement (Planifié)

Une bascule déplace le rôle principal vers une réplique de secours spécifique sans aucune perte de données.

Patroni attend que le candidat de secours soit complètement synchronisé avant de promouvoir.

Prérequis — ctl : section dans config.yml : Le ctl : la section doit être présente avec non sécurisé : vrai avant d'exécuter le basculement.

Sans cela, patronictl ne peut pas s'authentifier auprès de l'API REST de Patroni et la bascule échouera avec une erreur SSL.

Commandes en lecture seule comme patronictl lister ils n'exigent pas l'API REST (ils utilisent etcd) — la section manquante n'est donc pas évidente avant de tenter une opération d'écriture.

# Vérifiez que la section ctl est présente sur tous les nœuds PostgreSQL avant de tenter le basculement
sudo tail -5 /etc/patroni/config.yml
# Attendu :
#   ctl:
#     insecure: true
# En cas d'absence : ajoutez-la et rechargez patroni (sudo systemctl reload patroni)
# REMARQUE : n'ajoutez pas cacert ou certfile à la section ctl — seulement insecure: true
# L'ajout d'un fichier de certificat serveur provoque une mauvaise négociation TLS et le basculement échoue toujours
# Sur n'importe quel nœud PostgreSQL
sudo patronictl -c /etc/patroni/config.yml switchover postgresql-cluster \
  --leader postgresql-01 \
  # --leader : le primaire actuel rétrogradé
  # REMARQUE : les anciennes versions de Patroni utilisaient --master — les versions plus récentes utilisent --leader
  --candidate postgresql-02
  # --candidate : le standby promu
# patronictl affichera la topologie actuelle et demandera une confirmation — appuyez sur Entrée pour " maintenant ", puis sur y

# Patroni effectue ces étapes automatiquement :
# 1. Met en pause les écritures sur le primaire (point de contrôle)
# 2. Attend que le candidat confirme qu'il a appliqué toutes les WAL
# 3. Rétrograde le primaire actuel en standby
# 4. Proscrit le candidat en primaire
# 5. Reconfigure l'ancien primaire en standby en utilisant pg_rewind
sudo patronictl -c /etc/patroni/config.yml list
# Attendu : postgresql-02 affiche maintenant Rôle = Leader sur une nouvelle timeline ; postgresql-01 affiche Rôle = Réplique
# postgresql-01 peut brièvement afficher "stopped" — c'est pg_rewind en cours d'exécution ; attendez 10 secondes et vérifiez à nouveau
# Si postgresql-01 reste "stopped" : pg_rewind a échoué
#   Correction : sudo patronictl -c /etc/patroni/config.yml reinit postgresql-cluster postgresql-01

14. Basculement (non planifié)

Lorsque le primaire échoue, Patroni le détecte automatiquement :

  1. Le primaire ne parvient pas à renouveler son bail de leader dans etcd dans les ttl secondes (30 par défaut)
  2. Patroni sur les nœuds restants organise une élection dans etcd
  3. Le veille à moins de décalage qui se trouve à l'intérieur latence_maximale_en_cas_de_basculement est élu et promu
  4. Les autres solutions de secours se reconnectent au nouveau primaire à l'aide de pg_rewind

Simuler une panne primaire

Arrêtez Patroni sur le responsable actuel pour simuler un crash.

Patroni gère PostgreSQL — arrêter Patroni arrête également PostgreSQL sur ce nœud.

# Sur postgres-01 (le primaire actuel)
sudo systemctl stop patroni
# Ceci simule un crash du primaire — PostgreSQL s'arrête et le bail du leader expire

Sur postgres-02 ou postgres-03, observez le basculement automatique :

# Sur postgres-02 ou postgres-03
watch -n2 "sudo patronictl -c /etc/patroni/config.yml list"
# Séquence attendue sur ~30 secondes (ttl) :
# 1. postgresql-01 disparaît ou affiche "stopped"
# 2. Un des nœuds restants affiche Leader sur une nouvelle timeline
# 3. L'autre nœud restant affiche Replica streaming
# Appuyez sur Ctrl+C lorsque le nouveau leader est confirmé

Ramener postgres-01 :

# Sur postgres-01
sudo systemctl start patroni

Vérifiez le cluster — postgres-01 devrait se rattacher en tant que réplique :

sudo patronictl -c /etc/patroni/config.yml list
# Attendu : postgres-01 affiche Replica streaming, Lag = 0
# Si postgres-01 affiche " start failed " : vérifiez sudo journalctl -u patroni -n 30 --no-pager

Rebasculer sur postgres-01 comme primaire quand vous serez prêt :

sudo patronictl -c /etc/patroni/config.yml switchover postgresql-cluster \
  --leader  \
  --candidate postgresql-01

Basculement manuel — à n'utiliser que lorsque le basculement automatique n'a pas été déclenché

sudo patronictl -c /etc/patroni/config.yml failover postgresql-cluster \
  --leader postgresql-01 \
  --candidate postgresql-02 \
  --force
  # --force : ignorer l'invite de confirmation
  # À n'utiliser que lorsque le primaire est confirmé comme étant hors service
  # Sans --force, patronictl vous demande confirmation avant de continuer

15. Opérations quotidiennes

Vérifier l'état du cluster

sudo patronictl -c /etc/patroni/config.yml list
# Affiche tous les membres, les rôles (Leader/Replica), l'état (running/stopped/start failed),
# la timeline et le décalage de réplication en Mo
# Exécutez ceci en premier chaque fois que vous diagnostiquez un problème de cluster

Mettre en pause et reprendre le basculement automatique

# Pause — Patroni ne promouvra aucun standby pendant la pause
# À utiliser lors de la maintenance planifiée pour éviter un basculement accidentel
sudo patronictl -c /etc/patroni/config.yml pause postgresql-cluster

# Reprendre — restaurer le basculement automatique
sudo patronictl -c /etc/patroni/config.yml resume postgresql-cluster

Redémarrer PostgreSQL sur un nœud

# Redémarrez toujours PostgreSQL via patronictl — jamais directement via systemctl
# L'exécution de "systemctl restart postgresql" contourne Patroni et provoque un état incohérent
sudo patronictl -c /etc/patroni/config.yml restart postgresql-cluster postgresql-02

Recharger la configuration

# Appliquer les changements de postgresql.conf sur tous les nœuds sans redémarrage
sudo patronictl -c /etc/patroni/config.yml reload postgresql-cluster

Modifier la configuration du cluster DCS

# Modifier les paramètres stockés dans etcd (ttl, maximum_lag_on_failover, synchronous_mode, etc.)
sudo patronictl -c /etc/patroni/config.yml edit-config postgresql-cluster
# Ouvre la configuration actuelle dans votre $EDITOR
# Les modifications prennent effet immédiatement après la sauvegarde — aucun redémarrage nécessaire

Réinitialiser un standby défaillant

# Si pg_rewind échoue après un basculement, nettoyez et reconstruisez la réplique à partir du maître
sudo patronictl -c /etc/patroni/config.yml reinit postgresql-cluster postgresql-03
# Nettoie data_dir sur postgresql-03 et exécute pg_basebackup à partir du maître actuel
# Nettoie la réplique et la reconstruit à partir du maître actuel

16. Mode Synchrone (Perte de données nulle)

sudo patronictl -c /etc/patroni/config.yml edit-config postgresql-cluster

Ajouter ou mettre à jour :

synchronous_mode: true
# true : les commits sur le primaire attendent qu'au moins un standby confirme avant de retourner
# Patroni définit synchronous_standby_names automatiquement — aucune configuration manuelle n'est nécessaire

synchronous_mode_strict: false
# false (par défaut) : si aucun standby synchrone n'est disponible, le primaire continue d'écrire
# true : si aucun standby synchrone n'est disponible, le primaire arrête totalement d'accepter les écritures
#       utilisez true uniquement lorsque la perte de données nulle est obligatoire et que la disponibilité peut être sacrifiée

17. Surveillance

API REST Patroni

# Vérifier le code d'état HTTP uniquement — c'est ce que HAProxy vérifie en interne
curl -k -o /dev/null -w "%{http_code}\n" https://192.168.0.203:8008/primary
# Attendu sur le primaire : 200
# Attendu sur une réplique : 503
# -k : ignorer la vérification du certificat ; -o /dev/null : ignorer le corps ; -w : afficher uniquement le code d'état

curl -k -o /dev/null -w "%{http_code}\n" https://192.168.0.203:8008/replica
# Attendu sur une réplique : 200
# Attendu sur le primaire : 503

# Vérifier la réponse JSON complète (utile pour le débogage)
curl -k https://192.168.0.203:8008/primary
# La réponse inclut : rôle, version du serveur, chronologie, état de réplication de chaque réplique

curl -k https://192.168.0.203:8008/cluster | python3 -m json.tool
# État complet du cluster au format JSON formaté
# Attendu : tous les membres listés avec leurs rôles, leur état et leur décalage

curl -k https://192.168.0.203:8008/health
# Attendu : HTTP 200 si le nœud fonctionne normalement

Décalage de réplication

Se connecter directement au nœud principal — pg_stat_replication n'a que des lignes sur le primaire, pas sur les répliques.

HAProxy achemine également le port 5432 du VIP vers le primaire, donc l'un ou l'autre fonctionne.

# Option 1 — direct vers le primaire
psql -h 192.168.0.203 -U postgres -c "SELECT client_addr, replay_lag, sync_state FROM pg_stat_replication;"

# Option 2 — via VIP (HAProxy redirige toutes les connexions sur le port 5432 vers le primaire)
psql -h 192.168.0.210 -p 5432 -U postgres -c "SELECT client_addr, replay_lag, sync_state FROM pg_stat_replication;"

Résultat attendu :

  client_addr | replay_lag | sync_state
------------+------------+------------
192.168.0.204 |            | async
192.168.0.205 |            | async
(2 lignes)
  • Une ligne par veille connectée
  • replay_lag = NUL (vide) : la veille est complètement à jour - normale au repos
  • latence_rejeu En croissance : le standby est à la traîne — vérifiez le réseau et les journaux du standby Patroni
  • 0 lignes : aucun standby en cours - vérifiez patronictl lister pour confirmer les états des répliques ; si les répliques s'affichent comme en cours d'exécution, vérifiez primary_conninfo en postgresql.auto.conf en veille

18. Réinitialisation complète

Utilisez cette procédure pour reconstruire entièrement le cluster à partir de zéro — par exemple, après une défaillance du laboratoire, une mauvaise configuration irrécupérable ou pour réexécuter le laboratoire à partir de la section 9. Les certificats TLS sont conservés sur tous les nœuds.

Seul l'état du cluster etcd et les données PostgreSQL sont effacés.

Exécutez toutes les commandes de cette section sur postgres-01, postgres-02, and postgres-03 sauf indication contraire.


Étape 1 — Arrêtez Patroni et etcd sur tous les nœuds PostgreSQL

Patroni doit s'arrêter avant etcd afin qu'il puisse libérer proprement son verrou de leader.

Si etcd est arrêté en premier, Patroni perd sa connexion DCS et peut se bloquer.

Sur postgres-01 :

# Sur postgres-01
sudo systemctl stop patroni
# Arrête PostgreSQL gracieusement via Patroni — n'utilisez pas systemctl stop postgresql directement

sudo systemctl stop etcd
# Arrête le membre etcd sur ce nœud

Répétez sur postgres-02 et postgres-03 avant de continuer.


Étape 2 — Effacer les répertoires de données etcd et PostgreSQL

# Sur postgres-01, postgres-02, postgres-03
sudo rm -rf /var/lib/etcd/
# Supprime toutes les données WAL et snapshot d'etcd — le cluster etcd redémarrera à partir de zéro

sudo rm -rf /var/lib/postgresql/data/
# Supprime tous les fichiers de données PostgreSQL — Patroni réinitialisera via pg_basebackup sur les répliques

Étape 3 — Recréer les répertoires avec la propriété correcte

rm -rf supprime le répertoire lui-même, pas seulement son contenu.

Les répertoires doivent être recréés avant que etcd et Patroni ne puissent y écrire.

# Sur postgres-01, postgres-02, postgres-03
sudo mkdir -p /var/lib/etcd/
sudo chown etcd:etcd /var/lib/etcd/
# etcd s'exécute en tant qu'utilisateur etcd — il doit être propriétaire de son répertoire de données

sudo mkdir -p /var/lib/postgresql/data
sudo chown postgres:postgres /var/lib/postgresql/data
# Patroni s'exécute en tant qu'utilisateur postgres — il doit être propriétaire du répertoire de données PostgreSQL

Étape 4 — Restaurer les autorisations ACL sur les certificats etcd

rm -rf sur le répertoire des données n'affecte pas /etc/etcd/certs/, mais si vous avez également effacé le répertoire certs lors du dépannage, le PostgreSQL l'utilisateur aura perdu l'accès en lecture aux certificats etcd.

Exécutez cette étape pour rétablir ces autorisations.

Sur postgres-01 :

# Sur postgres-01
sudo setfacl -m u:postgres:r /etc/etcd/certs/ca.crt
sudo setfacl -m u:postgres:r /etc/etcd/certs/etcd-node1.crt
sudo setfacl -m u:postgres:r /etc/etcd/certs/etcd-node1.key
# Donne à l'utilisateur OS postgres un accès en lecture aux fichiers TLS d'etcd
# Requis car Patroni (exécuté par postgres) se connecte à etcd via TLS

Sur postgres-02 :

# Sur postgres-02
sudo setfacl -m u:postgres:r /etc/etcd/certs/ca.crt
sudo setfacl -m u:postgres:r /etc/etcd/certs/etcd-node2.crt
sudo setfacl -m u:postgres:r /etc/etcd/certs/etcd-node2.key

Sur postgres-03 :

# Sur postgres-03
sudo setfacl -m u:postgres:r /etc/etcd/certs/ca.crt
sudo setfacl -m u:postgres:r /etc/etcd/certs/etcd-node3.crt
sudo setfacl -m u:postgres:r /etc/etcd/certs/etcd-node3.key

Étape 5 — Réinitialiser ETCD_INITIAL_CLUSTER_STATE à “new”

Après le premier amorçage, etcd.env sur tous les nœuds a été changé en existant.

Pour une réinitialisation complète, il doit être remis à nouveau donc etcd traite ceci comme une nouvelle formation de cluster.

Sur chaque nœud, sauvegarder et modifier /etc/etcd/etcd.env:

Sur postgres-01 :

# Sur postgres-01
sudo cp /etc/etcd/etcd.env /etc/etcd/etcd.env.$(date +%Y%m%d)
sudo vi /etc/etcd/etcd.env

Garder:

ETCD_INITIAL_CLUSTER_STATE="existing"

À :

ETCD_INITIAL_CLUSTER_STATE="new"

Répétez sur postgres-02 et postgres-03.


Étape 6 — Démarrer etcd sur tous les nœuds

etcd doit être en cours d'exécution sur les trois nœuds avant que Patroni ne démarre.

Démarrez etcd sur les trois nœuds avant de passer à l'étape 7.

# Sur postgres-01, postgres-02, postgres-03
sudo systemctl start etcd

Vérifiez que les trois membres sont sains avant de démarrer Patroni :

# Sur postgres-01
ETCDCTL_API=3 etcdctl \
  --cacert=/etc/etcd/certs/ca.crt \
  --cert=/etc/etcd/certs/etcd-node1.crt \
  --key=/etc/etcd/certs/etcd-node1.key \
  --endpoints=https://192.168.0.203:2379,https://192.168.0.204:2379,https://192.168.0.205:2379 \
  endpoint health
# Attendu : les trois points de terminaison indiquent "is healthy"
# Si un nœud n'est pas sain : vérifiez journalctl -u etcd sur ce nœud avant de démarrer Patroni

Étape 7 — Lancer Patroni sur tous les nœuds

Démarrez d'abord Patroni sur postgres-01.

Patroni sur postgres-01 va initialiser un nouveau maître PostgreSQL.

Ce n'est qu'après que postgres-01 soit en cours d'exécution et soit indiqué comme Leader que postgres-02 et postgres-03 devraient être démarrés — ils rejoindront en tant que répliques via pg_basebackup.

# Sur postgres-01 — démarrer en premier
sudo systemctl start patroni

Attendez que postgres-01 apparaisse comme Leader :

# Sur postgres-01
patronictl -c /etc/patroni/config.yml list
# Attendu : postgresql-01 est indiqué comme Leader, état en cours d'exécution
# Attendre cela avant de démarrer postgres-02 et postgres-03

Puis lancez Patroni sur les nœuds restants :

# Sur postgres-02
sudo systemctl start patroni
# Sur postgres-03
sudo systemctl start patroni

Vérification finale :

# Sur postgres-01
patronictl -c /etc/patroni/config.yml list
# Attendu :
# + Cluster : postgres-cluster --------+----+-----------+
# | Membre        | Hôte            | Rôle    | État   | TL | Latence en Mo |
# +---------------+-----------------+---------+---------+----+-----------+
# | postgresql-01 | 192.168.0.203:5432 | Leader | running |  1 |           |
# | postgresql-02 | 192.168.0.204:5432 | Replica | running |  1 |         0 |
# | postgresql-03 | 192.168.0.205:5432 | Replica | running |  1 |         0 |
# +---------------+-----------------+---------+---------+----+-----------+
# TL 1 : cluster fraîchement créé, la chronologie est réinitialisée à 1 lors d'une réinitialisation complète
# Si un nœud affiche "start failed" : vérifiez journalctl -u patroni sur ce nœud

Continuez à partir de la section 9, étape 3 pour revérifier le cluster et l'ensemble ETCD_INITIAL_CLUSTER_STATE=existing.


19. Problèmes courants

ProblèmeCauseRéparer
Aucun dirigeant éluperte de quorum etcdRestaurer etcd ; vérifier le port 2380 entre les nœuds PostgreSQL
Patroni ne peut pas se connecter à etcdMauvaise configuration TLSVérifier les chemins de cacert/cert/key dans config.yml ; vérifier les permissions setfacl
Nœud bloqué dans démarrage échouéPostgreSQL ne démarre pasVérifier journalctl -u patroni; corriger la configuration, puis patronictl reinit
En attente de transmissionIdentifiants incorrects ou pg_hbaVérifiez primary_conninfo dans postgresql.auto.conf; vérifiez le réplicateur dans pg_hba
pg_rewind échoue après basculementwal_log_hints non activéActiver wal\_log\_hints = on avant l'initialisation du cluster ; utiliser patronictl reinit en cas de secours
Le VIP ne répond paskeepalived ne fonctionne pasVérifier le statut de systemctl keepalived ; vérifier journalctl -u keepalived sur tous les nœuds HAProxy
Routage HAProxy vers un nœud incorrectAPI REST Patroni inaccessibleVérifier que le port 8008 est ouvert ; vérifier que Patroni fonctionne sur tous les nœuds PostgreSQL
Le cluster etcd se réforme au redémarrageétat initial du cluster toujours “ nouveau ”Modifier “existing” dans etcd.env sur tous les nœuds et redémarrer etcd
PostgreSQL démarre en dehors de Patronisystemctl start postgresql exécuter directementArrêter PostgreSQL ; redémarrer via patronictl restart

20. Référence des commandes clés

CommandeDescription
patronictl listerAfficher tous les membres du cluster, les rôles, l'état et le décalage de réplication
patronictl basculementCommutation planifiée vers un nœud spécifique — perte de données nulle
patronictl failover --forceBasculement forcé — à n'utiliser que lorsque le primaire est confirmé comme étant hors service
patronictl pauseDésactiver le basculement automatique — à utiliser lors de la maintenance planifiée
patronictl reprendreRéactiver le basculement automatique
patronictl restartRedémarrer PostgreSQL sur un nœud via Patroni — n'utilisez jamais systemctl directement
patronictl reloadRecharger postgresql.conf sur tous les nœuds du cluster
patronictl reinitEffacer et reconstruire une réplique de secours à partir de la primaire actuelle
patronictl edit-configModifier la configuration du cluster DCS stockée dans etcd
curl -k https://:8008/primaryHTTP 200 si ce nœud est actuellement le principal
curl -k https://:8008/répliqueHTTP 200 si ce nœud est actuellement une réplique
curl -k https://:8008/clusterFull cluster status in JSON
etcdctl endpoint healthVérifier l'état de tous les membres du cluster etcd
ip addr show enp0s3Confirmer quel nœud HAProxy détient actuellement le VIP (interface par défaut VirtualBox)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *