背景と導入理由:深夜2時にデータベースが停止したとき
日曜日の深夜2時15分、PagerDutyのアラートで叩き起こされました。3つの重要な決済マイクロサービス全体で、毎秒450件の書き込みクエリが失敗し続けていたのです。コネクションプールは上限の1,000接続に達し、2台のリードレプリカは完全に同期不能となっていました。Kubernetes上でステートフルなワークロードを運用した経験がある方なら、プライマリデータベースのノードが不安定になり、ネットワークから突如消失したときの恐怖が身に染みてわかるはずです。
従来のPostgreSQL環境では、Patroni、keepalived、あるいは独自のPacemakerスクリプトといった外部ツールに依存するのが一般的でした。しかし、そうしたレガシーなパターンを素のKubernetes StatefulSetに無理やり適用すると、往々にして悲劇を招きます。スプリットブレインの危険に直面し、60秒に及ぶリーダー選定タイムアウトを待たされ、AWS EBSボリュームのアタッチがスタックする問題と格闘することになります。単なるPodの素早い再起動で済むはずだったものが、本番環境における20分間のシステム停止へと発展してしまうのです。
CloudNativePG(CNPG)はまったく異なるアプローチをとります。Kubernetesを従来のハイパーバイザーのように無理に扱うのではなく、データベースをKubernetesのネイティブな市民として扱います。PostgreSQL自体の内部エンジン合意メカニズムを利用してストリーミングレプリケーションを管理するため、外部のetcdクラスタも、脆弱なBashプローブも必要ありません。
オペレーターがKubernetes APIに直接統合されているため、ローリングアップデートをスムーズに実行し、10秒未満でフェイルオーバーを発動できます。さらに、継続的アーカイブとポイントインタイムリカバリ(PITR)を実現するため、WALアーカイブをS3互換のオブジェクトストレージへ直接バックアップします。
秒間約12,000クエリを処理する3つの本番クラスタをCNPGへ移行して以来、計画外のデータベース停止時間はゼロになりました。本記事では、真夜中のフェイルオーバーで二度と慌てずに済むよう、宣言的GitOpsを用いてCNPGでPostgreSQLをデプロイ、設定、運用する手順を詳しく解説します。
インストール:CloudNativePGオペレーターのデプロイ
ローカルPCから命令的に helm install コマンドを実行するのはやめましょう。他のクラスタインフラ定義と同様に、オペレーターの定義もGitで管理します。本番環境でCNPGを運用する最もクリーンな方法は、公式のコントローラーリリースマニフェストをトラッキングすることです。
まず、専用のネームスペースを作成し、コントローラーのバンドルを適用します:
# オペレーター専用のネームスペースを作成
kubectl create namespace cnpg-system
# CloudNativePGオペレーターのリリースマニフェストをデプロイ
kubectl apply --server-side -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.22/releases/cnpg-1.22.0.yaml
オペレーターのPodが正常に稼働し、カスタムリソース定義(CRD)が登録されていることを確認します:
kubectl get deployment -n cnpg-system cnpg-controller-manager
kubectl get crds | grep postgresql.cnpg.io
clusters.postgresql.cnpg.io、backups.postgresql.cnpg.io、poolers.postgresql.cnpg.io といった主要な定義が表示されます。コントローラーマネージャーはこれらのリソースを24時間監視し続けます。状態の乖離(ドリフト)が発生すると、稼働中のデータベーストポロジを宣言された仕様に合わせて即座にリコンサイル(調整)します。
設定:本番グレードの高可用性およびバックアップマニフェスト
実戦に耐えうるデータベーストポロジには、異なるアベイラビリティゾーンに分散された奇数台のレプリカ構成、S3への継続的なWAL(Write-Ahead Log)アーカイブ、そして厳密なフェイルオーバー制御という3つのコアコンポーネントが不可欠です。
1. クラウドストレージの認証情報を保存
CloudNativePGは内部でBarmanを組み込んでおり、16MBのWALセグメントをストリーミングし、ベースとなる物理スナップショットをオブジェクトストレージ(AWS S3、Cloudflare R2、MinIOなど)に直接転送します。データベース用のネームスペース内に認証情報を保持するSecretを作成します:
kubectl create namespace database
kubectl create secret generic s3-backup-creds \
--namespace database \
--from-literal=ACCESS_KEY_ID=minioadmin \
--from-literal=SECRET_ACCESS_KEY=miniopassword
2. 高可用性クラスタマニフェスト
このマニフェストをGitリポジトリ内に postgres-ha-cluster.yaml として保存します。ゾーンレベルのアンチアフィニティ、自動WAL圧縮、自動バックアップを備えた3インスタンスのPostgreSQL 16クラスタをプロビジョニングします:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-prod-cluster
namespace: database
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.2
# インスタンスを別ノード(ゾーン)に分散配置
affinity:
enablePodAntiAffinity: true
topologyKey: topology.kubernetes.io/zone
storage:
size: 50Gi
storageClass: gp3-ebs
postgresql:
parameters:
shared_buffers: "1GB"
work_mem: "16MB"
maintenance_work_mem: "128MB"
max_connections: "200"
wal_keep_size: "512MB"
# WALアーカイブと継続的な物理バックアップ
backup:
barmanObjectStore:
destinationPath: s3://postgres-backups-bucket/pg-prod/
endpointURL: https://s3.us-east-1.amazonaws.com
s3Credentials:
accessKeyId:
name: s3-backup-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: s3-backup-creds
key: SECRET_ACCESS_KEY
wal:
compression: gzip
maxParallel: 2
retentionPolicy: "30d"
monitoring:
enablePodMonitor: true
このファイルをリポジトリにプッシュします。ArgoCDやFluxなどのGitOpsコントローラーがこれを検知し、自動的にクラスタを同期します。
3. 自動ポイントインタイムリカバリ(PITR)
バグのあるマイグレーションスクリプトが深夜にテーブル全体をドロップしてしまった場合、フェイルオーバーでは救えません。そうした場合に必要なのがポイントインタイムリカバリです。CNPGによるクラスタの復旧には、手動でのシェル操作は一切不要です。新しいクラスタリソースを定義し、バケットを指定して、障害発生直前の目標タイムスタンプを設定するだけで完了します:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-prod-restored
namespace: database
spec:
instances: 3
storage:
size: 50Gi
storageClass: gp3-ebs
bootstrap:
recovery:
source: pg-prod-cluster
recoveryTarget:
targetTime: "2026-11-07 02:10:00.000000+00"
externalClusters:
- name: pg-prod-cluster
barmanObjectStore:
destinationPath: s3://postgres-backups-bucket/pg-prod/
endpointURL: https://s3.us-east-1.amazonaws.com
s3Credentials:
accessKeyId:
name: s3-backup-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: s3-backup-creds
key: SECRET_ACCESS_KEY
検証とモニタリング:負荷下でのフェイルオーバーの実証
高可用性は、テスト環境で意図的に障害を発生させて検証するまでは単なる幻想にすぎません。
1. CNPGプラグインでクラスタの健全性を確認
cnpg CLIプラグインを使用すると、レプリケーションの遅延、ロールの割り当て、WALのステータスを即座に確認できます:
# CNPG kubectlプラグインをインストール
curl -sSfL https://github.com/cloudnative-pg/cloudnative-pg/releases/download/v1.22.0/kubectl-cnpg_1.22.0_linux_x86_64.tar.gz | tar -xz
sudo mv kubectl-cnpg /usr/local/bin/kubectl-cnpg
# クラスタステータスを確認
kubectl cnpg status pg-prod-cluster -n database
このサマリーには、アクティブなリーダー、レプリケーションモード、各レプリカの同期ステータスが表示されます:
Cluster Summary
Name: pg-prod-cluster
Namespace: database
Status: Cluster in healthy state
Instances: 3
Ready instances: 3
Current Primary: pg-prod-cluster-1
Instances status
Name Database Status Current LSN Replication role Status
---- --------------- ----------- ---------------- ------
pg-prod-cluster-1 OK 0/A000040 Primary OK
pg-prod-cluster-2 OK 0/A000040 Standby (sync) OK
pg-prod-cluster-3 OK 0/A000040 Standby (sync) OK
2. 本番障害のシミュレーション(カオステスト)
OSのシャットダウン通知なしにプライマリPodを強制終了することで、突発的なホスト障害をシミュレートします:
kubectl delete pod pg-prod-cluster-1 -n database --grace-period=0 --force
オペレーターが対応する様子を、クラスタのイベントを追跡して確認します:
kubectl get events -n database --field-selector reason=Switchover -w
およそ4〜8秒で、CNPGはハートビートの喪失を検知します。pg-prod-cluster-2 をプライマリに昇格させ、pg-prod-cluster-3 に新しいリーダーへ追従するよう指示し、KubernetesネイティブのServiceを再接続します:
pg-prod-cluster-rw: エンドポイントIPを再割り当てし、書き込み処理が新しく選出されたプライマリに即座に届くようにします。pg-prod-cluster-ro: コネクションプールを切断することなく、正常なスタンバイノード群へ読み取り専用クエリをルーティングし続けます。
停止したPodが再起動すると、CNPGが自動的にリコンサイルを処理します。新しいプライマリから不足しているWALセグメントをストリーミングしてデータベースを最新状態に追いつかせ、スタンバイとしてクラスタに復帰させます。エンジニアが手動で端末を操作する必要は一切ありません。データベースのトポロジをGitにコミットし、フェイルオーバーを事前に検証しておくことで、深夜の緊急対応はバックグラウンドで静かに完了する日常的なルーチンワークへと変わるのです。
