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.
Passende Infrastruktur bei centron
Datenbanken im Betrieb wollen gepflegt werden: centron übernimmt auf Wunsch Updates, Backups und Monitoring – von der einzelnen Instanz bis zum Cluster. Managed Cluster ansehen →
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 updateInstalliere HAProxy, Certbot und die benötigten Python-Pakete auf allen Knoten:
$ sudo apt install haproxy certbot pipx -yInstalliere Patroni und seine Abhängigkeiten mit pip3:
$ sudo pip3 install --break-system-packages 'patroni[etcd3]' psycopg2-binary psycopgLade 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/tcpDiese 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 statusSSL-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-effWiederhole 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.shFü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.shFühre das Skript aus, um die Änderungen anzuwenden:
$ sudo /usr/local/bin/prepare-ssl-certs.shPostgreSQL 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 = 1536MBAktualisiere 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.confFüge am Ende der Datei folgende Zeile hinzu:
hostssl all all 0.0.0.0/0 scram-sha-256Starte 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.yamlFü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/postgresqlSetze Eigentümer und Berechtigungen für den Benutzer patroni:
$ sudo chown patroni:patroni /var/run/postgresql
$ sudo chmod 755 /var/run/postgresqlSorge dafür, dass das Verzeichnis beim Systemstart automatisch wiederhergestellt wird:
$ echo 'd /var/run/postgresql 0755 patroni patroni -' | sudo tee /etc/tmpfiles.d/postgresql.confPostgreSQL-Datenverzeichnis für Patroni vorbereiten
Sichere das bestehende PostgreSQL-Datenverzeichnis:
$ sudo mv /var/lib/postgresql/18 /var/lib/postgresql/18.bak-$(date +%s) || trueErstelle ein neues Datenverzeichnis für Patroni:
$ sudo mkdir -p /var/lib/postgresql/18/mainWeise den Besitz dem Benutzer patroni zu:
$ sudo chown -R patroni:patroni /var/lib/postgresql/18Setze restriktive Berechtigungen für das Datenverzeichnis:
$ sudo chmod -R 700 /var/lib/postgresql/18Stelle 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 postgresqlSystemd-Dienst für Patroni erstellen
Erstelle eine neue Systemd-Dienstdatei für Patroni:
$ sudo nano /etc/systemd/system/patroni.serviceFü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.targetLade systemd neu, um die neue Dienstdatei zu registrieren:
$ sudo systemctl daemon-reloadAktiviere Patroni, damit es beim Systemstart automatisch ausgeführt wird:
$ sudo systemctl enable patroniStarte Patroni und überprüfe, ob der Dienst korrekt läuft:
$ sudo systemctl start patroniÜberprüfe den Clusterstatus:
$ patronictl -c /etc/patroni.yaml listErwartete 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.cfgNeue HAProxy-Konfiguration erstellen
Öffne eine neue Konfigurationsdatei:
$ sudo nano /etc/haproxy/haproxy.cfgFü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 30sHAProxy 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 haproxyCluster 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=# \qFailover 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 listDie 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 patroniFazit
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.
Testen Sie Ihr Setup auf ccloud³
Registrieren Sie sich in der ccloud³ und erhalten Sie 200 € Startguthaben für Ihr Projekt – z. B. für eine PostgreSQL-VM mit automatischen Backups.