目次
1. K8sでDBを運用すべきか?
1.1 2025年基準のメリット・デメリット分析
K8s DB運用のメリット
- 一貫したデプロイ:アプリケーションと同じGitOpsワークフローでDBも管理
- リソース効率:ノードリソースを他のワークロードと共有(専用VM比でコスト削減)
- 自動復旧:Operatorが障害検知と自動Failoverを実行(RTO 30秒以下)
- ポータビリティ:クラウドベンダーロックインなくどこでも同一構成
- 開発環境統合:開発/ステージングで本番と同一のDBスタック
K8s DB運用のデメリット
- 運用(うんよう)の複雑性:StorageClass、PV、Operatorアップグレードなど追加管理ポイント
- ストレージ性能:ネットワークストレージ(EBS/PD)はローカルディスク比でレイテンシが高い
- 専門性(せんもんせい)が必要:DBA + K8s運用の両方に深い理解が必要
- バックアップの複雑性:K8s特有のボリュームスナップショット、オブジェクトストレージ連携が必要
1.2 意思決定フレームワーク
| 基準 | K8s DB適合 | マネージドDB(RDS等)適合 |
|---|---|---|
| チーム能力 | K8s + DBA専門家あり | DBAなし |
| コスト感度 | 高い(インフラ最適化必要) | 中程度(管理コスト込みOK) |
| コンプライアンス | データ所在地制御が必要 | クラウドリージョンで十分 |
| マルチクラウド | 必須 | シングルクラウド |
| ワークロード規模 | 中小規模(数百GB) | 大規模(数TB以上) |
| SLA要件 | 99.9% 自社達成可能 | 99.99% ベンダーSLA必要 |
2. StatefulSet ディープダイブ
2.1 Pod Identity(順序インデックスと安定ホスト名)
StatefulSetは各Podに**順序(じゅんじょ)インデックス(ordinal index)**を付与します。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: database
spec:
serviceName: postgres-headless
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3-encrypted
resources:
requests:
storage: 100Gi
このStatefulSetが生成するPod:
postgres-0 -> postgres-0.postgres-headless.database.svc.cluster.local
postgres-1 -> postgres-1.postgres-headless.database.svc.cluster.local
postgres-2 -> postgres-2.postgres-headless.database.svc.cluster.local
各Podは再起動後も同じ名前、同じPVCにバインドされます。
2.2 PodManagementPolicy
spec:
podManagementPolicy: OrderedReady # デフォルト
# OrderedReady: 0->1->2 の順序で作成、2->1->0 の順序で削除
# Parallel: 全Pod同時作成/削除(DBでは注意が必要)
- OrderedReady:Primary(0番)が先に起動しReady状態になった後、Replicaが起動
- Parallel:初期クラスターブートストラップ時のみ使用(既存データがない場合)
2.3 volumeClaimTemplates
volumeClaimTemplates:
- metadata:
name: data
labels:
type: database-storage
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3-encrypted
resources:
requests:
storage: 100Gi
このテンプレートは各Podに個別のPVCを作成します:
data-postgres-0 -> 100Gi PV (gp3-encrypted)
data-postgres-1 -> 100Gi PV (gp3-encrypted)
data-postgres-2 -> 100Gi PV (gp3-encrypted)
StatefulSet削除時、PVCは自動削除されません(データ保護)。
2.4 Update戦略
spec:
updateStrategy:
type: RollingUpdate # または OnDelete
rollingUpdate:
partition: 0 # partition以上のインデックスのみ更新
maxUnavailable: 1 # K8s 1.24+
- RollingUpdate:高いインデックスから逆順に更新(2 then 1 then 0)。Replicaが先、Primaryが最後
- OnDelete:Podを手動削除すると更新される。カナリー更新に有用
- partition:
partition: 2に設定するとインデックス2以上のみ更新(カナリー)
2.5 Headless Service
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
namespace: database
spec:
type: ClusterIP
clusterIP: None # Headless Service
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
Headless ServiceはDNS Aレコードで各PodのIPを直接返却します:
# 全Pod IP照会
nslookup postgres-headless.database.svc.cluster.local
# 特定Podへの直接アクセス
psql -h postgres-0.postgres-headless.database.svc.cluster.local -U postgres
3. CloudNativePG(PostgreSQL)ディープダイブ
3.1 Cluster CRD
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres
namespace: database
spec:
instances: 3
postgresql:
parameters:
shared_buffers: "2GB"
effective_cache_size: "6GB"
work_mem: "64MB"
maintenance_work_mem: "512MB"
max_connections: "200"
max_wal_size: "2GB"
min_wal_size: "1GB"
wal_buffers: "64MB"
random_page_cost: "1.1"
effective_io_concurrency: "200"
max_worker_processes: "8"
max_parallel_workers_per_gather: "4"
max_parallel_workers: "8"
storage:
size: 100Gi
storageClass: gp3-encrypted
pvcTemplate:
accessModes:
- ReadWriteOnce
walStorage:
size: 20Gi
storageClass: gp3-encrypted
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "4"
memory: "8Gi"
monitoring:
enablePodMonitor: true
bootstrap:
initdb:
database: myapp
owner: myapp
secret:
name: myapp-db-credentials
3.2 自動フェイルオーバー
CloudNativePGはPrimary障害時に自動的にReplicaを昇格します。
正常状態:
my-postgres-1 (Primary, RW)
my-postgres-2 (Replica, RO) - streaming replication
my-postgres-3 (Replica, RO) - streaming replication
Primary障害発生:
1. CloudNativePGがPrimary障害を検知(health check失敗)
2. 最新WAL位置を持つReplicaを選択
3. pg_promote()を実行 → 新Primaryに昇格
4. 残りのReplicaが新Primaryをフォロー
5. pg_rewindで旧PrimaryをReplicaとして再合流
フェイルオーバー後:
my-postgres-2 (Primary, RW) ← 自動昇格
my-postgres-3 (Replica, RO) - 新Primaryをフォロー
my-postgres-1 (Replica, RO) - pg_rewind後に再合流
フェイルオーバー所要時間:一般的に10-30秒。
3.3 バックアップ設定(Barman + Object Storage)
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres
spec:
instances: 3
backup:
barmanObjectStore:
destinationPath: s3://my-backup-bucket/postgres/
endpointURL: https://s3.ap-northeast-1.amazonaws.com
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: ACCESS_SECRET_KEY
wal:
compression: gzip
maxParallel: 4
data:
compression: gzip
immediateCheckpoint: true
retentionPolicy: "30d"
スケジュールバックアップ:
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: daily-backup
namespace: database
spec:
schedule: "0 3 * * *" # 毎日午前3時
backupOwnerReference: self
cluster:
name: my-postgres
immediate: false
suspend: false
3.4 WALアーカイビングとPITR
WAL(Write-Ahead Log)アーカイビングは継続的なデータ保護を提供します。
PITR(Point-in-Time Recovery)の実行:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres-restored
spec:
instances: 3
bootstrap:
recovery:
source: my-postgres
recoveryTarget:
targetTime: "2024-09-01 14:30:00.00000+09"
externalClusters:
- name: my-postgres
barmanObjectStore:
destinationPath: s3://my-backup-bucket/postgres/
endpointURL: https://s3.ap-northeast-1.amazonaws.com
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: ACCESS_SECRET_KEY
3.5 Connection Pooling(内蔵PgBouncer)
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: my-postgres-pooler-rw
namespace: database
spec:
cluster:
name: my-postgres
instances: 2
type: rw
pgbouncer:
poolMode: transaction
parameters:
max_client_conn: "1000"
default_pool_size: "25"
min_pool_size: "5"
reserve_pool_size: "5"
reserve_pool_timeout: "5"
server_idle_timeout: "300"
# 読み取り専用プーラー(Replica対象)
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: my-postgres-pooler-ro
spec:
cluster:
name: my-postgres
instances: 2
type: ro
pgbouncer:
poolMode: transaction
parameters:
max_client_conn: "2000"
default_pool_size: "50"
アプリケーション接続:
# 読み書き(Primary)
my-postgres-pooler-rw.database.svc.cluster.local:5432
# 読み取り専用(Replica)
my-postgres-pooler-ro.database.svc.cluster.local:5432
3.6 モニタリング(Prometheus + Grafana)
apiVersion: v1
kind: ConfigMap
metadata:
name: pg-custom-queries
namespace: database
data:
queries: |
pg_database_size:
query: "SELECT datname, pg_database_size(datname) as size_bytes FROM pg_database WHERE datname NOT IN ('template0', 'template1')"
master: true
metrics:
- datname:
usage: "LABEL"
description: "Database name"
- size_bytes:
usage: "GAUGE"
description: "Database size in bytes"
pg_replication_lag:
query: "SELECT CASE WHEN pg_is_in_recovery() THEN EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::int ELSE 0 END as lag_seconds"
master: false
metrics:
- lag_seconds:
usage: "GAUGE"
description: "Replication lag in seconds"
3.7 Rolling Update(ゼロダウンタイム更新)
# イメージ更新
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:16.3
# CloudNativePGが自動的に:
# 1. Replicaから1つずつ再起動
# 2. 各ReplicaがReady状態を確認してから次へ進行
# 3. 最後にPrimaryをswitchover(新Primary昇格後、旧Primaryを再起動)
# → ダウンタイムなしで更新完了
4. Percona Operator for MySQL
4.1 XtraDB Cluster(同期レプリケーション)
apiVersion: pxc.percona.com/v1
kind: PerconaXtraDBCluster
metadata:
name: my-mysql
namespace: database
spec:
crVersion: "1.14.0"
secretsName: my-mysql-secrets
pxc:
size: 3
image: percona/percona-xtradb-cluster:8.0.35
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "4"
memory: "8Gi"
volumeSpec:
persistentVolumeClaim:
storageClassName: gp3-encrypted
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
configuration: |
[mysqld]
innodb_buffer_pool_size=4G
innodb_log_file_size=1G
innodb_flush_method=O_DIRECT
max_connections=500
wsrep_sync_wait=3
haproxy:
enabled: true
size: 2
image: percona/haproxy:2.8.5
backup:
image: percona/percona-xtradb-cluster-operator:1.14.0-pxc8.0-backup-pxb8.0.35
storages:
s3-backup:
type: s3
s3:
bucket: my-mysql-backups
region: ap-northeast-1
credentialsSecret: aws-s3-secret
schedule:
- name: daily-full
schedule: "0 3 * * *"
keep: 7
storageName: s3-backup
4.2 Percona XtraBackup
# 手動バックアップ
apiVersion: pxc.percona.com/v1
kind: PerconaXtraDBClusterBackup
metadata:
name: manual-backup-20240901
spec:
pxcCluster: my-mysql
storageName: s3-backup
# バックアップ状態確認
kubectl get pxc-backup -n database
# バックアップからの復元
kubectl apply -f - <<EOF
apiVersion: pxc.percona.com/v1
kind: PerconaXtraDBClusterRestore
metadata:
name: restore-20240901
spec:
pxcCluster: my-mysql
backupName: manual-backup-20240901
EOF
5. MongoDB Community Operator
5.1 ReplicaSet構成
apiVersion: mongodbcommunity.mongodb.com/v1
kind: MongoDBCommunity
metadata:
name: my-mongodb
namespace: database
spec:
members: 3
type: ReplicaSet
version: "7.0.12"
security:
authentication:
modes: ["SCRAM"]
users:
- name: admin
db: admin
passwordSecretRef:
name: mongodb-admin-password
roles:
- name: clusterAdmin
db: admin
- name: userAdminAnyDatabase
db: admin
scramCredentialsSecretName: admin-scram
- name: myapp
db: myapp
passwordSecretRef:
name: mongodb-myapp-password
roles:
- name: readWrite
db: myapp
scramCredentialsSecretName: myapp-scram
statefulSet:
spec:
template:
spec:
containers:
- name: mongod
resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
cpu: "4"
memory: "4Gi"
volumeClaimTemplates:
- metadata:
name: data-volume
spec:
storageClassName: gp3-encrypted
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
additionalMongodConfig:
storage.wiredTiger.engineConfig.cacheSizeGB: 2
net.maxIncomingConnections: 500
6. ストレージ ディープダイブ
6.1 StorageClass設定
# AWS EBS gp3 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-encrypted
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true"
iops: "6000" # gp3ベースライン3000、最大16000
throughput: "250" # gp3ベースライン125MB/s、最大1000MB/s
fsType: ext4
reclaimPolicy: Retain # DBデータは必ずRetain
volumeBindingMode: WaitForFirstConsumer # トポロジー認識バインディング
allowVolumeExpansion: true
6.2 Local PV vs Cloud EBS/PD 性能比較
| 指標 | Local NVMe | EBS gp3 (6000 IOPS) | EBS io2 (16000 IOPS) | GCP PD-SSD |
|---|---|---|---|---|
| ランダム読取IOPS | 500K+ | 6,000 | 16,000 | 30,000 |
| ランダム書込IOPS | 200K+ | 6,000 | 16,000 | 30,000 |
| シーケンシャル読取MB/s | 3,000+ | 250 | 1,000 | 1,200 |
| シーケンシャル書込MB/s | 2,000+ | 250 | 1,000 | 1,200 |
| P99レイテンシ | 0.1ms未満 | 1-3ms | 0.5-1ms | 1-2ms |
| データ永続性 | ノード障害時に喪失 | 99.999% | 99.999% | 99.999% |
| サイズ変更 | 不可 | 動的拡張 | 動的拡張 | 動的拡張 |
6.3 ストレージベンチマーク
# fioベンチマークPod
kubectl run fio-bench --image=nixery.dev/fio -- sleep 3600
# ランダム読取ベンチマーク
kubectl exec fio-bench -- fio \
--name=randread \
--ioengine=libaio \
--iodepth=32 \
--rw=randread \
--bs=4k \
--direct=1 \
--size=1G \
--numjobs=4 \
--runtime=60 \
--directory=/data \
--group_reporting
# pgbench(PostgreSQL性能テスト)
kubectl exec -it my-postgres-1 -- pgbench \
-i -s 100 myapp # 初期化(100スケールファクター)
kubectl exec -it my-postgres-1 -- pgbench \
-c 50 -j 4 -T 300 -P 10 myapp # 50クライアント、300秒
7. High Availabilityパターン
7.1 同期 vs 非同期レプリケーション
# CloudNativePG:同期レプリケーション設定
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres-sync
spec:
instances: 3
postgresql:
parameters:
synchronous_standby_names: "ANY 1 (*)"
minSyncReplicas: 1
maxSyncReplicas: 1
| 方式 | データ一貫性 | 書込レイテンシ | RPO | 使用シナリオ |
|---|---|---|---|---|
| 非同期 | 結果整合性 | 低い | 数秒のデータ損失の可能性 | 一般ワークロード |
| 同期(ANY 1) | 強い一貫性 | 高い(2倍) | 0(データ損失なし) | 金融、決済 |
| 半同期 | 中間 | 中間 | 非常に小さい | ほとんどの本番環境 |
7.2 Pod Disruption Budget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: postgres-pdb
namespace: database
spec:
maxUnavailable: 1 # 同時に最大1つのPodのみ中断を許可
selector:
matchLabels:
app: postgres
7.3 Topology Spread Constraints
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres
spec:
instances: 3
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
cnpg.io/cluster: my-postgres
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
cnpg.io/cluster: my-postgres
この設定は、DB Podを異なるアベイラビリティゾーン(AZ)と異なるノードに分散配置します。
7.4 Anti-Affinityルール
spec:
affinity:
enablePodAntiAffinity: true
topologyKey: kubernetes.io/hostname
podAntiAffinityType: required
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role
operator: In
values:
- database
tolerations:
- key: database-only
operator: Equal
value: "true"
effect: NoSchedule
8. バックアップとリカバリ戦略
8.1 論理バックアップ vs 物理バックアップ
| 区分 | 論理バックアップ(pg_dump) | 物理バックアップ(pgBackRest) |
|---|---|---|
| 速度 | 遅い(大容量時は数時間) | 速い(増分バックアップ対応) |
| サイズ | SQLテキスト(圧縮可能) | バイナリ(小さい) |
| 復元柔軟性 | テーブル単位の復元可能 | クラスター全体の復元 |
| バージョン互換性 | 異なるPGバージョン間で可能 | 同一PGバージョンのみ |
| PITR | 不可 | 可能 |
| 使用シナリオ | マイグレーション、部分復元 | 本番災害復旧 |
8.2 PITRウォークスルー
# 1. 現在のバックアップ一覧確認
kubectl get backup -n database
# 2. 復元対象時間の決定(例:誤ってDELETEを実行する直前)
# 3. 新クラスターでPITR復元
kubectl apply -f pitr-restore.yaml
# 4. 復元進行状況のモニタリング
kubectl get cluster my-postgres-restored -n database -w
# 5. 復元完了後のデータ検証
kubectl exec -it my-postgres-restored-1 -- psql -U myapp -d myapp \
-c "SELECT count(*) FROM important_table;"
# 6. アプリケーション接続の切り替え
kubectl patch service myapp-db -n database \
-p '{"spec":{"selector":{"cnpg.io/cluster":"my-postgres-restored"}}}'
8.3 Veleroクラスターレベルバックアップ
# Veleroインストール
velero install \
--provider aws \
--plugins velero/velero-plugin-for-aws:v1.9.0 \
--bucket my-velero-bucket \
--backup-location-config region=ap-northeast-1
# DBネームスペース全体バックアップ(PV含む)
velero backup create db-full-backup \
--include-namespaces database \
--include-resources '*' \
--snapshot-volumes=true
# スケジュールバックアップ
velero schedule create daily-db-backup \
--schedule="0 4 * * *" \
--include-namespaces database \
--snapshot-volumes=true \
--ttl 720h # 30日保持
8.4 クロスリージョン災害復旧
# ソースリージョンのCluster
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres-primary
spec:
instances: 3
backup:
barmanObjectStore:
destinationPath: s3://my-backup-bucket-primary/
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: ACCESS_SECRET_KEY
---
# DRリージョンのReplicaクラスター
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres-dr
spec:
instances: 2
replica:
enabled: true
source: my-postgres-primary
externalClusters:
- name: my-postgres-primary
barmanObjectStore:
destinationPath: s3://my-backup-bucket-primary/
s3Credentials:
accessKeyId:
name: aws-creds-dr
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds-dr
key: ACCESS_SECRET_KEY
9. パフォーマンスチューニング
9.1 リソースRequests/Limits
# DB Podは必ずQoS Guaranteedクラスに設定
resources:
requests:
cpu: "4"
memory: "16Gi"
limits:
cpu: "4" # requests == limits → Guaranteed
memory: "16Gi" # OOM Killer防止
メモリ配分ガイド:
総メモリ16Gi基準:
- shared_buffers: 4GB (25%)
- effective_cache_size: 12GB (75%)
- work_mem: 64MB (セッションあたり)
- maintenance_work_mem: 1GB
- OS/その他: 約2GB
9.2 PostgreSQLパフォーマンスパラメータ
postgresql:
parameters:
# メモリ
shared_buffers: "4GB"
effective_cache_size: "12GB"
work_mem: "64MB"
maintenance_work_mem: "1GB"
wal_buffers: "64MB"
# WAL
max_wal_size: "4GB"
min_wal_size: "1GB"
checkpoint_completion_target: "0.9"
wal_compression: "zstd"
# クエリプランナー
random_page_cost: "1.1" # SSDの場合(HDDは4.0)
effective_io_concurrency: "200" # SSDの場合(HDDは2)
# 並列処理
max_worker_processes: "8"
max_parallel_workers_per_gather: "4"
max_parallel_workers: "8"
# 接続
max_connections: "200"
idle_in_transaction_session_timeout: "30000"
# ロギング
log_min_duration_statement: "1000" # 1秒以上のクエリをログ
log_checkpoints: "on"
log_lock_waits: "on"
# 自動VACUUM
autovacuum_max_workers: "4"
autovacuum_naptime: "30"
autovacuum_vacuum_cost_limit: "1000"
9.3 Connection Pooling設定
PgBouncer推奨設定:
pool_mode = transaction
- トランザクション単位でコネクションを割当/返却
- K8s環境で数百Podの同時接続時に必須
- PREPARE文の使用制限(session modeでのみ可能)
default_pool_size = 25
- バックエンドDBコネクション数(max_connectionsの12.5%)
max_client_conn = 1000
- フロントエンド最大接続数
- Pod数 x Podあたりのコネクション数で算定
reserve_pool_size = 5
- ピークトラフィック用の予備コネクション
server_idle_timeout = 300
- アイドルコネクションを5分後にクリーンアップ
10. セキュリティ
10.1 Network Policies
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-network-policy
namespace: database
spec:
podSelector:
matchLabels:
cnpg.io/cluster: my-postgres
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
app.kubernetes.io/part-of: myapp
ports:
- protocol: TCP
port: 5432
- from:
- podSelector:
matchLabels:
cnpg.io/cluster: my-postgres
ports:
- protocol: TCP
port: 5432
egress:
- to: []
ports:
- protocol: TCP
port: 443
- to:
- podSelector:
matchLabels:
cnpg.io/cluster: my-postgres
ports:
- protocol: TCP
port: 5432
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
10.2 Secrets管理(External Secrets Operator)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: postgres-credentials
namespace: database
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: myapp-db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: production/database/postgres
property: username
- secretKey: password
remoteRef:
key: production/database/postgres
property: password
10.3 TLS設定
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-postgres
spec:
certificates:
serverTLSSecret: my-postgres-server-tls
serverCASecret: my-postgres-ca
postgresql:
parameters:
ssl: "on"
ssl_min_protocol_version: "TLSv1.3"
11. モニタリングとアラート
11.1 主要メトリクスとアラートルール
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: postgres-alerts
namespace: monitoring
spec:
groups:
- name: postgres.rules
rules:
- alert: PostgresReplicationLagHigh
expr: cnpg_pg_replication_lag > 10
for: 5m
labels:
severity: warning
annotations:
summary: "PostgreSQLレプリケーション遅延が10秒を超えています"
- alert: PostgresConnectionsNearLimit
expr: >
cnpg_pg_stat_activity_count /
cnpg_pg_settings_setting{name="max_connections"} > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "接続数が最大値の80%を超えました"
- alert: PostgresStorageNearFull
expr: >
kubelet_volume_stats_used_bytes{namespace="database"} /
kubelet_volume_stats_capacity_bytes{namespace="database"} > 0.85
for: 10m
labels:
severity: critical
annotations:
summary: "DBストレージ使用量が85%を超えました"
- alert: PostgresFailoverDetected
expr: changes(cnpg_pg_replication_is_primary[5m]) > 0
labels:
severity: critical
annotations:
summary: "PostgreSQL Failoverが検知されました"
11.2 Grafanaダッシュボード主要パネル
主要モニタリングパネル:
1. レプリケーション遅延(秒)- cnpg_pg_replication_lag
2. アクティブ接続数 - cnpg_pg_stat_activity_count
3. キャッシュヒット率 - blks_hit / (blks_hit + blks_read)
4. TPS(トランザクション/秒)- rate(cnpg_pg_stat_database_xact_commit[5m])
5. クエリレイテンシ P99
6. ディスクIOPS / スループット
7. WAL生成速度 - rate(cnpg_pg_stat_archiver_archived_count[5m])
8. ストレージ使用量 - kubelet_volume_stats_used_bytes
9. CPU / メモリ使用率
10. Vacuum / Analyze実行状態
12. マイグレーション戦略
12.1 VMからK8sへのマイグレーション
# 方法1:pg_dump/pg_restore(小規模DB)
pg_dump -Fc -d myapp -h vm-db.internal -U postgres > myapp.dump
kubectl cp myapp.dump database/my-postgres-1:/tmp/myapp.dump
kubectl exec -it my-postgres-1 -n database -- \
pg_restore -d myapp -U postgres /tmp/myapp.dump
# 方法2:論理レプリケーション(大規模DB、ゼロダウンタイム)
# 1. K8sで空のクラスターを作成
# 2. VM DBでpublicationを作成
# CREATE PUBLICATION my_pub FOR ALL TABLES;
# 3. K8s DBでsubscriptionを作成
# CREATE SUBSCRIPTION my_sub
# CONNECTION 'host=vm-db.internal dbname=myapp user=replicator'
# PUBLICATION my_pub;
# 4. 初期データ同期完了を待つ
# 5. アプリケーション接続をK8s DBに切り替え
# 6. subscriptionを削除
12.2 ゼロダウンタイムマイグレーションチェックリスト
マイグレーション前:
[ ] ソースDBサイズとテーブル数を把握
[ ] K8sクラスターリソース余裕を確認
[ ] StorageClass / PVサイズを算定(ソースの2倍の余裕)
[ ] ネットワーク接続確認(ソースDB → K8sクラスター)
マイグレーション中:
[ ] 初期データコピー完了を確認
[ ] レプリケーション遅延が0に収束することを確認
[ ] シーケンス値の同期を確認
[ ] アプリケーション読取トラフィックをK8sに切り替え(テスト)
[ ] 書込トラフィックの切り替え
マイグレーション後:
[ ] データ整合性検証(行数、チェックサム)
[ ] アプリケーション性能確認
[ ] ソースDBレプリケーション停止
[ ] バックアップポリシー適用確認
[ ] モニタリング/アラート動作確認
13. 本番チェックリスト
インフラストラクチャとストレージ
- StorageClassの
reclaimPolicyがRetainに設定 -
volumeBindingModeがWaitForFirstConsumerに設定 - EBS/PD IOPSがワークロードに適切なレベル(最低6000 IOPS)
- ボリューム自動拡張(
allowVolumeExpansion: true)有効化 - ノードにDB専用ラベル/テイント適用
HAとレプリケーション
- 最低3インスタンス構成(Primary 1 + Replica 2)
- Pod Anti-Affinityで異なるノードに分散
- Topology Spreadで異なるAZに分散
- PodDisruptionBudget設定(
maxUnavailable: 1) - 同期/非同期レプリケーションモードを決定・設定
バックアップとリカバリ
- 自動バックアップスケジュール設定(最低日1回)
- WALアーカイビング有効化(PITR可能)
- バックアップ保持ポリシー設定(最低30日)
- バックアップ復元テスト完了(最低四半期1回)
- クロスリージョンバックアップ複製(DR要件時)
パフォーマンス
- リソースrequests == limits(QoS Guaranteed)
- shared_buffers = 総メモリの25%
- effective_cache_size = 総メモリの75%
- Connection Pooling有効化(PgBouncer transaction mode)
- クエリロギング有効化(
log_min_duration_statement)
セキュリティ
- NetworkPolicyでDBアクセスを制限
- TLS有効化(最低TLSv1.2)
- SecretはExternal Secrets OperatorまたはVaultで管理
- RBAC:Operatorサービスアカウントに最小権限
- データ暗号化(ストレージレベル + 転送レベル)
モニタリング
- Prometheusメトリクス収集設定
- Grafanaダッシュボード構成
- レプリケーション遅延アラート(10秒超過)
- ストレージ容量アラート(85%超過)
- 接続数アラート(80%超過)
- Failover発生アラート
14. クイズ
Q1:StatefulSetでPod Identityが重要な理由は?
A: StatefulSetのPod Identity(順序インデックス + 安定ホスト名 + 専用PVC)はデータベース運用に不可欠です。Primary/Replica役割が特定のPodにバインドされ、再起動後も同じストレージに接続され、DNSベースで他のPodが特定インスタンスに直接アクセスできます。通常のDeploymentではこれらの保証が不可能です。
Q2:CloudNativePGの自動フェイルオーバーはどのように動作しますか?
A: CloudNativePGはPrimary Podのhealth check失敗を検知すると、最新WAL位置を持つReplicaを選択してpg_promote()で新Primaryに昇格させます。残りのReplicaは新Primaryをフォローするように再設定され、旧Primaryはpg_rewindを通じてReplicaとして再合流します。全プロセスは通常10-30秒以内に完了します。
Q3:DB PodのQoSクラスをGuaranteedに設定すべき理由は?
A: QoS Guaranteed(requests == limits)に設定すると、K8sが該当Podのリソースを保証し、ノードメモリ不足時にも最後にOOM Killされます。DBはメモリ内キャッシュ(shared_buffers)と安定したCPUが必須であるため、BurstableやBestEffortに設定すると予期しない性能低下や障害が発生する可能性があります。
Q4:volumeBindingModeをWaitForFirstConsumerに設定する理由は?
A: WaitForFirstConsumerはPodが実際にスケジュールされるノードが決定された後にPVをプロビジョニングします。これにより、PodとPVが同じアベイラビリティゾーン(AZ)に配置され、クロスAZネットワークレイテンシを防止します。ImmediateモードではPVが先に作成されて異なるAZに配置される可能性があり、Podスケジュールの失敗や性能低下を引き起こします。
Q5:PgBouncerのtransactionモードとsessionモードの違いは?
A: Transactionモードはトランザクション終了時にバックエンドコネクションをプールに返却し、数百のクライアントが少数のDBコネクションを共有できます。K8sで多数のPodが同時アクセスする環境に最適です。Sessionモードはクライアントセッション終了までコネクションを占有し、PREPARE文やセッション変数が必要な場合に使用します。一般的にK8s環境ではtransactionモードを推奨します。
15. 参考資料
- CloudNativePG公式ドキュメント - https://cloudnative-pg.io/documentation/
- Percona Operator for MySQL - https://docs.percona.com/percona-operator-for-mysql/pxc/
- MongoDB Community Operator - https://github.com/mongodb/mongodb-kubernetes-operator
- Kubernetes StatefulSetドキュメント - https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
- PgBouncerドキュメント - https://www.pgbouncer.org/config.html
- Velero公式ドキュメント - https://velero.io/docs/
- External Secrets Operator - https://external-secrets.io/
- AWS EBS CSI Driver - https://github.com/kubernetes-sigs/aws-ebs-csi-driver
- PostgreSQLパフォーマンスチューニングガイド - https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server
- Kubernetes Network Policies - https://kubernetes.io/docs/concepts/services-networking/network-policies/
- pgBackRestドキュメント - https://pgbackrest.org/
- Prometheus PostgreSQL Exporter - https://github.com/prometheus-community/postgres_exporter
- Barman(Backup and Recovery Manager)- https://pgbarman.org/
현재 단락 (1/854)
**K8s DB運用のメリット**