痛点
在 Kubernetes 上跑 PostgreSQL,最常见的方案是手搓 StatefulSet + PVC + 自定义脚本。看起来能跑,但真到生产环境就暴露三个致命问题:
- 故障转移靠人工:Primary 挂了,需要手动 promote Replica、改 Service endpoint、确认数据一致性,停机窗口轻松 10-30 分钟
- 备份是定时炸弹:CronJob +
pg_dump管备份,恢复时才发现 dump 文件损坏或缺了 WAL,PITR(时间点恢复)更是奢望 - Day-2 运维地狱:滚动升级 PostgreSQL 版本要手动 drain、重建 Pod,扩缩副本要改 StatefulSet + 重新配置流复制,每次操作都是一场冒险
社区里有多个 PostgreSQL Operator(Zalando、CrunchyData、Percona),但 CloudNativePG(CNPG) 是目前唯一由 CNCF 孵化、不依赖外部工具链(无需 Patroni/etcd)的 Kubernetes 原生方案。它直接利用 Kubernetes API 做共识和故障转移,架构更简洁、运维更轻量。
方案
CloudNativePG 的核心设计哲学:PostgreSQL 集群本身就是一个 Kubernetes 自定义资源(CR)。Operator 负责:
- 自动初始化 Primary + Streaming Replica 拓扑
- 基于 Kubernetes lease 的自动故障检测与 failover(秒级)
- 持续归档 WAL 到对象存储(S3/GCS/Azure Blob),支持 PITR
- 滚动更新、在线扩缩容、主从切换(switchover)零停机
- 原生集成 Prometheus 指标导出,无需额外 exporter Pod
实操步骤
第 1 步:安装 Operator
# 使用 Helm 安装(推荐生产环境)
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update
helm install cnpg cnpg/cloudnative-pg \
--namespace cnpg-system \
--create-namespace \
--set monitoring.podMonitorEnabled=true
# 验证 Operator 就绪
kubectl get pods -n cnpg-system
# NAME READY STATUS RESTARTS AGE
# cnpg-controller-xxx 1/1 Running 0 30s
第 2 步:部署 PostgreSQL 集群
创建一个 3 节点(1 Primary + 2 Replica)集群,配置 S3 备份:
# pg-cluster.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: app-db
namespace: production
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
postgresql:
parameters:
max_connections: "200"
shared_buffers: "512MB"
effective_cache_size: "1536MB"
work_mem: "8MB"
maintenance_work_mem: "128MB"
wal_level: "logical" # 如需逻辑复制
log_min_duration_statement: "500"
storage:
size: 50Gi
storageClass: gp3-enc # 按实际 StorageClass 修改
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
backup:
barmanObjectStore:
destinationPath: s3://my-pg-backups/app-db/
s3Credentials:
accessKeyId:
name: s3-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: s3-creds
key: SECRET_ACCESS_KEY
wal:
compression: gzip
retentionPolicy: "14d"
monitoring:
enablePodMonitor: true
# 创建 S3 凭证 Secret
kubectl create secret generic s3-creds -n production \
--from-literal=ACCESS_KEY_ID=<your-key> \
--from-literal=SECRET_ACCESS_KEY=<your-secret>
# 部署集群
kubectl apply -f pg-cluster.yaml
# 观察集群启动(约 60-90 秒)
kubectl get cluster app-db -n production -w
# NAME AGE INSTANCES READY STATUS
# app-db 90s 3 3 Cluster in healthy state
第 3 步:配置定时备份与 PITR
# scheduled-backup.yaml
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: app-db-daily
namespace: production
spec:
schedule: "0 2 * * *" # 每天凌晨 2 点
backupOwnerReference: self
cluster:
name: app-db
method: barmanObjectStore
kubectl apply -f scheduled-backup.yaml
# 手动触发一次备份验证
kubectl cnpg backup app-db -n production
# 查看备份状态
kubectl get backups -n production
# NAME AGE CLUSTER METHOD PHASE
# app-db-20260907 30s app-db barmanObjectStore completed
PITR 恢复示例——假设需要恢复到某个时间点:
# restore-cluster.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: app-db-restored
namespace: production
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
bootstrap:
recovery:
source: app-db-backup
recoveryTarget:
targetTime: "2026-09-06T18:30:00Z" # 恢复到此时间点
externalClusters:
- name: app-db-backup
barmanObjectStore:
destinationPath: s3://my-pg-backups/app-db/
s3Credentials:
accessKeyId:
name: s3-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: s3-creds
key: SECRET_ACCESS_KEY
storage:
size: 50Gi
storageClass: gp3-enc
第 4 步:连接应用与日常运维
CNPG 自动创建三个 Service,应用直接用 Kubernetes DNS 连接:
# 读写连接(指向 Primary)
app-db-rw.production.svc:5432
# 只读连接(负载均衡到 Replica)
app-db-ro.production.svc:5432
# 读连接(优先 Replica,Replica 不可用时回退 Primary)
app-db-r.production.svc:5432
常用运维命令:
# 查看集群状态
kubectl cnpg status app-db -n production
# 手动主从切换(零停机 switchover)
kubectl cnpg promote app-db-2 -n production
# 在线扩容副本数
kubectl patch cluster app-db -n production \
--type merge -p '{"spec":{"instances":4}}'
# 连接 psql 调试
kubectl cnpg psql app-db -n production -- -c "SELECT pg_is_in_recovery();"
避坑
坑 1:StorageClass 必须支持 volume expansion
CNPG 的 PVC 创建后如果磁盘不够,需要在线扩容。如果 StorageClass 没有设置 allowVolumeExpansion: true,扩容会直接失败。部署前先确认:
kubectl get sc <your-sc> -o jsonpath='{.allowVolumeExpansion}'
# 必须返回 true
坑 2:Pod Disruption Budget 与节点维护冲突
CNPG 自动创建 PDB(minAvailable=1),集群维护 kubectl drain 时如果只剩一个健康实例,drain 会卡住。正确做法是先确保所有 Replica 健康,再逐节点 drain:
# 先检查集群健康
kubectl cnpg status app-db -n production | grep -E "Instances|Ready"
# 确保 Ready = Instances 后再 drain 节点
坑 3:WAL 归档中断导致备份链断裂
S3 凭证过期、网络抖动都可能导致 WAL 归档失败。一旦归档中断超过 wal_keep_size,备份链断裂,PITR 窗口缩小甚至不可用。必须配置告警:
# Prometheus 告警规则
- alert: CNPGWalArchivingFailing
expr: cnpg_pg_stat_archiver_failed_count > 0
for: 5m
labels:
severity: critical
annotations:
summary: "PostgreSQL WAL archiving is failing on {{ $labels.cluster }}"
总结
| 维度 | StatefulSet 手搓 | CloudNativePG |
|---|---|---|
| 故障转移 | 手动,10-30 分钟 | 自动,秒级 |
| 备份/PITR | CronJob + pg_dump,无 PITR | 原生对象存储归档 + 精确 PITR |
| 滚动升级 | 手动 drain + 重建 | 声明式,自动滚动 |
| 监控集成 | 需额外部署 exporter | 内置 Prometheus 指标 |
| 运维复杂度 | 高(脚本、配置散落) | 低(一个 CR 描述全部) |
CNPG 是目前在 Kubernetes 上跑 PostgreSQL 最省心的方案。如果你的团队已经在 K8s 上跑应用但数据库还在集群外面"特殊照顾",CNPG 值得认真评估——把数据库也纳入 GitOps 工作流,备份、HA、监控全部声明式管理,运维负担直接减半。