HAProxy + PAF

Démo interne

ioguix

Sujet

  • Cluster without a VIP
    • gh #108
    • Google Cloud Engine
    • et probablement ailleurs
  • Comment diriger les connexions vers l’instance de prod ?
  • Comment configurer la réplication ?
  • tentative avec HAProxy

Archi présentée

  • OS : CentOS 7
  • Pacemaker/PAF
  • 4 nœuds: srv1, srv2, srv3 + log-sink
  • HAProxy 1.5.18

Rappel vIP

  • détails du démarrage
  • détails d’un switchover

HAProxy

  • proxy TCP couche 4…
  • les clients “parlent” à HAProxy et ne voient pas les instances pgsql
  • les instances “parlent” à HAProxy et ne voient pas directement les clients
  • méthodes de vérifications des serveurs variées
  • différents algos d’équilibrage de charge
  • capacités d’analyse jusqu’en couche 7 eg. pour le HTTP

Intégration dans la stack

  • HAProxy écoute sur 5432 pour l’instance primaire
  • HAProxy écoute sur 5433 pour les standby
  • PostgreSQL écoute sur 5434
  • démarré sur tous les serveurs
    • redondance (no SPoF)
    • pas en HA => simplicité

Configuration simplifiée de HAProxy

listen prod
    bind           *:5432
    server         srv1 srv1:5434
    server         srv2 srv2:5434
    server         srv3 srv3:5434

listen stdby
    bind           *:5433
    server         srv1 srv1:5434
    server         srv2 srv2:5434
    server         srv3 srv3:5434

Statut des instances

  • par défaut, roundrobin vers tous les nœuds !
  • supporte des health check pour filtrer les instances
  • l’API ReST de Patroni expose le rôle de chaque instance
  • rien dans Pacemaker/PAF
  • “développement” nécessaire: pgsql-state

systemd / pgsql-state

cat<<'EOF' > /etc/systemd/system/pgsql-state.socket
[Unit]
Description=Local PostgreSQL state

[Socket]
ListenStream=5431
Accept=yes

[Install] 
WantedBy=sockets.target
EOF

systemd / pgsql-state

cat<<'EOF' > /etc/systemd/system/pgsql-state@.service
[Unit]
Description=Local PostgreSQL state

[Service]
User=postgres
ExecStart=/usr/pgsql-12/bin/psql -p 5434 -Atc          \
  "SELECT CASE WHEN pg_is_in_recovery() THEN 'standby' \
               ELSE 'production' END"
StandardOutput=socket
EOF

systemctl --now enable pgsql-state.socket

systemd / pgsql-state

Test simpliste depuis l’un des serveurs:

srv2:~# for s in srv{1..3}; do
    echo -ne "$s: "
    nc --recv-only "$s" 5431
done
srv1: production
srv2: standby
srv3: standby

systemd / pgsql-state

Tests de défaillance autour de pgsql-state:

# socket arrêté
srv2:~# nc --recv-only srv3 5431
Ncat: Connection refused.

# PostgreSQL arrêté
srv2:~# nc --recv-only srv3 5431
psql: error: could not connect to server: could not connect to server: No such file or directory
  Is the server running locally and accepting
  connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5434"?

Intégration dans HAProxy

listen prod
    bind           *:5432
    option         tcp-check
    tcp-check      connect port 5431
    tcp-check      expect string production
    default-server inter 2s fastinter 1s rise 2 fall 1 on-marked-down shutdown-sessions
    server         srv1 srv1:5434 check
    server         srv2 srv2:5434 check
    server         srv3 srv3:5434 check

Paramètres

  • inter 2s: vérifie toutes les 2s
  • fastinter 1s: toutes les secondes en période de transition
  • rise 2: deux check valides pour devenir accessible
  • fall 1: inaccessible dès le premier check échoué
  • on-marked-down shutdown-sessions: déconnecte tous les clients si le backend devient inaccessible

Pour les standby

listen stdby
    bind           *:5433
    balance        leastconn
    option         tcp-check
    tcp-check      connect port 5431
    tcp-check      expect string standby
    default-server inter 2s fastinter 1s rise 2 fall 1 on-marked-down shutdown-sessions
    server         srv1 srv1:5434 check
    server         srv2 srv2:5434 check
    server         srv3 srv3:5434 check

HAProxy et pgsql-state

Récapitulons en image pour eg. le pool prod.

HAProxy et pgsql-state

Jamais plus d’une instance est désignée comme primaire…vraiment ?

  • Pacemaker (ou Patroni) n’autorise qu’un primaire à la fois…
    • aucun problème du point de vue des données
  • …mais HAProxy peut pointer une connexion vers un standby si mal configuré
  • default-server [...] rise 2 fall 1 [...]

HAProxy et pgsql-state

Mais pas suffisant:

  • race condition possible entre deux health check

  • un standby pourrait s’auto-repliquer !

  • solution (comme avec une vIP):

    local replication all              reject
    host  replication all $NODENAME    reject
    host  replication all 127.0.0.1/32 reject
    host  replication all ::1/128      reject

Archi finale

  • HAProxy et pgsql-state fusionnés pour simplifier les diagrammes
  • réplication au travers des HAProxy via localhost
  • pgsql-state sur le port 5431
  • clients rw sur le port 5432
  • clients ro sur le port 5433
  • PostgreSQL sur le port 5434

stats HAProxy

listen stats
    mode http
    bind *:7000
    stats enable
    stats uri /
    timeout connect 15s
    timeout client  15s
    timeout server  15s

stats HAProxy

stats HAProxy

Configuration:

global
    stats socket ipv4@*:9999
    stats socket /var/lib/haproxy/stats

Utilisation:

$ echo "show stat" | nc -U /var/lib/haproxy/stats | column -s, -t | less -S
$ echo "show stat" | ncat 10.20.30.51 9999 | column -s, -t | less -S

Travail à la maison

  • Vagrantfile dispo pour reconstruire cette archi
  • cf. README
    • remplacez 3nodes-vip par 3nodes-haproxy
  • faites moi des retours

Pros

  • pas de vIP, la contrainte de base
  • équilibrage de charge possible vers les standby
  • pgsql-state ouvert à tous
    • information dispo aux couches applicatives
  • accès direct aux instances toujours possible !

Cons

  • pgsql-state ouvert à tous
    • sécurité ? filtrer par un firewall ?
    • information dispo aux couches applicatives, pas nécessairement acceptable
  • archi plus complexe qu’avec une vIP
  • dépend de la disponibilité de Systemd
    • peu grave
  • systemd.socket accepte par défaut 100cnx/s
    • le service passe en failed
    • doit être redémarré (automatiquement ?)
    • attention au DoS
    • se configure aisément

Pistes de travail

  • health checks
    • utilisation du check-pgsql intégré
      • pas d’authent sécurisée possible
    • édition du pg_hba lors de la bascule…
    • fermeture de port avec le RA portblock
    • autres idées ?
  • keepalived/LVS

Démo