- Published on
Kubernetesでデータベース運用完全ガイド:StatefulSet、Operator、バックアップ/リカバリ戦略
- Authors

- Name
- Youngju Kim
- @fjvbn20031
TL;DR
- StatefulSet: DB等のステートフルアプリケーション向けK8sワークロード(安定したネットワークID + 永続ストレージ)
- DB Operator: CloudNativePG(PostgreSQL)、Percona Operator(MySQL/MongoDB)で運用自動化
- ストレージ: PV/PVC/StorageClassで永続データ管理、CSIドライバー活用
- 高可用性: Primary-Replica構成、自動フェイルオーバー、PodDisruptionBudget
- バックアップ/リカバリ: pgBackRest、Velero、PITR(Point-in-Time Recovery)戦略
- モニタリング: PMM、pg_exporter + Prometheus + Grafanaダッシュボード
目次
- K8sでDBを運用すべきか?
- StatefulSet詳細解説
- ストレージ戦略
- Headless ServiceとDNS
- Database Operator
- 高可用性(HA)
- バックアップとリカバリ
- モニタリング
- パフォーマンスチューニング
- セキュリティ
- VMからK8sへの移行
- プロダクションチェックリスト
- 実践クイズ
- 参考資料
1. K8sでDBを運用すべきか?
1.1 メリットとデメリット
K8s DB運用の判断マトリックス:
運用すべき場合: 運用すべきでない場合:
+ マルチクラウド/ハイブリッド環境 - マネージドDBサービスが利用可能
+ インフラの一貫性が重要 - DBAリソース不足
+ GitOps/IaCパイプライン統合 - 超大規模な単一DBインスタンス
+ 開発/テスト環境の自動化 - 極めて低いレイテンシ要件
+ コスト最適化が必須 - K8s経験不足
+ データ主権規制 - シンプルなアーキテクチャで十分
| 基準 | K8s DB運用 | マネージドサービス(RDS/CloudSQL) |
|---|---|---|
| 初期セットアップ複雑度 | 高い | 低い |
| 運用自動化 | Operatorで可能 | 標準提供 |
| コスト | 効率的(リソース共有) | プレミアム価格 |
| マルチクラウド | 容易 | ベンダーロックイン |
| カスタマイズ | 完全な自由 | 制限あり |
| バックアップ/リカバリ | 手動構成が必要 | 標準提供 |
| スケーラビリティ | 手動/半自動 | 自動スケーリング |
1.2 どのDBがK8sに適しているか?
K8s適合度ランキング:
非常に適合:
- PostgreSQL(CloudNativePGエコシステムが優秀)
- MongoDB(ReplicaSet構造がK8sと自然にマッチ)
- Redis(Sentinel/Clusterモード)
適合:
- MySQL(Percona/Oracle Operatorを使用)
- Elasticsearch(ECK Operator)
- Cassandra(K8ssandra Operator)
注意が必要:
- Oracle DB(ライセンス、複雑性)
- SQL Server(Windowsコンテナの制限)
- 大規模な単一インスタンスDB
2. StatefulSet詳細解説
2.1 StatefulSet vs Deployment
Deployment: StatefulSet:
- Pod名: ランダム (abc-xyz) - Pod名: 順番 (db-0, db-1, db-2)
- 並列作成/削除 - 順番に作成/削除 (0 -> 1 -> 2)
- 共有ボリュームまたはなし - Pod毎に専用PVC(自動作成)
- 交換可能なPod - 固有IDを持つPod
- ステートレスアプリに適合 - ステートフルアプリ(DB)に適合
2.2 StatefulSet YAMLの例
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: database
spec:
serviceName: postgres-headless # Headless Service名
replicas: 3
podManagementPolicy: OrderedReady # 順番に作成(デフォルト)
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # K8s 1.24+
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
terminationGracePeriodSeconds: 120 # DB停止に十分な時間
securityContext:
fsGroup: 999 # postgresグループ
runAsUser: 999 # postgresユーザー
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
name: postgresql
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-secret
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
livenessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 5
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
2.3 PodManagementPolicy
# OrderedReady(デフォルト): 順番に作成/削除
# Pod 0 Ready -> Pod 1作成 -> Pod 1 Ready -> Pod 2作成
podManagementPolicy: OrderedReady
# Parallel: 全Pod同時作成/削除
# 初期ブートストラップに注意(DBは通常OrderedReadyを使用)
podManagementPolicy: Parallel
2.4 UpdateStrategy
updateStrategy:
type: RollingUpdate
rollingUpdate:
# Partition: この値以上のordinalを持つPodのみ更新
# カナリアデプロイに活用(Pod 2のみ先に更新)
partition: 2
# OnDelete: 手動でPod削除時のみ更新
# DBアップグレード時のきめ細かな制御が可能
updateStrategy:
type: OnDelete
3. ストレージ戦略
3.1 PV / PVC / StorageClass
# StorageClass定義(AWS EBS gp3)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "5000"
throughput: "250" # MB/s
encrypted: "true"
reclaimPolicy: Retain # DBデータは必ずRetain!
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true # オンラインボリューム拡張を許可
# StorageClass定義(GCP PD SSD)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
replication-type: regional-pd # リージョナルPD(高可用性)
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
3.2 ローカルストレージ vs クラウドボリューム
パフォーマンス比較:
Local NVMe SSD:
- ランダム読み取り: 500K+ IOPS
- レイテンシ: 0.1ms未満
- 欠点: Pod移動不可、ノード障害時データ損失リスク
Cloud EBS gp3:
- ベースライン: 3,000 IOPS / 125 MB/s
- 最大: 16,000 IOPS / 1,000 MB/s
- 利点: Pod移動可能、スナップショットサポート
Cloud EBS io2:
- 最大: 64,000 IOPS
- 99.999%の耐久性
- コストは高いがミッションクリティカルなDBに適合
3.3 ボリューム拡張
# PVCサイズ拡張(StorageClassにallowVolumeExpansion: trueが必要)
kubectl patch pvc data-postgres-0 -n database \
-p '{"spec": {"resources": {"requests": {"storage": "200Gi"}}}}'
# 拡張ステータス確認
kubectl get pvc data-postgres-0 -n database -o yaml | grep -A 5 status
4. Headless ServiceとDNS
4.1 Headless Service定義
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
namespace: database
spec:
clusterIP: None # Headless Serviceの核心
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
name: postgresql
4.2 DNSルール
StatefulSetの各Podは予測可能なDNS名を持ちます。
DNSパターン:
pod-name.service-name.namespace.svc.cluster.local
例:
postgres-0.postgres-headless.database.svc.cluster.local
postgres-1.postgres-headless.database.svc.cluster.local
postgres-2.postgres-headless.database.svc.cluster.local
# 読み取り/書き込み分離のための追加Service
---
apiVersion: v1
kind: Service
metadata:
name: postgres-primary
namespace: database
spec:
selector:
app: postgres
role: primary
ports:
- port: 5432
targetPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: postgres-replica
namespace: database
spec:
selector:
app: postgres
role: replica
ports:
- port: 5432
targetPort: 5432
5. Database Operator
5.1 なぜOperatorが必要なのか?
DB運用は単純なデプロイを超えて、複雑なDay-2運用が必要です。
Operatorが自動化するタスク:
1. クラスター初期化(Primary + Replica構成)
2. 自動フェイルオーバー(Primary障害時にReplica昇格)
3. バックアップ/リカバリ(スケジューリング、PITR)
4. ローリングアップグレード(ゼロダウンタイム)
5. 水平スケーリング(Replica追加/削除)
6. モニタリング統合
7. 証明書管理(TLS)
8. 設定変更(再起動なし)
5.2 PostgreSQL - CloudNativePG(CNPG)
# CloudNativePGインストール
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/releases/cnpg-1.24.1.yaml
# CloudNativePGクラスター定義
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: myapp-db
namespace: database
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
postgresql:
parameters:
max_connections: "200"
shared_buffers: "1GB"
effective_cache_size: "3GB"
work_mem: "16MB"
maintenance_work_mem: "256MB"
wal_buffers: "16MB"
random_page_cost: "1.1"
effective_io_concurrency: "200"
max_wal_size: "2GB"
checkpoint_completion_target: "0.9"
bootstrap:
initdb:
database: myapp
owner: app_user
secret:
name: myapp-db-credentials
storage:
size: 100Gi
storageClass: fast-ssd
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
backup:
barmanObjectStore:
destinationPath: "s3://my-backup-bucket/cnpg/"
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: SECRET_ACCESS_KEY
wal:
compression: gzip
data:
compression: gzip
retentionPolicy: "30d"
monitoring:
enablePodMonitor: true
5.3 MySQL - Percona XtraDB Cluster Operator
# Percona Operatorインストール
kubectl apply -f https://raw.githubusercontent.com/percona/percona-xtradb-cluster-operator/v1.15.0/deploy/bundle.yaml
# Percona XtraDB Cluster定義
apiVersion: pxc.percona.com/v1
kind: PerconaXtraDBCluster
metadata:
name: myapp-mysql
namespace: database
spec:
crVersion: "1.15.0"
secretsName: myapp-mysql-secrets
pxc:
size: 3
image: percona/percona-xtradb-cluster:8.0.36
resources:
requests:
memory: 2G
cpu: "1"
limits:
memory: 4G
cpu: "2"
volumeSpec:
persistentVolumeClaim:
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
affinity:
antiAffinityTopologyKey: "kubernetes.io/hostname"
haproxy:
enabled: true
size: 3
image: percona/haproxy:2.8.5
resources:
requests:
memory: 512M
cpu: "500m"
backup:
image: percona/percona-xtradb-cluster-operator:1.15.0-pxc8.0-backup
storages:
s3-backup:
type: s3
s3:
bucket: my-backup-bucket
credentialsSecret: aws-creds
region: ap-northeast-2
schedule:
- name: daily-backup
schedule: "0 3 * * *"
keep: 7
storageName: s3-backup
5.4 MongoDB - Community Operator
# MongoDB Community Operatorインストール
kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes-operator/master/config/crd/bases/mongodbcommunity.mongodb.com_mongodbcommunity.yaml
kubectl apply -k https://github.com/mongodb/mongodb-kubernetes-operator/config/rbac/
kubectl create -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes-operator/master/config/manager/manager.yaml
# MongoDB ReplicaSet定義
apiVersion: mongodbcommunity.mongodb.com/v1
kind: MongoDBCommunity
metadata:
name: myapp-mongodb
namespace: database
spec:
members: 3
type: ReplicaSet
version: "7.0.14"
security:
authentication:
modes: ["SCRAM"]
users:
- name: app-user
db: admin
passwordSecretRef:
name: mongodb-password
roles:
- name: readWrite
db: myapp
- name: clusterAdmin
db: admin
scramCredentialsSecretName: app-user-scram
statefulSet:
spec:
template:
spec:
containers:
- name: mongod
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
cpu: "2"
memory: 4Gi
volumeClaimTemplates:
- metadata:
name: data-volume
spec:
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
5.5 Operator比較
| 特性 | CloudNativePG | Percona XtraDB | MongoDB Community |
|---|---|---|---|
| DB | PostgreSQL | MySQL | MongoDB |
| ライセンス | Apache 2.0 | Apache 2.0 | SSPL + Apache |
| HA方式 | Streaming Replication | Galera Cluster | ReplicaSet |
| 自動フェイルオーバー | サポート | サポート | サポート |
| バックアップ | Barman/S3 | xtrabackup/S3 | mongodump連携 |
| モニタリング | PodMonitor | PMM統合 | 基本メトリクス |
| 成熟度 | 非常に高い | 高い | 中程度 |
6. 高可用性(HA)
6.1 Primary-Replica構成
PostgreSQL HAアーキテクチャ(CloudNativePG):
[CNPG Operator]
|
v
[Primary Pod] ----Streaming Replication----> [Replica Pod 1]
| [Replica Pod 2]
|
[Headless Service]
|
postgres-primary(書き込み)----> Primaryのみにルーティング
postgres-replica(読み取り)----> Replicaのみにルーティング
6.2 自動フェイルオーバー
フェイルオーバーシナリオ:
1. Primary Podの障害発生
2. Operatorが検出(liveness probe失敗)
3. 最新のLSNを持つReplicaを選択
4. 選択されたReplicaをPrimaryに昇格
5. 残りのReplicaが新しいPrimaryに追従
6. Serviceエンドポイントを更新
7. 障害Podを再作成し新しいReplicaとして合流
全プロセス: 通常30秒以内
6.3 PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: postgres-pdb
namespace: database
spec:
minAvailable: 2 # 最低2つのPodを維持
selector:
matchLabels:
app: postgres
6.4 ノード障害への備え
# Pod Topology Spread Constraints
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: postgres
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: postgres
7. バックアップとリカバリ
7.1 バックアップの種類
論理バックアップ(pg_dump/mysqldump):
+ 移植性が高い(異なるバージョン/プラットフォームへ復元可能)
+ 個別テーブル/スキーマのバックアップが可能
- 大規模DBでは遅い
- 復元時にインデックス再構築が必要
物理バックアップ(pgBackRest/xtrabackup):
+ 大規模DBで高速
+ 増分バックアップサポート
+ PITR(Point-in-Time Recovery)可能
- 同じメジャーバージョンでのみ復元
- クラスター全体の単位でバックアップ
7.2 pgBackRest(PostgreSQL)
# CloudNativePGのバックアップ設定
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: myapp-db
spec:
backup:
barmanObjectStore:
destinationPath: "s3://my-backup-bucket/cnpg/myapp-db/"
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: SECRET_ACCESS_KEY
wal:
compression: gzip
maxParallel: 4
data:
compression: gzip
immediateCheckpoint: true
retentionPolicy: "30d"
---
# スケジュールバックアップ
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: myapp-db-daily
spec:
schedule: "0 3 * * *"
cluster:
name: myapp-db
backupOwnerReference: self
method: barmanObjectStore
7.3 PITR(Point-in-Time Recovery)
# PITRで特定時点にリカバリ
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: myapp-db-recovered
spec:
instances: 3
bootstrap:
recovery:
source: myapp-db-backup
recoveryTarget:
targetTime: "2026-03-24T10:30:00Z" # リカバリ時点
externalClusters:
- name: myapp-db-backup
barmanObjectStore:
destinationPath: "s3://my-backup-bucket/cnpg/myapp-db/"
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: SECRET_ACCESS_KEY
7.4 Veleroを活用したフルバックアップ
# Veleroインストール
velero install \
--provider aws \
--bucket my-velero-bucket \
--secret-file ./credentials-velero \
--plugins velero/velero-plugin-for-aws:v1.10.0
# Namespace単位のバックアップ
velero backup create database-backup \
--include-namespaces database \
--snapshot-volumes=true \
--volume-snapshot-locations default
# リストア
velero restore create --from-backup database-backup
8. モニタリング
8.1 Prometheus + Grafana
# PostgreSQL Exporter(pg_exporter)
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-exporter
namespace: database
spec:
replicas: 1
selector:
matchLabels:
app: postgres-exporter
template:
metadata:
labels:
app: postgres-exporter
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9187"
spec:
containers:
- name: exporter
image: prometheuscommunity/postgres-exporter:0.15.0
ports:
- containerPort: 9187
env:
- name: DATA_SOURCE_URI
value: "postgres-primary.database.svc:5432/myapp?sslmode=disable"
- name: DATA_SOURCE_USER
valueFrom:
secretKeyRef:
name: postgres-secret
key: username
- name: DATA_SOURCE_PASS
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
8.2 主要モニタリング指標
DBモニタリングの重要メトリクス:
パフォーマンス:
- クエリ数/秒(QPS)
- クエリレイテンシ(p50, p95, p99)
- アクティブコネクション数
- キャッシュヒット率(Buffer Cache Hit Ratio)
レプリケーション:
- レプリケーション遅延
- WAL受信遅延
- Replicaステータス
ストレージ:
- ディスク使用量
- IOPS / スループット
- WALサイズ
リソース:
- CPU使用率
- メモリ使用量
- Pod再起動回数
運用:
- Dead tuples比率
- Vacuum実行状態
- ロック待ち数
8.3 Percona Monitoring and Management(PMM)
# PMM Serverインストール
apiVersion: apps/v1
kind: Deployment
metadata:
name: pmm-server
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: pmm-server
template:
spec:
containers:
- name: pmm-server
image: percona/pmm-server:2
ports:
- containerPort: 443
volumeMounts:
- name: pmm-data
mountPath: /srv
volumes:
- name: pmm-data
persistentVolumeClaim:
claimName: pmm-data
9. パフォーマンスチューニング
9.1 リソースRequests/Limits
# DB Podリソース設定ガイド
resources:
requests:
# CPU: 保証される最低CPU
# DBはCPU競合に敏感なので余裕を持って
cpu: "2"
# Memory: shared_buffers + work_mem * max_connections + OS
memory: "4Gi"
limits:
# CPU limitは設定しないか余裕を持たせる
#(スロットリングがクエリ遅延を引き起こす)
cpu: "4"
# Memory limitはOOM Kill防止のためrequestより余裕を持って
memory: "8Gi"
9.2 AffinityとAnti-Affinity
# DB Podスケジューリング最適化
spec:
template:
spec:
# DB専用ノードにのみスケジューリング
nodeSelector:
node-type: database
# またはTolerationで専用ノード
tolerations:
- key: "dedicated"
operator: "Equal"
value: "database"
effect: "NoSchedule"
# Replicaを異なるノード/ゾーンに分散
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- postgres
topologyKey: "kubernetes.io/hostname"
9.3 カーネルパラメータチューニング
# initContainerでカーネルパラメータ設定
spec:
template:
spec:
initContainers:
- name: sysctl-tuning
image: busybox:1.36
securityContext:
privileged: true
command:
- sh
- -c
- |
sysctl -w vm.swappiness=1
sysctl -w vm.dirty_background_ratio=5
sysctl -w vm.dirty_ratio=10
sysctl -w vm.overcommit_memory=2
sysctl -w net.core.somaxconn=65535
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
10. セキュリティ
10.1 NetworkPolicy
# DB Podへのネットワークアクセス制限
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-network-policy
namespace: database
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: application
podSelector:
matchLabels:
app: backend
- podSelector:
matchLabels:
app: postgres # Pod間レプリケーションを許可
ports:
- port: 5432
protocol: TCP
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- port: 5432
protocol: TCP
- to: # DNSを許可
- namespaceSelector: {}
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
10.2 Secrets管理
# External Secrets Operatorの使用
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: postgres-secret
namespace: database
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: postgres-secret
data:
- secretKey: username
remoteRef:
key: prod/database/postgres
property: username
- secretKey: password
remoteRef:
key: prod/database/postgres
property: password
10.3 TLS設定
# cert-managerでDB証明書を発行
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: postgres-tls
namespace: database
spec:
secretName: postgres-tls-secret
duration: 8760h # 1年
renewBefore: 720h # 期限切れ30日前に更新
issuerRef:
name: internal-ca
kind: ClusterIssuer
dnsNames:
- postgres-primary.database.svc.cluster.local
- postgres-headless.database.svc.cluster.local
- "*.postgres-headless.database.svc.cluster.local"
11. VMからK8sへの移行
11.1 移行フェーズ
Phase 1: 準備
1. K8sクラスターにDB Operatorをインストール
2. StorageClass、NetworkPolicyを構成
3. ターゲットDBクラスターを作成(空の状態)
Phase 2: データ移行
方法A - 論理レプリケーション(最小ダウンタイム):
1. VM DBで論理レプリケーション(Logical Replication)を設定
2. K8s DBをSubscriberとして設定
3. 初期同期 + 変更分ストリーミング
4. アプリケーション切り替え(短時間のダウンタイム)
方法B - pg_dump/restore:
1. VM DBバックアップ(pg_dump)
2. K8s DBにリストア(pg_restore)
3. ダウンタイム中に切り替え
Phase 3: 切り替え
1. アプリケーションのDB接続文字列を変更
2. DNS変更またはServiceエンドポイント更新
3. 旧VM DBを読み取り専用に切り替え(ロールバック備え)
Phase 4: クリーンアップ
1. 検証完了後、旧VM DBを削除
2. モニタリング/アラートを更新
11.2 論理レプリケーション設定
-- VM DB(Publisher)設定
-- postgresql.conf
-- wal_level = logical
-- max_replication_slots = 10
CREATE PUBLICATION myapp_pub FOR ALL TABLES;
-- K8s DB(Subscriber)設定
CREATE SUBSCRIPTION myapp_sub
CONNECTION 'host=vm-db.example.com port=5432 dbname=myapp user=repl_user password=secret'
PUBLICATION myapp_pub;
12. プロダクションチェックリスト
K8s DBプロダクションチェックリスト:
ストレージ:
[ ] StorageClassにreclaimPolicy: Retainを設定
[ ] ボリュームサイズに20%以上の空き容量
[ ] allowVolumeExpansion: trueを確認
[ ] IOPS/スループットがワークロードに適合しているか確認
高可用性:
[ ] 最低3インスタンス(1 Primary + 2 Replica)
[ ] PodDisruptionBudgetを設定
[ ] Pod Anti-Affinity(異なるノード/ゾーンに分散)
[ ] 自動フェイルオーバーテスト完了
バックアップ:
[ ] 自動バックアップスケジュール設定(日1回以上)
[ ] WALアーカイブ有効化(PITR対応)
[ ] バックアップリストアテスト完了
[ ] 保持ポリシー設定(30日以上)
セキュリティ:
[ ] NetworkPolicyでアクセス制限
[ ] SecretsはExternal Secrets Operatorで管理
[ ] TLS暗号化を有効化
[ ] DBユーザー権限を最小化(最小権限の原則)
モニタリング:
[ ] Prometheus + Grafanaダッシュボード構成
[ ] アラートルール設定(レプリケーション遅延、ディスク使用率、コネクション数)
[ ] ログ収集(Loki/EFK)
[ ] クエリパフォーマンスモニタリング
パフォーマンス:
[ ] リソースrequests/limitsを適切に設定
[ ] DBパラメータチューニング(shared_buffers、work_memなど)
[ ] カーネルパラメータ最適化
[ ] 専用ノードを使用(Taint/Toleration)
13. 実践クイズ
Q1: StatefulSetとDeploymentの核心的な違いは何ですか?
回答:
StatefulSetは以下を保証します:
- 安定したネットワークID: 各Podに順番の名前が付与されます(例:db-0、db-1、db-2)。Podが再作成されても同じ名前を維持します。
- 永続ストレージバインディング: volumeClaimTemplatesにより各Podに専用のPVCが自動作成されます。Podが削除/再作成されても同じPVCに再接続されます。
- 順番通りの作成/削除: Pod 0がReady状態になった後にPod 1が作成されます(OrderedReadyポリシー)。
一方、DeploymentはランダムなPod名、共有ボリューム、並列作成を使用するため、ステートレスアプリに適しています。
Q2: DBストレージのreclaimPolicyをRetainに設定すべき理由は?
回答:
reclaimPolicyがDelete(デフォルト)の場合、PVCが削除される際にPVと実際のストレージ(EBSボリュームなど)も一緒に削除されます。これは以下の状況でデータ損失を引き起こします:
- 誤ってStatefulSetやPVCを削除
- Namespace削除
- Helm uninstall
Retainに設定すると、PVCが削除されてもPVと実際のストレージが維持され、データリカバリが可能になります。プロダクションDBでは必ずRetainを使用すべきです。
Q3: CloudNativePGの自動フェイルオーバーはどのように動作しますか?
回答:
- CNPG Operatorが全インスタンスの状態を継続的にモニタリングします。
- Primary Podのliveness probeが失敗すると、Operatorが障害を検出します。
- Operatorは全ReplicaのWAL LSN(Log Sequence Number)を比較し、最新のデータを持つReplicaを選択します。
- 選択されたReplicaがPrimaryに昇格され、pg_promoteが実行されます。
- 残りのReplicaが新しいPrimaryに追従するように再設定されます。
- Serviceエンドポイントが新しいPrimaryを指すように自動更新されます。
- 障害Podは再作成され、新しいReplicaとして合流します。
全プロセスは通常30秒以内に完了します。
Q4: K8sでDB PodにCPU limitを設定しないことが推奨される理由は?
回答:
CPU limitが設定されると、KubernetesはCFS(Completely Fair Scheduler)スロットリングを適用します。DBが瞬間的に高いCPUを必要とする場合(例:複雑なクエリ、VACUUM)、スロットリングが発生するとクエリレイテンシが大幅に増加します。
代わりに以下の戦略を推奨します:
- CPU requestのみ設定して保証されたCPUを確保
- DB専用ノードを使用(Taint/Toleration)して他のワークロードとのリソース競合を防止
- ノードレベルでCPUリソースが十分であることを確認
Memory limitはOOM Killを防止するために設定すべきですが、requestよりも十分に余裕を持たせます。
Q5: VMからK8sにDBを移行する際の最小ダウンタイム戦略は?
回答:
論理レプリケーション(Logical Replication)を活用します:
- VM DBで
wal_level = logicalを設定し、Publicationを作成します。 - K8s DBでSubscriptionを作成し、VM DBに接続します。
- 初期データ同期が自動的に進行します。
- 同期完了後、変更分がリアルタイムでストリーミングされます。
- アプリケーションを一時停止(数秒~数分)し、レプリケーション遅延が0であることを確認します。
- アプリケーションのDB接続文字列をK8s DBに変更します。
- アプリケーションを再起動します。
この方法でダウンタイムを数秒~数分に最小化できます。
14. 参考資料
- CloudNativePG Documentation
- Percona Operator for MySQL Documentation
- MongoDB Kubernetes Operator
- Kubernetes StatefulSet Documentation
- Kubernetes Persistent Volumes
- Velero - Backup and Restore
- External Secrets Operator
- Percona Monitoring and Management (PMM)
- PostgreSQL Kubernetes Best Practices
- Zalando Postgres Operator
- CrunchyData PGO
- K8ssandra - Cassandra on Kubernetes
- Data on Kubernetes Community
- CNCF Storage Landscape