PostgreSQL-Datenbank-Referenzimplementierung in GDC mit Air Gap

Dieser Leitfaden bietet eine umfassende Anleitung zum Bereitstellen eines hochverfügbaren PostgreSQL-Stacks über drei Zonen in einer Google Distributed Cloud (GDC) mit Air Gap-Umgebung. Sie erfahren, wie Sie die erforderlichen Softwareartefakte vorbereiten, die Ziel-VMs bootstrappen 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 Failover 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 Stack von Diensten am selben Standort ausgeführt wird.

Alle VMs sind identisch und führen einen Stack von Diensten aus, die sich am selben Standort befinden:

  • PostgreSQL 17: Die zentrale relationale Datenbank-Engine.
  • Patroni: Der Hochverfügbarkeitsmanager. Er verwaltet den Lebenszyklus des PostgreSQL-Prozesses und führt automatische Failovers durch. Sie 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 (DCS). Sie stellt die Konsensebene für die Leader-Auswahl bereit und speichert die Konfiguration von Patroni.
  • PgBouncer: Ein Verbindungspooler, der vor PostgreSQL platziert wird, um den Verbindungsaufwand zu stabilisieren. Er bietet den empfohlenen Einstiegspunkt für Anwendungstraffic an Port 6432.

Der Stack enthält auch einen globalen L4-Load-Balancer für GDC mit Air Gap. Dies ist ein von der Plattform verwalteter Dienst, der eine stabile virtuelle IP-Adresse (VIP) bereitstellt. 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. Zur Verwaltung des Trafficflusses fragt der Load Balancer die Patroni-HTTPS-Endpunkte 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 dem Load Balancer zu signalisieren, dass er sie umgehen soll. Wenn der Leader ausfällt, wird ein neuer Leader gewählt und seine Patroni-Instanz gibt HTTP 200 OK zurück. Der Load Balancer leitet den Traffic dann 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 die 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 Totalausfall einer einzelnen VM oder Zone automatisch zu tolerieren.

Hinweise zur Leistung

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

  • Hardware-Dimensionierung: 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 (Mindestanforderung für stabilen Betrieb).
    • Kleine Produktion: 4 vCPUs, 16 GB RAM. Geeignet für interne Tools mit moderater Nebenläufigkeit.
    • Standardproduktion: 8 vCPUs, 32 GB RAM. Die empfohlene Baseline für geschäftskritische Anwendungen.
    • Hoher Durchsatz: mindestens 16 vCPUs, mindestens 64 GB RAM. Für Arbeitslasten, die umfangreiches Daten-Caching im Arbeitsspeicher erfordern (gemeinsame PostgreSQL-Zwischenspeicher).
  • Speicherleistung: Ein leistungsstarker Speicher ist unerlässlich. SSD-Laufwerke werden für die etcd-Stabilität dringend empfohlen. etcd reagiert extrem empfindlich auf die Schreiblatenz von Laufwerken. In den offiziellen etcd-Hardwarerichtlinien wird eine p99-Laufwerk-WAL-fdatasync-Latenz von < 10 ms empfohlen.
  • Netzwerklatenz: Die Latenz zwischen VMs wirkt sich direkt auf die Replikationsleistung aus:
    • etcd-Quorum: Die durchschnittliche Umlaufzeit (RTT) sollte unter 50 ms (idealerweise unter 10 ms) liegen, um Zeitüberschreitungen bei der Auswahl und Clusterinstabilität zu vermeiden.
    • Synchrone Replikation: Wenn konfiguriert, muss jede Schreibtransaktion auf die Bestätigung eines Replikats warten. Die Latenz zwischen Zonen in GDC mit Air Gap beträgt in der Regel weniger als 1 ms. Das ist ideal, um den Schreibaufwand minimal zu halten (normalerweise 10–30 %).
  • Die Rolle von PgBouncer: PostgreSQL erstellt für jede Verbindung einen neuen Betriebssystemprozess, der etwa 10 MB RAM verbraucht und CPU-Kontextwechselkosten verursacht. PgBouncer reduziert diesen Aufwand, indem es einen Pool persistenter Verbindungen verwaltet. So kann die Datenbank Tausende von Anwendungsverbindungen mit deutlich weniger Backend-Prozessen verarbeiten.
  • Sidecar-Komponenten: Patroni und etcd sind leichtgewichtig, erfordern aber eine konsistente CPU-Verfügbarkeit. Achten Sie in Szenarien mit hoher Last darauf, dass die VMs auf Hypervisor-Ebene nicht überbelegt sind, um zu vermeiden, dass CPU-Zyklen, die für Heartbeats und die Leader-Wartung erforderlich sind, „gestohlen“ werden.
  • Kernel-Abstimmung: Die Autobase-Automatisierung wendet automatisch Optimierungen an, die für PostgreSQL vorteilhaft 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 Aufwand für die 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 ausfallsicher ist, sollten Sie die VMs auf drei verschiedene Verfügbarkeitszonen verteilen. Die Bereitstellung bleibt jedoch identisch, wenn sich die VMs in zwei Zonen oder sogar in einer einzelnen Zone befinden. Am wichtigsten ist, dass alle VMs über das Netzwerk mit ihren internen IP-Adressen miteinander kommunizieren können.
  • Betriebssystem: In dieser Anleitung wird davon ausgegangen, dass Sie Ubuntu 22.04-Images verwenden. Die weiteren Schritte in dieser Anleitung können abweichen, wenn Sie eine andere Distribution verwenden.
  • Ressourcen: Sie sollten für diese Anleitung 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 Überlegungen zur Leistung).
  • Netzwerk-IPs: Notieren Sie sich die internen und externen IP-Adressen für jede VM. In dieser Anleitung verwenden Sie externe IP-Adressen für die Ansible-Steuerung, da Sie Befehle von einer externen Workstation aus ausführen. Interne IP-Adressen werden für die Kommunikation zwischen Diensten und die Bindung verwendet. Wenn Sie eine Bootstrapper-VM im Netzwerk bereitgestellt haben, benötigen Sie nur die internen IP-Adressen.
  • Zugriff: Für den Bereitstellungsnutzer ist ein passwortloser sudo-Zugriff erforderlich, da die Ansible-Automatisierung administrative Aufgaben (Installieren von Paketen, Ändern von Systemkonfigurationen) ausführen muss, ohne durch Passwortaufforderungen blockiert zu werden.
  • SSH: Die schlüsselbasierte Authentifizierung muss aktiviert sein, damit Ansible eine sichere und nicht interaktive Verbindung zu den Ziel-VMs herstellen kann.

Software für lokale Workstation vorbereiten

Zum Verwalten der Bereitstellung und Vorbereiten der Artefakte mit Air Gap benötigen Sie eine Reihe von Automatisierungs- und Containerisierungstools, die auf Ihrer lokalen Workstation installiert sind.

  • Ansible 2.17.0+: Die Automatisierungs-Engine, die die Playbooks und Rollen für die Bereitstellung ausführt.
  • Docker: Wird verwendet, um Betriebssystemabhängigkeiten in einer Umgebung abzurufen und zu verpacken, die mit den Ziel-VMs (Ubuntu 22.04) identisch ist.
  • PostgreSQL-Client (psql): Erforderlich, um die Testabfragen auszuführen und die Datenreplikation von Ihrer lokalen Workstation 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 dieser Anleitung können abweichen, wenn Sie eine andere Distribution verwenden.

    • Wenn Sie die Playbooks in diesem Leitfaden ausführen möchten, 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, deren Hostnamen und die Labels gespeichert, die vom Load Balancer zur Identifizierung Ihres Clusters verwendet werden. 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.

Sie können zwar beliebige Namen für Ihre VMs festlegen, in diesem Leitfaden werden jedoch postgres-vm-1, postgres-vm-2 und postgres-vm-3 als beliebige 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. Dies umfasst alle drei Datenbankknoten, um Hochverfügbarkeit zu gewährleisten.
  • ansible_host: (Für jede VM) Die externe eingehende IP-Adresse 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, den Ansible verwendet, um über SSH eine Verbindung zu den Ziel-VMs 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 der Quelle zu kompilieren oder Python pip zu verwenden.
  • install_..._repo=false und _repository=[]: Diese Überschreibungen verhindern, dass Ansible versucht, über das Internet 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 ausdrücklich an, das von Ihnen 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 Air-Gap-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. Mit dem folgenden Befehl wird apt-rdepends verwendet, um rekursiv alle gemeinsam genutzten Bibliotheken und Abhängigkeiten zu identifizieren, die von den Zielanwendungen benötigt werden. Es konfiguriert das offizielle PostgreSQL-Repository im Container, um Artefakte der Version 17 abzurufen, und geht dann die Abhängigkeitsliste durch, um einzelne .deb-Dateien herunterzuladen. Dabei werden Kernsystembibliotheken (wie 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 Air-Gap-Umgebung zu vermeiden, verwenden Sie das Flag --force-depends gefolgt von apt-get install -fy, um den Abhängigkeitsbaum 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, um zu verhindern, dass sie mit unkonfigurierten Standardstatus 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 gebootstrappt und das Inventar konfiguriert wurde, können Sie die Autobase-Automatisierungs-Playbooks mit Ansible verwenden, um den hochverfügbaren PostgreSQL-Stack bereitzustellen.

Führen Sie zuerst die Preflight-Prü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 einem erfolgreichen „PLAY RECAP“ abgeschlossen werden, das alle Ziel-VMs als erreicht und aktualisiert anzeigt:

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

Deployment prüfen

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

Hochverfügbarkeitsstatus prüfen

Prüfen Sie den Status des Hochverfügbarkeitsmanagers, um die Rollen zu sehen, die den einzelnen VMs 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 einzelner 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 Zustand 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

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

  • Voraussetzungen:

    • Sie benötigen die Rolle load-balancer-admin für Ihr Projekt.
    • Wenden Sie ein Label auf Ihre VMs an, damit der Load Balancer die Instanzen, die er bedienen muss, korrekt ansprechen kann. 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 das Load Balancing. Legen Sie EXTERNAL fest, wenn Sie eine Verbindung von außerhalb des Projektnetzwerks 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 macht die Datenbank auf Port 6432 verfügbar:

    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 (projektbezogene Netzwerkrichtlinie), um eingehenden Traffic zum PgBouncer-Port zuzulassen. Ersetzen Sie den Parameterwert kubeconfig durch die entsprechende globale API-kubeconfig-Datei 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

Wenn Sie wissen möchten, ob der Hochverfügbarkeits-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 während der ersten Bereitstellung ein zufälliges Passwort für den postgres-Nutzer, wenn in inventory.ini keines angegeben ist. Sie können sie 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 Load-Balancer-VIP auf dem PgBouncer-Port (6432) her und erstellen Sie eine Beispieltabelle:

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 auf allen VMs eine SELECT-Abfrage aus, um zu prüfen, ob 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)

Manuelle Umschaltung testen

Mit einem manuellen Switchover können Sie die Leader-Rolle ordnungsgemäß auf eine bestimmte Kandidaten-VM übertragen. Dies geschieht in der Regel für geplante Wartungsarbeiten, Softwareupgrades oder um die Ressourcenauslastung über Zonen hinweg auszugleichen.

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 |
+---------------+--------------+---------+---------+----+-------------+-----+------------+-----+

Systemdiagnose-Schicht prüfen

Prüfen Sie nach der Umstellung, ob der Systemdiagnosestatus auf den neuen Leader verschoben wurde:

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 Switchover erfolgt ein automatisches Failover, wenn die Leader-VM nicht mehr verfügbar ist. Mit diesem Test wird bestätigt, dass Patroni einen neuen Leader auswählt und der Load Balancer den Traffic ohne manuelles Eingreifen weiterleitet. Angenommen, postgres-vm-2 ist der aktuelle Leader nach dem zuvor durchgeführten manuellen Wechsel.

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-Fehler simulieren

Beenden Sie den Dienst patroni 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–20 Sekunden und prüfen Sie den Status von einer anderen VM, um die Hochstufung 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.

Systemdiagnose-Verschiebung 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.

Fehlerhafte VM wiederherstellen

Starten Sie den patroni-Dienst auf der ursprünglichen VM, damit sie wieder als Replikat in den Stack aufgenommen wird und alle fehlenden Daten nachholen kann:

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