Postgres Cluster ohne LB aber mit Patroni

Ich wollte mich schon länger mit Patroni (Doku) beschäftigen, um in der Lage zu sein, hochverfügbare Postgres-Datenbanken in Cloud-Umgebungen bereitstellen zu können. Insbesondere aus dem Grund, dass herkömmliche Cluster- und Failovermechanismen sich dort meist nicht realisieren lassen. Eigentlich wollte ich das Ganze mit Containern ausprobieren - aber da ich damit zunächst nicht weitergekommen bin, habe ich mir Patroni zunächst auf VMs angesehen.

Da die Dokumentation zunächst (wie so oft) von einer Installation auf nur einem Node ausgeht, war ich sehr froh, als ich auf die unten stehenden Blogeinträge stieß, die Patroni in einer Drei-Knoten Konfiguration zeigen.

Patroni konfigurieren

Patroni wird mit Hilfe einer Yaml-Datei konfiguriert. Die genannten Blogs hatten netterweise schon eine parat:

postgres@patroni1:~/patroni$ cat postgres-ha.yml
name: postgres1
scope: pglab
namespace: /digitalis.io/
consul:
  url: http://127.0.0.1:8500
  register_service: true
postgresql:
  connect_address: 192.168.129.155:5432
  bin_dir: /usr/lib/postgresql/12/bin
  data_dir: /mnt/pg_data/12/main
  listen: "*:5432"
  authentication:
    replication:
      username: replicator
      password: geheim
      sslmode: require
restapi:
  connect_address: 192.168.129.155:8008
  listen: 192.168.129.155:8008
bootstrap:
  dcs:
    postgresql:
      parameters:
        ssl: "on"
        ssl_ciphers: "TLSv1.2:!aNULL:!eNULL"
        ssl_cert_file: /var/lib/postgresql/patroni/certs/server.crt
        ssl_key_file: /var/lib/postgresql/patroni/certs/server.key
  users:
    app_user:
      password: "geheim"
  pg_hba:
    - local all all  md5
    - hostssl all all 127.0.0.1/32 md5
    - hostssl all all ::1/128 md5
    - hostssl all all ::1/128 md5
    - hostssl all all 0.0.0.0/0 md5
    - hostssl replication replicator patroni1.node.de-gt-2.consul md5
    - hostssl replication replicator patroni2.node.de-gt-2.consul md5
    - hostssl replication replicator patroni3.node.de-gt-2.consul md5
    - hostssl replication replicator 192.168.129.155/32 md5
    - hostssl replication replicator 192.168.129.156/32 md5
    - hostssl replication replicator 192.168.129.157/32 md5

  initdb:
    - encoding: UTF8

Meine Nodes verfügen über mindestens zwei Netzwerkinterfaces. Ich habe für die Clusterkommunikation das interne gewählt. Unten sieht man, warum die Consul-Konfiguration wichtig ist: Es hilft, wenn Consul in die DNS-Auflösung eingebunden wird. Die Client Konfiguration von Consul sieht dementsprechend aus:

postgres@patroni1:~/patroni$ cat /etc/consul/consul.hcl
datacenter = "de-gt-2"
data_dir = "/data/consul"
node_name = "patroni1"
 
ports {
  grpc = 8502
}
 
connect {
  enabled = true
}
 
recursors = [ "127.0.0.53" ]

Auf den VMs läuft systemd-resolved. In der Consul-Dokumentation gibt es Hinweise dazu, wie Consul dort eingeschleift werden kann. Damit läuft dann Consul DNS auf 127.0.0.1:53, iptables leitet Anfragen an Port 53 auf Consul-DNS auf Port 8600 um und die Consul-Clientkonfiguration sorgt dafür, dass Anfragen, die nicht an die consul.Domain gehen, an systemd-resolved weitergegeben werden:

postgres@patroni1:~$ dig +short  master.pglab.service.consul
192.168.129.156
postgres@patroni1:~$ dig +short  www.heise.de
193.99.144.85

Beim Aufsetzen von Patroni bin ich dann von VM zu VM vorgegangen. Als postgres-User setzt der Aufruf von patroni <config.yml> den Prozess in Bewegung. Die Konfigurationsdateien unterscheiden sich nur in den IP-Adressen. Die erste von Patroni gestartete Postgres-Instanz wird automatisch "Leader" und bekommt im Consul den Tag "master". Die nächste von Patroni gestartete Instanz repliziert sich automatisch den aktuellen Datenbestand (der Replikationsuser muß allerdings im Leader zunächst angelegt werden):

postgres@patroni1:~/patroni$ createuser -U postgres replicator -P --replication

Jede weitere von Patroni gestartete Node macht es ebenso. Der Aufruf von patronictl zeigt dann den Status:

postgres@patroni1:~/patroni$ patronictl -c postgres-ha.yml list
+-----------+-----------------+---------+---------+----+-----------+
| Member    | Host            | Role    | State   | TL | Lag in MB |
+ Cluster: pglab (7111756342097541380) -+---------+----+-----------+
| postgres1 | 192.168.129.155 | Replica | running |  3 |         0 |
| postgres2 | 192.168.129.156 | Leader  | running |  3 |           |
| postgres3 | 192.168.129.157 | Replica | running |  3 |         0 |
+-----------+-----------------+---------+---------+----+-----------+

Consul-DNS sorgt dafür, dass unter master.pglab.service.consul immer der aktuelle "Leader" angesprochen wird. Unter replica sind alle Replicas zu erreichen:

postgres@patroni1:~/patroni$ dig +short replica.pglab.service.consul
192.168.129.157
192.168.129.155

Auf diese Weise lässt sich auf den Clients auch ein Loadbalancing über die Replicas realisieren. Die Funktion, die ein Loadbalancer übernehmen würde, wird hier von Consul übernommen.

Die Anzahl der Nodes ist frei wählbar (zwei sollten es schon sein, um ein Failover hinzubekommen). Consul braucht mindestens drei Nodes, fünf sind besser. Als Komponente für Service Discovery (und Einsparung von Loadbalancern) ist Consul sowieso sinnvoll und kann ggfs. auch für andere Dienste genutzt werden.