Simple PostgreSQL-HA-Cluster

Diese Anleitung richtet sich an diejenigen, die mit wenig Aufwand eine PostgreSQL-Datenbank zur Verfügung stellen möchten, die auch beim Ausfall eines Servers ihre Daten nicht verliert und eine Art "Hochverfügbarkeit für Arme" anbietet.
Es existieren bereits Systeme, die eine "echte" Hochverfügbarkeit bietet, erszeugen aber ungleich mehr Aufwand und Systemkenntnisse. Wer also "echte" HA sucht, die auch Standortübergreifend funktioniert, dem sei zu "Patroni" geraten, oder aber gleich ein Cluster mit Corosync und Pacemaker

In diesem Beispiel wird ein Debian bzw. Ubuntu-System ausgegangen, das in 2 Instanzen bereit steht, die sich ihrerseits auch in demselben IP-Subnetz befinden. In dieser ersten Variante wird ein manuelles Failover vorausgesetzt. Wenn ein Server ausfällt, muss manuell der Cluster wieder in Synchronisation gebracht werden. Die meisten der zitierten Befehle und aktionen erfolgen als User "root" (unter Ubuntu: "sudo- i"), wird ein anderer user verwendet, wird es explizit erwähnt!

1. Server-Installation

Zunächst werden zwei reale oder virtuelle Server mit dem Betriebssytem versehen. Hier wird nur eine minimale Installation notwendig, die "Server-Variante" des Ubuntu Linux ist völlig ausreichend. Wichtig ist die SecureShell und die Datenbank und die zugehörigen Tools. Der befehl dazu:

   apt-get install openssh-server openssh-client postgresql postgresql-client

2. Netzwerkkonfiguration

In diesem Beispiel werden die beiden Netz-Adressen manuell eingetrahen. Das zugehörige Subnetz sei hier 192.168.10.0/24, Default Gateway ist 192.168.10.1. Namensserver sind in diesem speziellen Beispiel ohne Relevanz, weil wir uns auf die feste Konfiguration der Server verlassen wollen. In großen Umgebungen kann allerdings schwerlich auf DHCP und DNS verzichtet werden, doch behandeln wir hier nur eine minimal-Konfiguration. der erste server sei hier pg-cluster1, der zweite pg-cluster2. Die datei /etc/hosts bekommt daher diese beiden zusätzlichen Einträge:

        192.168.10.11  pg-cluster1
        192.168.10.12  pg-cluster2

3. Konfiguration des Master-Servers (pg-cluster1)

Für den Anfang wird der erste Rechner pg-cluster1 als Masterserver auserkoren, dager wird an diesem die Konfiguration begonnen:

Die Datenbank wird gestartet und der DB-Superuser konfiguriert. Der Einfachheit halber wird der User postgres mit einem einfachen Passwort ("admin") versehen und auch zur Replikation verwendet.
VORSICHT: DIESE EINSTELLUNG AUF KEINEN FALL FUER PRODUKTIVZWECKE VERWENDEN!!!!!
Der User postgres sollte gar nicht verwendet werden, sondern ein selbst erstellter user mit Superuser-Rechten, sowie ein dedizierter Replikationsuser.

    template1=# alter user postgres with password 'admin';
    ALTER ROLE
    template1=#

Zum Test wird an dieser Stelle eine Datenbank angelegt, damit die Eigenschaften des Clusters und der Replikation überprüft werden können.

    postgres@pg-cluster1:~$ psql template1
    psql (9.5.6)
    Type "help" for help.

    postgres@pg-cluster1:~$ createdb db01
    postgres@pg-cluster1:~$ psql db01
    psql (9.5.6)
    Type "help" for help.
    
    db01=# create table meintest(uid serial, description varchar(20));
    CREATE TABLE
    db01=# insert into meintest (description) values ('Erster');
    INSERT 0 1
    db01=# insert into meintest (description) values ('Zweiter');
    INSERT 0 1
    db01=# select * from meintest;
     uid | description
    -----+-------------
       1 | Erster
       2 | Zweiter
    (2 rows)

Unter Root-Berechtigung wird nun die Haupt-Konfigurationsdatei /etc/postgresql/9.5/main/postgresql.conf verändert. Bitte ersetzen Sie die den Teil das Pfades, der die Versionsnummer darstellt (hier: 9.5) mit der bei Ihnen eingesetzten PostgreSQL-Version. In dieser Datei finden Sie bereits eine hanze Reihe Werte, die bereits durch die Installation gesetzt wurden. di zu ändernden Zeilen sind diese:

      listen_addresses = '*'
       wal_level = logical
       max_wal_senders = 10
       hot_standby = on
       wal_keep_segments = 8
       hot_standby = on

den Rest belassen Sie so, wie Sie sie vorfinden!

Analog wird die datei /etc/postgresql/9.5/main/pg_hba.conf angepasst, in dem diese beiden Zeilen hinzu gefügt werden:

       host    replication     postgres        192.168.10.12/32              md5
       host    replication     postgres        192.168.10.11/32              md5

Jetzt wird es zeit für einen Neustart der Datenbank:

systemctl restart postgresql

Die Datenbank sollte danach wieder vollständig verfügbar sein.

3. Konfiguration des Slave-Servers (pg-cluster2)

Auf dem Slave wird zunächst die vorhandene Datenbank gelöscht und mit einem Replikat des Master-Servers ersetzt. Zuerst wird die Datenbank gestoppt

    systemctl stop postgresql

Ändern Sie dazu die Felder der /etc/postgresql/9.5/main/postgresql.conf wie beim Master-Server:

      listen_addresses = '*'
       wal_level = logical
       max_wal_senders = 3
       wal_keep_segments = 8
       hot_standby = on

Auch die /etc/postgresql/9.5/main/pg_hba.conf wird ergänzt:

       host    replication     postgres        192.168.10.11/32              md5
       host    replication     postgres        192.168.10.12/32              md5

Dann erzeugen Sie als user "postgres" die Replikation. Zunächst werden alle Dateien der alten, nicht mehr benötigten Datenbank gelöscht:

    rm -r var/lib/postgresql/9.5/main/*

Die gelöschten Dateien werden mit einem Replikat des Masterservers pg-cluster1 ersetzt. Dazu wird dieser Befehl verwendet:

    pg_basebackup -D /var/lib/postgresql/9.5/main -h pg-cluster1 --checkpoint=fast --xlog-method=stream -R

Das Passwort ist hier 'admin'!
Hier wird automatisch die Datei recovery.conf erzeugt, die die Zugangsdaten zum Master-Server enthält. Wird dann der Postgres-Server wieder gestartet, begint er sich automatich in den Recovery-Modus, und ist auch durch das gesetzte hot_standby=on für den ReadOnly-Zugriff bereit. Die Datenbank wird wieder als User "root" gestartet:

    systemctl start postgresql

Test: Auf dem Master wird eine Tabelle verändert:

      insert into meintest (description) values ('Zweiter');

Auf dem Slave wird die Tabelle gelesen und die neue Zeile bereits angezeigt: select * from meintest; uid | description -----+------------- 1 | Erster 2 | Zweiter Wird eine Schreib-Operation auf dem Slave versucht, wird sie abgewiesen:

      db01=# update meintest set description='Fuenf' where uid=5;
      ERROR:  cannot execute UPDATE in a read-only transaction

HAVARIE 1: Master fällt aus.

Auf dem alten Slave pg-cluster2 als user postgres

ausführen:

      /usr/lib/postgresql/9.5/bin/pg_ctl -D/var/lib/postgresql/9.5/main promote

Fertig. Hier ist kein Restart nötig!!! Der Server ist ab sofort im RW-Modus und enthält nun die aktive Datenbank! Der alte Master (pg-cluster1) muss nun zum neuen Slave gemacht werden. Zunächst muss der alte Server irgendwie aus einem Backup gezogen, oder manuell nach obigen Schema nachgebaut werden. Im ersteren Fall darf der Server aber nicht "einfach so gestartet werden, sondern die Daten vom neuen Master übernommen werden. Das erfolgt mit diesen Schritten: a)Sicherstellen das die DB nicht läuft:

systemctl stop postgresql

Weitere Befehler werden als User postgres ausgeführt (su - postgres):
b) Verzeichnis entsorgen

rm -r /var/lib/postgresql/9.5/main/*

c) Die Dateien /etc/postgresql/9.5/main/postgresql.conf und /etc/postgresql/9.5/main/pg_hba.conf nach obigen Vorbild kontrolliern und ggf. ändern.
Dateien replizieren:

pg_basebackup -D /var/lib/postgresql/9.5/main -h pg-cluster2 --checkpoint=fast --xlog-method=stream -R

Das Passwort ist hier wieder 'admin'!
d) Als User root wird die Datenbank wieder gestartet:

systemctl start postgresql

Damit ist der Server wieder vollständig.

HAVARIE 2: Slave fällt aus.

An dieser Stelle wird der Zustand des Clusters nicht verändert. Der Slave wird mit den obigen Befehlen unter Postgres wieder hergestellt:

    rm -r /var/lib/postgresql/9.5/main/*
    pg_basebackup -D /var/lib/postgresql/9.5/main -h pg-cluster1 --checkpoint=fast -x -P -R

Automatisches Verfahren

Das Verfahren für einen automatischen Wechsel des Master/Slave-Zustandes kann beim Starten geschehen und lässt sich im Pseudocode folgendermaßen beschreiben:

if [remote_db_avaliable]
then
        if [local_db_is_slave]
        then
                if [remote_db_is_master]
                then
                        start postgres
                else
                        if [Quorum_avaliable]
                        then
                                Stop remote DB (STONITH)
                                Promote localDB
                                start postgres
                        else
                                Stop remote DB
                                STOP DB
                        fi
                fi
        else
                if [remote_db_is_master]
                then
                        Demote local DB
                        start postgres
                else
                        start postgres
                fi
        fi
else
        if [Quorum_avaliable]
        then
                if [local_db_is_slave]
                then
                        Promote local DB
                        start postgres
                else
                        start postgres
                fi
        else
                STOP DB
        fi
fi

Eine Abfrage auf den Master-/Slave-Zustand kann durch eine SQL-Abreage geschehen:

select pg_is_in_recovery();
        -> "f" (false) ==> Master
        -> "t" (true) ==> Slave

Alternativ kann per Shell Befehl daif Existenz der Datei recovery.con getestet werden:

test -f /etc/postgresql/9.5/main/recovery.conf
        -> false ==> Master
        -> true ==> Slave

Promote zum Master:

    /usr/lib/postgresql/9.5/bin/pg_ctl -D/var/lib/postgresql/9.5/main promote

Demote zum Slave:

    rm -r /var/lib/postgresql/9.5/main/*
    pg_basebackup -D /var/lib/postgresql/9.5/main -h pg-cluster1 --checkpoint=fast -x -P -R