PostgreSQL-Datenbank-Referenzimplementierung in GDC mit Air Gap

In diesem Leitfaden wird ausführlich beschrieben, wie Sie einen hochverfügbaren PostgreSQL-Stack in einer isolierten Google Distributed Cloud-Umgebung (GDC) über drei Zonen hinweg bereitstellen. Sie erfahren, wie Sie die erforderlichen Softwareartefakte vorbereiten, die Ziel-VMs booten und Autobase verwenden, um den gesamten Bereitstellungsprozess zu automatisieren. In diesem Leitfaden wird Patroni als primäre Verwaltungsebene verwendet, um den PostgreSQL-Lebenszyklus zu orchestrieren und automatische Failovers zu verarbeiten.

Architektur

Die Architektur besteht aus einer Umgebung mit drei VMs, die auf drei Verfügbarkeitszonen verteilt sind.

Architektur mit drei VMs, auf denen ein gemeinsam genutzter Stapel von Diensten ausgeführt wird.

Jede VM ist identisch und führt einen gemeinsam genutzten Stack von Diensten aus:

  • PostgreSQL 17: Das relationale Datenbankmodul.
  • Patroni: Der Hochverfügbarkeitsmanager. Er verwaltet den Lebenszyklus des PostgreSQL-Prozesses und führt automatische Failovers durch. Er stellt eine HTTPS-REST API auf Port 8008 (Endpunkt /primary) bereit, die vom Load-Balancer verwendet wird, um den aktuellen Leader zu identifizieren.
  • etcd: Der verteilte Konfigurationsspeicher (Distributed Configuration Store, DCS). Er bietet die Konsensschicht für die Leader-Wahl und speichert die Konfiguration von Patroni.
  • PgBouncer: Ein Verbindungspooler, der vor PostgreSQL steht, um den Verbindungs-Overhead zu stabilisieren. Er bietet den empfohlenen Einstiegspunkt für Anwendungstraffic auf Port 6432.

Der Stack umfasst auch einen isolierten globalen L4-Load-Balancer von GDC, einen von der Plattform verwalteten Dienst, der eine stabile virtuelle IP-Adresse (VIP) bietet. Anwendungen stellen eine Verbindung zur stabilen VIP auf Port 6432 her, die der Load-Balancer an PgBouncer auf der aktuellen Leader-VM weiterleitet. PgBouncer leitet die Anfrage dann an die lokale PostgreSQL-Instanz weiter. Um den Trafficfluss zu verwalten, fragt der Load-Balancer die HTTPS-Endpunkte von Patroni kontinuierlich als Systemdiagnose ab.

Die Systemdiagnose auf der Leader-VM gibt HTTP 200 OK zurück, um zu signalisieren, dass die VM für Traffic bereit ist. Die Systemdiagnosen auf den Replikat-VMs geben HTTP 503 Service Unavailable zurück, um den Load-Balancer anzuweisen, sie zu umgehen. Wenn der Leader ausfällt, wird ein neuer Leader gewählt und seine Patroni-Instanz beginnt, HTTP 200 OK zurückzugeben. Dadurch leitet der Load-Balancer den Traffic automatisch an den PgBouncer-Port der neuen VM weiter.

Um Hochverfügbarkeit zu gewährleisten und Datenverlust zu verhindern, basiert der Stack auf dem Konzept eines Quorums. Bei drei VMs muss eine Mehrheit von mindestens zwei Mitgliedern fehlerfrei sein und kommunizieren, um einen Leader zu wählen und betriebsbereit zu bleiben. Dieser mehrheitsbasierte Konsens, der von etcd und Patroni verwaltet wird, ermöglicht es dem Stack, den vollständigen Ausfall einer einzelnen VM oder Zone automatisch zu tolerieren.

Hinweise zur Leistung

Berücksichtigen Sie bei der Planung Ihrer Bereitstellung die folgenden konkreten Faktoren, um Leistung und Zuverlässigkeit zu optimieren:

  • Hardwaregröße: Die Anforderungen variieren je nach Arbeitslast. Verwenden Sie diese Standardprofile als Ausgangspunkt für jede VM:
    • Entwicklung/Proof of Concept: 2 vCPUs, 8 GB RAM (Minimum für stabilen Betrieb).
    • Kleine Produktion: 4 vCPUs, 16 GB RAM. Geeignet für interne Tools mit moderater Parallelität.
    • Standardproduktion: 8 vCPUs, 32 GB RAM. Die empfohlene Baseline für geschäftskritische Anwendungen.
    • Hoher Durchsatz: 16 oder mehr vCPUs, 64 GB oder mehr RAM. Für Arbeitslasten, die umfangreiches Daten-Caching im Arbeitsspeicher erfordern (gemeinsam genutzte PostgreSQL-Puffer).
  • Speicherleistung: Hochleistungsspeicher ist unerlässlich. SSDs werden für die Stabilität von etcd dringend empfohlen. etcd reagiert sehr empfindlich auf die Latenz beim Schreiben auf das Laufwerk. Die offiziellen Hardware-Richtlinien für etcd empfehlen eine p99-Latenz für WAL-fdatasync von < 10 ms.
  • Netzwerklatenz: Die Latenz zwischen VMs wirkt sich direkt auf die Replikationsleistung aus:
    • etcd-Quorum: Die durchschnittliche Umlaufzeit (Round-Trip Time, RTT) sollte weniger als 50 ms (idealerweise weniger als 10 ms) betragen, um Zeitüberschreitungen bei der Wahl und Clusterinstabilität zu vermeiden.
    • Synchrone Replikation: Wenn konfiguriert, muss jede Schreibtransaktion auf die Bestätigung eines Replikats warten. Die Latenz zwischen Zonen in einer isolierten GDC-Umgebung beträgt in der Regel weniger als 1 ms. Das ist hervorragend, um den Schreib-Overhead minimal zu halten (in der Regel 10–30%).
  • Die Rolle von PgBouncer: PostgreSQL erstellt für jede Verbindung einen neuen Betriebssystemprozess, der etwa 10 MB RAM verbraucht und Kosten für den CPU-Kontextwechsel verursacht. PgBouncer reduziert diesen Overhead, indem ein Pool persistenter Verbindungen verwaltet wird. So kann die Datenbank Tausende von Anwendungsverbindungen mit deutlich weniger Backend-Prozessen verarbeiten.
  • Sidecar-Komponenten: Patroni und etcd sind ressourcenschonend, erfordern aber eine konsistente CPU-Verfügbarkeit. In Szenarien mit hoher Last sollten Sie darauf achten, dass die VMs auf Hypervisor-Ebene nicht überlastet sind, um zu vermeiden, dass CPU-Zyklen „gestohlen“ werden, die für Heartbeats und die Leader-Wartung erforderlich sind.
  • Kernel-Optimierung: Die Autobase-Automatisierung wendet automatisch Optimierungen an, die für PostgreSQL von Vorteil sind, z. B. die Konfiguration von sysctl Parametern (z. B. vm.swappiness, net.core.somaxconn) und die Deaktivierung von Transparent Huge Pages (THP). Diese Änderungen reduzieren den Overhead bei der Speicherverwaltung und verbessern den Netzwerkdurchsatz für Datenbankinstanzen mit hohem Traffic.

Hinweis

Bevor Sie mit der Bereitstellung beginnen, müssen Sie sicherstellen, dass Ihre Umgebung die folgenden Anforderungen erfüllt.

VM-Anforderungen prüfen

Für diese Anleitung müssen Sie drei VMs in Ihrem GDC mit Air Gap-Projekt erstellen. Sie müssen die folgenden Punkte und Anforderungen für die VMs berücksichtigen:

  • Zonenverteilung: Damit diese Bereitstellung wirklich widerstandsfähig gegen Zonenausfälle ist, sollten Sie die VMs auf drei verschiedene Verfügbarkeitszonen verteilen. Die Bereitstellung bleibt jedoch identisch, wenn sich die VMs in zwei oder sogar einer einzigen Zone befinden. Am wichtigsten ist, dass alle VMs über ihre internen IP-Adressen über das Netzwerk miteinander kommunizieren können.
  • Betriebssystem: In dieser Anleitung wird davon ausgegangen, dass Sie Ubuntu 22.04-Images verwenden. Die weiteren Schritte in diesem Leitfaden können abweichen, wenn Sie eine andere Distribution verwenden.
  • Ressourcen: Für diese Anleitung sollten Sie mindestens 2 CPUs und 8 GB Arbeitsspeicher pro VM bereitstellen. In der Produktion müssen Sie Ressourcen bereitstellen, die für Ihre spezifischen Arbeitslasten geeignet sind (siehe Hinweise zur Leistung).
  • Netzwerk-IPs: Sie sollten sich sowohl die internen als auch die externen IP-Adressen für jede VM notieren. In diesem Leitfaden verwenden Sie externe IPs für die Ansible-Steuerung, da Sie Befehle von einer externen Workstation aus ausführen. Interne IPs werden für die Kommunikation und Bindung zwischen Diensten verwendet. Wenn Sie eine Bootstrapper-VM im Netzwerk bereitgestellt haben, benötigen Sie nur die internen IPs.
  • Zugriff: Für den Bereitstellungsnutzer ist ein passwortloser sudo-Zugriff erforderlich, da die Ansible-Automatisierung administrative Aufgaben ausführen muss (Pakete installieren, Systemkonfigurationen ändern), ohne durch Passworteingabeaufforderungen blockiert zu werden.
  • SSH: Die schlüsselbasierte Authentifizierung muss aktiviert sein, damit Ansible sicher und nicht interaktiv eine Verbindung zu den Ziel-VMs herstellen kann.

Software für die lokale Workstation vorbereiten

Um die Bereitstellung zu verwalten und die isolierten Artefakte vorzubereiten, benötigen Sie eine Reihe von Automatisierungs- und Containerisierungstools, die auf Ihrer lokalen Workstation installiert sind.

  • Ansible 2.17.0 oder höher: Die Automatisierungs-Engine, die die Bereitstellungs-Playbooks und -Rollen ausführt.
  • Docker: Wird verwendet, um Betriebssystemabhängigkeiten in einer Umgebung abzurufen und zu verpacken, die mit den Ziel-VMs identisch ist (Ubuntu 22.04).
  • PostgreSQL-Client (psql): Erforderlich, um die Testabfragen auszuführen und die Datenreplikation von Ihrer lokalen Workstation aus zu prüfen.
  • Das Autobase-Repository:

    • Klonen Sie das Repository, um auf die Automatisierungs-Playbooks und -Rollen zuzugreifen: https://github.com/vitabaks/autobase
    • Checken Sie einen bestimmten Release aus (in diesem Leitfaden wird Version 2.5.2 verwendet):

      git checkout 2.5.2

      Die weiteren Schritte in diesem Leitfaden können abweichen, wenn Sie eine andere Distribution verwenden.

    • Um die Playbooks in diesem Leitfaden auszuführen, müssen Sie den lokalen autobase-Quellcode als Ansible-Sammlung installieren, damit die Rollenpräfixe aufgelöst werden können:

      cd autobase/automation
      ansible-galaxy collection install . --force
      

Umgebungsvariablen erstellen

In diesem Leitfaden verwenden Sie die folgenden Umgebungsvariablen, um Befehle zu vereinfachen. In diesen Variablen werden wichtige Parameter wie Ihre Projekt-ID, die Verfügbarkeitszonen für Ihre VMs, ihre Hostnamen und die Labels gespeichert, die vom Load-Balancer verwendet werden, um Ihren Cluster zu identifizieren. Legen Sie sie in Ihrer aktuellen Shell-Sitzung mit den tatsächlichen Werten für Ihre Umgebung fest (achten Sie darauf, dass die Zonen durch Leerzeichen getrennt sind).

Obwohl Sie beliebige Namen für Ihre VMs festlegen können, werden in diesem Leitfaden postgres-vm-1, postgres-vm-2 und postgres-vm-3 als willkürliche Beispielnamen für die Clusterknoten verwendet:

export PROJECT_ID="your-project-id"
export ZONES="zone1 zone2 zone3"
export VM1_NAME="postgres-vm-1"
export VM2_NAME="postgres-vm-2"
export VM3_NAME="postgres-vm-3"
export VM_LABEL="app=my-postgres-cluster"
export CLUSTER_NAME="my-postgres-cluster"

Ansible konfigurieren

Definieren Sie Ihre VM-Umgebung in der Datei inventory.ini mit der folgenden Vorlage:

[master]
postgres-vm-1 ansible_host=XX.XX.XX.XX hostname=postgres-vm-1 bind_address=XX.XX.XX.XX

[replica]
postgres-vm-2 ansible_host=XX.XX.XX.XX hostname=postgres-vm-2 bind_address=XX.XX.XX.XX
postgres-vm-3 ansible_host=XX.XX.XX.XX hostname=postgres-vm-3 bind_address=XX.XX.XX.XX

[postgres_cluster:children]
master
replica

[etcd_cluster]
postgres-vm-1
postgres-vm-2
postgres-vm-3

[all:vars]
ansible_user=...
ansible_ssh_private_key_file=~/.ssh/...
postgresql_version=17
with_haproxy_load_balancing=false
patroni_superuser_password=...
etcd_package_repo="file:///tmp/packages/etcd-v3.5.25-linux-amd64.tar.gz"
installation_method="packages"
install_postgresql_repo=false
install_timescale_repo=false
install_citus_repo=false
apt_repository=[]
yum_repository=[]
install_system_packages=false
patroni_installation_method=deb

Informationen zur Konfiguration:

  • [master] und [replica]: Definiert die primären und sekundären Datenbank-VMs. Verwenden Sie die tatsächlichen VM-Namen, die in den Umgebungsvariablen VM1_NAME, VM2_NAME und VM3_NAME festgelegt wurden.
  • [postgres_cluster:children]: Eine Gruppe, die sowohl die Master- als auch die Replikatknoten zusammenfasst, sodass Ansible den gesamten Datenbankcluster mit einem einzigen Befehl ansprechen kann.
  • [etcd_cluster]: Definiert die Knoten, die am etcd-Konsenscluster teilnehmen. Dazu gehören alle drei Datenbankknoten, um Hochverfügbarkeit zu gewährleisten.
  • ansible_host: (Für jede VM) Die externe Ingress-IP der VM, die von Ansible verwendet wird, um eine Verbindung zu dieser VM herzustellen. Ersetzen Sie XX.XX.XX.XX durch die tatsächliche externe IP-Adresse.
  • bind_address: (Für jede VM) Die interne IP-Adresse der VM. Ersetzen Sie XX.XX.XX.XX durch die tatsächliche interne IP-Adresse.
  • ansible_user: Der Remote-Nutzer, der von Ansible verwendet wird, um eine Verbindung zu den Ziel-VMs mit SSH herzustellen. Ersetzen Sie ... durch den tatsächlichen Nutzernamen.
  • ansible_ssh_private_key_file: Der lokale Pfad zum privaten SSH-Schlüssel, der für die Authentifizierung bei den Ziel-VMs verwendet wird. Ersetzen Sie ~/.ssh/... durch den tatsächlichen Pfad.
  • patroni_superuser_password: Das Passwort für den postgres-Nutzer. Verwenden Sie hier ein starkes, sicheres Passwort.
  • with_haproxy_load_balancing=false: Deaktiviert den lokalen HAProxy, da Sie den plattformnativen L4-Load-Balancer verwenden.
  • etcd_package_repo: Verweist auf den lokalen Pfad der etcd-Binärdatei im Bootstrap-Verzeichnis der VM.
  • installation_method="packages": Weist die Automatisierung an, Komponenten mit Betriebssystempaketen zu installieren, anstatt sie aus dem Quellcode zu kompilieren oder Python pip zu verwenden.
  • install_..._repo=false und _repository=[]: Diese Überschreibungen verhindern, dass Ansible versucht, eine Verbindung zum Internet herzustellen, um externe Repositories hinzuzufügen oder Paketlisten zu aktualisieren.
  • install_system_packages=false: Verhindert, dass die Automatisierung versucht, Pakete herunterzuladen und zu installieren, die Sie bereits während der Initialisierungs- oder Bootstrap-Phase bereitgestellt haben.
  • patroni_installation_method=deb: Weist die Rolle speziell an, das installierte .deb-Paket zu verwenden.

VMs initialisieren

Der Datenbank-Stack erfordert mehrere Betriebssystempakete und -bibliotheken, die möglicherweise nicht in Ihrem Ubuntu-Basis-Image enthalten sind. Da sich die VMs in einer isolierten Umgebung ohne Internetzugriff befinden, können sie diese Abhängigkeiten nicht selbst herunterladen.

Gehen Sie so vor, um dieses Problem zu beheben:

  • Verwenden Sie einen Docker-Container auf Ihrer lokalen Workstation, um alle erforderlichen Dateien herunterzuladen. Der folgende Befehl verwendet apt-rdepends, um rekursiv alle freigegebenen Bibliotheken und Abhängigkeiten zu identifizieren, die von den Zielanwendungen benötigt werden. Er konfiguriert das offizielle PostgreSQL-Repository im Container, um Artefakte der Version 17 abzurufen, und durchläuft dann die Liste der Abhängigkeiten, um einzelne .deb-Dateien herunterzuladen. Dabei werden Kernsystembibliotheken (z. B. libc6 oder hostname) herausgefiltert, um Versionskonflikte auf den Ziel-VMs zu vermeiden. Schließlich wird die eigenständige etcd-Binärdatei direkt von GitHub abgerufen.

    Erstellen Sie zuerst ein Verzeichnis für die Pakete:

    mkdir -p ./packages
    

    Führen Sie dann den Docker-Befehl aus, um alle erforderlichen Pakete und die etcd-Binärdatei herunterzuladen:

    docker run --rm --platform linux/amd64 -v "$(pwd)/packages:/packages" \
      ubuntu:22.04 bash -c "
      set -e
      apt-get update
      apt-get install -y ca-certificates curl gnupg apt-rdepends
    
      # Add PostgreSQL Repository
      curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | \
        gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
      echo 'deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main' > \
        /etc/apt/sources.list.d/pgdg.list
      apt-get update
    
      # Define Application Targets + Explicit dependencies needed for air-gap
      TARGETS='unzip tar pgbouncer patroni netdata postgresql-17 \
        postgresql-client-17 postgresql-contrib-17 \
        postgresql-server-dev-17 postgresql-17-dbgsym \
        python3-psycopg2 python3-click python3-yaml python3-prettytable \
        python3-urllib3 python3-tz python3-pip python3-setuptools \
        python3-cryptography moreutils vim jq acl zstd libjq1 \
        libpython3.10-stdlib libexpat1-dev zlib1g-dev libipc-run-perl \
        libtime-duration-perl libjson-perl libpython3-dev \
        libjs-sphinxdoc python3-wheel'
    
      # Resolve all recursive dependencies
      ALL_DEPS=\$(apt-cache depends --recurse --no-recommends --no-suggests \
        --no-conflicts --no-breaks --no-replaces --no-enhances \$TARGETS | \
        grep '^\w' | sort -u)
    
      cd /packages
      for pkg in \$ALL_DEPS; do
        if apt-cache show \"\$pkg\" > /dev/null 2>&1; then
          # Filter system core to avoid VM conflicts/breaks
          # We exclude core OS libraries (libc, systemd, etc.) because these
          # often cause version conflicts if the VM's patch level differs
          # from the online container.
          FILTER='base-files|debianutils|coreutils|findutils|diffutils|sed'
          FILTER+='|grep|gzip|hostname|ncurses|perl-base|libc6|binutils'
          FILTER+='|linux-libc|libc-bin|libc-dev-bin|systemd|dpkg|init'
          if [[ ! \"\$pkg\" =~ \$FILTER ]]; then
            apt-get download \"\$pkg\" || echo \"Failed \$pkg\"
          fi
        fi
      done
    
      # Download etcd binary
      if [ ! -f etcd-v3.5.25-linux-amd64.tar.gz ]; then
        curl -L https://github.com/etcd-io/etcd/releases/download/v3.5.25/\
    etcd-v3.5.25-linux-amd64.tar.gz -o etcd-v3.5.25-linux-amd64.tar.gz
      fi
    "
    
  • Verwenden Sie Ansible, um das Archiv gleichzeitig auf alle drei Ziel-VMs hochzuladen:

    ansible all -i inventory.ini -m copy -a "src=packages.tar.gz dest=/tmp/" -b
    
  • Löschen Sie alle vorhandenen Paketdaten auf den VMs und extrahieren Sie die neue tar-Datei:

    ansible all -i inventory.ini -m shell -a "rm -rf /tmp/packages && \
      mkdir -p /tmp/packages && tar -xzf /tmp/packages.tar.gz -C /tmp/packages" -b
    
  • Führen Sie eine nicht interaktive Installation aller heruntergeladenen .deb-Pakete durch. Um Probleme mit bestimmten Vorabhängigkeiten in einer isolierten Umgebung zu vermeiden, verwenden Sie das Flag --force-depends gefolgt von apt-get install -fy, um die Abhängigkeitsstruktur lokal aufzulösen:

    ansible all -i inventory.ini -m shell -a "DEBIAN_FRONTEND=noninteractive \
      NEEDRESTART_MODE=a dpkg -i --force-depends /tmp/packages/*.deb" -b
    
    ansible all -i inventory.ini -m shell -a "DEBIAN_FRONTEND=noninteractive \
      NEEDRESTART_MODE=a apt-get install -fy" -b
    
  • Beenden Sie sofort alle Dienste, damit sie nicht mit Standardzuständen ohne Konfiguration gestartet werden, bevor die Automatisierung bereit ist:

    ansible all -i inventory.ini -m shell -a \
      "systemctl stop patroni etcd pgbouncer postgresql || true" -b
    
  • Entfernen Sie schließlich die Standard-PostgreSQL-Cluster und alle vorhandenen etcd-Daten, um eine saubere Initialisierung zu ermöglichen:

    ansible all -i inventory.ini -m shell -a \
      "pg_dropcluster 17 main --stop || true" -b
    ansible all -i inventory.ini -m shell -a \
      "rm -rf /var/lib/postgresql/17/main/* /var/lib/etcd/default.etcd/*" -b
    

Datenbankinfrastruktur bereitstellen

Nachdem die VMs gebootet und das Inventar konfiguriert wurde, können Sie jetzt die Autobase-Automatisierungs-Playbooks mit Ansible verwenden, um den hochverfügbaren PostgreSQL-Stack bereitzustellen.

Führen Sie zuerst die Vorabprüfungen aus, um sicherzustellen, dass die Umgebung bereit ist:

ansible-playbook vitabaks.autobase.deploy_pgcluster -i inventory.ini \
  --tags pre_checks

Wenn die Prüfungen erfolgreich sind, fahren Sie mit der vollständigen Bereitstellung fort:

ansible-playbook vitabaks.autobase.deploy_pgcluster -i inventory.ini

Erwartete Ausgabe: Das Playbook sollte mit einer erfolgreichen Zusammenfassung (PLAY RECAP) abgeschlossen werden, in der alle Ziel-VMs als erreicht und aktualisiert angezeigt werden:

PLAY RECAP ********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=254  rescued=0    ignored=0
postgres-vm-1              : ok=160  changed=53   unreachable=0    failed=0    skipped=514  rescued=0    ignored=2
postgres-vm-2              : ok=116  changed=40   unreachable=0    failed=0    skipped=505  rescued=0    ignored=2
postgres-vm-3              : ok=116  changed=40   unreachable=0    failed=0    skipped=505  rescued=0    ignored=2

Bereitstellung prüfen

Nach Abschluss der Bereitstellung sollten Sie mehrere Prüfungen durchführen, um sicherzustellen, dass alle Komponenten ordnungsgemäß funktionieren.

HA-Status prüfen

Prüfen Sie den Status des Hochverfügbarkeitsmanagers, um die Rollen zu sehen, die jeder VM zugewiesen sind:

ansible master -i inventory.ini -m shell -a "patronictl list" -b

Beispielausgabe:

+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Leader  | running   |  1 |             |     |            |     |
| postgres-vm-2 | 10.253.1.253 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

Systemdiagnose-Endpunkte prüfen

Testen Sie, ob Patroni den Leader und die Replikate mithilfe der REST API korrekt identifiziert. Die Systemdiagnose der Leader-VM sollte 200 OK zurückgeben, während die Systemdiagnosen der Replikat-VMs 503 Service Unavailable zurückgeben sollten:

ansible ${VM1_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

ansible ${VM2_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

ansible ${VM3_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

Status der einzelnen VMs prüfen

Prüfen Sie die Bereitschaft aller PostgreSQL-Instanzen:

ansible postgres_cluster -i inventory.ini -m shell -a "pg_isready -p 5432" -b

Beispielausgabe:

postgres-vm-1 | CHANGED | rc=0 >>
/var/run/postgresql:5432 - accepting connections

postgres-vm-2 | CHANGED | rc=0 >>
/var/run/postgresql:5432 - accepting connections

postgres-vm-3 | CHANGED | rc=0 >>
/var/run/postgresql:5432 - accepting connections

etcd-Status prüfen

Prüfen Sie den Status der Konsensschicht auf allen VMs mit „localhost“ als Endpunkt:

ansible all -i inventory.ini -m shell -a "ETCDCTL_API=3 etcdctl \
  --endpoints=https://localhost:2379 \
  --cacert=/etc/etcd/tls/ca.crt \
  --cert=/etc/etcd/tls/server.crt \
  --key=/etc/etcd/tls/server.key \
  endpoint health" -b

Beispielausgabe:

postgres-vm-1 | CHANGED | rc=0 >>
https://localhost:2379 is healthy: successfully committed proposal: \
took = 28.78311ms

postgres-vm-2 | CHANGED | rc=0 >>
https://localhost:2379 is healthy: successfully committed proposal: \
took = 33.081265ms

postgres-vm-3 | CHANGED | rc=0 >>
https://localhost:2379 is healthy: successfully committed proposal: \
took = 24.414291ms

Globalen Load-Balancer konfigurieren

Um eine stabile virtuelle IP-Adresse (VIP) für den Datenbank-Stack bereitzustellen, konfigurieren Sie den plattformnativen globalen L4-Load-Balancer mit der gdcloud-Befehlszeile.

  • Voraussetzungen:

    • Sie benötigen die Rolle load-balancer-admin in Ihrem Projekt.
    • Wenden Sie ein Label auf Ihre VMs an, damit der Load-Balancer die Instanzen korrekt ansprechen kann, die er bedienen muss. Ersetzen Sie die Parameterwerte kubeconfig durch die entsprechenden kubeconfig-Dateien der Management API für jede Zone:

      kubectl --kubeconfig=ZONE_A_MANAGEMENT_API label VirtualMachine \
        -n ${PROJECT_ID} \
        ${VM1_NAME} \
        ${VM_LABEL}
      
      kubectl --kubeconfig=ZONE_B_MANAGEMENT_API label VirtualMachine \
        -n ${PROJECT_ID} \
        ${VM2_NAME} \
        ${VM_LABEL}
      
      kubectl --kubeconfig=ZONE_C_MANAGEMENT_API label VirtualMachine \
        -n ${PROJECT_ID} \
        ${VM3_NAME} \
        ${VM_LABEL}
      
  • Definieren Sie die Zugriffsebene für den Load-Balancer. Legen Sie EXTERNAL fest, wenn Sie eine Verbindung von außerhalb des Netzwerks des Projekts herstellen müssen, oder INTERNAL, wenn der Zugriff nur innerhalb der VPC erforderlich ist. In dieser Anleitung verwenden wir eine externe Einrichtung:

    export LB_SCHEME=EXTERNAL
    
  • Erstellen Sie eine Systemdiagnose. Der Load-Balancer verwendet die REST API von Patroni, um den Leader zu identifizieren:

    gdcloud compute health-checks create https ${CLUSTER_NAME}-hc \
      --project=${PROJECT_ID} \
      --port=8008 \
      --request-path="/primary" \
      --check-interval=10 \
      --timeout=5 \
      --healthy-threshold=2 \
      --unhealthy-threshold=3 \
      --global
    
  • Erstellen Sie für jede Zone, in der sich Ihre VMs befinden, ein separates zonales Backend:

    for zone in $(echo $ZONES); do
      gdcloud compute backends create ${CLUSTER_NAME}-backend-${zone} \
        --project=${PROJECT_ID} \
        --zone=${zone} \
        --labels="${VM_LABEL}"
    done
    
  • Erstellen Sie einen globalen Backend-Dienst:

    gdcloud compute backend-services create ${CLUSTER_NAME}-bes \
      --project=${PROJECT_ID} \
      --health-check="${CLUSTER_NAME}-hc" \
      --global
    
  • Fügen Sie dem globalen Dienst Ihre zonalen Backends hinzu:

    for zone in $(echo $ZONES); do
      gdcloud compute backend-services add-backend ${CLUSTER_NAME}-bes \
        --project=${PROJECT_ID} \
        --backend=${CLUSTER_NAME}-backend-${zone} \
        --backend-zone=${zone} \
        --global
    done
    
  • Erstellen Sie eine globale Weiterleitungsregel (die VIP). Diese Regel stellt die Datenbank auf Port 6432 bereit:

    gdcloud compute forwarding-rules create ${CLUSTER_NAME}-fr \
      --project=${PROJECT_ID} \
      --load-balancing-scheme=${LB_SCHEME} \
      --backend-service=${CLUSTER_NAME}-bes \
      --ip-protocol-port="TCP:6432" \
      --global
    
  • Rufen Sie die VIP-Adresse ab:

    LB_IP=$(gdcloud compute forwarding-rules describe ${CLUSTER_NAME}-fr \
      --project=${PROJECT_ID} \
      --load-balancing-scheme=${LB_SCHEME} \
      --global \
      --format=json \
      | jq -r '.metadata.annotations["networking.gke.io/forwardingRuleCIDR"]'  \
      | cut -d '/' -f 1)
    
    echo "The load balancer IP is: ${LB_IP}"
    
  • Erstellen Sie eine ProjectNetworkPolicy (PNP), um eingehenden Traffic zum PgBouncer-Port zuzulassen. Ersetzen Sie den Parameterwert kubeconfig durch die entsprechende kubeconfig-Datei der globalen API Ihrer Umgebung:

    kubectl --kubeconfig=GLOBAL_API_KUBECONFIG apply -f - <<EOF
    apiVersion: networking.global.gdc.goog/v1
    kind: ProjectNetworkPolicy
    metadata:
      name: allow-pgbouncer
      namespace: ${PROJECT_ID}
    spec:
      ingress:
      - ports:
        - port: 6432
          protocol: TCP
      policyType: Ingress
      subject:
        subjectType: UserWorkload
    EOF
    

Datenreplikation prüfen

Um zu bestätigen, dass der hochverfügbare Stack wie erwartet funktioniert, können Sie Beispieldaten auf dem Leader erstellen und prüfen, ob sie auf den Replikaten vorhanden sind.

Beispieldaten einfügen

Die Automatisierung generiert bei der ersten Bereitstellung ein zufälliges Passwort für den postgres-Nutzer, wenn in inventory.ini keines angegeben ist. Sie können es von jeder VM abrufen:

export PG_PASSWORD=$(ansible master -i inventory.ini -m shell -a \
  "grep -A10 'authentication:' /etc/patroni/patroni.yml | \
  grep -A3 'superuser' | grep 'password:' | awk '{ print \$2 }'" -b | \
  tail -n 1)
echo $PG_PASSWORD

Stellen Sie eine Verbindung zur VIP des Load-Balancers auf dem PgBouncer-Port (6432) her und erstellen Sie eine Beispieltable:

PGPASSWORD="${PG_PASSWORD}" psql -h ${LB_IP} -p 6432 -U postgres -c "
  CREATE TABLE employees (first_name TEXT, last_name TEXT);
  INSERT INTO employees (first_name, last_name) VALUES ('John', 'Doe');
"

Erwartete Ausgabe:

INSERT 0 1

Replikationsstatus prüfen

Führen Sie eine SELECT-Abfrage auf allen VMs aus, um sicherzustellen, dass die Daten vom Leader auf alle Replikate repliziert wurden:

ansible postgres_cluster -i inventory.ini -m shell -a "psql -U postgres -c \
  'SELECT * FROM employees;'" -b

Beispielausgabe:

postgres-vm-1 | CHANGED | rc=0 >>
 first_name | last_name
------------+-----------
 John       | Doe
(1 row)

postgres-vm-2 | CHANGED | rc=0 >>
 first_name | last_name
------------+-----------
 John       | Doe
(1 row)

postgres-vm-3 | CHANGED | rc=0 >>
 first_name | last_name
------------+-----------
 John       | Doe
(1 row)

Manuellen Wechsel testen

Mit einem manuellen Wechsel können Sie die Leader-Rolle ordnungsgemäß auf eine bestimmte Kandidaten-VM verschieben. Dies wird in der Regel für geplante Wartungsarbeiten, Softwareupgrades oder zum Ausgleich der Ressourcennutzung über Zonen hinweg durchgeführt.

Aktuellen Leader identifizieren

Prüfen Sie die aktuelle Rolle und den Status der VMs:

ansible master -i inventory.ini -m shell -a "patronictl list" -b

Beispielausgabe:

+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Leader  | running   |  1 |             |     |            |     |
| postgres-vm-2 | 10.253.1.253 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

Wechsel durchführen

Lösen Sie einen Wechsel vom aktuellen Leader zu einer anderen VM aus (in diesem Fall von postgres-vm-1 zu postgres-vm-2). Der Befehl verwendet --force, um manuelle Bestätigungsaufforderungen zu überspringen:

ansible master -i inventory.ini -m shell -a "patronictl switchover \
  --leader ${VM1_NAME} --candidate ${VM2_NAME} --force" -b

Beispielausgabe:

Successfully switched over to "postgres-vm-2"
+ Cluster: postgres-cluster (7607933704953386478) -+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State   | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+---------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Replica | stopped |    |     unknown |     |    unknown |     |
| postgres-vm-2 | 10.253.1.253 | Leader  | running |  1 |             |     |            |     |
| postgres-vm-3 | 10.253.1.252 | Replica | running |  1 |   0/70000A0 |   0 |  0/70000A0 |   0 |
+---------------+--------------+---------+---------+----+-------------+-----+------------+-----+

Verschiebung der Systemdiagnose prüfen

Prüfen Sie nach dem Wechsel, ob sich der Status der Systemdiagnose auf den neuen Leader verschoben hat:

ansible ${VM1_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

ansible ${VM2_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

Der alte Leader (postgres-vm-1) sollte 503 zurückgeben, während der neue Leader (postgres-vm-2) 200 zurückgeben sollte.

Automatischen Failover testen

Im Gegensatz zu einem manuellen Wechsel tritt ein automatischer Failover auf, wenn die Leader-VM nicht mehr verfügbar ist. Dieser Test bestätigt, dass Patroni einen neuen Leader wählt und der Load-Balancer den Traffic ohne manuellen Eingriff weiterleitet. Angenommen, postgres-vm-2 ist nach dem zuvor durchgeführten manuellen Wechsel der aktuelle Leader.

Aktuellen Leader identifizieren

Prüfen Sie die aktuelle Rolle und den Status der VMs:

ansible master -i inventory.ini -m shell -a "patronictl list" -b

Beispielausgabe:

+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Replica | streaming |  2 |   0/8000000 |   0 |  0/8000000 |   0 |
| postgres-vm-2 | 10.253.1.253 | Leader  | running   |  2 |             |     |            |     |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  2 |   0/8000000 |   0 |  0/8000000 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

VM-Ausfall simulieren

Beenden Sie den patroni-Dienst auf der Leader-VM, um einen Absturz oder einen schwerwiegenden Fehler zu simulieren:

ansible master -i inventory.ini -m shell -a "systemctl stop patroni" -b

Neue Wahl beobachten

Warten Sie 10 bis 20 Sekunden und prüfen Sie den Status von einer anderen VM aus, um die Beförderung eines neuen Leaders zu sehen:

ansible replica -i inventory.ini -m shell -a "patronictl list" -b

Beispielausgabe:

postgres-vm-3 | CHANGED | rc=0 >>
+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Leader  | running   |  3 |             |     |            |     |
| postgres-vm-2 | 10.253.1.253 | Replica | stopped   |    |     unknown |     |    unknown |     |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  3 |   0/90003F8 |   0 |  0/90003F8 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

Sie sollten sehen, dass eine der anderen VMs (postgres-vm-1 oder postgres-vm-3) zum Leader geworden ist und der alte Leader (postgres-vm-2) als beendet markiert ist.

Verschiebung der Systemdiagnose prüfen

Prüfen Sie, ob die Systemdiagnosen des Load-Balancers den neu gewählten Leader jetzt korrekt identifizieren:

ansible ${VM1_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

Der neue Leader sollte 200 OK zurückgeben.

Ausgefallene VM wiederherstellen

Starten Sie den patroni-Dienst wieder auf der ursprünglichen VM, damit sie dem Stack als Replikat beitreten und alle verpassten Daten nachholen kann:

ansible ${VM2_NAME} -i inventory.ini -m shell -a \
  "systemctl start patroni" -b