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)
--------------------------

.. code-block:: bash

    # 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)
----------------------------------------------------------

.. code-block:: bash

    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)
----------------------------------

.. code-block:: bash

    /usr/pgsql-18/bin/postgresql-18-setup initdb

.. code-block:: ini
   :caption: /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

.. code-block:: ini
   :caption: /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 :

.. code-block:: bash

    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) :

.. code-block:: bash

    # 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``) :

.. code-block:: bash

    # 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 :

.. code-block:: bash

   # 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)
-------------------------------------

.. code-block:: bash

   # 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)
----------------------------

.. code-block:: bash

    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

.. tip::

    `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)
------------------------------------

.. code-block:: bash

    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`** :

.. code-block:: bash

    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) :

.. code-block:: bash

    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

.. tip::

    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.

.. code-block:: bash

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

État cible :

.. code-block:: ini

    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 :

.. code-block:: bash

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

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

.. code-block:: bash

    # 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

.. tip::

    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.

.. code-block::

    # 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

.. tip::

    **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.
