7 Minuten Lesezeit (1375 Worte)

"Expecto Patronum": Wie betreibe ich PostgreSQL Systeme sicher mit Patroni

Patroni ist eine Art Cluster-Manager-Framework, welches von vielen Kunden genutzt wird, um PostgreSQL (hoch)verfügbar zu betreiben. In diesem Blog-Beitrag möchten wir Ihnen zeigen, wie einfach ein Setup eines PostgreSQL-Clusters mithilfe dieser Lösung möglich ist. Wir stellen die grundlegenden Komponenten vor und erklären die Funktionsweise. 

Der Grundstein des Clusters 

Für unser beispielhaftes Setup nutzen wird drei Ubuntu-Linux Server (Docker Container). Die Knoten hören auf die folgenden Namen:

  • node1; 172.28.0.2
  • node2; 172.28.0.3
  • node3; 172.28.0.4

Die Server sind netzwerktechnisch so verbunden und konfiguriert, dass sie miteinander problemlos (über Hostnamen und IPs) kommunizieren können.

Für den Aufbau des Clusters wird einiges an zusätzlicher Software benötigt, die auf den Systemen im Vorfeld installiert wurde.

Zunächst wurde das aktuelle PostgreSQL-Repository runtergeladen und PostgreSQL in der Version 14 installiert.
root@node1:/# wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
root@node1:/# RELEASE=$(lsb_release -cs)
root@node1:/# echo "deb http://apt.postgresql.org/pub/repos/apt/ ${RELEASE}"-pgdg main | tee  /etc/apt/sources.list.d/pgdg.list
root@node1:/# apt-get update
root@node1:/# apt-get -y install postgresql-14
root@node1:/# psql --version
psql (PostgreSQL) 12.9 (Ubuntu 12.9-0ubuntu0.20.04.1) 
 

Als nächstes wurde etcd installiert. 

root@node1:/# apt install etcd
root@node1:/# etcd --version
etcd Version: 3.2.26
Git SHA: Not provided (use ./build instead of go build)
Go Version: go1.13.7
Go OS/Arch: linux/amd64
 

Bei etcd handelt es sich um einen hierarchisch verteilten Key-Value-Store. Er dient als Speicher für kritische Informationen von verteilten Anwendungen, so z.B. von Clustern oder komplexen IOT-Systemen. Im Umfeld von PostgreSQL und Patroni wird etcd genutzt, um festzulegen, welches System führend (Leader) ist und welche Systeme von diesem replizieren (Replica). Diese Art von Diensten wird auch DCS (Distributed Consensus Store) genannt. Generell können unterschiedliche DCS-Varianten mit Patroni genutzt werden (so auch z.B. Consul, Zookeeper).

„Last but not least" erfolgte die Installation von Patroni inkl. einer grundlegenden Python-Umgebung mit einiges Zusatzpaketen.

root@node1:/# apt-get install python3-pip python3-dev libpq-dev -y
root@node1:/# pip3 install --upgrade pip
root@node1:/# pip install patroni
root@node1:/# pip install python-etcd
root@node1:/# pip install psycopg2
 

„YAML" nicht rum. 

Die Grundlage unseres Clusters ist etcd. Die Konfiguration erfolgt im YAML-Format uns muss auf jedem Knoten erfolgen: 

root@node1:/# su - postgres
postgres@node1:~$ mkdir patroni
postgres@node1:~$ cd patroni/
postgres@node1:~/patroni$ cat etcd.yml
    name: 'node1'
    listen-peer-urls: 'http://172.28.0.2:2380'
    listen-client-urls: 'http://172.28.0.2:2379, http://127.0.0.1:2379'
    initial-advertise-peer-urls: 'http://172.28.0.2:2380'
    advertise-client-urls: 'http://172.28.0.2:2379'
    initial-cluster: 'node1=http://172.28.0.2:2380,node2=http://172.28.0.3:2380,node3=http://172.28.0.4:2380'
    initial-cluster-state: 'new'
    initial-cluster-token: 'etcd-cluster-1'
 

Nachdem auch die anderen Knoten (mit den entsprechenden Anpassungen der Hostnamen und IP-Adressen) erfolgt ist, kann der etcd-Dienst gestartet und getestet werden: 

postgres@node1:~/patroni$ etcd --config-file etcd.yml > etcd.log 2>&1 & # auf allen drei Knoten
postgres@node1:~/patroni$ etcdctl member list
9d46a62f4e15a43c: name=node2 peerURLs=http://172.28.0.3:2380 clientURLs=http://172.28.0.3:2379 isLeader=false
ca69017d8279746a: name=node1 peerURLs=http://172.28.0.2:2380 clientURLs=http://172.28.0.2:2379 isLeader=true
f53b5cb1cfa54b7b: name=node3 peerURLs=http://172.28.0.4:2380 clientURLs=http://172.28.0.4:2379 isLeader=false
 

Wir sehen, dass etcd den zweiten Knoten „node1" zum „Leader" erkoren hat. 

Noch mehr zum „YAML"en…

Auch Patroni wird per YAML konfiguriert. Beim initialen Aufruf wird auf dem ersten Knoten das „initdb"-Kommando zur Erstellung der Datenbank aufgerufen. Zusätzlich werden einige weitere User-Accounts zur Cluster-Steuerung angelegt.

postgres@node1:~/patroni$ cat node1.yml
    scope: ordix_cluster
    name: node1
    
    restapi:
      listen: 172.28.0.2:8008
      connect_address: 172.28.0.2:8008
    
    etcd:
      #Provide host to do the initial discovery of the cluster topology:
      hosts: 172.28.0.2:2379,172.28.0.3:2379,172.28.0.4:2379
    
    bootstrap:
      # this section will be written into Etcd:/<namespace>/<scope>/config after initializing new cluster
      # and all other cluster members will use it as a `global configuration`
      dcs:
        ttl: 10
        loop_wait: 5
        retry_timeout: .10
        maximum_lag_on_failover: 1048576
        postgresql:
          use_pg_rewind: true
          use_slots: true
          parameters:
            hot_standby: "on"
            wal_keep_segments: 20
            max_wal_senders: 8
            max_replication_slots: 8
    
      # some desired options for 'initdb'
      initdb:  # Note: It needs to be a list (some options need values, others are switches)
      - encoding: UTF8
      - data-checksums
    
      pg_hba:  # Add following lines to pg_hba.conf after running 'initdb'
      - host replication replicator 172.28.0.2/32 md5
      - host replication replicator 172.28.0.3/32 md5
      - host replication replicator 172.28.0.4/32 md5
      - host all all 0.0.0.0/0 md5
    
      # Some additional users users which needs to be created after initializing new cluster
      users:
        admin:
          password: ordix
          options:
            - createrole
            - createdb
    
    postgresql:
      listen: 172.28.0.2:5432
      connect_address: 172.28.0.2:5432
      data_dir: /var/lib/postgresql/pgsql
      pgpass: /tmp/pgpass0
      authentication:
        replication:
          username: replicator
          password: ordix
        superuser:
          username: postgres
          password: ordix
        rewind:  # Has no effect on postgres 10 and lower
          username: rewind_user
          password: ordix
      parameters:
        unix_socket_directories: '.'
    
    tags:
        nofailover: false
        noloadbalance: false

postgres@node1:~/patroni$ patroni node1.yml > patroni_node1.log 2>&1 &
 
Damit wird das Cluster zum Leben erweckt und die Datenbank auf dem ersten Knoten erstellt und mit dem Start der Knoten 2 und 3 dorthin jeweils repliziert.

Das Cluster lässt sich mit dem folgenden Kommando überprüfen:

postgres@node1:~/patroni$ patronictl -c node1.yml topology
+---------+------------+---------+---------+----+-----------+
| Member  | Host       | Role    | State   | TL | Lag in MB |
+ Cluster: ordix_cluster (7066452659603940111) -+-----------+
| node1   | 172.28.0.2 | Leader  | running |  3 |           |
| + node2 | 172.28.0.3 | Replica | running |  3 |         0 |
| + node3 | 172.28.0.4 | Replica | running |  3 |         0 |
+---------+------------+---------+---------+----+-----------+
 

Cluster-Spielereien 

Nach diesem Erfolgserlebnis wollen wir noch ein bisschen mit dem Cluster spielen und ein Fehlerszenario simulieren. Dazu stoppen wir den ersten Knoten „node1" und schauen uns den Zustand des Clusters an. 

postgres@node2:~/patroni$ patronictl -c node2.yml topology
2022-02-19 17:20:36,371 - ERROR - Failed to get list of machines from http://172.28.0.2:2379/v2: MaxRetryError("HTTPConnectionPool(host='172.28.0.2', port=2379): Max retries exceeded with url: /v2/machines (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x7f7f2064f6d0>, 'Connection to 172.28.0.2 timed out. (connect timeout=1)'))")
+---------+------------+---------+---------+----+-----------+
| Member  | Host       | Role    | State   | TL | Lag in MB |
+ Cluster: ordix_cluster (7066452659603940111) -+-----------+
| node3   | 172.28.0.4 | Leader  | running |  4 |           |
| + node2 | 172.28.0.3 | Replica | running |  3 |         0 |
+---------+------------+---------+---------+----+-----------+
 

Der Knoten „node1" ist verschwunden und nicht mehr erreichbar. Der Cluster hat reagiert und den Knoten „node3" zum neuen Leader erkoren. Nun bringen wir den ersten Knoten „node1" wieder ins Spiel. Nach dem Start der etcd- und patroni-Dienste ist der Cluster wieder vollständig, wenn auch in einer neuen Rollenverteilung:

postgres@node2:~/patroni$ patronictl -c node2.yml topology
+---------+------------+---------+---------+----+-----------+
| Member  | Host       | Role    | State   | TL | Lag in MB |
+ Cluster: ordix_cluster (7066452659603940111) -+-----------+
| node3   | 172.28.0.4 | Leader  | running |  4 |           |
| + node1 | 172.28.0.2 | Replica | running |  3 |         0 |
| + node2 | 172.28.0.3 | Replica | running |  4 |         0 |
+---------+------------+---------+---------+----+-----------+
 

Zum guten Schluss versuchen wir den Originalzustand wieder herzustellen. Dazu „switchen" wir den „Leader" zurück auf „node1".

postgres@node2:~/patroni$ patronictl -c node2.yml switchover
Master [node3]:
Candidate ['node1', 'node2'] []: node1
When should the switchover take place (e.g. 2022-02-19T18:25 )  [now]:
Current cluster topology
+--------+------------+---------+---------+----+-----------+
| Member | Host       | Role    | State   | TL | Lag in MB |
+ Cluster: ordix_cluster (7066452659603940111) +-----------+
| node1  | 172.28.0.2 | Replica | running |  3 |         0 |
| node2  | 172.28.0.3 | Replica | running |  4 |         0 |
| node3  | 172.28.0.4 | Leader  | running |  4 |           |
+--------+------------+---------+---------+----+-----------+
Are you sure you want to switchover cluster ordix_cluster, demoting current master node3? [y/N]: y
2022-02-19 17:25:59.00638 Successfully switched over to "node1"
+--------+------------+---------+---------+----+-----------+
| Member | Host       | Role    | State   | TL | Lag in MB |
+ Cluster: ordix_cluster (7066452659603940111) +-----------+
| node1  | 172.28.0.2 | Leader  | running |  3 |           |
| node2  | 172.28.0.3 | Replica | running |  4 |         0 |
| node3  | 172.28.0.4 | Replica | stopped |    |   unknown |
+--------+------------+---------+---------+----+-----------+
 

Direkt nach dem Switchover ist der Knoten „node3" noch nicht wieder als „Replica" online. Daher warten wir ein paar Sekunden und schauen uns den Zustand des Clusters erneut an. 

postgres@node2:~/patroni$ sleep 5
postgres@node2:~/patroni$ patronictl -c node2.yml topology
+---------+------------+---------+---------+----+-----------+
| Member  | Host       | Role    | State   | TL | Lag in MB |
+ Cluster: ordix_cluster (7066452659603940111) -+-----------+
| node1   | 172.28.0.2 | Leader  | running |  5 |           |
| + node2 | 172.28.0.3 | Replica | running |  5 |         0 |
| + node3 | 172.28.0.4 | Replica | running |  5 |         0 |
+---------+------------+---------+---------+----+-----------+
 

Nach einigen Sekunden hat sich das Cluster wieder „gefangen". Beide „Replica" hängen wieder am „Leader" „node1".

Fazit 

Ein PostgreSQL Cluster mit Patroni lässt sich schnell aufbauen und recht unkompliziert verwalten. Der Aufwand der Konfiguration hält sich in einem Basis-Setup in Grenzen.

Sie haben Fragen rund um den Betrieb von PostgreSQL? Sprechen Sie uns an. 

Seminarempfehlung

Principal Consultant bei ORDIX

 

Kommentare

Derzeit gibt es keine Kommentare. Schreibe den ersten Kommentar!
Freitag, 22. November 2024

Sicherheitscode (Captcha)

×
Informiert bleiben!

Bei Updates im Blog, informieren wir per E-Mail.

Weitere Artikel in der Kategorie