Deploiement#

Corosync / Pacemaker / PAF (pgsqlms) — réplication streaming native, sans DRBD.

Runbook validé sur Pacemaker 4.0 (pcs récent) et PostgreSQL 18.

Topologie de référence#

Nœud

IP

RĂ´le initial

pg-01

192.168.14.161

primaire (amorçage)

pg-02

192.168.14.162

standby

pg-03

192.168.14.163

standby

VIP

192.168.14.160

suit le primaire

  • PGDATA = /var/lib/pgsql/18/data

  • bindir = /usr/pgsql-18/bin

  • utilisateur de rĂ©plication : replicator

Règle d’or apprise à la dure : le primary_conninfo de chaque nœud pointe la VIP (192.168.14.160), jamais l’IP d’un nœud. Un standby suit « le primaire », pas « tel serveur ».

0. Prérequis (les 3 nœuds)#

# Résolution de noms cohérente
cat >> /etc/hosts <<'EOF'
192.168.14.161 pg-01
192.168.14.162 pg-02
192.168.14.163 pg-03
EOF

# Horloge synchronisée — critique pour Corosync
systemctl enable --now chronyd

A toi de preparer la conf firewall en adequation avec ton infra.

1. Installer PostgreSQL 18 et de la stack HA (les 3 nœuds)#

dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-10-x86_64/pgdg-redhat-repo-latest.noarch.rpm
dnf -qy module disable postgresql
dnf install -y postgresql18 postgresql18-server postgresql18-contrib

# Dépôt HighAvailability
dnf install -y rocky-release-ha
dnf config-manager --set-enabled highavailability

# Stack + agents de fencing
dnf install -y pcs pacemaker corosync fence-agents-all

# PAF (resource agent pgsqlms)
dnf install -y resource-agents-paf
# vérifier :
dnf provides '*/pgsqlms'
ls /usr/lib/ocf/resource.d/heartbeat/pgsqlms

# Mot de passe hacluster (identique sur les 3)
echo "hacluster:motdepassehacluster" | chpasswd
systemctl enable --now pcsd

2. Initialiser le primaire (pg-01)#

/usr/pgsql-18/bin/postgresql-18-setup initdb
/var/lib/pgsql/18/data/postgresql.conf#
 listen_addresses = '*'
 wal_level = replica
 max_wal_senders = 10
 max_replication_slots = 10
 hot_standby = on
 hot_standby_feedback = on
 wal_log_hints = on
/var/lib/pgsql/18/data/pg_hba.conf#
 host  replication  replicator  192.168.14.161/32  scram-sha-256
 host  replication  replicator  192.168.14.162/32  scram-sha-256
 host  replication  replicator  192.168.14.163/32  scram-sha-256
 host  all           all         192.168.14.0/24    scram-sha-256

Créer le rôle de réplication :

sudo -u postgres /usr/pgsql-18/bin/pg_ctl -D /var/lib/pgsql/18/data start
sudo -u postgres psql -c "CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'motdepasse';"

Attention

PAS de `recovery_template` / fichier `pgsqlms.conf`. PAF l’interdit en PG 12+ (Recovery template file … is forbidden for PostgreSQL 12 and above). La config standby vit dans postgresql.auto.conf (standby.signal + primary_conninfo).

3. Bootstrap des standbys (pg-02 et pg-03)#

Sur chaque standby, basebackup depuis le primaire d’amorçage (pg-01, IP directe) :

# sur pg-02, puis pg-03
sudo -u postgres /usr/pgsql-18/bin/pg_basebackup \
    -h 192.168.14.161 -U replicator \
    -D /var/lib/pgsql/18/data \
    -R -X stream -c fast -P

-R écrit standby.signal + primary_conninfo, -X stream récupère les WAL en parallèle (évite le requested WAL segment has already been removed).

Corriger primary_conninfo → VIP + bon application_name (le -R met l’IP directe et pas d”application_name) :

# sur pg-02  (adapter application_name=pg-03 sur pg-03)
sudo -u postgres bash -c '
cd /var/lib/pgsql/18/data
sed -i "/^primary_conninfo/d" postgresql.auto.conf
cat >> postgresql.auto.conf <<EOF
primary_conninfo = '\''host=192.168.14.160 application_name=pg-02 user=replicator password=motdepasse'\''
EOF
'

Poser aussi le primary_conninfo sur pg-01 (utile à son futur passage en standby), via ALTER SYSTEM — surtout PAS de standby.signal sur le primaire d’amorçage :

# sur pg-01
 sudo -u postgres psql -c "ALTER SYSTEM SET primary_conninfo = 'host=192.168.14.160 application_name=pg-01 user=replicator password=motdepasse';"

4. Arret des PostgreSQL (les 3 nœuds)#

# les 3 nœuds
 sudo -u postgres /usr/pgsql-18/bin/pg_ctl -D /var/lib/pgsql/18/data stop -m fast
 systemctl disable --now postgresql-18
 systemctl mask postgresql-18.service

5. Former le cluster (pg-01)#

pcs host auth pg-01 pg-02 pg-03 -u hacluster -p motdepassehacluster
pcs cluster setup pgcluster pg-01 pg-02 pg-03 --transport knet
pcs cluster start --all
pcs cluster enable --all

pcs status   # attendu : 3 nœuds Online, Quorate: Yes

Astuce

pcs cluster start –all démarre corosync + pacemaker seulement. PostgreSQL sera lancé par Pacemaker/PAF, pas par systemd (d’où le systemctl disable de l’étape 3).

6. Configurer les ressources (pg-01)#

pcs property set maintenance-mode=true
pcs property set stonith-enabled=false

pcs property set no-quorum-policy=stop
pcs resource defaults update resource-stickiness=10

Ressource PostgreSQL via PAF — sans `recovery_template`, avec `promoted-max=1` :

pcs resource create pgsqld ocf:heartbeat:pgsqlms \
    bindir=/usr/pgsql-18/bin \
    pgdata=/var/lib/pgsql/18/data \
    pgport=5432 \
    op start timeout=60s \
    op stop timeout=60s \
    op promote timeout=30s \
    op demote timeout=120s \
    op monitor interval=15s timeout=10s role="Promoted" \
    op monitor interval=16s timeout=10s role="Unpromoted" \
    op notify timeout=60s \
    promotable notify=true promoted-max=1 promoted-node-max=1

Attention

promoted-max=1 est obligatoire sur Pacemaker 4.0, sinon PAF renvoie You must set meta parameter promoted_max=1 for your « promotable » resource. (Les warnings de dépréciation sur la syntaxe promotable … sont sans effet ; la forme future est promotable meta notify=true promoted-max=1 ….)

VIP + contraintes — sans `symmetric=false` (attribut retiré du schéma pacemaker-4.0) :

pcs resource create pgsql-vip ocf:heartbeat:IPaddr2 \
    ip=192.168.14.160 cidr_netmask=24 \
    op monitor interval=10s

pcs constraint colocation add pgsql-vip with Promoted pgsqld-clone INFINITY
pcs constraint order promote pgsqld-clone then start pgsql-vip kind=Mandatory

Astuce

Recréer la ressource pgsqld supprime les contraintes qui la référencent : il faut reposer colocation + order après chaque pcs resource delete/create pgsqld.

7. Amorçage du cluster (pg-01)#

On peut tenter de demarrer le cluster la commande est plutot simple, on sort pacemaker de maintenance et on vois ce qu’il ce passe.

pcs property set maintenance-mode=false
watch -n1 pcs status

État cible :

Clone Set: pgsqld-clone [pgsqld] (promotable):
    Promoted:   [ pg-01 ]
    Unpromoted: [ pg-02 pg-03 ]
pgsql-vip  (ocf:heartbeat:IPaddr2):  Started pg-01

Au tout premier démarrage, les scores de promotion PAF peuvent valoir -1 partout : aucun nœud n’a jamais été primaire connu de PAF, donc personne n’est promu spontanément. Il faut présenter à PAF un état de réplication déjà sain (1 primaire + 2 standbys raccrochés) avant de lui rendre la main.

Vérifier que le score de pg-01 est devenu positif :

crm_simulate -sL 2>/dev/null | grep -i promotion

Si c’est bien ce probleme on peut suivre la procedure suivante:

# 1) pg-01 en primaire réel : PAS de standby.signal
# sur pg-01
sudo -u postgres rm -f /var/lib/pgsql/18/data/standby.signal
sudo -u postgres /usr/pgsql-18/bin/pg_ctl -D /var/lib/pgsql/18/data start
sudo -u postgres psql -c "SELECT pg_is_in_recovery();"   # attendu : f

# 2) pg-02 et pg-03 en standby raccrochés à pg-01
# sur pg-02 puis pg-03
sudo -u postgres /usr/pgsql-18/bin/pg_ctl -D /var/lib/pgsql/18/data start
sudo -u postgres psql -c "SELECT pg_is_in_recovery();"   # attendu : t

# 3) vérifier la réplication depuis pg-01
# sur pg-01
sudo -u postgres psql -c "SELECT application_name, state, sync_state FROM pg_stat_replication ORDER BY application_name;"
# attendu : pg-02 et pg-03 en streaming

# 4) tout arrêter — PAF va reprendre
# les 3 nœuds
sudo -u postgres /usr/pgsql-18/bin/pg_ctl -D /var/lib/pgsql/18/data stop -m fast

# 5) rendre la main Ă  Pacemaker
pcs resource cleanup pgsqld
pcs property set maintenance-mode=false
watch -n1 pcs status

Astuce

Felicitation tu a maintenant un cluster postgresql-18 qui tiendra l’extinction d’un noeud.

Ă€ FAIRE avant la production#

STONITH est resté désactivé pendant tout le montage (stonith-enabled=false). Un nœud partant en primaire-fantôme pendant que Pacemaker promeut un autre = split-brain (rencontré pendant le debug). Le fencing l’empêche.

# exemple IPMI (adapter à l'infra : Proxmox → fence_virsh / agent API)
pcs stonith create fence-pg01 fence_ipmilan \
    pcmk_host_list="pg-01" ip=<bmc-ip-pg01> \
    username=<user> password=<pass> lanplus=1 \
    op monitor interval=60s
# idem pg-02, pg-03

pcs property set stonith-enabled=true
pcs stonith status

Astuce

Slots de réplication : vérifier pg_replication_slots (protège du recyclage WAL) ; prévoir max_slot_wal_keep_size comme garde-fou contre la saturation disque si un standby reste mort longtemps.

pg_hba homogène sur les 3 nœuds (via Ansible configuration-management) : après bascule n’importe quel nœud devient primaire.

Test de failover documenté, à rejouer STONITH actif : pcs node standby <primaire> → promotion d’un standby + VIP déplacée → pcs node unstandby → l’ancien primaire revient en standby.