高可用性 PostgreSQL データベースを GKE にデプロイする

PostgreSQL は、信頼性とデータの完全性で有名なオープンソースのオブジェクト リレーショナル データベースです。ACID に準拠し、外部キー、結合、ビュー、トリガー、ストアド プロシージャをサポートしています。

このドキュメントは、高可用性 PostgreSQL トポロジを Google Kubernetes Engine にデプロイすることに関心があるデータベース管理者、クラウド アーキテクト、運用の担当者を対象としています。

クラスタ インフラストラクチャを作成する

このセクションでは、Terraform スクリプトを実行して、カスタム Virtual Private Cloud(VPC)、PostgreSQL イメージを保存する Artifact Registry リポジトリ、および 2 つの GKE リージョン クラスタを作成します。1 つ目のクラスタは us-central1 にデプロイされ、バックアップ用の 2 つ目のクラスタは us-west1 にデプロイされます。

クラスタの作成手順は次のとおりです。

Autopilot

Cloud Shell で、次のコマンドを実行します。

terraform -chdir=terraform/gke-autopilot init
terraform -chdir=terraform/gke-autopilot apply -var project_id=$PROJECT_ID

プロンプトが表示されたら、「yes」と入力します。

Terraform の構成を理解する

Terraform 構成ファイルは、次のリソースを作成して、インフラストラクチャをデプロイします。

  • Docker イメージを保存するための Artifact Registry リポジトリを作成します。
    resource "google_artifact_registry_repository" "main" {
      location      = "us"
      repository_id = "main"
      format        = "DOCKER"
      project       = var.project_id
    }
  • VM のネットワーク インターフェース用に VPC ネットワークとサブネットを作成します。
    module "gcp-network" {
      source  = "terraform-google-modules/network/google"
      version = "< 8.0.0"
    
      project_id   = var.project_id
      network_name = "vpc-gke-postgresql"
    
      subnets = [
        {
          subnet_name           = "snet-gke-postgresql-us-central1"
          subnet_ip             = "10.0.0.0/17"
          subnet_region         = "us-central1"
          subnet_private_access = true
        },
        {
          subnet_name           = "snet-gke-postgresql-us-west1"
          subnet_ip             = "10.0.128.0/17"
          subnet_region         = "us-west1"
          subnet_private_access = true
        },
      ]
    
      secondary_ranges = {
        ("snet-gke-postgresql-us-central1") = [
          {
            range_name    = "ip-range-pods-db1"
            ip_cidr_range = "192.168.0.0/18"
          },
          {
            range_name    = "ip-range-svc-db1"
            ip_cidr_range = "192.168.64.0/18"
          },
        ],
        ("snet-gke-postgresql-us-west1") = [
          {
            range_name    = "ip-range-pods-db2"
            ip_cidr_range = "192.168.128.0/18"
          },
          {
            range_name    = "ip-range-svc-db2"
            ip_cidr_range = "192.168.192.0/18"
          },
        ]
      }
    }
    
    output "network_name" {
      value = module.gcp-network.network_name
    }
    
    output "primary_subnet_name" {
      value = module.gcp-network.subnets_names[0]
    }
    
    output "secondary_subnet_name" {
      value = module.gcp-network.subnets_names[1]
    }
  • プライマリ GKE クラスタを作成します。

    Terraform は us-central1 リージョンにプライベート クラスタを作成し、Backup for GKE で障害復旧を、Managed Service for Prometheus でクラスタのモニタリングを有効にします。

    Managed Service for Prometheus は、GKE バージョン 1.25 以降を実行している Autopilot クラスタでのみサポートされます。

    module "gke-db1-autopilot" {
      source                          = "../modules/beta-autopilot-private-cluster"
      project_id                      = var.project_id
      name                            = "cluster-db1"
      kubernetes_version              = "1.25" # Will be ignored if use "REGULAR" release_channel
      region                          = "us-central1"
      regional                        = true
      zones                           = ["us-central1-a", "us-central1-b", "us-central1-c"]
      network                         = module.network.network_name
      subnetwork                      = module.network.primary_subnet_name
      ip_range_pods                   = "ip-range-pods-db1"
      ip_range_services               = "ip-range-svc-db1"
      horizontal_pod_autoscaling      = true
      release_channel                 = "RAPID" # Default version is 1.22 in REGULAR. GMP on Autopilot requires V1.25 via var.kubernetes_version
      enable_vertical_pod_autoscaling = true
      enable_private_endpoint         = false
      enable_private_nodes            = true
      master_ipv4_cidr_block          = "172.16.0.0/28"
      create_service_account          = false
    }

  • 障害復旧のために us-west1 リージョンにバックアップ クラスタを作成します。

    module "gke-db2-autopilot" {
      source                          = "../modules/beta-autopilot-private-cluster"
      project_id                      = var.project_id
      name                            = "cluster-db2"
      kubernetes_version              = "1.25" # Will be ignored if use "REGULAR" release_channel
      region                          = "us-west1"
      regional                        = true
      zones                           = ["us-west1-a", "us-west1-b", "us-west1-c"]
      network                         = module.network.network_name
      subnetwork                      = module.network.secondary_subnet_name
      ip_range_pods                   = "ip-range-pods-db2"
      ip_range_services               = "ip-range-svc-db2"
      horizontal_pod_autoscaling      = true
      release_channel                 = "RAPID" # Default version is 1.22 in REGULAR. GMP on Autopilot requires V1.25 via var.kubernetes_version
      enable_vertical_pod_autoscaling = true
      enable_private_endpoint         = false
      enable_private_nodes            = true
      master_ipv4_cidr_block          = "172.16.0.16/28"
      create_service_account          = false
    }

Standard

Cloud Shell で、次のコマンドを実行します。

terraform -chdir=terraform/gke-standard init
terraform -chdir=terraform/gke-standard apply -var project_id=$PROJECT_ID

プロンプトが表示されたら、「yes」と入力します。

Terraform の構成を理解する

Terraform 構成ファイルは、次のリソースを作成して、インフラストラクチャをデプロイします。

  • Docker イメージを保存するための Artifact Registry リポジトリを作成します。
    resource "google_artifact_registry_repository" "main" {
      location      = "us"
      repository_id = "main"
      format        = "DOCKER"
      project       = var.project_id
    }
    resource "google_artifact_registry_repository_iam_binding" "binding" {
      provider   = google-beta
      project    = google_artifact_registry_repository.main.project
      location   = google_artifact_registry_repository.main.location
      repository = google_artifact_registry_repository.main.name
      role       = "roles/artifactregistry.reader"
      members = [
        "serviceAccount:${module.gke-db1.service_account}",
      ]
    }
  • VM のネットワーク インターフェース用に VPC ネットワークとサブネットを作成します。
    module "gcp-network" {
      source  = "terraform-google-modules/network/google"
      version = "< 8.0.0"
    
      project_id   = var.project_id
      network_name = "vpc-gke-postgresql"
    
      subnets = [
        {
          subnet_name           = "snet-gke-postgresql-us-central1"
          subnet_ip             = "10.0.0.0/17"
          subnet_region         = "us-central1"
          subnet_private_access = true
        },
        {
          subnet_name           = "snet-gke-postgresql-us-west1"
          subnet_ip             = "10.0.128.0/17"
          subnet_region         = "us-west1"
          subnet_private_access = true
        },
      ]
    
      secondary_ranges = {
        ("snet-gke-postgresql-us-central1") = [
          {
            range_name    = "ip-range-pods-db1"
            ip_cidr_range = "192.168.0.0/18"
          },
          {
            range_name    = "ip-range-svc-db1"
            ip_cidr_range = "192.168.64.0/18"
          },
        ],
        ("snet-gke-postgresql-us-west1") = [
          {
            range_name    = "ip-range-pods-db2"
            ip_cidr_range = "192.168.128.0/18"
          },
          {
            range_name    = "ip-range-svc-db2"
            ip_cidr_range = "192.168.192.0/18"
          },
        ]
      }
    }
    
    output "network_name" {
      value = module.gcp-network.network_name
    }
    
    output "primary_subnet_name" {
      value = module.gcp-network.subnets_names[0]
    }
    
    output "secondary_subnet_name" {
      value = module.gcp-network.subnets_names[1]
    }
  • プライマリ GKE クラスタを作成します。

    Terraform は us-central1 リージョンにプライベート クラスタを作成し、Backup for GKE で障害復旧を、Managed Service for Prometheus でクラスタのモニタリングを有効にします。

    module "gke-db1" {
      source                   = "../modules/beta-private-cluster"
      project_id               = var.project_id
      name                     = "cluster-db1"
      regional                 = true
      region                   = "us-central1"
      network                  = module.network.network_name
      subnetwork               = module.network.primary_subnet_name
      ip_range_pods            = "ip-range-pods-db1"
      ip_range_services        = "ip-range-svc-db1"
      create_service_account   = true
      enable_private_endpoint  = false
      enable_private_nodes     = true
      master_ipv4_cidr_block   = "172.16.0.0/28"
      network_policy           = true
      cluster_autoscaling = {
        "autoscaling_profile": "OPTIMIZE_UTILIZATION",
        "enabled" : true,
        "gpu_resources" : [],
        "min_cpu_cores" : 36,
        "min_memory_gb" : 144,
        "max_cpu_cores" : 48,
        "max_memory_gb" : 192,
      }
      monitoring_enable_managed_prometheus = true
      gke_backup_agent_config = true
    
      node_pools = [
        {
          name            = "pool-sys"
          autoscaling     = true
          min_count       = 1
          max_count       = 3
          max_surge       = 1
          max_unavailable = 0
          machine_type    = "e2-standard-4"
          node_locations  = "us-central1-a,us-central1-b,us-central1-c"
          auto_repair     = true
        },
        {
          name            = "pool-db"
          autoscaling     = true
          max_surge       = 1
          max_unavailable = 0
          machine_type    = "e2-standard-8"
          node_locations  = "us-central1-a,us-central1-b,us-central1-c"
          auto_repair     = true
        },
      ]
      node_pools_labels = {
        all = {}
        pool-db = {
          "app.stateful/component" = "postgresql"
        }
        pool-sys = {
          "app.stateful/component" = "postgresql-pgpool"
        }
      }
      node_pools_taints = {
        all = []
        pool-db = [
          {
            key    = "app.stateful/component"
            value  = "postgresql"
            effect = "NO_SCHEDULE"
          },
        ],
        pool-sys = [
          {
            key    = "app.stateful/component"
            value  = "postgresql-pgpool"
            effect = "NO_SCHEDULE"
          },
        ],
      }
      gce_pd_csi_driver = true
    }

  • 障害復旧のために us-west1 リージョンにバックアップ クラスタを作成します。

    module "gke-db2" {
      source                   = "../modules/beta-private-cluster"
      project_id               = var.project_id
      name                     = "cluster-db2"
      regional                 = true
      region                   = "us-west1"
      network                  = module.network.network_name
      subnetwork               = module.network.secondary_subnet_name
      ip_range_pods            = "ip-range-pods-db2"
      ip_range_services        = "ip-range-svc-db2"
      create_service_account   = false
      service_account          = module.gke-db1.service_account
      enable_private_endpoint  = false
      enable_private_nodes     = true
      master_ipv4_cidr_block   = "172.16.0.16/28"
      network_policy           = true
      cluster_autoscaling = {
        "autoscaling_profile": "OPTIMIZE_UTILIZATION",
        "enabled" : true,
        "gpu_resources" : [],
        "min_cpu_cores" : 10,
        "min_memory_gb" : 144,
        "max_cpu_cores" : 48,
        "max_memory_gb" : 192,
      }
      monitoring_enable_managed_prometheus = true
      gke_backup_agent_config = true
      node_pools = [
        {
          name            = "pool-sys"
          autoscaling     = true
          min_count       = 1
          max_count       = 3
          max_surge       = 1
          max_unavailable = 0
          machine_type    = "e2-standard-4"
          node_locations  = "us-west1-a,us-west1-b,us-west1-c"
          auto_repair     = true
        },
        {
          name            = "pool-db"
          autoscaling     = true
          max_surge       = 1
          max_unavailable = 0
          machine_type    = "e2-standard-8"
          node_locations  = "us-west1-a,us-west1-b,us-west1-c"
          auto_repair     = true
        },
      ]
      node_pools_labels = {
        all = {}
        pool-db = {
          "app.stateful/component" = "postgresql"
        }
        pool-sys = {
          "app.stateful/component" = "postgresql-pgpool"
        }
      }
      node_pools_taints = {
        all = []
        pool-db = [
          {
            key    = "app.stateful/component"
            value  = "postgresql"
            effect = "NO_SCHEDULE"
          },
        ],
        pool-sys = [
          {
            key    = "app.stateful/component"
            value  = "postgresql-pgpool"
            effect = "NO_SCHEDULE"
          },
        ],
      }
      gce_pd_csi_driver = true
    }

PostgreSQL をクラスタにデプロイする

このセクションでは、Helm チャートを使用して、PostgreSQL データベース インスタンスをデプロイして、GKE で実行します。

PostgreSQL をインストールする

クラスタに PostgreSQL をインストールするには、次の手順で操作します。

  1. Docker アクセスを構成します。

    gcloud auth configure-docker us-docker.pkg.dev
    
  2. 必要な PostgreSQL Docker イメージを Artifact Registry に入力します。

    ./scripts/gcr.sh bitnami/postgresql-repmgr 15.1.0-debian-11-r0
    ./scripts/gcr.sh bitnami/postgres-exporter 0.11.1-debian-11-r27
    ./scripts/gcr.sh bitnami/pgpool 4.3.3-debian-11-r28
    

    スクリプトにより、次の Bitnami イメージが Helm 用の Artifact Registry に push され、インストールされます。

    • postgresql-repmgr: この PostgreSQL クラスタ ソリューションには、PostgreSQL クラスタでのレプリケーションとフェイルオーバーを管理するためのオープンソース ツールである PostgreSQL レプリケーション マネージャー(repmgr)が含まれています。
    • postgres-exporter: PostgreSQL Exporter は、Prometheus の使用に必要な PostgreSQL 指標を収集します。
    • pgpool: Pgpool-II は PostgreSQL プロキシです。接続プーリングとロード バランシングを提供します。
  3. 正しいイメージがリポジトリに保存されていることを確認します。

    gcloud artifacts docker images list us-docker.pkg.dev/$PROJECT_ID/main \
        --format="flattened(package)"
    

    出力は次のようになります。

    ---
    image: us-docker.pkg.dev/[PROJECT_ID]/main/bitnami/pgpool
    ---
    image: us-docker.pkg.dev/[PROJECT_ID]/main/bitnami/postgres-exporter
    ---
    image: us-docker.pkg.dev/h[PROJECT_ID]/main/bitnami/postgresql-repmgr
    
  4. プライマリ クラスタへの kubectl コマンドライン アクセスを構成します。

    gcloud container clusters get-credentials $SOURCE_CLUSTER \
    --location=$REGION --project=$PROJECT_ID
    
  5. Namespace を作成します。

    export NAMESPACE=postgresql
    kubectl create namespace $NAMESPACE
    
  6. Autopilot クラスタにデプロイする場合は、3 つのゾーンにわたるノード プロビジョニングを構成します。Standard クラスタにデプロイする場合は、この手順をスキップしてください。

    デフォルトでは、Autopilot は 2 つのゾーンにのみリソースをプロビジョニングします。prepareforha.yaml で定義された Deployment により、Autopilot は次の値を設定して、クラスタ内の 3 つのゾーンにノードをプロビジョニングします。

    • replicas:3
    • requiredDuringSchedulingIgnoredDuringExecutiontopologyKey: "topology.kubernetes.io/zone" を使用した podAntiAffinity
    kubectl -n $NAMESPACE apply -f scripts/prepareforha.yaml
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: prepare-three-zone-ha
      labels:
        app: prepare-three-zone-ha
        app.kubernetes.io/name: postgresql-ha
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: prepare-three-zone-ha
          app.kubernetes.io/name: postgresql-ha
      template:
        metadata:
          labels:
            app: prepare-three-zone-ha
            app.kubernetes.io/name: postgresql-ha
        spec:
          affinity:
            podAntiAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
              - labelSelector:
                  matchExpressions:
                  - key: app
                    operator: In
                    values:
                    - prepare-three-zone-ha
                topologyKey: "topology.kubernetes.io/zone"
            nodeAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
              - preference:
                  matchExpressions:
                  - key: cloud.google.com/compute-class
                    operator: In
                    values:
                    - "Scale-Out"
                weight: 1
          nodeSelector:
            app.stateful/component: postgresql
          tolerations:
          - effect: NoSchedule
            key: app.stateful/component
            operator: Equal
            value: postgresql
          containers:
          - name: prepare-three-zone-ha
            image: busybox:latest
            command:
                - "/bin/sh"
                - "-c"
                - "while true; do sleep 3600; done"
            resources:
              limits:
                cpu: "500m"
                ephemeral-storage: "10Mi"
                memory: "0.5Gi"
              requests:
                cpu: "500m"
                ephemeral-storage: "10Mi"
                memory: "0.5Gi"
    
  7. Helm の依存関係を更新します。

    cd helm/postgresql-bootstrap
    helm dependency update
    
  8. Helm によってインストールされるチャートを調べて確認します。

    helm -n postgresql template postgresql . \
      --set global.imageRegistry="us-docker.pkg.dev/$PROJECT_ID/main"
    
  9. Helm チャートをインストールします。

    helm -n postgresql upgrade --install postgresql . \
        --set global.imageRegistry="us-docker.pkg.dev/$PROJECT_ID/main"
    

    出力は次のようになります。

    NAMESPACE: postgresql
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    
  10. PostgreSQL のレプリカが動作していることを確認します。

    kubectl get all -n $NAMESPACE
    

    出力は次のようになります。

    NAME                                                          READY   STATUS    RESTARTS   AGE
    pod/postgresql-postgresql-bootstrap-pgpool-75664444cb-dkl24   1/1     Running   0          8m39s
    pod/postgresql-postgresql-ha-pgpool-6d86bf9b58-ff2bg          1/1     Running   0          8m39s
    pod/postgresql-postgresql-ha-postgresql-0                     2/2     Running   0          8m39s
    pod/postgresql-postgresql-ha-postgresql-1                     2/2     Running   0          8m39s
    pod/postgresql-postgresql-ha-postgresql-2                     2/2     Running   0          8m38s
    
    NAME                                                   TYPE        CLUSTER-IP        EXTERNAL-IP   PORT(S)    AGE
    service/postgresql-postgresql-ha-pgpool                ClusterIP   192.168.99.236    <none>        5432/TCP   8m39s
    service/postgresql-postgresql-ha-postgresql            ClusterIP   192.168.90.20     <none>        5432/TCP   8m39s
    service/postgresql-postgresql-ha-postgresql-headless   ClusterIP   None              <none>        5432/TCP   8m39s
    service/postgresql-postgresql-ha-postgresql-metrics    ClusterIP   192.168.127.198   <none>        9187/TCP   8m39s
    
    NAME                                                     READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/postgresql-postgresql-bootstrap-pgpool   1/1     1            1           8m39s
    deployment.apps/postgresql-postgresql-ha-pgpool          1/1     1            1           8m39s
    
    NAME                                                                DESIRED   CURRENT   READY   AGE
    replicaset.apps/postgresql-postgresql-bootstrap-pgpool-75664444cb   1         1         1       8m39s
    replicaset.apps/postgresql-postgresql-ha-pgpool-6d86bf9b58          1         1         1       8m39s
    
    NAME                                                   READY   AGE
    statefulset.apps/postgresql-postgresql-ha-postgresql   3/3     8m39s
    

テスト データセットを作成する

このセクションでは、データベースとサンプル値を含むテーブルを作成します。データベースは、このチュートリアルの後半でテストするフェイルオーバー プロセスのテスト データセットとして機能します。

  1. PostgreSQL インスタンスに接続します。

    cd ../../
    ./scripts/launch-client.sh
    

    出力は次のようになります。

    Launching Pod pg-client in the namespace postgresql ...
    pod/pg-client created
    waiting for the Pod to be ready
    Copying script files to the target Pod pg-client ...
    Pod: pg-client is healthy
    
  2. シェル セッションを開始します。

    kubectl exec -it pg-client -n postgresql -- /bin/bash
    
  3. データベースとテーブルを作成し、いくつかのテスト行を挿入します。

    psql -h $HOST_PGPOOL -U postgres -a -q -f /tmp/scripts/generate-db.sql
    
  4. 各テーブルの行数を確認します。

    psql -h $HOST_PGPOOL -U postgres -a -q -f /tmp/scripts/count-rows.sql
    

    出力は次のようになります。

    select COUNT(*) from tb01;
     count
    --------
     300000
    (1 row)
    
    select COUNT(*) from tb02;
     count
    --------
     300000
    (1 row)
    
  5. テストデータを生成します。

    export DB=postgres
    pgbench -i -h $HOST_PGPOOL -U postgres $DB -s 50
    

    出力は次のようになります。

    dropping old tables...
    creating tables...
    generating data (client-side)...
    5000000 of 5000000 tuples (100%) done (elapsed 29.85 s, remaining 0.00 s)
    vacuuming...
    creating primary keys...
    done in 36.86 s (drop tables 0.00 s, create tables 0.01 s, client-side generate 31.10 s, vacuum 1.88 s, primary keys 3.86 s).
    
  6. postgres クライアント Pod を終了します。

    exit
    

PostgreSQL をモニタリングする

このセクションでは、PostgreSQL インスタンス用の指標を表示して、アラートを設定します。Google Cloud Managed Service for Prometheus を使用して、モニタリングとアラートを実行します。

指標を表示する

PostgreSQL の Deployment には、postgresql-exporter サイドカー コンテナが含まれています。このコンテナは /metrics エンドポイントを公開します。Google Cloud Managed Service for Prometheus は、このエンドポイントで PostgreSQL Pod をモニタリングするように構成されています。これらの指標は、 Google Cloud コンソールのダッシュボードで確認できます。

Google Cloud コンソールには、ダッシュボードの構成を作成して保存する方法がいくつか用意されています。

  • 作成とエクスポート: Google Cloud コンソールでダッシュボードを直接作成してから、コード リポジトリにそれらをエクスポートして保存できます。それには、ダッシュボードのツールバーで JSON エディタを開き、ダッシュボードの JSON ファイルをダウンロードします。
  • ストレージとインポート: JSON ファイルからダッシュボードをインポートできます。それには、[+ ダッシュボードの作成] をクリックし、JSON エディタのメニューを使用して、ダッシュボードの JSON コンテンツをアップロードします。

PostgreSQL アプリケーションと GKE クラスタからのデータを可視化する手順は次のとおりです。

  1. 次のダッシュボードを作成します。

    cd monitoring
    gcloud monitoring dashboards create \
            --config-from-file=dashboard/postgresql-overview.json \
            --project=$PROJECT_ID
    gcloud monitoring dashboards create \
            --config-from-file dashboard/gke-postgresql.json \
            --project $PROJECT_ID
    
  2. Google Cloud コンソールで、Cloud Monitoring ダッシュボードに移動します。Cloud Monitoring ダッシュボードに移動

  3. ダッシュボードのリストから [カスタム] を選択します。次のダッシュボードが表示されます。

    • PostgreSQL の概要: PostgreSQL アプリケーションの指標(データベースの稼働時間、データベースのサイズ、トランザクションのレイテンシなど)を表示します。
    • GKE PostgreSQL クラスタ: PostgreSQL が実行されている GKE クラスタの指標(CPU 使用率、メモリ使用量、ボリューム使用率など)を表示します。
  4. 各リンクをクリックして、生成されたダッシュボードを調べます。

アラートを設定する

アラートを使用すると、アプリケーションの問題をタイムリーに認識できるため、問題をすばやく解決できます。アラート ポリシーを作成して、アラートを受け取る状況と通知方法を指定できます。通知チャンネルを作成して、アラートの送信先を選択することもできます。

このセクションでは、Terraform を使用して次の例のアラートを構成します。

  • db_max_transaction: トランザクションの最大ラグを秒単位でモニタリングします。値が 10 より大きい場合は、アラートがトリガーされます。
  • db_node_up: データベース Pod のステータスをモニタリングします。0 は、Pod がダウンしていることを意味し、アラートがトリガーされます。

アラートを設定する手順は次のとおりです。

  1. Terraform を使用してアラートを構成します。

    EMAIL=YOUR_EMAIL
    cd alerting/terraform
    terraform init
    terraform plan -var project_id=$PROJECT_ID -var email_address=$EMAIL
    terraform apply -var project_id=$PROJECT_ID -var email_address=$EMAIL
    

    次の値を置き換えます。

    • YOUR_EMAIL: メールアドレス。

    出力は次のようになります。

    Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
    
  2. クライアント Pod に接続します。

    cd ../../../
    kubectl exec -it --namespace postgresql pg-client -- /bin/bash
    
  3. 負荷テストを生成して、db_max_transaction アラートをテストします。

    pgbench -i -h $HOST_PGPOOL -U postgres -s 200 postgres
    

    出力は次のようになります。

    dropping old tables...
    creating tables...
    generating data (client-side)...
    20000000 of 20000000 tuples (100%) done (elapsed 163.22 s, remaining 0.00 s)
    vacuuming...
    creating primary keys...
    done in 191.30 s (drop tables 0.14 s, create tables 0.01 s, client-side generate 165.62 s, vacuum 4.52 s, primary keys 21.00 s).
    

    アラートがトリガーされると、件名が「[ALERT] Max Lag of transaction」で始まるメールが YOUR_EMAIL に送信されます。

  4. Google Cloud コンソールで、[アラート ポリシー] ページに移動します。

    [アラート ポリシー] に移動

  5. リスト内のポリシーから db_max_transaction を選択します。チャートから、Prometheus 指標 pg_stat_activity_max_tx_duration/gauge のしきい値の 10 を超える負荷テストの急増が確認できます。

  6. postgres クライアント Pod を終了します。

    exit
    

PostgreSQL と GKE のアップグレードを管理する

PostgreSQL と Kubernetes の両方のバージョンの更新は定期的なスケジュールでリリースされます。運用に関するベスト プラクティスに沿って、ソフトウェア環境を定期的に更新します。デフォルトでは、GKE ではクラスタとノードプールのアップグレードが自動的に管理されます。

PostgreSQL をアップグレードする

このセクションでは、PostgreSQL のバージョン アップグレードを行う方法について説明します。このチュートリアルでは、どの時点でもすべての Pod がダウンすることがないように、Pod のアップグレードにローリング アップデート戦略を使用します。

バージョン アップグレードの手順は次のとおりです。

  1. 更新されたバージョンの postgresql-repmgr イメージを Artifact Registry に push します。新しいバージョンを定義します(例: postgresql-repmgr 15.1.0-debian-11-r1)。

    NEW_IMAGE=us-docker.pkg.dev/$PROJECT_ID/main/bitnami/postgresql-repmgr:15.1.0-debian-11-r1
    ./scripts/gcr.sh bitnami/postgresql-repmgr 15.1.0-debian-11-r1
    
  2. kubectl を使用してローリング アップデートをトリガーします。

    kubectl set image statefulset -n postgresql postgresql-postgresql-ha-postgresql postgresql=$NEW_IMAGE
    kubectl rollout restart statefulsets -n postgresql postgresql-postgresql-ha-postgresql
    kubectl rollout status statefulset -n postgresql postgresql-postgresql-ha-postgresql
    

    StatefulSet により、ローリング アップデートの完了が確認できます(序数の大きいレプリカから小さいものの順)。

    出力は次のようになります。

    Waiting for 1 pods to be ready...
    waiting for statefulset rolling update to complete 1 pods at revision postgresql-postgresql-ha-postgresql-5c566ccf49...
    Waiting for 1 pods to be ready...
    Waiting for 1 pods to be ready...
    waiting for statefulset rolling update to complete 2 pods at revision postgresql-postgresql-ha-postgresql-5c566ccf49...
    Waiting for 1 pods to be ready...
    Waiting for 1 pods to be ready...
    statefulset rolling update complete 3 pods at revision postgresql-postgresql-ha-postgresql-5c566ccf49...
    

Standard クラスタで GKE のアップグレードを計画する

このセクションは、Standard クラスタを実行している場合に適用されます。ステートフル サービスの実行時に、次のように予防的措置を講じて、リスクを軽減してクラスタのアップグレードをよりスムーズに行うための構成を設定することができます。

Standard クラスタのアップグレード中にデータベースの可用性を確認する

このセクションは、Standard クラスタを実行している場合に適用されます。アップグレード中に PostgreSQL の可用性を検証するには、アップグレード処理中に PostgreSQL データベースに対してトラフィックを生成することが一般的な処理です。その後、pgbench を使用して、データベースが完全に使用可能になったときに比べて、アップグレード中にデータベースがトラフィックのベースライン レベルを処理できることを確認します。

  1. PostgreSQL インスタンスに接続します。

    ./scripts/launch-client.sh
    

    出力は次のようになります。

    Launching Pod pg-client in the namespace postgresql ...
    pod/pg-client created
    waiting for the Pod to be ready
    Copying script files to the target Pod pg-client ...
    Pod: pg-client is healthy
    
  2. Cloud Shell で、クライアント Pod にシェルを追加します。

    kubectl exec -it -n postgresql pg-client -- /bin/bash
    
  3. pgbench を初期化します。

    pgbench -i -h $HOST_PGPOOL -U postgres postgres
    
  4. 次のコマンドを使用して、アップグレードの時間枠内に PostgreSQL アプリケーションの高可用性が維持されていることを確認するベースラインの結果を取得します。ベースラインの結果を取得するには、30 秒間、マルチジョブ(スレッド)を介したマルチ接続でテストします。

    pgbench -h $HOST_PGPOOL -U postgres postgres -c10 -j4 -T 30 -R 200
    

    出力は次のようになります。

    pgbench (14.5)
    starting vacuum...end.
    transaction type: <builtin: TPC-B (sort of)>
    scaling factor: 1
    query mode: simple
    number of clients: 10
    number of threads: 4
    duration: 30 s
    number of transactions actually processed: 5980
    latency average = 7.613 ms
    latency stddev = 2.898 ms
    rate limit schedule lag: avg 0.256 (max 36.613) ms
    initial connection time = 397.804 ms
    tps = 201.955497 (without initial connection time)
    
  5. アップグレード中の可用性を確保するには、データベースに対してある程度の負荷を生成し、PostgreSQL アプリケーションがアップグレード中も一貫した応答率を確実に示すようにします。このテストを行うには、pgbench コマンドを使用して、データベースに対するある程度のトラフィックを生成します。次のコマンドは、200 TPS(1 秒あたりのトランザクション数)を目標に、2 秒ごとにリクエスト率を一覧表示しながら、1 時間 pgbench を実行します。

    pgbench -h $HOST_PGPOOL -U postgres postgres --client=10 --jobs=4 --rate=200 --time=3600 --progress=2 --select-only
    

    ここで

    • --client: シミュレートされたクライアントの数、つまりデータベース セッションの同時実行数。
    • --jobs: pgbench 内のワーカー スレッド数。マルチ CPU マシンでは、複数のスレッドの使用が便利です。クライアントは、使用可能なスレッド間でできるだけ均等に分散されます。デフォルトは 1 です。
    • --rate: レートは 1 秒あたりのトランザクション数で指定します。
    • --progress: 進行状況レポートを 1 秒ごとに表示します。

    出力は次のようになります。

    pgbench (14.5)
    starting vacuum...end.
    progress: 5.0 s, 354.8 tps, lat 25.222 ms stddev 15.038
    progress: 10.0 s, 393.8 tps, lat 25.396 ms stddev 16.459
    progress: 15.0 s, 412.8 tps, lat 24.216 ms stddev 14.548
    progress: 20.0 s, 405.0 tps, lat 24.656 ms stddev 14.066
    
  6. Google Cloud コンソールで、Cloud Monitoring の [PostgreSQL の概要] ダッシュボードに戻ります。DB あたりの接続のグラフと Pod あたりの接続のグラフの急増に注意してください。

  7. クライアント Pod を終了します。

    exit
    
  8. クライアント Pod を削除します。

    kubectl delete pod -n postgresql pg-client
    

PostgreSQL サービスの中断をシミュレートする

このセクションでは、レプリケーション マネージャー サービスを停止して、PostgreSQL レプリカの 1 つでのサービスの中断をシミュレートします。これにより、Pod がピアレプリカにトラフィックを送信できなくなり、liveness プローブも失敗します。

  1. 新しい Cloud Shell セッションを開き、プライマリ クラスタへの kubectl コマンドライン アクセスを構成します。

    gcloud container clusters get-credentials $SOURCE_CLUSTER \
    --location=$REGION --project=$PROJECT_ID
    
  2. Kubernetes で出力された PostgreSQL イベントを表示します。

    kubectl get events -n postgresql --field-selector=involvedObject.name=postgresql-postgresql-ha-postgresql-0 --watch
    
  3. 以前の Cloud Shell セッションで、PostgreSQL repmgr を停止してサービス障害をシミュレートします。

    1. セッションをデータベース コンテナにアタッチします。

      kubectl exec -it -n $NAMESPACE postgresql-postgresql-ha-postgresql-0 -c postgresql -- /bin/bash
      
    2. repmgr を使用してサービスを停止し、チェックポイントと dry-run 引数を削除します。

      export ENTRY='/opt/bitnami/scripts/postgresql-repmgr/entrypoint.sh'
      export RCONF='/opt/bitnami/repmgr/conf/repmgr.conf'
      $ENTRY repmgr -f $RCONF node service --action=stop --checkpoint
      

PostgreSQL コンテナ用に構成された liveness プローブが開始して 5 秒以内に失敗します。失敗回数がしきい値の 6 回に達するまで、10 秒ごとにこれが繰り返されます。failureThreshold の値に達すると、コンテナが再起動されます。これらのパラメータを構成して、liveness プローブの許容範囲を下げて、Deployment の SLO 要件を調整できます。

イベント ストリームから、Pod の liveness プローブと readiness プローブが失敗し、コンテナを再起動する必要があるというメッセージが確認できます。出力は次のようになります。

0s          Normal    Killing                pod/postgresql-postgresql-ha-postgresql-0   Container postgresql failed liveness probe, will be restarted
0s          Warning   Unhealthy              pod/postgresql-postgresql-ha-postgresql-0   Readiness probe failed: psql: error: connection to server at "127.0.0.1", port 5432 failed: Connection refused...
0s          Normal    Pulled                 pod/postgresql-postgresql-ha-postgresql-0   Container image "us-docker.pkg.dev/psch-gke-dev/main/bitnami/postgresql-repmgr:14.5.0-debian-11-r10" already present on machine
0s          Normal    Created                pod/postgresql-postgresql-ha-postgresql-0   Created container postgresql
0s          Normal    Started                pod/postgresql-postgresql-ha-postgresql-0   Started container postgresql

障害復旧に備える

サービス中断イベントの発生時に本番環境のワークロードの可用性を確保するには、障害復旧(DR)計画を準備する必要があります。DR 計画の詳細については、障害復旧計画ガイドをご覧ください。

Kubernetes の障害復旧は、次の 2 つのフェーズで実装できます。

  • バックアップ。サービス中断イベントの発生前に、状態またはデータの特定の時点のスナップショットを作成します。
  • 復旧。障害の発生後に、バックアップ コピーから状態またはデータを復元します。

GKE クラスタでワークロードをバックアップして復元するには、Backup for GKE を使用します。このサービスは、新規および既存のクラスタで有効にできます。これにより、クラスタで実行される Backup for GKE エージェントがデプロイされます。エージェントは、構成とボリュームのバックアップ データの取得と、復旧のオーケストレートを担当します。

バックアップと復元のスコープは、クラスタ全体、Namespace、アプリケーション(matchLabels などのセレクタで定義)に設定できます。

PostgreSQL のバックアップと復元のシナリオの例

このセクションの例では、ProtectedApplication カスタム リソースを使用して、アプリケーション スコープでバックアップと復元の操作を行う方法を紹介しています。

次の図は、ProtectedApplication のコンポーネント リソースを示しています。つまり、postgresql-ha アプリケーションを表す StatefulSet と、同じラベル(app.kubernetes.io/name: postgresql-ha)を使用する pgpool の Deployment を示しています。

高可用性 PostgreSQL クラスタ用のバックアップと復元のソリューションの例を示す図。
図 2: 高可用性 PostgreSQL クラスタのバックアップと復元のソリューションの例

PostgreSQL ワークロードのバックアップと復元を準備する手順は次のとおりです。

  1. 環境変数を設定します。この例では、ProtectedApplication を使用して、PostgreSQL ワークロードとそのボリュームを移行元の GKE クラスタ(us-central1)から復元してから、異なるリージョン(us-west1)の別の GKE クラスタに復元します。

    export SOURCE_CLUSTER=cluster-db1
    export TARGET_CLUSTER=cluster-db2
    export REGION=us-central1
    export DR_REGION=us-west1
    export NAME_PREFIX=g-db-protected-app
    export BACKUP_PLAN_NAME=$NAME_PREFIX-bkp-plan-01
    export BACKUP_NAME=bkp-$BACKUP_PLAN_NAME
    export RESTORE_PLAN_NAME=$NAME_PREFIX-rest-plan-01
    export RESTORE_NAME=rest-$RESTORE_PLAN_NAME
    
  2. クラスタで Backup for GKE が有効になっていることを確認します。以前に行った Terraform 設定の一部として、すでに有効になっているはずです。

    gcloud container clusters describe $SOURCE_CLUSTER \
        --project=$PROJECT_ID  \
        --location=$REGION \
        --format='value(addonsConfig.gkeBackupAgentConfig)'
    

    Backup for GKE が有効になっている場合、コマンドの出力に enabled=True と表示されます。

バックアップ プランを設定して復元を実行する

Backup for GKE を使用すると、cron ジョブとしてバックアップ プランを作成できます。バックアップ プランには、ソースクラスタ、バックアップするワークロードの選択、このプランで生成したバックアップ アーティファクトが保存されているリージョンなどのバックアップ構成が含まれます。

バックアップと復元を行う手順は次のとおりです。

  1. cluster-db1 で ProtectedApplication のステータスを確認します。

    kubectl get ProtectedApplication -A
    

    出力は次のようになります。

    NAMESPACE    NAME            READY TO BACKUP
    postgresql   postgresql-ha   true
    
  2. ProtectedApplication のバックアップ プランを作成します。

    export NAMESPACE=postgresql
    export PROTECTED_APP=$(kubectl get ProtectedApplication -n $NAMESPACE | grep -v 'NAME' | awk '{ print $1 }')
    
    gcloud beta container backup-restore backup-plans create $BACKUP_PLAN_NAME \
    --project=$PROJECT_ID \
    --location=$DR_REGION \
    --cluster=projects/$PROJECT_ID/locations/$REGION/clusters/$SOURCE_CLUSTER \
    --selected-applications=$NAMESPACE/$PROTECTED_APP \
    --include-secrets \
    --include-volume-data \
    --cron-schedule="0 3 * * *" \
    --backup-retain-days=7 \
    --backup-delete-lock-days=0
    
  3. バックアップを手動で作成します。

    gcloud beta container backup-restore backups create $BACKUP_NAME \
    --project=$PROJECT_ID \
    --location=$DR_REGION \
    --backup-plan=$BACKUP_PLAN_NAME \
    --wait-for-completion
    
  4. 復元プランを設定します。

    gcloud beta container backup-restore restore-plans create $RESTORE_PLAN_NAME \
      --project=$PROJECT_ID \
      --location=$DR_REGION \
      --backup-plan=projects/$PROJECT_ID/locations/$DR_REGION/backupPlans/$BACKUP_PLAN_NAME \
      --cluster=projects/$PROJECT_ID/locations/$DR_REGION/clusters/$TARGET_CLUSTER \
      --cluster-resource-conflict-policy=use-existing-version \
      --namespaced-resource-restore-mode=delete-and-restore \
      --volume-data-restore-policy=restore-volume-data-from-backup \
      --selected-applications=$NAMESPACE/$PROTECTED_APP \
      --cluster-resource-scope-selected-group-kinds="storage.k8s.io/StorageClass","scheduling.k8s.io/PriorityClass"
    
  5. バックアップから復元します。

    gcloud beta container backup-restore restores create $RESTORE_NAME \
      --project=$PROJECT_ID \
      --location=$DR_REGION \
      --restore-plan=$RESTORE_PLAN_NAME \
      --backup=projects/$PROJECT_ID/locations/$DR_REGION/backupPlans/$BACKUP_PLAN_NAME/backups/$BACKUP_NAME \
      --wait-for-completion
    

クラスタが復元されていることを確認する

復元されたクラスタに、想定どおりのすべての Pod、PersistentVolume、StorageClass リソースが含まれていることを確認する手順は次のとおりです。

  1. バックアップ クラスタ cluster-db2 への kubectl コマンドライン アクセスを構成します。

    gcloud container clusters get-credentials $TARGET_CLUSTER --location $DR_REGION --project $PROJECT_ID
    
  2. 3/3 Pod で StatefulSet の準備ができていることを確認します。

    kubectl get all -n $NAMESPACE
    

    出力は次のようになります。

    NAME                                                   READY   STATUS    RESTARTS        AGE
    pod/postgresql-postgresql-ha-pgpool-778798b5bd-k2q4b   1/1     Running   0               4m49s
    pod/postgresql-postgresql-ha-postgresql-0              2/2     Running   2 (4m13s ago)   4m49s
    pod/postgresql-postgresql-ha-postgresql-1              2/2     Running   0               4m49s
    pod/postgresql-postgresql-ha-postgresql-2              2/2     Running   0               4m49s
    
    NAME                                                   TYPE        CLUSTER-IP        EXTERNAL-IP   PORT(S)    AGE
    service/postgresql-postgresql-ha-pgpool                ClusterIP   192.168.241.46    <none>        5432/TCP   4m49s
    service/postgresql-postgresql-ha-postgresql            ClusterIP   192.168.220.20    <none>        5432/TCP   4m49s
    service/postgresql-postgresql-ha-postgresql-headless   ClusterIP   None              <none>        5432/TCP   4m49s
    service/postgresql-postgresql-ha-postgresql-metrics    ClusterIP   192.168.226.235   <none>        9187/TCP   4m49s
    
    NAME                                              READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/postgresql-postgresql-ha-pgpool   1/1     1            1           4m49s
    
    NAME                                                         DESIRED   CURRENT   READY   AGE
    replicaset.apps/postgresql-postgresql-ha-pgpool-778798b5bd   1         1         1       4m49s
    
    NAME                                                   READY   AGE
    statefulset.apps/postgresql-postgresql-ha-postgresql   3/3     4m49s
    
  3. postgres Namespace 内のすべての Pod が動作していることを確認します。

    kubectl get pods -n $NAMESPACE
    

    出力は次のようになります。

    postgresql-postgresql-ha-pgpool-569d7b8dfc-2f9zx   1/1     Running   0          7m56s
    postgresql-postgresql-ha-postgresql-0              2/2     Running   0          7m56s
    postgresql-postgresql-ha-postgresql-1              2/2     Running   0          7m56s
    postgresql-postgresql-ha-postgresql-2              2/2     Running   0          7m56s
    
  4. PersistentVolume と StorageClass を確認します。復元処理中に、Backup for GKE は、ターゲット ワークロードにプロキシクラスを作成して、ソース ワークロード(サンプル出力の gce-pd-gkebackup-dn)でプロビジョニングされた StorageClass を置き換えます。

    kubectl get pvc -n $NAMESPACE
    

    出力は次のようになります。

    NAME                                         STATUS   VOLUME                 CAPACITY   ACCESS MODES   STORAGECLASS          AGE
    data-postgresql-postgresql-ha-postgresql-0   Bound    pvc-be91c361e9303f96   8Gi        RWO            gce-pd-gkebackup-dn   10m
    data-postgresql-postgresql-ha-postgresql-1   Bound    pvc-6523044f8ce927d3   8Gi        RWO            gce-pd-gkebackup-dn   10m
    data-postgresql-postgresql-ha-postgresql-2   Bound    pvc-c9e71a99ccb99a4c   8Gi        RWO            gce-pd-gkebackup-dn   10m
    

想定どおりのデータが復元されていることを確認する

想定どおりのデータが復元されていることを確認する手順は次のとおりです。

  1. PostgreSQL インスタンスに接続します。

    ./scripts/launch-client.sh
    kubectl exec -it pg-client -n postgresql -- /bin/bash
    
  2. 各テーブルの行数を確認します。

    psql -h $HOST_PGPOOL -U postgres -a -q -f /tmp/scripts/count-rows.sql
    select COUNT(*) from tb01;
    

    前述のテスト データセットを作成するで書き込んだデータと同様の結果を確認できます。出力は次のようになります。

    300000
    (1 row)
    
  3. クライアント Pod を終了します。

    exit