Drei-Node-PostgreSQL-Cluster mit Patroni und HAProxy unter Ubuntu 24.04 einrichten

PostgreSQL ist ein leistungsstarkes, zuverlässiges und skalierbares Open-Source-Datenbankmanagementsystem. In geschäftskritischen Umgebungen ist hohe Verfügbarkeit entscheidend, die durch Replikation und Lastverteilung in einer Cluster-Architektur erreicht werden kann. Ein Patroni-basierter Cluster benötigt eine ungerade Anzahl an Knoten, um die Quorum-Integrität zu gewährleisten. Das Quorum bedeutet, dass die Mehrheit der Knoten einer Entscheidung – wie der Wahl eines Leaders oder einer Konfigurationsänderung – zustimmen muss. Bei drei Knoten kann der Cluster selbst dann sicher weiterlaufen, wenn einer ausfällt, da die verbleibenden zwei weiterhin die Mehrheit bilden.

In dieser Anleitung erfährst du, wie du einen dreiknotigen PostgreSQL-Cluster auf Ubuntu 24.04 mit Patroni für automatische Replikation und Failover sowie HAProxy für die Lastverteilung der Clientverbindungen einrichtest.

Voraussetzungen

Bevor du beginnst, stelle sicher, dass du die folgenden Voraussetzungen erfüllst:

  • Drei Ubuntu-24.04-Server mit jeweils mindestens 2 CPU-Kernen und 4 GB RAM, konfiguriert mit einem nicht-root sudo-Benutzer.
  • PostgreSQL auf allen drei Servern installiert.
  • Eine registrierte Domain mit A-Records, die auf die IP-Adressen der drei Server zeigen:
    • node1.example.com
    • node2.example.com
    • node3.example.com

Hinweis: Ersetze in dieser Anleitung alle Beispiel-Domains durch deine tatsächlichen Werte. Wenn deine Domain beispielsweise mydb.com lautet, ersetze node1.example.com durch node1.mydb.com.

Abhängigkeiten installieren

Beginne mit der Installation der benötigten Pakete, dem Öffnen relevanter Firewall-Ports und der Einrichtung von SSL-Zertifikaten, um eine sichere Kommunikation zwischen den Cluster-Knoten zu gewährleisten.

Pakete installieren und Firewall konfigurieren

Aktualisiere den Paketindex jedes Systems:

Installiere HAProxy, Certbot und die benötigten Python-Pakete auf allen Knoten:

$ sudo apt install haproxy certbot pipx -y

Installiere Patroni und seine Abhängigkeiten mit pip3:

$ sudo pip3 install --break-system-packages 'patroni[etcd3]' psycopg2-binary psycopg

Lade die etcd-Binärdateien herunter und installiere sie auf allen Knoten:

$ wget https://github.com/etcd-io/etcd/releases/download/v3.6.4/etcd-v3.6.4-linux-amd64.tar.gz
$ tar -xvf etcd-v3.6.4-linux-amd64.tar.gz
$ sudo mv etcd-v3.6.4-linux-amd64/etcd etcd-v3.6.4-linux-amd64/etcdctl /usr/local/bin/

Öffne die erforderlichen Firewall-Ports auf jedem Knoten:

$ sudo ufw allow 80,2379,2380,5432,5433,8008,8009/tcp

Diese Ports haben folgende Aufgaben:

  • 80: SSL-Verifizierung durch Certbot
  • 2379, 2380: etcd-Client- und Peer-Kommunikation
  • 5432, 5433: PostgreSQL und Patroni-verwaltetes PostgreSQL
  • 8008, 8009: Patroni REST API

Firewall neu laden:

Überprüfe die Firewall-Regeln:

SSL-Zertifikate konfigurieren

Um die Kommunikation zwischen den Knoten zu sichern, verwende Let’s Encrypt SSL-Zertifikate.

Fordere für jeden Knoten ein SSL-Zertifikat an, beginnend mit Node1:

$ sudo certbot certonly --standalone -d node1.example.com -m admin@example.com --agree-tos --no-eff

Wiederhole diesen Vorgang für Node2 und Node3 und passe jeweils die Subdomain an.

Erstelle nun auf jedem Knoten ein Skript zur Vorbereitung der SSL-Zertifikate. Passe die Variable HOSTNAME an den jeweiligen Knoten an:

$ sudo nano /usr/local/bin/prepare-ssl-certs.sh

Füge den folgenden Inhalt hinzu und ändere HOSTNAME entsprechend:

#!/bin/bash
# SSL-Zertifikats-Setup-Skript
HOSTNAME="node1.example.com"  # Für jeden Knoten anpassen
CERT_DIR="/etc/letsencrypt/live/$HOSTNAME"
ARCHIVE_DIR="/etc/letsencrypt/archive/$HOSTNAME"

# Gruppe ssl-users erstellen
getent group ssl-users >/dev/null || sudo groupadd ssl-users

# Benutzer erstellen und zur Gruppe ssl-users hinzufügen
for user in etcd patroni haproxy postgres; do
    if ! id "$user" >/dev/null 2>&1 && [[ "$user" == "etcd" || "$user" == "patroni" ]]; then
        getent group "$user" >/dev/null && sudo useradd -r -m -s /bin/bash -g "$user" "$user" || sudo useradd -r -m -s /bin/bash "$user"
    fi
    groups "$user" 2>/dev/null | grep -q ssl-users || sudo usermod -aG ssl-users "$user"
done

# Kombiniertes Zertifikat erstellen und Berechtigungen setzen
cat "$CERT_DIR/fullchain.pem" "$CERT_DIR/privkey.pem" > "$CERT_DIR/combined.pem"
sudo chmod 755 /etc/letsencrypt /etc/letsencrypt/live /etc/letsencrypt/archive
sudo chgrp ssl-users "$CERT_DIR" "$ARCHIVE_DIR"
sudo chmod 755 "$CERT_DIR" "$ARCHIVE_DIR"
sudo chown root:ssl-users "$ARCHIVE_DIR"/*.pem "$CERT_DIR/combined.pem"
sudo chmod 644 "$ARCHIVE_DIR"/cert*.pem "$ARCHIVE_DIR"/chain*.pem "$ARCHIVE_DIR"/fullchain*.pem
sudo chmod 640 "$ARCHIVE_DIR"/privkey*.pem "$CERT_DIR/combined.pem"

# Benutzer linuxuser zur Gruppe ssl-users hinzufügen
id "linuxuser" >/dev/null 2>&1 && ! groups linuxuser | grep -q ssl-users && sudo usermod -aG ssl-users linuxuser

echo "SSL-Zertifikat erfolgreich eingerichtet für $HOSTNAME"

Mache das Skript ausführbar:

$ sudo chmod +x /usr/local/bin/prepare-ssl-certs.sh

Führe das Skript aus, um die Änderungen anzuwenden:

$ sudo /usr/local/bin/prepare-ssl-certs.sh

PostgreSQL konfigurieren

Konfiguriere PostgreSQL so, dass SSL-Zertifikate verwendet werden und passe die Leistungseinstellungen für einen stabilen und optimierten Betrieb an.

PostgreSQL-Konfiguration bearbeiten

Öffne auf jedem Knoten die PostgreSQL-Konfigurationsdatei:

$ sudo nano /etc/postgresql/18/main/postgresql.conf

Ändere die folgenden Einstellungen für Node1:

# SSL-Konfiguration
ssl_cert_file = '/etc/letsencrypt/live/node1.example.com/fullchain.pem'
ssl_key_file = '/etc/letsencrypt/live/node1.example.com/privkey.pem'

# Speichereinstellungen
shared_buffers = 512MB
work_mem = 8MB
effective_cache_size = 1536MB

Aktualisiere die Zertifikatspfade für Node2 und Node3 entsprechend.

SSL-Verbindungen erzwingen

Um SSL zu erzwingen, öffne die Datei pg_hba.conf:

$ sudo nano /etc/postgresql/18/main/pg_hba.conf

Füge am Ende der Datei folgende Zeile hinzu:

hostssl all             all             0.0.0.0/0               scram-sha-256

Starte PostgreSQL neu, um die Änderungen zu übernehmen:

$ sudo systemctl restart postgresql

Überprüfe, ob SSL aktiviert ist:

$ sudo -u postgres psql -c "SHOW ssl;"

Erwartete Ausgabe:

 ssl 
-----
 on
(1 row)

Patroni konfigurieren

Patroni verwaltet automatisch die Replikation und das Failover von PostgreSQL. Richte Patroni auf jedem Knoten mit aktivierter HTTPS-Unterstützung ein.

Patroni-Konfigurationsdatei erstellen

Öffne die Konfigurationsdatei:

$ sudo nano /etc/patroni.yaml

Füge für Node1 den folgenden Inhalt ein:

scope: postgres
name: node1

restapi:
  listen: 0.0.0.0:8008
  connect_address: node1.example.com:8008
  certfile: /etc/letsencrypt/live/node1.example.com/fullchain.pem
  keyfile: /etc/letsencrypt/live/node1.example.com/privkey.pem

etcd3:
  hosts:
    - node1.example.com:2379
    - node2.example.com:2379
    - node3.example.com:2379
  protocol: https
  cacert: /etc/ssl/certs/ca-certificates.crt

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 5
    retry_timeout: 5
    maximum_lag_on_failover: 1048576
  initdb:
    - encoding: UTF8
    - data-checksums
  pg_hba:
    - host replication replicator 0.0.0.0/0 scram-sha-256
    - host all all 0.0.0.0/0 scram-sha-256

postgresql:
  listen: 0.0.0.0:5433
  connect_address: node1.example.com:5433
  data_dir: /var/lib/postgresql/18/main
  bin_dir: /usr/lib/postgresql/18/bin
  parameters:
    ssl: 'on'
    ssl_cert_file: '/etc/letsencrypt/live/node1.example.com/fullchain.pem'
    ssl_key_file: '/etc/letsencrypt/live/node1.example.com/privkey.pem'
  authentication:
    replication:
      username: replicator
      password: StrongPassword123!
    superuser:
      username: postgres
      password: StrongPassword123!

Für Node2 und Node3 ändere die Werte für name, connect_address und die Pfade der Zertifikate. Verwende auf allen Knoten die gleichen Passwörter.

PostgreSQL-Laufzeitverzeichnis vorbereiten

Erstelle das Laufzeitverzeichnis für PostgreSQL:

$ sudo mkdir -p /var/run/postgresql

Setze Eigentümer und Berechtigungen für den Benutzer patroni:

$ sudo chown patroni:patroni /var/run/postgresql
$ sudo chmod 755 /var/run/postgresql

Sorge dafür, dass das Verzeichnis beim Systemstart automatisch wiederhergestellt wird:

$ echo 'd /var/run/postgresql 0755 patroni patroni -' | sudo tee /etc/tmpfiles.d/postgresql.conf

PostgreSQL-Datenverzeichnis für Patroni vorbereiten

Sichere das bestehende PostgreSQL-Datenverzeichnis:

$ sudo mv /var/lib/postgresql/18 /var/lib/postgresql/18.bak-$(date +%s) || true

Erstelle ein neues Datenverzeichnis für Patroni:

$ sudo mkdir -p /var/lib/postgresql/18/main

Weise den Besitz dem Benutzer patroni zu:

$ sudo chown -R patroni:patroni /var/lib/postgresql/18

Setze restriktive Berechtigungen für das Datenverzeichnis:

$ sudo chmod -R 700 /var/lib/postgresql/18

Stelle sicher, dass die PostgreSQL-Binärdateien für alle Benutzer ausführbar sind (notwendig für einige Skripte):

$ sudo chmod o+rx /usr/lib/postgresql/18/bin/*

Stoppe den PostgreSQL-Dienst, damit Patroni die Kontrolle übernehmen kann:

$ sudo systemctl stop postgresql

Systemd-Dienst für Patroni erstellen

Erstelle eine neue Systemd-Dienstdatei für Patroni:

$ sudo nano /etc/systemd/system/patroni.service

Füge die folgende Konfiguration ein:

[Unit]
Description=Patroni PostgreSQL cluster
After=network.target

[Service]
Type=simple
User=patroni
Group=patroni
ExecStart=/usr/local/bin/patroni /etc/patroni.yaml
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
TimeoutSec=30
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Lade systemd neu, um die neue Dienstdatei zu registrieren:

$ sudo systemctl daemon-reload

Aktiviere Patroni, damit es beim Systemstart automatisch ausgeführt wird:

$ sudo systemctl enable patroni

Starte Patroni und überprüfe, ob der Dienst korrekt läuft:

$ sudo systemctl start patroni

Überprüfe den Clusterstatus:

$ patronictl -c /etc/patroni.yaml list

Erwartete Ausgabe:

+ Cluster: postgres (7561190282296399779) --+-----------+----+-------------+-----+------------+-----+
| Member | Host                   | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+--------+------------------------+---------+-----------+----+-------------+-----+------------+-----+
| node1  | node1.example.com:5433 | Leader  | running   |  1 |             |     |            |     |
| node2  | node2.example.com:5433 | Replica | streaming |  1 |   0/4000000 |   0 |  0/4000000 |   0 |
| node3  | node3.example.com:5433 | Replica | streaming |  1 |   0/4000000 |   0 |  0/4000000 |   0 |
+--------+------------------------+---------+-----------+----+-------------+-----+------------+-----+

HAProxy konfigurieren

Richte HAProxy auf allen Knoten ein, um PostgreSQL-Verbindungen gleichmäßig zu verteilen.

Standardkonfiguration entfernen

Lösche die vorhandene Standardkonfigurationsdatei von HAProxy:

$ sudo rm /etc/haproxy/haproxy.cfg

Neue HAProxy-Konfiguration erstellen

Öffne eine neue Konfigurationsdatei:

$ sudo nano /etc/haproxy/haproxy.cfg

Füge die folgende Konfiguration ein, die auf allen Knoten identisch ist:

global
    daemon
    maxconn 4096
    log stdout local0
    stats socket /var/lib/haproxy/stats mode 660 level admin

defaults
    mode tcp
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
    log global

frontend postgres_ssl
    bind *:5432
    default_backend postgres_backend

backend postgres_backend
    balance roundrobin
    server node1 node1.example.com:5433 check inter 5000ms rise 2 fall 3
    server node2 node2.example.com:5433 check inter 5000ms rise 2 fall 3
    server node3 node3.example.com:5433 check inter 5000ms rise 2 fall 3

listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 30s

HAProxy neu starten

Starte den HAProxy-Dienst neu, um die neue Konfiguration zu übernehmen:

$ sudo systemctl restart haproxy

Überprüfe den Status von HAProxy, um sicherzustellen, dass der Dienst ordnungsgemäß läuft:

$ sudo systemctl status haproxy

Cluster testen

Teste nun die Hochverfügbarkeit und das automatische Failover, um sicherzustellen, dass der Cluster korrekt funktioniert.

Verbinde dich von einem Client mit dem PostgreSQL-Cluster (ersetze Domain- und Passwortwerte entsprechend):

$ psql 'postgresql://postgres:StrongPassword123!@node1.example.com:5433,node2.example.com:5433,node3.example.com:5433/postgres?sslmode=require&target_session_attrs=read-write'

Prüfe, welcher Knoten aktuell als Primärknoten fungiert:

postgres=# SELECT inet_server_addr() as ip, 
       CASE WHEN pg_is_in_recovery() THEN 'Replica' ELSE 'Primary' END as role;

Beende die PostgreSQL-Konsole:

Failover simulieren

Simuliere einen Failover-Vorgang, indem du Patroni auf dem aktuellen Leader (zum Beispiel Node1) stoppst:

$ sudo systemctl stop patroni

Überprüfe den neuen Clusterstatus auf einem anderen Knoten:

$ patronictl -c /etc/patroni.yaml list

Die Ausgabe bestätigt, dass automatisch ein neuer Leader gewählt wurde.

Überprüfe anschließend, ob der Cluster weiterhin über die gleiche Verbindungszeichenkette erreichbar ist:

$ psql 'postgresql://postgres:StrongPassword123!@node1.example.com:5433,node2.example.com:5433,node3.example.com:5433/postgres?sslmode=require&target_session_attrs=read-write'

Starte anschließend den gestoppten Knoten neu, damit er dem Cluster wieder beitritt:

$ sudo systemctl start patroni

Fazit

Du hast erfolgreich einen fehlertoleranten PostgreSQL-Cluster unter Ubuntu 24.04 eingerichtet, der mit Patroni Replikation und automatisches Failover verwaltet und mit HAProxy die Verbindungen verteilt. Diese Konfiguration sorgt für kontinuierliche Verfügbarkeit selbst bei Knotenausfällen, während alle Datenübertragungen durch SSL/TLS-Verschlüsselung geschützt sind. Für den Produktiveinsatz empfiehlt es sich, Überwachungstools wie Prometheus und Grafana zu integrieren, um die Leistung und den Zustand des Clusters im Blick zu behalten.

Quelle: vultr.com

Jetzt 200€ Guthaben sichern

Registrieren Sie sich jetzt in unserer ccloud³ und erhalten Sie 200€ Startguthaben für Ihr Projekt.

Das könnte Sie auch interessieren: