メインコンテンツまでスキップ

【チュートリアル】ゼロから始める企業級高可用 PostgreSQL クラスタの構築

342.webp

著者序

本文は 私のブログサイト から転載しました。原文アドレス:【チュートリアル】ゼロから始める企業級高可用 PostgreSQL クラスタの構築

すべてが始まる前に、以下の事項にご注意ください:

  1. 本文は、あなたが既に【チュートリアル】ゼロから始める最小高可用 k3s クラスタの構築を完了していることを前提としています
  2. 本文で使用する開発環境は ArchLinux + Fish Shell です。システム間のコマンド構文の違いにより、実際の状況に応じてコマンド形式を調整してください。
  3. 本文で使用するオブジェクトストレージサービスは S3 プロトコルに対応したオブジェクトストレージです。実際の状況に応じて設定方法を調整してください。
  4. 本文のコマンドは各種 CLI ツール(本文中に記載されているか、前置チュートリアルで記載されています。Linux システムの一般的な CLI ツールは本文中に記載されていません)に依存しています。CLI ツールのインストールプロセスは複雑で、各プラットフォームのインストール手順は一貫していないため、ここでは詳しく説明しません。公式ドキュメントのインストールチュートリアルを参照してください。

序論

PostgreSQL は最も機能が豊富なオープンソースリレーショナルデータベースです(具体的な利点についてはネット上で何度も紹介されているため、ここでは詳しく説明しません)。これは著者が最も使用しているリレーショナルデータベースでもあります(他にはありません)。したがって、本人のチュートリアルで使用するリレーショナルデータベースは一般的に PostgreSQL を中心としているため、このチュートリアルを Kubernetes 関連チュートリアルの非常に基本的な前置チュートリアルと見なすことができます。
次に、この CloudNativePG(CNPG)について説明します。それは何ですか?簡単に言えば、Kubernetes 上の PostgreSQL の特化版と見なすことができます。PostgreSQL のデプロイと管理プロセスを大幅に簡素化し、同時に企業級の要件を満たすために、高可用性(HA)、自動バックアップ、障害演習、自動スケーリングなど、多くの高度な機能を提供しています。 本チュートリアルは k3s + Longhorn に基づき、CloudNativePG と汎用の S3 互換オブジェクトストレージを使用して、ゼロから高可用 PostgreSQL クラスタを構築し、基本的なバックアップと運用操作を実演します。最後に、手動で障害を作成し、データベースの自動復旧能力をテストします。

前提条件

デプロイを開始する前に、以下の前提条件が満たされていることを確認してください:

基盤インフラストラクチャ

まず、【チュートリアル】ゼロから始める最小高可用 k3s クラスタの構築 のような最小高可用 k3s クラスタを準備する必要があります。

CLI ツール

チュートリアルを開始する前に、以下の CLI ツールを準備する必要があります:

  1. kubectl。クラスタの管理に使用されます。前のチュートリアルでは、既に kubectl をインストールし、リモートクラスタの管理権限(kubeconfig)を設定しました。
    • 検証コマンド:
      kubectl version --short
    • 理想的な出力:Client Version: <client_version> を表示します。正常に接続されている場合は、追加で Server Version: <server_version> が返されます。
  2. helm。Operator のデプロイに使用されます。前のチュートリアルでは、既に helm をインストールし、リモートクラスタの管理権限(kubeconfig)を設定しました。
    • 検証コマンド:
      helm version
    • 理想的な出力:version.BuildInfo フィールドに Version: <helm_version>GitCommit: <commit_hash> などの情報が含まれています。
  3. kubeseal。Kubernetes Secret を暗号化するために使用されます。公式チュートリアルを参照して自分でインストールしてください。
    • 検証コマンド:
      kubeseal --version
    • 理想的な出力:kubeseal version: <kubeseal_version> を出力します。
  4. openssl。ランダムパスワードを生成するために使用されます。これは通常、システムに付属しています。ない場合は、公式チュートリアルを参照して自分でインストールしてください。
    • 検証コマンド:
      openssl version
    • 理想的な出力:OpenSSL バージョン情報を出力します(例:OpenSSL <openssl_version> ...

サードパーティ基盤インフラストラクチャ

S3 互換のオブジェクトストレージサービスアカウント(AWS S3、Wasabi、Backblaze B2、または自構築 MinIO など)を準備し、Bucket を作成してアクセスキーにアクセスできることを確認する必要があります。

デプロイアーキテクチャ概要

本チュートリアルでは、以下の操作を実装します:

  1. cnpg-system 名前空間で CloudNativePG Operator を実行します。
  2. data-infra 名前空間で 3 レプリカの PostgreSQL クラスタ(1 Primary + 2 Replica)を作成します。
  3. barman 持続バックアップをオブジェクトストレージに有効にし、アプリケーション用に ReadWrite Service を公開します。
  4. cloudflared サイドカーコンテナを使用して、データベースを安全に公開ネットワークに公開し、開発テストに使用します。
  5. 運用演習。データベースのローリング更新とプラグインのインストールをテストします。
  6. 災害演習。ランダムにノードを爆発させ、自動復旧能力を観察します。極端な環境でオブジェクトストレージ内のバックアップデータを使用してデータベースを再構築するテストを実施します。
  7. 可観測性を実装し、PrometheusGrafana を通じてデータベースの状態を監視します。

349.webp

ステップ 1:CloudNativePG Operator のインストール

公式では Helm を使用した Operator のデプロイを推奨しています。まず Helm リポジトリを追加してインストールを完了します:

helm repo add cloudnative-pg https://cloudnative-pg.github.io/charts
helm repo update

kubectl create namespace cnpg-system

helm upgrade --install cloudnative-pg cloudnative-pg/cloudnative-pg \
--namespace cnpg-system \
--set controllerManager.resources.requests.cpu=200m \
--set controllerManager.resources.requests.memory=256Mi \
--set controllerManager.resources.limits.cpu=500m \
--set controllerManager.resources.limits.memory=512Mi

結果は以下の図のとおりです:

353.webp 354.webp

インストールコマンドパラメータの説明:

パラメータ/フラグメント機能備考
helm upgrade --installRelease が既に存在する場合はアップグレード、そうでない場合は初回インストールデプロイコマンドをべき等に保つ
cloudnative-pgHelm Release の名前Helm がこのデプロイを追跡するために使用
cloudnative-pg/cloudnative-pgChart のリポジトリと名前事前に helm repo add cloudnative-pg … が必要
--namespace cnpg-system使用する Kubernetes 名前空間Operator はこの名前空間で実行されます
--set controllerManager.resources.requests.cpu=200mController Manager がリクエストする最小 CPU最低 0.2 コアのスケジューリングを保証
--set controllerManager.resources.requests.memory=256MiController Manager がリクエストする最小メモリ最低 256Mi のスケジューリングを保証
--set controllerManager.resources.limits.cpu=500mController Manager の CPU 上限0.5 コアを超えると制限されます
--set controllerManager.resources.limits.memory=512MiController Manager のメモリ上限512Mi を超えると OOM がトリガーされる可能性があります

Chart またはイメージバージョンを固定したい場合は、明示的に --version <chartVersion>--set image.tag=<controllerTag> を追加できます。これらのパラメータを省略すると、Helm はリポジトリの現在の最新安定版と Chart のデフォルトイメージを使用します。

スケジューリングメカニズムの補足:controllerManager Pod は Kubernetes Scheduler によってスケジュールされます。まずリソースリクエスト(最低 200m CPU と 256Mi メモリ)を満たさないノードをフィルタリングし、次にクラスタの現在の負荷に基づいてスコアリングし、最終的に最適なノードを選択します。追加の nodeSelector、汚染容認、またはアフィニティ設定がない場合、任意の条件を満たすノードに落ちる可能性がありますが、結果はアルゴリズムとノードの状態に基づいて計算され、完全にランダムではありません。

クラスタが kube-routerCilium などのネットワークポリシーを有効にしている場合は、cnpg-system 名前空間内のコントローラが Kubernetes API とターゲット名前空間にアクセスできることを確認してください。

インストール完了後、Operator が準備完了しているかを確認します:

kubectl get pods -n cnpg-system
kubectl get crds | grep postgresql.cnpg.io

cluster.postgresql.cnpg.io などの CRD が登録され、Operator Pod が Running 状態にあることが確認できるはずです。

355.webp

ステップ 2:名前空間とアクセス認証情報の準備

Sealed Secrets のインストール

本チュートリアルでは、Sealed Secrets を使用して機密情報を管理する必要があります。Sealed Secrets を使用する前に、クラスタにコントローラをデプロイし、ローカルに kubeseal CLI をインストールする必要があります:

よくある質問

  • Q:なぜ Sealed Secrets を導入する必要があるのですか?
    A:Sealed Secrets は Kubernetes Secret を Git に安全に保存できるマニフェストに暗号化できます。バージョンリポジトリに保存されても平文は漏洩しません。クラスタ側のコントローラは権限に応じて解封して実際の Secret を生成し、機密設定を標準 GitOps フローで同期できるようにします。
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm repo update

kubectl create namespace sealed-secrets

helm upgrade --install sealed-secrets sealed-secrets/sealed-secrets \
--namespace sealed-secrets \
--set fullnameOverride=sealed-secrets-controller

上記のコマンドは sealed-secrets 名前空間でコントローラを実行し、名前を sealed-secrets-controller に固定します。これは後続の例で参照しやすくするためです。クラスタに既存のデプロイがある場合は、このステップをスキップできます。

次に、ローカルに kubeseal をインストールし、kubeseal --version を実行してバージョンを確認します。公式インストールチュートリアルを参照してください。

363.webp

注意:kubeseal はデフォルトで kube-system 名前空間で sealed-secrets-controller という名前のサービスを探します。例のように sealed-secrets 名前空間に配置され、パラメータが指定されていない場合、error: cannot get sealed secret service: services "sealed-secrets-controller" not found が表示されます。したがって、後続のコマンドは必ず --controller-namespace sealed-secrets --controller-name sealed-secrets-controller を明示的に渡すか、実際のインストール場所に応じて調整してください。

よくある質問

  • Q:kubeseal を実行すると error: cannot get sealed secret service: services "sealed-secrets-controller" not found というエラーが表示されるのはなぜですか?
    A:kubeseal はデフォルトで kube-system 名前空間で同名のサービスを探します。例ではコントローラを sealed-secrets 名前空間にデプロイしているため、--controller-namespace sealed-secrets --controller-name sealed-secrets-controller を追加する必要があります。そうしないと見つかりません。
  • Q:それなら、kubeseal がデフォルトで見つけられる kube-system 名前空間にサービスを直接デプロイしないのはなぜですか?
    A:主に分離と保守性のためです。kube-system は通常、クラスタの下位レイヤーコンポーネントによって占有されており、その名前空間をきれいに保つことで問題の診断が容易になります。同時に、独立した sealed-secrets 名前空間は関連リソースを集中管理でき、マルチ環境またはマルチテナント時にも権限制御が容易です。ツールがデフォルトで kube-system を指すのは、一般的なインストール方法との互換性のためです。実際のデプロイに応じてパラメータを渡すだけで十分です。

ビジネス Secret の生成

データベースレイヤー用に独立した名前空間を準備し、Sealed Secrets を使用して機密情報を管理し、雑多なリソースが同じスペースに混在するのを避けます:

kubectl create namespace data-infra

データベースアプリケーションアカウントの暗号化 Secret を生成します:

ステップ 1:元の Secret マニフェストを生成します(クラスタに書き込まれません)

kubectl create secret generic cnpg-app-user \
--namespace data-infra \
--from-literal=username=app_user \
--from-literal=password="$(openssl rand -base64 20)" \
--dry-run=client -o json > cnpg-app-user.secret.json

このコマンドは、ユーザー名とランダムパスワードを Kubernetes Secret の JSON マニフェストにカプセル化し、cnpg-app-user.secret.json に書き込みます。同時にクラスタへの副作用を保ちます。生成されたファイルの内容は以下のようなものです:

{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "cnpg-app-user",
"namespace": "data-infra"
},
"data": {
"password": "random-password",
"username": "random-username"
}
}

ステップ 2:kubeseal を使用して SealedSecret に暗号化します

kubeseal \
--controller-namespace sealed-secrets \
--controller-name sealed-secrets-controller \
--format yaml < cnpg-app-user.secret.json > cnpg-app-user.sealed-secret.yaml

ここで前のステップの JSON を手動で読み込み、kubeseal に渡します。名前空間とコントローラパラメータにより、CLI がデプロイした Sealed Secrets コントローラに接続できることを確認します。--format yaml は Git にコミットできる SealedSecret ファイルを出力します。ここで生成されたファイルの内容は以下のようなものです:

---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: cnpg-app-user
namespace: data-infra
spec:
encryptedData:
password: random-password(locked)
username: random-username(locked)
template:
metadata:
name: cnpg-app-user
namespace: data-infra

ステップ 3:SealedSecret をクラスタに適用し、元の Secret を削除します

kubectl apply -f cnpg-app-user.sealed-secret.yaml
rm cnpg-app-user.secret.json

コントローラが受け取った後、自動的に解封して同名の通常 Secret(cnpg-app-user)を生成します。これは後続のデータベースリソースで参照されます。cnpg-app-user.sealed-secret.yaml を適切に保管し、後続の CI/CD フローで使用できるようにしてください。

S3 互換オブジェクトストレージ認証情報の準備

  1. オブジェクトストレージサービスコンソール(AWS S3、Wasabi、Backblaze B2、または自構築 MinIO クラスタなど)にログインし、本チュートリアル用の専用 Bucket を新規作成します。例として cnpg-backup という名前を付け、Region は Kubernetes クラスタに最も近い地域を選択します。
  2. Bucket のアクセスポリシーで公開読み取り/書き込みを無効にし、認証情報アクセスのみを許可します。供給業者がライフサイクルまたはバージョン管理をサポートしている場合は、コンプライアンス要件に応じて事前に設定できます。
  3. 認証情報またはキー管理ページに移動し、本チュートリアル用のアクセスキーペアを作成します。最低限、ターゲット Bucket の Object ReadObject Write 権限を付与し、必要に応じて特定のプレフィックス範囲に制限できます。
  4. コンソールが返す Access Key IDSecret Access KeyRegion(該当する場合)、および S3 Endpoint(例:https://s3.ap-northeast-1.amazonaws.com または供給業者が提供するカスタムドメイン)を記録します。一部の供給業者は Path Style アクセスが必要かどうかを注記します。同時に記録してください。
  5. この情報をパスワードマネージャーに保存し、後続で Kubernetes Secret に書き込む平文値を準備します:aws_access_key_idaws_secret_access_keyregion(Region が固定されていない場合は供給業者の推奨値を入力)。同時に、CNPG で使用される destinationPath(例:s3://my-cnpg-backups/production)を確認します。

コンソール設定を完了した後、Sealed Secrets を使用してこのグループの認証情報を管理できます。

SealedSecret を使用したオブジェクトストレージ認証情報の設定

オブジェクトストレージバックアップを有効にする場合、同様に Sealed Secrets を使用してアクセス認証情報を生成します(S3 互換サービスの例):

kubectl create secret generic cnpg-backup-s3 \
--namespace data-infra \
--from-literal=aws_access_key_id=AKIA... \
--from-literal=aws_secret_access_key=xxxxxxxxxxxx \
--from-literal=region=ap-northeast-1 \
--dry-run=client -o json \
| kubeseal \
--controller-namespace sealed-secrets \
--controller-name sealed-secrets-controller \
--format yaml > cnpg-backup-s3.sealed-secret.yaml

kubectl apply -f cnpg-backup-s3.sealed-secret.yaml

ステップ 3:PostgreSQL クラスタの宣言

CloudNativePG は Cluster カスタムリソースを使用してデータベーススタックを定義します。以下は本番環境に適したサンプルです(ストレージクラスは前のチュートリアルでデプロイした Longhorn を使用):

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cnpg-ha
namespace: data-infra
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
primaryUpdateStrategy: unsupervised
storage:
size: 40Gi
storageClass: longhorn
postgresql:
parameters:
max_connections: "200"
shared_buffers: "512MB"
wal_level: logical
pg_hba:
- hostssl all all 0.0.0.0/0 md5
authentication:
superuser:
secret:
name: cnpg-app-user
replication:
secret:
name: cnpg-app-user
bootstrap:
initdb:
database: appdb
owner: app_user
secret:
name: cnpg-app-user
backup:
barmanObjectStore:
destinationPath: s3://my-cnpg-backups/production
endpointURL: https://s3.ap-northeast-1.amazonaws.com
s3Credentials:
accessKeyId:
name: cnpg-backup-s3
key: aws_access_key_id
secretAccessKey:
name: cnpg-backup-s3
key: aws_secret_access_key
region:
name: cnpg-backup-s3
key: region
retentionPolicy: "14d"
monitoring:
enablePodMonitor: true

cnpg-ha.yaml として保存して適用します:

kubectl apply -f cnpg-ha.yaml

CNPG は自動的に StatefulSet、PVC、サービスなどのリソースを作成します。Pod の起動完了を待ちます:

kubectl get pods -n data-infra -l cnpg.io/cluster=cnpg-ha -w

Primary と Replica の両方が Running 状態になると、データベースクラスタのデプロイが完了します。

ステップ 4:高可用性とサービス公開の検証

サービスと接続情報の確認

kubectl get svc -n data-infra -l cnpg.io/cluster=cnpg-ha

kubectl port-forward svc/cnpg-ha-rw 6432:5432 -n data-infra
psql "postgresql://app_user:$(kubectl get secret cnpg-app-user -n data-infra -o jsonpath='{.data.password}' | base64 -d)@127.0.0.1:6432/appdb"

CNPG はデフォルトで -rw(読み取り/書き込み)と -ro(読み取り専用)Service を作成し、アプリケーションがそれぞれプライマリデータベースとレプリカデータベースに接続しやすくします。

障害フェイルオーバーのシミュレーション

Primary Pod を強制的に削除して自動復旧能力を検証します:

kubectl delete pod -n data-infra \
$(kubectl get pods -n data-infra -l cnpg.io/cluster=cnpg-ha,role=primary -o jsonpath='{.items[0].metadata.name}')

数秒以内に、CNPG は自動的に Replica の 1 つを Primary に昇格させます。以下のコマンドでロール変更を確認します:

kubectl get pods -n data-infra -L role
kubectl logs -n data-infra -l cnpg.io/cluster=cnpg-ha,role=primary --tail=20

ステップ 5:バックアップと災害復旧戦略

  • 持続バックアップ(PITR)barmanObjectStore 設定は WAL を継続的にオブジェクトストレージに書き込み、kubectl cnpg backup コマンドで全量バックアップをトリガーできます。
  • 定期的な復旧演習:一時的な名前空間で Backup + Cluster CR を作成し、オブジェクトストレージから指定時点への復旧を検証します。
  • 監視とアラートmonitoring.enablePodMonitor を有効にした後、Prometheus で cnpg_pg_replication_lagcnpg_pg_is_in_recovery などの重要指標を取得できます。
  • リソースクォータdata-infra 名前空間に LimitRangeResourceQuota を設定し、データベースインスタンスがノードリソースを圧迫するのを防ぎます。

一般的な運用操作

  • オンライン拡張spec.instances の数を調整するだけで、CNPG は自動的に新しい Replica を作成してデータを同期します。
  • パラメータ調整spec.postgresql.parameters を更新すると、Operator はローリング再起動をトリガーして設定を下発します。
  • バージョンアップグレードspec.imageName を変更して新しいバージョンのイメージを指定すると、CNPG は最初に新しい Replica を作成し、switchover を実行した後に古い Primary を回収します。
  • 論理レプリケーションpg_hbawal_level 設定を通じて、特定のデータベース/テーブルを外部データウェアハウスに同期できます。

まとめと次のステップ

これで、高可用 k3s クラスタに自動フェイルオーバー、持続バックアップ、宣言型管理機能を備えた PostgreSQL クラスタをデプロイしました。次のステップは以下を検討できます:

  1. Grafana Loki または ELK を導入してデータベースログを収集し、可観測性を改善します。
  2. CloudNativePG Pooler または PgBouncer を使用して接続プール管理を最適化します。
  3. Argo CD/Fleet などの GitOps ツールと組み合わせて、データベース CR を継続的デリバリーフローに組み込み、設定変更の審査とロールバックを実現します。