Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Heutzutage ist Hochverfügbarkeit von Diensten überall und jederzeit erforderlich, nicht nur in großen, teuren Projekten. Temporär nicht erreichbare Websites mit der Nachricht „Entschuldigung, Wartungsarbeiten“ begegnen uns zwar noch, doch meist lösen sie nur ein mildes Lächeln aus. Dazu kommt das Leben in der Cloud, in dem das Starten eines zusätzlichen Servers lediglich einen API-Aufruf erfordert, ohne sich um die „Hardware“-Betriebsabläufe kümmern zu müssen. Damit gibt es keine Entschuldigung mehr, warum ein kritisches System nicht zuverlässig mit Cluster-Technologien und Redundanz aufgebaut wurde.

Wir werden erläutern, welche Lösungen wir zur Sicherstellung der Zuverlässigkeit unserer Datenbanken in Betracht gezogen haben und zu welchem Ergebnis wir gekommen sind. Außerdem gibt es eine Demo mit weitreichenden Erkenntnissen.

Legacy in der Architektur der Hochverfügbarkeit

Dies wird besonders deutlich, wenn man die Entwicklung verschiedener Open-Source-Systeme betrachtet. Ältere Lösungen mussten Hochverfügbarkeitstechnologien integrieren, um der steigenden Nachfrage gerecht zu werden, und deren Qualität variierte. Die neuen Generationen setzen Hochverfügbarkeit als Grundlage ihrer Architektur. Zum Beispiel positioniert MongoDB Cluster als primären Anwendungsfall. Der Cluster lässt sich horizontal skalieren, was einen erheblichen Wettbewerbsvorteil für diese Datenbank darstellt.

Kehren wir zu PostgreSQL zurück. Es handelt sich um eines der ältesten und beliebtesten Open-Source-Projekte, dessen erste Veröffentlichung im Jahr 1995 stattfand. Das Projektteam betrachtete lange Zeit Hochverfügbarkeit nicht als eine Aufgabe, die auf Systemebene gelöst werden musste. Daher wurde die Replikationstechnologie zur Erstellung von Datenkopien erst in Version 8.2 im Jahr 2006 integriert, und zwar in Form des File-Shippings. In Version 9.0, die 2010 veröffentlicht wurde, kam die Streaming-Replikation hinzu, die die Grundlage für die Bildung verschiedener Cluster bildet. Das überrascht viele, die PostgreSQL nach Enterprise SQL oder modernen NoSQL-Lösungen kennenlernen – die Standardlösung der Community ist einfach ein Paar Master-Replica mit synchroner oder asynchroner Replikation. Dabei erfolgt das Master-Switching manuell, und das Switching der Clients wird ebenfalls als eigenständige Herausforderung betrachtet.

Wie wir beschlossen haben, ein zuverlässiges PostgreSQL zu etablieren und welche Optionen wir dafür gewählt haben

Dennoch wäre PostgreSQL nicht so populär geworden, wenn es nicht eine Vielzahl von Projekten und Werkzeugen gäbe, die helfen, eine ausfallsichere Lösung zu schaffen, die keine ständige Aufmerksamkeit erfordert. In der Cloud Mail.ru Cloud Lösungen (MCS) seit dem Start von DBaaS standen einzelne PostgreSQL-Server und Master-Slave-Paare mit asynchroner Replikation zur Verfügung.

Natürlich wollten wir das Leben aller vereinfachen und eine PostgreSQL-Installation zugänglich machen, die als Grundlage für hochverfügbare Dienste dienen kann und bei der man nicht ständig aufpassen und nachts aufwachen muss, um einen Failover durchzuführen. In diesem Bereich gibt es sowohl bewährte Lösungen als auch neue Generationen von Tools, die die neuesten Entwicklungen nutzen.

Heute dreht sich das Problem der Hochverfügbarkeit nicht mehr um die Redundanz (die ist selbstverständlich), sondern um den Konsens — den Algorithmus zur Wahl des Leaders. Oft sind große Ausfälle nicht auf unzureichende Server zurückzuführen, sondern auf Probleme mit dem Konsens: es wurde kein neuer Leader gewählt, oder es gab zwei Leader in verschiedenen Rechenzentren usw. Ein Beispiel dafür ist der Ausfall des MySQL-Clusters von Github — sie haben einen detaillierten Post-Mortem-Bericht geschrieben..

Die mathematische Basis in diesem Bereich ist sehr ernsthaft. Einerseits gibt es die CAP-Theorie., die theoretische Einschränkungen bei der Erstellung von HA-Lösungen mit sich bringt, während auf der anderen Seite mathematisch bewiesene Konsensbestimmungsalgorithmen wie Paxos und Raft. Auf dieser Grundlage gibt es recht beliebte DCS (Dezentralisierte Konsens-Systeme) wie Zookeeper, etcd und Consul. Daher ist Vorsicht geboten, wenn ein Entscheidungssystem einen eigenen Algorithmus nutzt, der selbst programmiert wurde. Nach der Analyse einer Vielzahl von Systemen haben wir uns für Patroni entschieden – ein Open-Source-System, das überwiegend von der Firma Zalando entwickelt wird.

Als lyrischen Einschub möchte ich erwähnen, dass wir auch Multi-Master-Lösungen in Betracht gezogen haben, also Cluster, die horizontal für Schreibvorgänge skalierbar sind. Aufgrund von zwei Hauptgründen haben wir jedoch beschlossen, ein solches Cluster nicht zu implementieren. Erstens sind solche Lösungen sehr komplex und damit anfälliger. Es wird schwierig sein, eine stabile Lösung für alle Fälle zu finden. Zweitens wird PostgreSQL in diesem Fall nicht mehr nativ (clean) sein, einige Funktionen werden nicht verfügbar sein und es können verborgene Bugs in einigen Anwendungen auftreten.

Patroni

Wie funktioniert also Patroni? Die Entwickler haben das Rad nicht neu erfunden und sich entschieden, eine bewährte DCS-Lösung als Basis zu verwenden. Alle Fragen zur Konfiguration, Wahl des Leaders und Quorum werden ihm anvertraut. Wir haben dafür etcd gewählt.

Anschließend kümmert sich Patroni um die korrekte Anwendung aller Einstellungen auf PostgreSQL sowie um die Replikationseinstellungen und die Durchführung von Switchover- und Failover-Befehlen (also dem planmäßigen und unplanmäßigen Wechsel des Masters). Konkrekt in der MCS-Cloud kann ein Cluster aus einem Master, einer synchronen Replik und einer oder mehreren asynchronen Replikate erstellt werden. Das Vorhandensein einer synchronen Replik gewährleistet die Datensicherheit auf mindestens zwei Servern und genau diese Replik wird der Hauptkandidat für den Master sein.

Da etcd auf denselben Servern bereitgestellt wird, wird eine Anzahl von 3 oder 5 Servern für einen optimalen Quorum-Wert empfohlen. Ein solcher Cluster skaliert horizontal beim Lesen (zur Skalierung beim Schreiben habe ich oben geschrieben). Es ist jedoch zu beachten, dass asynchrone Replikate dazu neigen, hinterherzuhinken, insbesondere bei hoher Last.

Der Einsatz solcher Lesereplikate (Hot Standby) ist sinnvoll für Reporting- oder Analyse-Aufgaben und entlastet den Hauptserver.

Wenn Sie einen solchen Cluster selbst einrichten möchten, benötigen Sie:

  • mindestens 3 Server, die IP-Adressen und Firewall-Regeln zwischen ihnen eingerichtet haben;
  • Pakete für die Dienste etcd, Patroni, PostgreSQL installieren;
  • den etcd-Cluster konfigurieren;
  • den Dienst Patroni für die Zusammenarbeit mit PostgreSQL einrichten.

Das bedeutet, Sie müssen insgesamt etwa ein Dutzend Konfigurationsdateien korrekt erstellen und keine Fehler machen. Dafür lohnt es sich definitiv, ein Configuration Management Tool wie Ansible zu verwenden. Allerdings fehlt hier dennoch ein hochverfügbarer TCP-Load-Balancer. Dessen Einrichtung ist eine separate Aufgabe.

Für diejenigen, die einen fertigen Cluster benötigen, aber nicht selbst herumexperimentieren möchten, haben wir uns bemüht, das Leben zu erleichtern und einen fertigen Cluster auf Patroni in unserer Cloud eingerichtet, den man kostenlos testen kann. Neben dem Cluster selbst haben wir folgende Funktionen bereitgestellt:

  • einen TCP-Load-Balancer; er verweist immer auf den aktuellen Master, die synchrone oder asynchrone Replik, je nach Port;
  • eine API für den Wechsel des aktiven Masters von Patroni.

Sie können sowohl über die MCS-Cloud-API als auch über die Web-Konsole verbunden werden.

Demo

Um die Funktionen des PostgreSQL-Clusters in der MCS-Cloud zu testen, lassen Sie uns sehen, wie sich eine Live-Anwendung bei Problemen mit der Datenbank verhält.

Im Folgenden finden Sie den Code der Anwendung, die künstliche Ereignisse protokolliert und diese auf dem Bildschirm anzeigt. Bei Fehlern informiert sie darüber und setzt ihre Arbeit im Loop fort, bis wir sie mit der Kombination Strg + C stoppen.

from __future__ import print_function

from datetime import datetime
from random import randint
from time import sleep
import psycopg2


def main():
    try:
        connection = psycopg2.connect(user = "admin",
                                      password = "P@ssw0rd",
                                      host = "89.208.87.38",
                                      port = "5432",
                                      database = "myproddb")

        cursor = connection.cursor()
        cursor.execute("SELECT version();")
        record = cursor.fetchone()
        print("Verbindung hergestellt zu", record[0])

        cursor.execute(
            "INSERT INTO log VALUES ({});".format(randint(1, 10000)))
        connection.commit()
        cursor.execute("SELECT COUNT(event_id) from log;")
        record = cursor.fetchone()
        print("Wert protokolliert, Gesamtanzahl: {}".format(record[0]))
    except Exception as error:
        print ("Fehler beim Verbinden mit PostgreSQL", error)
    finally:
        if connection:
            cursor.close()
            connection.close()
            print("Verbindung geschlossen")


if __name__ == '__main__':
    try:
        while True:
            try:
                print(datetime.now())
                main()
                sleep(3)
            except Exception as e:
                print("Fehler gefangen:n", e)
                sleep(1)
    except KeyboardInterrupt:
        print("Beenden")

Die Anwendung benötigt PostgreSQL. Lassen Sie uns einen Cluster in der MCS-Cloud erstellen, indem wir die API verwenden. In einem regulären Terminal, wo das Token für den API-Zugang in der Variable OS_TOKEN gespeichert ist (dies kann mit dem Befehl openstack token issue erhalten werden), geben Sie die Befehle ein:

Cluster erstellen:

cat < pgc10.json
{"cluster":{"name":"postgres10","allow_remote_access":true,"datastore":{"type":"postgresql","version":"10"},"databases":[{"name":"myproddb"}],"users":[{"databases":[{"name":"myproddb"}],"name":"admin","password":"P@ssw0rd"}],"instances":[{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}},{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}},{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}}]}}
EOF

curl -s -H "X-Auth-Token: $OS_TOKEN" 
-H 'Accept: application/json' 
-H 'Content-Type: application/json' 
-d @pgc10.json https://infra.mail.ru:8779/v1.0/ce2a41bbd1434013b85bdf0ba07c770f/clusters

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Sobald der Cluster den Status aktiv erreicht, werden alle Felder aktualisiert – der Cluster ist bereit.

Im GUI:

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Lass uns versuchen, uns zu verbinden und eine Tabelle zu erstellen:

psql -h 89.208.87.38 -U admin -d myproddb
Passwort für Benutzer admin:
psql (11.1, Server 10.7)
Tippe "help" für Hilfe.

myproddb=> CREATE TABLE log (event_id integer NOT NULL);
CREATE TABLE
myproddb=> INSERT INTO log VALUES (1),(2),(3);
INSERT 0 3
myproddb=> SELECT * FROM log;
 event_id
----------
        1
        2
        3
(3 Zeilen)

myproddb=>

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

In der Anwendung geben wir die aktuellen Einstellungen für die Verbindung zu PostgreSQL an. Wir werden die Adresse des TCP-Loadbalancers angeben, sodass ein manuelles Umschalten auf die Adresse des Masters entfällt. Lassen Sie uns ihn starten. Wie zu sehen ist, werden die Ereignisse erfolgreich in der Datenbank protokolliert.

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Geplantes Umschalten des Masters

Jetzt testen wir die Funktion unseres Systems beim geplanten Umschalten des Masters:

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Wir beobachten die Anwendung. Wir sehen, dass der Betrieb der Anwendung tatsächlich unterbrochen wird, jedoch dauert dies nur wenige Sekunden, in diesem speziellen Fall maximal 9.

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Ausfall der Maschine

Jetzt versuchen wir, einen Ausfall der virtuellen Maschine, des aktuellen Masters, zu simulieren. Man könnte die virtuelle Maschine einfach über die Horizon-Schnittstelle ausschalten, aber das wäre ein reguläres Ausschalten. Ein solches Umschalten würde von allen Diensten, einschließlich Patroni, verarbeitet werden.

Wir benötigen jedoch ein unerwartetes Ausschalten. Deshalb habe ich unsere Administratoren gebeten, aus Testgründen die virtuelle Maschine — den aktuellen Master — auf unregelmäßige Weise abzuschalten.

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Währenddessen arbeitete unsere Anwendung weiter. Natürlich könnte ein solches Not-Umschalten des Masters nicht unbemerkt bleiben.

2019-03-29 10:45:56.071234
Verbindung zu PostgreSQL 10.7 auf x86_64-pc-linux-gnu geöffnet, kompiliert von gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-Bit
Wert protokolliert, Gesamtsumme: 453
Verbindung geschlossen
2019-03-29 10:45:59.205463
Verbindung zu PostgreSQL 10.7 auf x86_64-pc-linux-gnu geöffnet, kompiliert von gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-Bit
Wert protokolliert, Gesamtsumme: 454

Verbindung geschlossen
2019-03-29 10:46:02.661440
Fehler beim Verbinden mit dem PostgreSQL-Server, Verbindung wurde unerwartet geschlossen
        Dies bedeutet wahrscheinlich, dass der Server abnormal beendet wurde
        bevor oder während der Verarbeitung der Anfrage.

Fehler gefangen:
 lokale Variable 'connection' wurde vor der Zuweisung referenziert
……………………………………………………….. - hier sind einige Fehler
2019-03-29 10:46:30.930445
Fehler beim Verbinden mit dem PostgreSQL-Server, Verbindung wurde unerwartet geschlossen
        Dies bedeutet wahrscheinlich, dass der Server abnormal beendet wurde
        bevor oder während der Verarbeitung der Anfrage.

Fehler gefangen:
 lokale Variable 'connection' wurde vor der Zuweisung referenziert
2019-03-29 10:46:31.954399
Verbindung zu PostgreSQL 10.7 auf x86_64-pc-linux-gnu geöffnet, kompiliert von gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-Bit
Wert protokolliert, Gesamtsumme: 455
Verbindung geschlossen
2019-03-29 10:46:35.409800
Verbindung zu PostgreSQL 10.7 auf x86_64-pc-linux-gnu geöffnet, kompiliert von gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-Bit
Wert protokolliert, Gesamtsumme: 456
Verbindung geschlossen
^Cexit

Wie zu erkennen ist, konnte die Anwendung ihren Betrieb in weniger als 30 Sekunden fortsetzen. Ja, eine bestimmte Anzahl von Nutzern des Dienstes wird die Probleme bemerken. Allerdings handelt es sich um eine schwerwiegende Serverstörung, die nicht häufig vorkommt. Außerdem hätte ein Mensch (Administrator) kaum so schnell reagieren können, es sei denn, er hätte bereit am Terminal gesessen mit einem Skript zum Umschalten.

Fazit

Meiner Meinung nach bietet ein solcher Cluster enorme Vorteile für Administratoren. Im Grunde werden schwerwiegende Störungen und Ausfälle von Datenbankservern für die Anwendung und somit für den Nutzer nicht erkennbar sein. Es wird nicht nötig sein, in Eile etwas zu reparieren und auf temporäre Konfigurationen, Server usw. umzuschalten. Und wenn eine solche Lösung als fertiger Cloud-Service genutzt wird, entfällt der Zeitaufwand für die Vorbereitung. So kann man sich interessanteren Aufgaben widmen.

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster