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:
$ sudo apt update
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:
$ sudo ufw reload
Überprüfe die Firewall-Regeln:
$ sudo ufw status
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:
postgres=# \q
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.


