CloudNativePG を使用して PostgreSQL を GKE にデプロイする


このガイドでは、CloudNativePG オペレーターを使用して PostgreSQL クラスタを Google Kubernetes Engine(GKE)にデプロイする方法について説明します。

PostgreSQL は、数十年にわたって積極的に開発され、安定したクライアント パフォーマンスを実現するオープンソースのオブジェクト リレーショナル データベースです。レプリケーション、ポイントインタイム リカバリ、セキュリティ機能、拡張性など、幅広い機能を備えています。PostgreSQL は主要なオペレーティング システムと互換性があり、ACID(アトミック性、整合性、独立性、耐久性)標準に完全に準拠しています。

このガイドは、GKE に Postgres クラスタをデプロイすることに関心があるプラットフォーム管理者、クラウド アーキテクト、運用担当者を対象としています。Cloud SQL を使用する代わりに GKE で Postgres を実行すると、経験豊富なデータベース管理者がより柔軟に構成を管理できるようになります。

利点

CloudNativePG は、Apache 2 ライセンスの下で EBD が開発したオープンソースのオペレーターです。これにより、PostgreSQL のデプロイメントに次の機能が追加されます。

  • 宣言型および Kubernetes ネイティブの方法による PostgreSQL クラスタの管理と構成
  • Volume Snapshots または Cloud Storage を使用したバックアップ管理
  • 転送中の暗号化された TLS 接続、独自の認証局を使用する機能、TLS 証明書の自動発行とローテーションのための Certificate Manager とのインテグレーション
  • PostgreSQL のマイナー リリースに対するローリング アップデート
  • Kubernetes API サーバーを使用して、追加のツールなしで PostgreSQL クラスタのステータス維持と、高可用性のためのフェイルオーバーを実現
  • SQL で記述されたユーザー定義指標による組み込みの Prometheus エクスポータ構成

目標

  • Postgres 向けに GKE インフラストラクチャを計画して、デプロイする
  • Helm を使用して CloudNativePG Postgres オペレーターをデプロイして構成する
  • PostgreSQL クラスタをデプロイする
  • PostgreSQL の認証とオブザーバビリティを構成する

デプロイ アーキテクチャ

PostgreSQL には、スタンドアロン データベース サーバーからレプリケーションされた高可用性クラスタまで、さまざまなデプロイ オプションがあります。このチュートリアルでは、GKE への高可用性クラスタのデプロイに焦点を当てています。

このデプロイでは、リージョン GKE クラスタ内の複数のアベイラビリティ ゾーンに PostgreSQL クラスタのワークロードが分散され、高可用性と冗長性が確保されます。詳細については、リージョン クラスタをご覧ください。

次の図は、GKE クラスタ内の複数のノードとゾーンで実行されている Postgres クラスタを示しています。

GKE 上の Postgres クラスタ

  • デフォルトの設定には、1 つのメイン PostgreSQL サーバーと、メインサーバーが故障した場合に引き継ぐ準備ができている 2 つのバックアップ サーバーが含まれています。これにより、データベースの可用性が継続的に確保されます。

  • CloudNativePG オペレーター リソースは、GKE クラスタの別の Namespace を使用してリソースの隔離を強化し、PostgreSQL クラスタごとに 1 つのデータベースという推奨されるマイクロサービス アプローチを遵守します。データベースとそれに対応するユーザー(アプリユーザー)は、クラスタを表す Kubernetes カスタム リソースで定義されます。

  • ストレージは、データベースについて議論する際に重要な要素となります。ストレージを効率的に実行し、継続的な可用性を確保して、データの整合性を保証する必要があります。このような理由から、SSD ディスクに基づく premium-rwo ストレージ クラスをおすすめします。CloudNativePG オペレーターは、PostgreSQL クラスタの Pod を設定すると、必要に応じて PersistentVolumeClaims を自動的に作成します。

料金

このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

このドキュメントに記載されているタスクの完了後、作成したリソースを削除すると、それ以上の請求は発生しません。詳細については、クリーンアップをご覧ください。

始める前に

Cloud Shell には、このチュートリアルに必要なソフトウェア(kubectlgcloud CLIHelmTerraform など)がプリインストールされています。Cloud Shell を使用しない場合は、gcloud CLI をインストールする必要があります。

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. Google Cloud CLI をインストールします。
  3. gcloud CLI を初期化するには:

    gcloud init
  4. Google Cloud プロジェクトを作成または選択します

    • Google Cloud プロジェクトを作成します。

      gcloud projects create PROJECT_ID

      PROJECT_ID は、作成する Google Cloud プロジェクトの名前に置き換えます。

    • 作成した Google Cloud プロジェクトを選択します。

      gcloud config set project PROJECT_ID

      PROJECT_ID は、実際の Google Cloud プロジェクト名に置き換えます。

  5. Google Cloud プロジェクトで課金が有効になっていることを確認します

  6. Compute Engine, IAM, GKE, Resource Manager API を有効にします。

    gcloud services enable compute.googleapis.comiam.googleapis.comcontainer.googleapis.comcloudresourcemanager.googleapis.com
  7. Google Cloud CLI をインストールします。
  8. gcloud CLI を初期化するには:

    gcloud init
  9. Google Cloud プロジェクトを作成または選択します

    • Google Cloud プロジェクトを作成します。

      gcloud projects create PROJECT_ID

      PROJECT_ID は、作成する Google Cloud プロジェクトの名前に置き換えます。

    • 作成した Google Cloud プロジェクトを選択します。

      gcloud config set project PROJECT_ID

      PROJECT_ID は、実際の Google Cloud プロジェクト名に置き換えます。

  10. Google Cloud プロジェクトで課金が有効になっていることを確認します

  11. Compute Engine, IAM, GKE, Resource Manager API を有効にします。

    gcloud services enable compute.googleapis.comiam.googleapis.comcontainer.googleapis.comcloudresourcemanager.googleapis.com
  12. Google アカウントにロールを付与します。次の IAM ロールごとに次のコマンドを 1 回実行します。 roles/compute.securityAdmin, roles/compute.viewer, roles/container.clusterAdmin, roles/container.admin, roles/iam.serviceAccountAdmin, roles/iam.serviceAccountUser

    gcloud projects add-iam-policy-binding PROJECT_ID --member="user:EMAIL_ADDRESS" --role=ROLE
    • PROJECT_ID は、実際のプロジェクト ID に置き換えます。
    • EMAIL_ADDRESS は実際のメールアドレスに置き換えます。
    • ROLE は、個々のロールに置き換えます。

環境を設定する

環境の設定手順は次のとおりです。

  1. 環境変数を設定します。

    export PROJECT_ID=PROJECT_ID
    export KUBERNETES_CLUSTER_PREFIX=postgres
    export REGION=us-central1
    

    PROJECT_ID は、実際の Google Cloud プロジェクト ID に置き換えます。

  2. GitHub リポジトリのクローンを作成します。

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    
  3. 作業ディレクトリを変更します。

    cd kubernetes-engine-samples/databases/postgresql-cloudnativepg
    

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

このセクションでは、Terraform スクリプトを実行して、限定公開の高可用性リージョン GKE クラスタを作成します。

オペレーターは、Standard または Autopilot クラスタを使用してインストールできます。

Standard

次の図は、3 つの異なるゾーンにデプロイされた限定公開のリージョン GKE Standard クラスタを示しています。

このインフラストラクチャをデプロイするには、次のコマンドを実行します。

export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
terraform -chdir=terraform/gke-standard init
terraform -chdir=terraform/gke-standard apply \
-var project_id=${PROJECT_ID}   \
-var region=${REGION}  \
-var cluster_prefix=${KUBERNETES_CLUSTER_PREFIX}

プロンプトが表示されたら、「yes」と入力します。このコマンドが完了し、クラスタが準備完了ステータスになるまでに数分かかることがあります。

Terraform が次のリソースを作成します。

  • Kubernetes ノード用の VPC ネットワークとプライベート サブネット
  • NAT 経由でインターネットにアクセスするためのルーター
  • us-central1 リージョンの限定公開 GKE クラスタ
  • 自動スケーリングが有効になっているノードプール(ゾーンあたり 1~2 ノード、最小ゾーンあたり 1 ノード)

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

...
Apply complete! Resources: 14 added, 0 changed, 0 destroyed.
...

Autopilot

次の図は、限定公開のリージョン GKE Autopilot クラスタを示しています。

このインフラストラクチャをデプロイするには、次のコマンドを実行します。

export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
terraform -chdir=terraform/gke-autopilot init
terraform -chdir=terraform/gke-autopilot apply \
-var project_id=${PROJECT_ID} \
-var region=${REGION} \
-var cluster_prefix=${KUBERNETES_CLUSTER_PREFIX}

プロンプトが表示されたら、「yes」と入力します。このコマンドが完了し、クラスタが準備完了ステータスになるまでに数分かかることがあります。

Terraform が次のリソースを作成します。

  • Kubernetes ノード用の VPC ネットワークとプライベート サブネット
  • NAT 経由でインターネットにアクセスするためのルーター
  • us-central1 リージョンの限定公開 GKE クラスタ
  • ロギングとモニタリングの権限を持つ ServiceAccount
  • Google Cloud Managed Service for Prometheus(クラスタ モニタリング用)

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

...
Apply complete! Resources: 12 added, 0 changed, 0 destroyed.
...

クラスタに接続する

クラスタと通信を行うように kubectl を構成します。

gcloud container clusters get-credentials ${KUBERNETES_CLUSTER_PREFIX}-cluster --region ${REGION}

CloudNativePG オペレーターをデプロイする

Helm チャートを使用して、CloudNativePG を Kubernetes クラスタにデプロイします。

  1. CloudNativePG オペレーターの Helm チャート リポジトリを追加します。

    helm repo add cnpg https://cloudnative-pg.github.io/charts
    
  2. Helm コマンドライン ツールを使用して CloudNativePG オペレーターをデプロイします。

    helm upgrade --install cnpg \
        --namespace cnpg-system \
        --create-namespace \
        cnpg/cloudnative-pg
    

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

    Release "cnpg" does not exist. Installing it now.
    NAME: cnpg
    LAST DEPLOYED: Fri Oct 13 13:52:36 2023
    NAMESPACE: cnpg-system
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    ...
    

Postgres をデプロイする

次のマニフェストは、CloudNativePG オペレーターのカスタム リソースで定義された PostgreSQL クラスタを記述しています。

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: gke-pg-cluster
spec:
  description: "Standard GKE PostgreSQL cluster"
  imageName: ghcr.io/cloudnative-pg/postgresql:16.2
  enableSuperuserAccess: true
  instances: 3
  startDelay: 300
  primaryUpdateStrategy: unsupervised
  postgresql:
    pg_hba:
      - host all all 10.48.0.0/20 md5
  bootstrap:
    initdb:
      database: app
  storage:
    storageClass: premium-rwo
    size: 2Gi
  resources:
    requests:
      memory: "1Gi"
      cpu: "1000m"
    limits:
      memory: "1Gi"
      cpu: "1000m"
  affinity:
    enablePodAntiAffinity: true
    tolerations:
    - key: cnpg.io/cluster
      effect: NoSchedule
      value: gke-pg-cluster
      operator: Equal
    additionalPodAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app.component
              operator: In
              values:
              - "pg-cluster"
          topologyKey: topology.kubernetes.io/zone
  monitoring:
    enablePodMonitor: true

このマニフェストには次のフィールドがあります。

  • spec.instances: クラスタ Pod の数
  • spec.primaryUpdateStrategy: ローリング アップデート戦略。
    • Unsupervised: レプリカノードの後にプライマリ クラスタノードを自律的に更新します。
    • Supervised: プライマリ クラスタノードの手動切り替えが必要です
  • spec.postgresql: postgres.conf ファイル パラメータのオーバーライド(pg-hba ルール、LDAP、満たすべき同期レプリカの要件など)。
  • spec.storage: ストレージ関連の設定(ストレージ クラス、Volume のサイズ、write-ahead log の設定など)。
  • spec.bootstrap: クラスタで作成された初期データベースのパラメータ、ユーザー認証情報、データベースの復元オプション
  • spec.resources: クラスタ Pod のリクエストと上限
  • spec.affinity: クラスタ ワークロードのアフィニティと反アフィニティ ルール

基本的な Postgres クラスタを作成する

  1. Namespace を作成します。

    kubectl create ns pg-ns
    
  2. カスタム リソースを使用して PostgreSQL クラスタを作成します。

    kubectl apply -n pg-ns -f manifests/01-basic-cluster/postgreSQL_cluster.yaml
    

    このコマンドの完了までに数分かかることがあります。

  3. クラスタのステータスを確認します。

    kubectl get cluster -n pg-ns --watch
    

    出力に Cluster in healthy state のステータスが表示されるまで待ってから、次のステップに進みます。

    NAME             AGE     INSTANCES   READY   STATUS                     PRIMARY
    gke-pg-cluster   2m53s   3           3       Cluster in healthy state   gke-pg-cluster-1
    

リソースを検査する

GKE がクラスタのリソースを作成したことを確認します。

kubectl get cluster,pod,svc,pvc,pdb,secret,cm -n pg-ns

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

NAME                                        AGE   INSTANCES   READY   STATUS                     PRIMARY
cluster.postgresql.cnpg.io/gke-pg-cluster   32m   3           3       Cluster in healthy state   gke-pg-cluster-1

NAME                   READY   STATUS    RESTARTS   AGE
pod/gke-pg-cluster-1   1/1     Running   0          31m
pod/gke-pg-cluster-2   1/1     Running   0          30m
pod/gke-pg-cluster-3   1/1     Running   0          29m

NAME                        TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/gke-pg-cluster-r    ClusterIP   10.52.11.24   <none>        5432/TCP   32m
service/gke-pg-cluster-ro   ClusterIP   10.52.9.233   <none>        5432/TCP   32m
service/gke-pg-cluster-rw   ClusterIP   10.52.1.135   <none>        5432/TCP   32m

NAME                                     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/gke-pg-cluster-1   Bound    pvc-bbdd1cdd-bdd9-4e7c-8f8c-1a14a87e5329   2Gi        RWO            standard       32m
persistentvolumeclaim/gke-pg-cluster-2   Bound    pvc-e7a8b4df-6a3e-43ce-beb0-b54ec1d24011   2Gi        RWO            standard       31m
persistentvolumeclaim/gke-pg-cluster-3   Bound    pvc-dac7f931-6ac5-425f-ac61-0cfc55aae72f   2Gi        RWO            standard       30m

NAME                                                MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
poddisruptionbudget.policy/gke-pg-cluster           1               N/A               1                     32m
poddisruptionbudget.policy/gke-pg-cluster-primary   1               N/A               0                     32m

NAME                                TYPE                       DATA   AGE
secret/gke-pg-cluster-app           kubernetes.io/basic-auth   3      32m
secret/gke-pg-cluster-ca            Opaque                     2      32m
secret/gke-pg-cluster-replication   kubernetes.io/tls          2      32m
secret/gke-pg-cluster-server        kubernetes.io/tls          2      32m
secret/gke-pg-cluster-superuser     kubernetes.io/basic-auth   3      32m

NAME                                DATA   AGE
configmap/cnpg-default-monitoring   1      32m
configmap/kube-root-ca.crt          1      135m

オペレーターが次のリソースを作成します。

  • オペレーターが制御する PostgreSQL クラスタを表すクラスタ カスタム リソース
  • 対応する永続ボリュームがある PersistentVolumeClaim リソース
  • データベースへのアクセスや Postgres ノード間のレプリケーションのためのユーザー認証情報を含むシークレット。
  • クラスタに接続する <name>-rw<name>-ro<name>-r の 3 つのデータベース エンドポイント サービス。詳細については、PostgreSQL のアーキテクチャをご覧ください。

Postgres に対する認証を行う

PostgreSQL データベースに接続し、オペレーターによって作成されたさまざまなサービス エンドポイントを介してアクセスを確認できます。これを行うには、PostgreSQL クライアントがあり、同期アプリケーション ユーザー認証情報が環境変数としてマウントされた追加の Pod を使用します。

  1. クライアント Pod を実行して Postgres クラスタとやり取りします。

    kubectl apply -n pg-ns -f manifests/02-auth/pg-client.yaml
    
  2. pg-client Pod で exec コマンドを実行し、gke-pg-cluster-rw Service にログインします。

    kubectl wait --for=condition=Ready -n pg-ns pod/pg-client --timeout=300s
    kubectl exec -n pg-ns -i -t pg-client -- /bin/sh
    
  3. gke-pg-cluster-rw Service を使用してデータベースにログインし、読み取り / 書き込み権限で接続を確立します。

    psql postgresql://$CLIENTUSERNAME:$CLIENTPASSWORD@gke-pg-cluster-rw.pg-ns/app
    

    ターミナルはデータベース名で始まります。

    app=>
    
  4. テーブルを作成します。

    CREATE TABLE travel_agency_clients (
    client VARCHAR ( 50 ) UNIQUE NOT NULL,
    address VARCHAR ( 50 ) UNIQUE NOT NULL,
    phone VARCHAR ( 50 ) UNIQUE NOT NULL);
    
  5. テーブルにデータを挿入します。

    INSERT INTO travel_agency_clients(client, address, phone)
    VALUES ('Tom', 'Warsaw', '+55555')
    RETURNING *;
    
  6. 作成したデータを表示します。

    SELECT * FROM travel_agency_clients ;
    

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

    client | address |  phone
    --------+---------+---------
    Tom    | Warsaw  | +55555
    (1 row)
    
  7. 現在のデータベース セッションからログアウトします。

    exit
    
  8. gke-pg-cluster-ro Service を使用してデータベースにログインし、読み取り専用アクセス権を確認します。この Service ではデータのクエリは許可されますが、書き込みオペレーションは制限されます。

    psql postgresql://$CLIENTUSERNAME:$CLIENTPASSWORD@gke-pg-cluster-ro.pg-ns/app
    
  9. 新しいデータの挿入を試みます。

    INSERT INTO travel_agency_clients(client, address, phone)
    VALUES ('John', 'Paris', '+55555')
    RETURNING *;
    

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

    ERROR:  cannot execute INSERT in a read-only transaction
    
  10. データの読み取りを試みます。

    SELECT * FROM travel_agency_clients ;
    

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

    client | address |  phone
    --------+---------+---------
    Tom    | Warsaw  | +55555
    (1 row)
    
  11. 現在のデータベース セッションからログアウトします。

    exit
    
  12. Pod のシェルを終了します。

    exit
    

Prometheus が Postgres クラスタの指標を収集する仕組みを理解する

次の図は、Prometheus 指標の収集の仕組みを示しています。

この図では、GKE 限定公開クラスタに次のものが存在します。

  • パス / とポート 9187 の指標を収集する Postgres Pod
  • Postgres Pod の指標を処理する Prometheus ベースのコレクタ
  • Cloud Monitoring に指標を送信する PodMonitoring リソース

Pod から指標を収集できるようにするには、次の手順で操作します。

  1. PodMonitoring リソースを作成します。

    kubectl apply -f manifests/03-observability/pod-monitoring.yaml -n pg-ns
    
  2. Google Cloud コンソールで、[Metrics Explorer] ページに移動します。

    [Metrics Explorer] に移動

    ダッシュボードにゼロ以外の指標の取り込み率が表示されます。

  3. [指標を選択] で、「Prometheus Target」と入力します。

  4. [有効な指標カテゴリ] セクションで、[Cnpg] を選択します。

指標のダッシュボードを作成する

エクスポートされた指標を可視化するには、指標ダッシュボードを作成します。

  1. ダッシュボードをデプロイします。

    gcloud --project "${PROJECT_ID}" monitoring dashboards create --config-from-file manifests/03-observability/gcp-pg.json
    
  2. Google Cloud コンソールで [ダッシュボード] ページに移動します。

    ダッシュボードに移動する

  3. [PostgresQL Prometheus Overview] ダッシュボードを選択します。

    ダッシュボードがどのように関数をモニタリングしているかを確認するには、[データベース認証] セクションのアクションを再利用し、データベースに読み取り / 書き込みリクエストを適用した後、収集した指標の可視化をダッシュボードで確認します。

  4. クライアント Pod に接続します。

    kubectl exec -n pg-ns -i -t pg-client -- /bin/sh
    
  5. ランダムなデータを挿入します。

    psql postgresql://$CLIENTUSERNAME:$CLIENTPASSWORD@gke-pg-cluster-rw.pg-ns/app -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);INSERT INTO test (randomdata) VALUES (generate_series(1, 1000));"
    
  6. ダッシュボードを更新します。グラフは実際の指標で更新されます。

  7. Pod のシェルを終了します。

    exit
    

クリーンアップ

プロジェクトを削除する

    Delete a Google Cloud project:

    gcloud projects delete PROJECT_ID

リソースを個別に削除する

  1. 環境変数を設定します。

    export PROJECT_ID=${PROJECT_ID}
    export KUBERNETES_CLUSTER_PREFIX=postgres
    export REGION=us-central1
    
  2. terraform destroy コマンドを実行します。

    export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
    terraform  -chdir=terraform/FOLDER destroy \
      -var project_id=${PROJECT_ID} \
      -var region=${REGION} \
      -var cluster_prefix=${KUBERNETES_CLUSTER_PREFIX}
    

    FOLDER は、gke-autopilot または gke-standard に置き換えます。

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

  3. アタッチされていないすべてのディスクを検索します。

    export disk_list=$(gcloud compute disks list --filter="-users:* AND labels.name=${KUBERNETES_CLUSTER_PREFIX}-cluster" --format "value[separator=|](name,zone)")
    
  4. ディスクを削除します。

    for i in $disk_list; do
      disk_name=$(echo $i| cut -d'|' -f1)
      disk_zone=$(echo $i| cut -d'|' -f2|sed 's|.*/||')
      echo "Deleting $disk_name"
      gcloud compute disks delete $disk_name --zone $disk_zone --quiet
    done
    

次のステップ