GDC エアギャップでの PostgreSQL データベースのリファレンス実装

このガイドでは、Google Distributed Cloud(GDC)のエアギャップ環境の 3 つのゾーンに高可用性 PostgreSQL スタックをデプロイするための包括的なチュートリアルを提供します。必要なソフトウェア アーティファクトを準備し、ターゲット VM をブートストラップし、Autobase を使用してプロビジョニング プロセス全体を自動化する方法を学習します。このガイドでは、PostgreSQL のライフサイクルをオーケストレートし、自動フェイルオーバーを処理するプライマリ管理レイヤとして Patroni を使用します。

アーキテクチャ

このアーキテクチャは、3 つのアベイラビリティ ゾーンに分散された 3 つの VM 環境で構成されています。

サービスがコロケーションされたスタックを実行する 3 台の VM アーキテクチャ。

すべての VM は同一で、サービスがコロケーションされたスタックを実行します。

  • PostgreSQL 17: コア リレーショナル データベース エンジン。
  • Patroni: 高可用性マネージャー。PostgreSQL プロセスのライフサイクルを処理し、自動フェイルオーバーを実行します。ロードバランサが現在のリーダーを特定するために使用する HTTPS REST API をポート 8008(エンドポイント /primary)で公開します。
  • etcd: 分散構成ストア(DCS)。リーダー選出のコンセンサス レイヤを提供し、Patroni の構成を保存します。
  • PgBouncer: PostgreSQL の前に配置され、接続オーバーヘッドを安定させる接続プーラー。ポート 6432 のアプリ トラフィックに推奨されるエントリ ポイントを提供します。

このスタックには、GDC エアギャップ グローバル L4 ロードバランサも含まれています。これは、安定した仮想 IP(VIP)を提供するプラットフォーム マネージド サービスです。アプリケーションはポート 6432 の安定した VIP に接続します。ロードバランサは、現在のリーダー VM の PgBouncer にこの VIP を転送します。次に、PgBouncer はリクエストをローカル PostgreSQL インスタンスにプロキシします。トラフィック フローを管理するため、ロードバランサはヘルスチェックとして Patroni HTTPS エンドポイントを継続的にポーリングします。

リーダー VM のヘルスチェックは HTTP 200 OK を返し、VM がトラフィックの準備ができていることを示します。一方、レプリカ VM のヘルスチェックは HTTP 503 Service Unavailable を返し、ロードバランサにバイパスするように指示します。リーダーに障害が発生すると、新しいリーダーが選出され、その Patroni インスタンスが HTTP 200 OK を返し始めます。これにより、ロードバランサはトラフィックを新しい VM の PgBouncer ポートに自動的にリダイレクトします。

高可用性を確保し、データ損失を防ぐため、スタックはクォーラムの概念に依存しています。VM が 3 つの場合、リーダーを選出して運用を維持するには、少なくとも 2 つのメンバーが正常で通信状態にある必要があります。etcd と Patroni によって管理されるこの多数決ベースのコンセンサスにより、スタックは単一の VM またはゾーンの完全な障害を自動的に許容できます。

パフォーマンスに関する注意事項

デプロイを計画する際は、パフォーマンスと信頼性を最適化するために、次の具体的な要素を考慮してください。

  • ハードウェアのサイジング: 要件はワークロードによって異なりますが、各 VM の出発点として次の標準プロファイルを使用します。
    • 開発/概念実証: 2 個の vCPU、8 GB の RAM(安定した動作の最小要件)。
    • 小規模な本番環境: 4 vCPU、16 GB RAM。同時実行性が中程度の内部ツールに適しています。
    • 標準本番環境: 8 vCPU、32 GB RAM。ミッション クリティカルなアプリケーションに推奨されるベースライン。
    • 高スループット: 16 個以上の vCPU、64 GB 以上の RAM。メモリ内(PostgreSQL 共有バッファ)での大規模なデータ キャッシュを必要とするワークロード。
  • ストレージ パフォーマンス: 高性能ストレージが不可欠です。etcd の安定性には SSD ディスクを強くおすすめします。etcd はディスク書き込みレイテンシに非常に敏感です。公式の etcd ハードウェア ガイドラインでは、p99 ディスク WAL fdatasync レイテンシを 10 ミリ秒未満にすることを推奨しています。
  • ネットワーク レイテンシ: VM 間のレイテンシは、レプリケーションのパフォーマンスに直接影響します。
    • etcd クォーラム: 選挙のタイムアウトとクラスタの不安定性を防ぐため、平均ラウンドトリップ時間(RTT)は 50 ミリ秒未満(理想的には 10 ミリ秒未満)にする必要があります。
    • 同期レプリケーション: 構成されている場合、すべての書き込みトランザクションはレプリカの確認応答を待機する必要があります。GDC エアギャップのゾーン間レイテンシは通常 1 ミリ秒未満です。これは、書き込みオーバーヘッドを最小限に抑える(通常 10 ~ 30%)のに最適です。
  • PgBouncer の役割: PostgreSQL は接続ごとに新しい OS プロセスを作成します。このプロセスは約 10 MB の RAM を消費し、CPU コンテキスト切り替えのコストが発生します。PgBouncer は、永続接続のプールを維持することでこのオーバーヘッドを削減し、データベースが大幅に少ないバックエンド プロセスで数千のアプリケーション接続を処理できるようにします。
  • サイドカー コンポーネント: Patroni と etcd は軽量ですが、CPU の可用性を一貫して確保する必要があります。高負荷のシナリオでは、ハイパーバイザ レベルで VM がオーバーサブスクライブされていないことを確認して、ハートビートとリーダーのメンテナンスに必要な CPU サイクルが「盗まれる」のを防ぎます。
  • カーネル チューニング: Autobase 自動化により、sysctl パラメータ(vm.swappiness、net.core.somaxconn など)の構成や透過的巨大ページ(THP)の無効化など、PostgreSQL に有益な最適化が自動的に適用されます。これらの変更により、メモリ管理のオーバーヘッドが削減され、トラフィックの多いデータベース インスタンスのネットワーク スループットが向上します。

始める前に

デプロイを開始する前に、環境が次の要件を満たしていることを確認する必要があります。

VM の要件を確認する

このチュートリアルでは、GDC エアギャップ プロジェクトに 3 つの VM を作成する必要があります。VM については、次の点と要件を考慮する必要があります。

  • ゾーンの分散: このデプロイをゾーン障害に対して真に復元力のあるものにするには、VM を 3 つの異なるアベイラビリティ ゾーンに分散する必要があります。ただし、VM が 2 つのゾーンまたは 1 つのゾーンに配置されている場合でも、デプロイは同じままです。最も重要なのは、すべての VM が内部 IP アドレスを使用してネットワーク経由で相互に通信できることです。
  • オペレーティング システム: このチュートリアルでは、Ubuntu 22.04 イメージを使用することを前提としています。別のディストリビューションを使用している場合は、このガイドの以降の手順が異なることがあります。
  • リソース: このチュートリアルでは、VM ごとに少なくとも 2 つの CPU と 8 GB のメモリをプロビジョニングする必要があります。本番環境では、特定のワークロードに適したリソースをプロビジョニングする必要があります(パフォーマンスに関する考慮事項をご覧ください)。
  • ネットワーク IP: 各 VM の内部 IP アドレスと外部 IP アドレスの両方をメモしておきます。このガイドでは、外部ワークステーションからコマンドを実行するため、Ansible コントロールに外部 IP を使用します。内部 IP は、サービス間の通信とバインディングに使用されます。ネットワーク内にブートストラップ VM をプロビジョニングした場合は、内部 IP のみが必要になります。
  • アクセス: Ansible 自動化では、パスワード プロンプトでブロックされることなく管理タスク(パッケージのインストール、システム構成の変更)を実行する必要があるため、デプロイ ユーザーのパスワードなしの sudo アクセスが必要です。
  • SSH: Ansible がターゲット VM に安全かつ非対話的に接続できるように、キーベースの認証を有効にする必要があります。

ローカル ワークステーション ソフトウェアを準備する

デプロイを管理し、エアギャップ アーティファクトを準備するには、ローカル ワークステーションにインストールされた一連の自動化ツールとコンテナ化ツールが必要です。

  • Ansible 2.17.0 以降: デプロイのプレイブックとロールを実行する自動化エンジン。
  • Docker: ターゲット VM(Ubuntu 22.04)と同じ環境内で OS の依存関係を pull してパッケージ化するために使用されます。
  • PostgreSQL クライアント(psql): テストクエリを実行し、ローカル ワークステーションからデータ レプリケーションを確認するために必要です。
  • Autobase リポジトリ:

    • リポジトリのクローンを作成して、自動化プレイブックとロールにアクセスします。 https://github.com/vitabaks/autobase
    • 特定のリリースをチェックアウトします(このガイドではバージョン 2.5.2 を使用します)。

      git checkout 2.5.2

      別のディストリビューションを使用している場合は、このガイドの以降の手順が異なることがあります。

    • このガイドのプレイブックを実行するには、ローカルの autobase ソースコードを Ansible コレクションとしてインストールして、ロールの接頭辞を解決できるようにする必要があります。

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

環境変数をいくつか作成する

このガイドでは、コマンドを簡略化するために次の環境変数を使用します。これらの変数には、プロジェクト ID、VM のアベイラビリティ ゾーン、ホスト名、ロードバランサがクラスタの識別に使用するラベルなどの重要なパラメータが格納されます。現在のシェル セッションで、環境の実際の値を使用して設定します(ゾーンがスペースで区切られていることを確認してください)。

VM に任意の名前を設定できますが、このガイドでは、クラスタノードの任意の名前として postgres-vm-1、postgres-vm-2、postgres-vm-3 を使用します。

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 を構成する

次のテンプレートを使用して、inventory.ini ファイルで VM 環境を定義します。

[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

構成について:

  • [master] と [replica]: プライマリ データベース VM とセカンダリ データベース VM を定義します。VM1_NAME、VM2_NAME、VM3_NAME 環境変数で設定された実際の VM 名を使用してください。
  • [postgres_cluster:children]: マスターノードとレプリカノードの両方を集約するグループ。Ansible は単一のコマンドでデータベース クラスタ全体をターゲットにできます。
  • [etcd_cluster]: etcd コンセンサス クラスタに参加するノードを定義します。これには、高可用性を確保するために 3 つのデータベース ノードすべてが含まれます。
  • ansible_host:(各 VM)Ansible がその VM に接続するために使用する VM の外部上り(内向き)IP。XX.XX.XX.XX は、実際の外部 IP に置き換えます。
  • bind_address:(各 VM)VM の内部 IP アドレス。XX.XX.XX.XX は、実際の内部 IP に置き換えます。
  • ansible_user: Ansible が SSH でターゲット VM に接続するために使用するリモートユーザー。... は実際のユーザー名に置き換えます。
  • ansible_ssh_private_key_file: ターゲット VM への認証に使用される秘密 SSH 認証鍵のローカルパス。~/.ssh/... は実際のパスに置き換えます。
  • patroni_superuser_password: postgres ユーザーのパスワード。ここで、強力で安全なパスワードを使用してください。
  • with_haproxy_load_balancing=false: プラットフォーム ネイティブの L4 ロードバランサを使用しているため、ローカル HAProxy を無効にします。
  • etcd_package_repo: VM のブートストラップ ディレクトリ内の etcd バイナリのローカルパスを指します。
  • installation_method="packages": ソースからコンパイルしたり、Python pip を使用したりするのではなく、OS パッケージを使用してコンポーネントをインストールするように自動化に指示します。
  • install_..._repo=false と _repository=[]: これらのオーバーライドにより、Ansible がインターネットにアクセスして外部リポジトリを追加したり、パッケージ リストを更新したりすることを防ぎます。
  • install_system_packages=false: 初期化フェーズまたはブートストラップ フェーズですでにプロビジョニングしたパッケージを自動化でダウンロードしてインストールしようとしないようにします。
  • patroni_installation_method=deb: インストールした .deb パッケージを使用するようにロールに明示的に指示します。

VM を初期化する

データベース スタックには、ベースの Ubuntu イメージに含まれていない可能性のある複数の OS パッケージとライブラリが必要です。VM はインターネット アクセスのないエアギャップ環境にあるため、これらの依存関係を自分でダウンロードできません。

この問題を解決する手順は次のとおりです。

  • ローカル ワークステーションで Docker コンテナを使用して、必要なファイルをすべてダウンロードします。次のコマンドは、apt-rdepends を使用して、ターゲット アプリケーションに必要なすべての共有ライブラリと依存関係を再帰的に特定します。コンテナ内の公式 PostgreSQL リポジトリを構成してバージョン 17 のアーティファクトを取得し、依存関係リストを反復処理して個々の .deb ファイルをダウンロードします。このとき、ターゲット VM でのバージョンの競合を避けるため、コア システム ライブラリ(libc6 や hostname など)を除外します。最後に、スタンドアロンの etcd バイナリを GitHub から直接取得します。

    まず、パッケージを保持するディレクトリを作成します。

    mkdir -p ./packages
    

    次に、Docker コマンドを実行して、必要なすべてのパッケージと etcd バイナリをダウンロードします。

    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
    "
    
  • Ansible を使用して、アーカイブを 3 つのターゲット VM すべてに同時にアップロードします。

    ansible all -i inventory.ini -m copy -a "src=packages.tar.gz dest=/tmp/" -b
    
  • VM 上の既存のパッケージ データを消去し、新しい tar ファイルを抽出します。

    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
    
  • ダウンロードしたすべての .deb パッケージの非インタラクティブ インストールを実行します。エアギャップ環境で特定の前依存関係に関する問題を回避するには、--force-depends フラグの後に apt-get install -fy を使用して、依存関係ツリーをローカルで解決します。

    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
    
  • 自動化の準備が整う前に、デフォルトの未構成の状態ですべてのサービスが起動しないように、すべてのサービスを直ちに停止します。

    ansible all -i inventory.ini -m shell -a \
      "systemctl stop patroni etcd pgbouncer postgresql || true" -b
    
  • 最後に、デフォルトの PostgreSQL クラスタと既存の etcd データを削除して、クリーンな初期化を可能にします。

    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
    

データベース インフラストラクチャをプロビジョニングする

VM がブートストラップされ、インベントリが構成されたので、Ansible で Autobase 自動化プレイブックを使用して、高可用性の PostgreSQL スタックをデプロイできます。

まず、移行前チェックを実行して、環境の準備ができていることを確認します。

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

チェックに合格したら、完全なデプロイに進みます。

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

出力の予想: ハンドブックが完了し、すべてのターゲット VM が到達して更新されたことを示す「PLAY RECAP」が正常に表示されます。

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

デプロイを確認する

デプロイが完了したら、すべてのコンポーネントが正しく機能していることを確認するために、いくつかのチェックを行う必要があります。

HA のステータスを確認する

高可用性マネージャーのステータスを確認して、各 VM に割り当てられているロールを確認します。

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

出力例:

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

ヘルスチェック エンドポイントを確認する

Patroni の REST API を使用して、Patroni がリーダーとレプリカを正しく識別することを確認します。リーダー VM のヘルスチェックは 200 OK を返し、レプリカ VM のヘルスチェックは 503 Service Unavailable を返します。

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

個々の VM の健全性を確認する

すべての PostgreSQL インスタンスの準備状況を確認します。

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

出力例:

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 の健全性を確認する

localhost をエンドポイントとして使用して、すべての VM でコンセンサス レイヤの健全性を確認します。

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

出力例:

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

グローバル ロードバランサを構成する

データベース スタックに安定した仮想 IP(VIP)を提供するには、gdcloud CLI を使用してプラットフォーム ネイティブのグローバル L4 ロードバランサを構成します。

  • 前提条件:

    • プロジェクトに load-balancer-admin ロールがあることを確認します。
    • ロードバランサがサービス提供に必要なインスタンスを正しくターゲットにできるように、VM にラベルを適用します(kubeconfig パラメータ値を、各ゾーンに対応する管理 API kubeconfig ファイルに置き換えます)。

      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}
      
  • ロード バランシングのアクセスレベルを定義します。プロジェクトのネットワーク外から接続する必要がある場合は EXTERNAL を設定し、VPC 内からのみアクセスする必要がある場合は INTERNAL を設定します。このチュートリアルでは、外部設定を使用します。

    export LB_SCHEME=EXTERNAL
    
  • ヘルスチェックを作成します。ロードバランサは、Patroni の REST API を使用してリーダーを識別します。

    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
    
  • VM が配置されているゾーンごとに個別のゾーン バックエンドを作成します。

    for zone in $(echo $ZONES); do
      gdcloud compute backends create ${CLUSTER_NAME}-backend-${zone} \
        --project=${PROJECT_ID} \
        --zone=${zone} \
        --labels="${VM_LABEL}"
    done
    
  • グローバル バックエンド サービスを作成します。

    gdcloud compute backend-services create ${CLUSTER_NAME}-bes \
      --project=${PROJECT_ID} \
      --health-check="${CLUSTER_NAME}-hc" \
      --global
    
  • ゾーン バックエンドをグローバル サービスに追加します。

    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
    
  • グローバル転送ルール(VIP)を作成します。このルールは、ポート 6432 でデータベースを公開します。

    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
    
  • VIP アドレスを取得します。

    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}"
    
  • ProjectNetworkPolicy(PNP)を作成して、PgBouncer ポートへの上り(内向き)トラフィックを許可します(kubeconfig パラメータ値を、環境に対応するグローバル API kubeconfig ファイルに置き換えます)。

    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
    

データ レプリケーションを確認する

高可用性スタックが想定どおりに動作していることを確認するには、リーダーにサンプルデータを作成し、レプリカにそのデータが存在することを確認します。

サンプルデータを挿入する

inventory.ini で指定されていない場合、自動化により最初のデプロイ時に postgres ユーザーのランダムなパスワードが生成されます。これは、任意の VM から取得できます。

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

PgBouncer ポート(6432)でロードバランサ VIP に接続し、サンプル テーブルを作成します。

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');
"

予想される出力:

INSERT 0 1

レプリケーションの状態を確認する

すべての VM で SELECT クエリを実行して、データがリーダーからすべてのレプリカに複製されていることを確認します。

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

出力例:

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)

手動切り替えをテストする

手動フェイルオーバーでは、リーダーロールを特定の候補 VM に正常に移動できます。これは通常、計画メンテナンス、ソフトウェア アップグレード、またはゾーン間のリソース使用率のバランスを取るために行われます。

現在のリーダーを特定する

VM の現在のロールとステータスを確認します。

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

出力例:

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

スイッチオーバーを実行する

現在のリーダーから別の VM(この場合は postgres-vm-1 から postgres-vm-2)への切り替えをトリガーします。このコマンドは --force を使用して、手動確認プロンプトをスキップします。

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

出力例:

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

ヘルスチェックのシフトを確認する

切り替え後、ヘルスチェックのステータスが新しいリーダーに移行したことを確認します。

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

古いリーダー(postgres-vm-1)は 503 を返し、新しいリーダー(postgres-vm-2)は 200 を返します。

自動フェイルオーバーをテストする

手動切り替えとは異なり、自動フェイルオーバーはリーダー VM が使用できなくなったときに発生します。このテストでは、Patroni が新しいリーダーを選出し、ロードバランサが手動操作なしでトラフィックをリダイレクトすることを確認します。postgres-vm-2 は、以前に実行された手動切り替え後の現在のリーダーであるとします。

現在のリーダーを特定する

VM の現在のロールとステータスを確認します。

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

出力例:

+ 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 の障害をシミュレートする

リーダー VM で patroni サービスを停止して、クラッシュまたはハード障害をシミュレートします。

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

新しい選挙を観察する

10 ~ 20 秒待ってから、別の VM からステータスを確認し、新しいリーダーの昇格を確認します。

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

出力例:

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

他の VM(postgres-vm-1 または postgres-vm-3)のいずれかがリーダーになり、以前のリーダー(postgres-vm-2)が停止としてマークされていることがわかります。

ヘルスチェックのシフトを確認する

ロードバランサのヘルスチェックで、新しく選出されたリーダーが正しく識別されることを確認します。

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

新しいリーダーは 200 OK を返す必要があります。

障害が発生した VM を復元する

元の VM で patroni サービスを再起動して、レプリカとしてスタックに再参加し、欠落したデータをキャッチアップします。

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