このガイドでは、Zalando Postgres オペレーターを使用して Postgres クラスタを Google Kubernetes Engine(GKE)にデプロイする方法について説明します。
PostgreSQL は、オープンソースの優れたオブジェクト リレーショナル データベース システムであり、数十年にわたって積極的に開発され、信頼性、機能の堅牢性、パフォーマンスで高い評価を得ています。
このガイドは、Cloud SQL for PostgreSQL の代わりに、GKE でデータベース アプリケーションとして PostgreSQL を実行することに関心があるプラットフォーム管理者、クラウド アーキテクト、運用担当者を対象としています。
目標
- Postgres 向けに GKE インフラストラクチャを計画して、デプロイする
- Zalando Postgres オペレーターをデプロイして構成する
- オペレーターを使用して Postgres を構成し、可用性、セキュリティ、オブザーバビリティ、パフォーマンスを確保する
利点
Zalando には次のようなメリットがあります。
- PostgreSQL クラスタを管理、構成するための宣言型 Kubernetes ネイティブの方法
- Patroni による高可用性
- Cloud Storage バケットを使用したバックアップ管理のサポート
- Postgres クラスタの変更に対するローリング アップデート(簡単なマイナー バージョンの更新を含む)
- カスタム リソースを使用したパスワードの生成とローテーションを伴う宣言型ユーザー管理
- TLS、証明書のローテーション、接続プールのサポート
- クラスタのクローン作成とデータ レプリケーション
Deployment のアーキテクチャ
このチュートリアルでは、Zalando Postgres オペレーターを使用し、高可用性 Postgres クラスタを GKE にデプロイして構成します。クラスタには、Patroni によって管理される 1 つのリーダー レプリカと 2 つのスタンバイ(読み取り専用)レプリカがあります。Patroni は、Zalando が管理するオープンソース ソリューションであり、Postgres に高可用性と自動フェイルオーバーに関する機能を提供します。リーダーに障害が発生した場合、1 つのスタンバイ レプリカが自動的にリーダーロールに昇格します。
また、複数のアベイラビリティ ゾーンに複数の Kubernetes ノードを分散させ、高可用性のリージョンの Postgres 用 GKE クラスタをデプロイします。この設定により、フォールト トレラント、スケーラビリティ、地理的冗長性を確保できます。稼働時間と可用性の SLA を提供しながら、ローリング アップデートとメンテナンスが可能になります。詳細については、リージョン クラスタをご覧ください。
次の図は、GKE クラスタ内の複数のノードとゾーンで実行されている Postgres クラスタを示しています。
この図では、Postgres StatefulSet
が 3 つの異なるゾーンの 3 つのノードにデプロイされています。postgresql
カスタム リソースの仕様にアフィニティと反アフィニティに関する Pod の必要なルールを設定することで、GKE がノードにデプロイする方法を制御できます。1 つのゾーンで障害が発生した場合、GKE は推奨構成を使用してクラスタ内の他の使用可能なノードで Pod を再スケジュールします。データを永続化するには、低レイテンシと高 IOPS によりデータベースを読み込む際に優れたパフォーマンスが得られることから、ほとんどのケースで推奨される SSD ディスク(premium-rwo
StorageClass)を使用します。
費用
このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。
料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。
このドキュメントに記載されているタスクの完了後、作成したリソースを削除すると、それ以上の請求は発生しません。詳細については、クリーンアップをご覧ください。
始める前に
Cloud Shell には、このチュートリアルに必要なソフトウェア(kubectl
、gcloud CLI、Helm、Terraform など)がプリインストールされています。Cloud Shell を使用しない場合は、gcloud CLI をインストールする必要があります。
- 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.
- Install the Google Cloud CLI.
-
To initialize the gcloud CLI, run the following command:
gcloud init
-
Create or select a Google Cloud project.
-
Create a Google Cloud project:
gcloud projects create PROJECT_ID
Replace
PROJECT_ID
with a name for the Google Cloud project you are creating. -
Select the Google Cloud project that you created:
gcloud config set project PROJECT_ID
Replace
PROJECT_ID
with your Google Cloud project name.
-
-
Make sure that billing is enabled for your Google Cloud project.
-
Enable the Compute Engine, IAM, GKE, Backup for GKE APIs:
gcloud services enable compute.googleapis.com
iam.googleapis.com container.googleapis.com gkebackup.googleapis.com - Install the Google Cloud CLI.
-
To initialize the gcloud CLI, run the following command:
gcloud init
-
Create or select a Google Cloud project.
-
Create a Google Cloud project:
gcloud projects create PROJECT_ID
Replace
PROJECT_ID
with a name for the Google Cloud project you are creating. -
Select the Google Cloud project that you created:
gcloud config set project PROJECT_ID
Replace
PROJECT_ID
with your Google Cloud project name.
-
-
Make sure that billing is enabled for your Google Cloud project.
-
Enable the Compute Engine, IAM, GKE, Backup for GKE APIs:
gcloud services enable compute.googleapis.com
iam.googleapis.com container.googleapis.com gkebackup.googleapis.com -
Grant roles to your user account. Run the following command once for each of the following IAM roles:
roles/storage.objectViewer, roles/container.admin, roles/iam.serviceAccountAdmin, roles/compute.admin, roles/gkebackup.admin, roles/monitoring.viewer
gcloud projects add-iam-policy-binding PROJECT_ID --member="user:USER_IDENTIFIER" --role=ROLE
- Replace
PROJECT_ID
with your project ID. -
Replace
USER_IDENTIFIER
with the identifier for your user account. For example,user:myemail@example.com
. - Replace
ROLE
with each individual role.
- Replace
環境を設定する
環境の設定手順は次のとおりです。
環境変数を設定します。
export PROJECT_ID=PROJECT_ID export KUBERNETES_CLUSTER_PREFIX=postgres export REGION=us-central1
PROJECT_ID
は、実際の Google Cloud プロジェクト ID に置き換えます。GitHub リポジトリのクローンを作成します。
git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
作業ディレクトリを変更します。
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 --region ${REGION}
Zalando オペレーターをクラスタにデプロイする
Helm チャートを使用して、Zalando オペレーターを Kubernetes クラスタにデプロイします。
Zalando オペレーターの Helm チャート リポジトリを追加します。
helm repo add postgres-operator-charts https://opensource.zalando.com/postgres-operator/charts/postgres-operator
Zalando オペレーターと Postgres クラスタの名前空間を作成します。
kubectl create ns postgres kubectl create ns zalando
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
設定をグローバルに設定します。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、
nodeAffinities
、topologySpreadConstraints
は各ワークロードに構成され、それぞれのノードプールと異なるアベイラビリティ ゾーンを利用して Kubernetes ノード間で適切に分散されます。
この構成は、本番環境に対応した Postgres クラスタの作成に必要な最小限の設定を表しています。
次のマニフェストは、Postgres クラスタを記述しています。
このマニフェストには次のフィールドがあります。
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 クラスタを作成する
基本構成を使用して新しい Postgres クラスタを作成します。
kubectl apply -n postgres -f manifests/01-basic-cluster/my-cluster.yaml
このコマンドは、次の設定内容で Zalando オペレーターの PostgreSQL カスタム リソースを作成します。
- CPU とメモリのリクエストと上限
- プロビジョニングされた Pod レプリカを GKE ノードに分散するための taint とアフィニティ。
- データベース
- データベース所有者権限を持つ 2 人のユーザー
- 権限のないユーザー
GKE が必要なワークロードを開始するまで待ちます。
kubectl wait pods -l cluster-name=my-cluster --for condition=Ready --timeout=300s -n postgres
このコマンドが完了するまで数分かかる場合があります。
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
ユーザーには、SUPERUSER
とCREATEDB
のロールが付与されており、完全な管理者権限(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 のパスワードを使用してデータベースに接続する方法について説明します。
クライアント Pod を実行して Postgres クラスタとやり取りします。
kubectl apply -n postgres -f manifests/02-auth/client-pod.yaml
myuser
ユーザーとmydatabaseowner
ユーザーの認証情報が関連する Secret から取得され、環境変数として Pod にマウントされます。準備ができたら、Pod に接続します。
kubectl wait pod postgres-client --for=condition=Ready --timeout=300s -n postgres kubectl exec -it postgres-client -n postgres -- /bin/bash
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 へのログインとデータベースの一覧取得のみであるため、このコマンドは失敗します。
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
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
挿入した値を取得します。
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)
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 形式でクラスタ指標を公開します。
labelSelector
で指標を収集するPodMonitoring
リソースを作成します。kubectl apply -n postgres -f manifests/03-prometheus-metrics/pod-monitoring.yaml
Google Cloud コンソールで、GKE クラスタのダッシュボード ページに移動します。
ダッシュボードにゼロ以外の指標の取り込み率が表示されます。
Google Cloud コンソールで [ダッシュボード] ページに移動します。
PostgreSQL Prometheus Overview ダッシュボードを開きます。ダッシュボードには取得された行数が表示されます。ダッシュボードの自動プロビジョニングには数分かかる場合があります。
クライアント Pod に接続します。
kubectl exec -it postgres-client -n postgres -- /bin/bash
ランダムなデータを挿入します。
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
ページを更新すると、[Rows] グラフと [Blocks] グラフが更新され、実際のデータベースの状態が表示されます。
Pod のシェルを終了します。
exit
クリーンアップ
プロジェクトの削除
Delete a Google Cloud project:
gcloud projects delete PROJECT_ID
リソースを個別に削除する
環境変数を設定します。
export PROJECT_ID=${PROJECT_ID} export KUBERNETES_CLUSTER_PREFIX=postgres export REGION=us-central1
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
」と入力します。アタッチされていないすべてのディスクを検索します。
export disk_list=$(gcloud compute disks list --filter="-users:* AND labels.name=${KUBERNETES_CLUSTER_PREFIX}-cluster" --format "value[separator=|](name,zone)")
ディスクを削除します。
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
GitHub リポジトリを削除します。
rm -r ~/kubernetes-engine-samples/
次のステップ
- Google Cloud に関するリファレンス アーキテクチャ、図、ベスト プラクティスを確認する。Cloud アーキテクチャ センターをご覧ください。