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

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

PostgreSQL は、オープンソースの優れたオブジェクト リレーショナル データベース システムであり、数十年にわたって積極的に開発され、信頼性、機能の堅牢性、パフォーマンスで高い評価を得ています。

このガイドは、Cloud SQL for PostgreSQL の代わりに、GKE でデータベース アプリケーションとして PostgreSQL を実行することに関心があるプラットフォーム管理者、クラウド アーキテクト、運用担当者を対象としています。

環境の設定

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

  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/postgres-zalando
    

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

このセクションでは、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 ノード)
  • ロギングとモニタリングの権限を持つ ServiceAccount
  • Backup for GKE(障害復旧用)
  • Google Cloud Managed Service for Prometheus(クラスタ モニタリング用)

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

...
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 --location ${REGION}

Zalando オペレーターをクラスタにデプロイする

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

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

    helm repo add postgres-operator-charts https://opensource.zalando.com/postgres-operator/charts/postgres-operator
    
  2. Zalando オペレーターと Postgres クラスタの名前空間を作成します。

    kubectl create ns postgres
    kubectl create ns zalando
    
  3. Helm コマンドライン ツールを使用して Zalando オペレーターをデプロイします。

    helm install postgres-operator postgres-operator-charts/postgres-operator -n zalando \
        --set configKubernetes.enable_pod_antiaffinity=true \
        --set configKubernetes.pod_antiaffinity_preferred_during_scheduling=true \
        --set configKubernetes.pod_antiaffinity_topology_key="topology.kubernetes.io/zone" \
        --set configKubernetes.spilo_fsgroup="103"
    

    Postgres クラスタを表すカスタム リソースで podAntiAffinity 設定を直接構成することはできません。代わりに、オペレーターの設定ですべての Postgres クラスタの podAntiAffinity 設定をグローバルに設定します。

  4. Helm を使用して Zalando オペレーターのデプロイ ステータスを確認します。

    helm ls -n zalando
    

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

    NAME                 NAMESPACE    REVISION    UPDATED                                STATUS      CHART                       APP VERSION
    postgres-operator    zalando     1           2023-10-13 16:04:13.945614 +0200 CEST    deployed    postgres-operator-1.10.1    1.10.1
    

Postgres をデプロイする

Postgres クラスタ インスタンスの基本構成には、次のコンポーネントが含まれています。

  • Postgres の 3 つのレプリカ: 1 つのリーダー レプリカと 2 つのスタンバイ レプリカ。
  • 1 つの CPU リクエストと 2 つの CPU 上限に対する CPU リソースの割り当て(4 GB のメモリ リクエストと上限)。
  • toleration、nodeAffinitiestopologySpreadConstraints は各ワークロードに構成され、それぞれのノードプールと異なるアベイラビリティ ゾーンを利用して Kubernetes ノード間で適切に分散されます。

この構成は、本番環境に対応した Postgres クラスタの作成に必要な最小限の設定を表しています。

次のマニフェストは、Postgres クラスタを記述しています。

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: my-cluster
spec:
  dockerImage: ghcr.io/zalando/spilo-15:3.0-p1
  teamId: "my-team"
  numberOfInstances: 3
  users:
    mydatabaseowner:
    - superuser
    - createdb
    myuser: []
  databases:
    mydatabase: mydatabaseowner
  postgresql:
    version: "15"
    parameters:
      shared_buffers: "32MB"
      max_connections: "10"
      log_statement: "all"
      password_encryption: scram-sha-256
  volume:
    size: 5Gi
    storageClass: premium-rwo
  enableShmVolume: true
  podAnnotations:
    cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
  tolerations:
  - key: "app.stateful/component"
    operator: "Equal"
    value: "postgres-operator"
    effect: NoSchedule
  nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 1
      preference:
        matchExpressions:
        - key: "app.stateful/component"
          operator: In
          values:
          - "postgres-operator"
  resources:
    requests:
      cpu: "1"
      memory: 4Gi
    limits:
      cpu: "2"
      memory: 4Gi
  sidecars:
    - name: exporter
      image: quay.io/prometheuscommunity/postgres-exporter:v0.14.0
      args:
      - --collector.stat_statements
      ports:
      - name: exporter
        containerPort: 9187
        protocol: TCP
      resources:
        limits:
          cpu: 500m
          memory: 256M
        requests:
          cpu: 100m
          memory: 256M
      env:
      - name: "DATA_SOURCE_URI"
        value: "localhost/postgres?sslmode=require"
      - name: "DATA_SOURCE_USER"
        value: "$(POSTGRES_USER)"
      - name: "DATA_SOURCE_PASS"
        value: "$(POSTGRES_PASSWORD)"

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

  • spec.teamId: 選択したクラスタ オブジェクトの接頭辞
  • spec.numberOfInstances: クラスタの合計インスタンス数
  • spec.users: 権限を持つユーザーリスト
  • spec.databases: dbname: ownername 形式のデータベース リスト
  • spec.postgresql: postgres パラメータ
  • spec.volume: Persistent Disk のパラメータ
  • spec.tolerations: クラスタ Pod を pool-postgres ノードにスケジュールできるようにする toleration Pod テンプレート
  • spec.nodeAffinity: クラスタ Pod が pool-postgres ノードでスケジューリングされるよう GKE に指示する nodeAffinity Pod テンプレート。
  • spec.resources: クラスタ Pod のリクエストと上限
  • spec.sidecars: postgres-exporter を含むサイドカー コンテナのリスト

詳細については、Postgres ドキュメントのクラスタ マニフェスト リファレンスをご覧ください。

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

  1. 基本構成を使用して新しい Postgres クラスタを作成します。

    kubectl apply -n postgres -f manifests/01-basic-cluster/my-cluster.yaml
    

    このコマンドは、次の設定内容で Zalando オペレーターの PostgreSQL カスタム リソースを作成します。

    • CPU とメモリのリクエストと上限
    • プロビジョニングされた Pod レプリカを GKE ノードに分散するための taint とアフィニティ。
    • データベース
    • データベース所有者権限を持つ 2 人のユーザー
    • 権限のないユーザー
  2. GKE が必要なワークロードを開始するまで待ちます。

    kubectl wait pods -l cluster-name=my-cluster  --for condition=Ready --timeout=300s -n postgres
    

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

  3. GKE によって Postgres ワークロードが作成されたことを確認します。

    kubectl get pod,svc,statefulset,deploy,pdb,secret -n postgres
    

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

    NAME                                    READY   STATUS  RESTARTS   AGE
    pod/my-cluster-0                        1/1     Running   0         6m41s
    pod/my-cluster-1                        1/1     Running   0         5m56s
    pod/my-cluster-2                        1/1     Running   0         5m16s
    pod/postgres-operator-db9667d4d-rgcs8   1/1     Running   0         12m
    
    NAME                        TYPE        CLUSTER-IP  EXTERNAL-IP   PORT(S)   AGE
    service/my-cluster          ClusterIP   10.52.12.109   <none>       5432/TCP   6m43s
    service/my-cluster-config   ClusterIP   None        <none>      <none>  5m55s
    service/my-cluster-repl     ClusterIP   10.52.6.152 <none>      5432/TCP   6m43s
    service/postgres-operator   ClusterIP   10.52.8.176 <none>      8080/TCP   12m
    
    NAME                        READY   AGE
    statefulset.apps/my-cluster   3/3   6m43s
    
    NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/postgres-operator   1/1     1           1           12m
    
    NAME                                                MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
    poddisruptionbudget.policy/postgres-my-cluster-pdb   1              N/A             0                   6m44s
    
    NAME                                                            TYPE                DATA   AGE
    secret/my-user.my-cluster.credentials.postgresql.acid.zalan.do  Opaque              2   6m45s
    secret/postgres.my-cluster.credentials.postgresql.acid.zalan.do   Opaque            2   6m44s
    secret/sh.helm.release.v1.postgres-operator.v1                  helm.sh/release.v1   1      12m
    secret/standby.my-cluster.credentials.postgresql.acid.zalan.do  Opaque              2   6m44s
    secret/zalando.my-cluster.credentials.postgresql.acid.zalan.do  Opaque              2   6m44s
    

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

  • Postgres の 3 つの Pod レプリカを制御する Postgres StatefulSet
  • 少なくとも 1 つのレプリカを使用できる状態にする PodDisruptionBudgets
  • リーダー レプリカのみをターゲットとする my-cluster Service
  • 受信接続と Postgres レプリカ間のレプリケーション用に Postgres ポートを公開する my-cluster-repl Service
  • 実行中の Postgres Pod レプリカのリストを取得するための my-cluster-config ヘッドレス Service
  • データベースへのアクセスと Postgres ノード間のレプリケーションのためのユーザー認証情報を含むシークレット

Postgres に対する認証を行う

Postgres ユーザーを作成し、データベース権限を割り当てることができます。たとえば、次のマニフェストは、ユーザーとロールを割り当てるカスタム リソースを記述しています。

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: my-cluster
spec:
  ...
  users:
    mydatabaseowner:
    - superuser
    - createdb
    myuser: []
  databases:
    mydatabase: mydatabaseowner

このマニフェストの内容:

  • mydatabaseowner ユーザーには、SUPERUSERCREATEDB のロールが付与されており、完全な管理者権限(Postgres 構成の管理、新しいデータベース、テーブル、ユーザーの作成)が含まれます。このユーザーをクライアントと共有しないでください。たとえば、Cloud SQL では、お客様に SUPERUSER ロールを持つユーザーへのアクセス権を付与することは許可されません
  • myuser ユーザーにはロールが割り当てられていません。これは、SUPERUSER を使用して最小権限を付与されたユーザーを作成するベスト プラクティスに従っています。mydatabaseowner から myuser に詳細な権限が付与されます。セキュリティを維持するため、myuser 認証情報はクライアント アプリケーションとのみ共有する必要があります。

パスワードを保存する

scram-sha-256パスワードを保存するためのおすすめの方法を使用してください。たとえば、次のマニフェストは、postgresql.parameters.password_encryption フィールドを使用して scram-sha-256 暗号化を指定するカスタム リソースを記述しています。

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: my-cluster
spec:
  ...
  postgresql:
    parameters:
      password_encryption: scram-sha-256

ユーザー認証情報をローテーションする

Zalando を使用して、Kubernetes Secret に保存されているユーザー認証情報をローテーションできます。たとえば、次のマニフェストは、usersWithSecretRotation フィールドを使用してユーザー認証情報のローテーションを定義するカスタム リソースを記述しています。

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: my-cluster
spec:
  ...
  usersWithSecretRotation:
  - myuser
  - myanotheruser
  - ...

認証の例: Postgres に接続する

このセクションでは、サンプルの Postgres クライアントをデプロイし、Kubernetes Secret のパスワードを使用してデータベースに接続する方法について説明します。

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

    kubectl apply -n postgres -f manifests/02-auth/client-pod.yaml
    

    myuser ユーザーと mydatabaseowner ユーザーの認証情報が関連する Secret から取得され、環境変数として Pod にマウントされます。

  2. 準備ができたら、Pod に接続します。

    kubectl wait pod postgres-client --for=condition=Ready --timeout=300s -n postgres
    kubectl exec -it postgres-client -n postgres -- /bin/bash
    
  3. Postgres に接続し、myuser 認証情報を使用して新しいテーブルを作成してみます。

    PGPASSWORD=$CLIENTPASSWORD psql \
      -h my-cluster \
      -U $CLIENTUSERNAME \
      -d mydatabase \
      -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);"
    

    コマンドは、次のようなエラーにより失敗します。

    ERROR:  permission denied for schema public
    LINE 1: CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR...
    

    デフォルトで権限が割り当てられていないユーザーが行うことができるのは、Postgres へのログインとデータベースの一覧取得のみであるため、このコマンドは失敗します。

  4. mydatabaseowner 認証情報を使用してテーブルを作成し、テーブルに対するすべての権限を myuser に付与します。

    PGPASSWORD=$OWNERPASSWORD psql \
      -h my-cluster \
      -U $OWNERUSERNAME \
      -d mydatabase \
      -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);GRANT ALL ON test TO myuser;GRANT ALL ON SEQUENCE test_id_seq TO myuser;"
    

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

    CREATE TABLE
    GRANT
    GRANT
    
  5. myuser 認証情報を使用してランダムなデータをテーブルに挿入します。

    for i in {1..10}; do
      DATA=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13)
      PGPASSWORD=$CLIENTPASSWORD psql \
      -h my-cluster \
      -U $CLIENTUSERNAME \
      -d mydatabase \
      -c "INSERT INTO test(randomdata) VALUES ('$DATA');"
    done
    

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

    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    
  6. 挿入した値を取得します。

    PGPASSWORD=$CLIENTPASSWORD psql \
      -h my-cluster \
      -U $CLIENTUSERNAME \
      -d mydatabase \
      -c "SELECT * FROM test;"
    

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

    id |  randomdata
    ----+---------------
      1 | jup9HYsAjwtW4
      2 | 9rLAyBlcpLgNT
      3 | wcXSqxb5Yz75g
      4 | KoDRSrx3muD6T
      5 | b9atC7RPai7En
      6 | 20d7kC8E6Vt1V
      7 | GmgNxaWbkevGq
      8 | BkTwFWH6hWC7r
      9 | nkLXHclkaqkqy
     10 | HEebZ9Lp71Nm3
    (10 rows)
    
  7. Pod のシェルを終了します。

    exit
    

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

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

この図では、GKE のプライベート クラスタに次のものが存在します。

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

Google Cloud Managed Service for Prometheus は、Prometheus 形式での指標の収集をサポートしています。Cloud Monitoring は、Postgres 指標に対して統合ダッシュボードを使用します。

Zalando は、postgres_exporter コンポーネントサイドカー コンテナとして使用し、Prometheus 形式でクラスタ指標を公開します。

  1. labelSelector で指標を収集する PodMonitoring リソースを作成します。

    kubectl apply -n postgres -f manifests/03-prometheus-metrics/pod-monitoring.yaml
    
  2. Google Cloud コンソールで、GKE クラスタのダッシュボード ページに移動します。

    GKE クラスタ ダッシュボードに移動する

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

  3. Google Cloud コンソールで [ダッシュボード] ページに移動します。

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

  4. PostgreSQL Prometheus Overview ダッシュボードを開きます。ダッシュボードには取得された行数が表示されます。ダッシュボードの自動プロビジョニングには数分かかる場合があります。

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

    kubectl exec -it postgres-client -n postgres -- /bin/bash
    
  6. ランダムなデータを挿入します。

    for i in {1..100}; do
      DATA=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13)
      PGPASSWORD=$CLIENTPASSWORD psql \
      -h my-cluster \
      -U $CLIENTUSERNAME \
      -d mydatabase \
      -c "INSERT INTO test(randomdata) VALUES ('$DATA');"
    done
    
  7. ページを更新すると、[Rows] グラフと [Blocks] グラフが更新され、実際のデータベースの状態が表示されます。

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

    exit