- Published on
CloudNativePG 在 Kubernetes 上跑起 Postgres 再杀掉它 — 实测故障转移 23 秒
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 引言 — 把 Postgres 交给 Operator 意味着什么
- 第 1 部 — 安装:一份清单
- 第 2 部 — 创建 3 实例集群
- 第 3 部 — 复制确认
- 第 4 部 — 杀掉主库:故障转移 23.1 秒
- 第 5 部 — 诚实的数字与陷阱
- 结语
- 参考资料
引言 — 把 Postgres 交给 Operator 意味着什么
在 Kubernetes 上运行有状态(stateful)数据库,长期以来被视为一种禁忌。CloudNativePG(CNPG) 就是一款正面打破这一禁忌的 Operator — 只要用一个 CRD 声明 Postgres 的高可用、故障转移、备份和滚动升级,Operator 就会替你完成主库选举和副本管理。这篇文章不是介绍,而是实测:在真实的 8 节点集群上装好 CNPG,启动 3 实例的 Postgres,真的把主库杀掉,量一量故障转移到底要花几秒。它与我亲手写 Operator 的Rust GPU Operator 篇成对,讲的是"使用一款做得好的 Operator"这一侧的故事。
第 1 部 — 安装:一份清单
CNPG 的安装很简单。apply 一份发布清单即可完成。
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.0.yaml
这一行会一次性装好多个 CRD(clusters、poolers、scheduledbackups、publications、subscriptions 等)、cnpg-system 命名空间里的控制器 Deployment、RBAC 和 Webhook。控制器启动大约花了 20 秒:
$ kubectl -n cnpg-system rollout status deploy/cnpg-controller-manager
deployment "cnpg-controller-manager" successfully rolled out
NAME READY UP-TO-DATE AVAILABLE AGE
cnpg-controller-manager 1/1 1 1 16s
第 2 部 — 创建 3 实例集群
Postgres 集群通过 Cluster CR 来声明。主库 1 + 副本 2,也就是 3 个实例。
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-test
namespace: cnpg-test
spec:
instances: 3
storage:
size: 1Gi
storageClass: nfs-client # 家庭实验室的 NFS — 陷阱见第 5 部
bootstrap:
initdb:
database: appdb
owner: appuser
apply 之后,Operator 就开始引导启动。我实时观察了状态迁移:
Setting up primary
→ Waiting for the instances to become active
→ ready=1 Creating a new replica ← 主库起来后开始复制副本
→ ready=2 Creating a new replica
→ ready=3 Cluster in healthy state ← 约 2 分钟后 3/3 正常
三个实例被自动分散到了不同的节点(cubi02、cubi03、cubi04)上 — Operator 通过 anti-affinity 避免它们挤在同一个节点。CNPG 还会创建三个用于连接的服务:
服务 角色
──────────── ─────────────────────────────
pg-test-rw 读写 → 始终路由到当前主库
pg-test-ro 只读 → 负载均衡到各副本
pg-test-r 任意实例(读取)
-rw 服务是关键 — 应用只要盯着这一个名字,即便故障转移换了主库,也会自动连接到新的主库。
第 3 部 — 复制确认
往主库插入 1000 行,看三个实例是否完全一致。
$ 向主库(pg-test-1)INSERT 1000 行
INSERT 0 1000
$ 各实例的行数
pg-test-1 (primary): 1000 rows
pg-test-2 (replica): 1000 rows ← 已复制
pg-test-3 (replica): 1000 rows ← 已复制
流复制立刻把三个节点同步了。接下来才是真正的实验。
第 4 部 — 杀掉主库:故障转移 23.1 秒
我最好奇的问题 — 主库突然消失,几秒能恢复?用 --grace-period=0 --force 让主库 Pod 瞬间死亡,然后每隔 0.5 秒轮询一次,直到新主库出现,同时计时。
=== FAILOVER TEST: killing primary pg-test-1 ===
deleted at t0; polling for new primary...
=== NEW PRIMARY: pg-test-2 (was pg-test-1) ===
failover time: 23.1 s
rows after failover: 1000 ← 数据零丢失
23.1 秒内 pg-test-2 晋升为新主库,1000 行原封不动地存活着。而死掉的 pg-test-1 并不会被丢弃 — Operator 会自动把它接回来,重新编入为副本:
=== 自愈后的最终角色 ===
pg-test-1: replica ← 死而复生,降级为副本回归
pg-test-2: primary ← 晋升后的新主库
pg-test-3: replica
$ 向新主库追加写入 → 正常,共 1500 行
新主库立刻开始接受写入(1000→1500 行),集群又回到了 3/3 healthy。**零次人工介入。**这就是 Operator 的价值。
第 5 部 — 诚实的数字与陷阱
秉持"博客只写经过验证的内容"这一原则,这次实验的局限也如实保留。
- 23 秒算快吗? 视情况而定。CNPG 的故障转移时间,是主库死亡检测(健康检查周期)、副本晋升,以及
-rw服务端点更新三者之和。生产环境里更常见的不是删除 Pod,而是节点故障,那种情况下还要加上节点检测时间(node-monitor-grace-period等),可能会更长。反过来,做了调优则会更短。"23 秒"是这套家庭实验室、这套配置下的实测值,并不是普适常量。 - NFS 存储的陷阱。 我用的是
nfs-client(NFS provisioner)存储,但把 Postgres 放到 NFS 上在生产环境里并不推荐 — 原因在于 fsync 保证和文件锁问题。在家庭实验室的测试里跑得不错,但真正上线的话应该用本地 SSD 或块存储(如 Ceph RBD)。 - 同步 vs 异步复制。 这次测试用的是默认(异步)复制。异步复制在理论上,可能会在主库死亡前的一瞬丢失极少量尚未复制的事务。如果需要零丢失,就得打开 CNPG 的同步复制(
minSyncReplicas),代价是写入延迟会增加。
结语
CNPG 用实测反驳了"在 Kubernetes 上跑数据库很危险"这一成见 — 一份清单就能拉起 3 节点的 HA Postgres,即便让主库瞬间死亡也能在 23 秒内自我恢复,死掉的节点则回归为副本。当然,存储、复制模式、故障转移调优这些真正的功课还在,但那已不是"能不能把数据库交给 Operator"的问题,而是"怎样才能交得好"的问题。下次我打算用同样的方式,把备份(ScheduledBackup)和时间点恢复(PITR)也杀一杀来验证。