Crunchy PGO(PostgreSQL Operator by Crunchy Data)在 Kubernetes 生态里深耕多年,但 CloudNativePG 作为 CNCF 沙箱项目,正以更声明式的 API 和更紧密的社区节奏吸引团队迁移。Gabriele Bartolini 的 Recipe 24 给了一条清晰路径:把 Crunchy PGO v6 管理的 PostgreSQL 17 集群,搬到 CloudNativePG 下的 PostgreSQL 18。两条路——离线声明式迁移和在线逻辑复制迁移——覆盖了从"能停机"到"几乎不能停"的全部场景。
迁移前要理清的几件事
Crunchy PGO 和 CloudNativePG 对集群的抽象方式不同:
- Crunchy PGO 用
PostgresClusterCRD,配置偏过程式,备份/恢复逻辑内嵌较深。 - CloudNativePG 用
ClusterCRD,一切声明式,从实例拓扑到备份策略都在同一个资源里写清楚。
这意味着迁移不只是"换个 Operator",而是换一种描述集群的方式。在做任何操作之前,先确认:
- 当前 Crunchy PGO 集群的版本、副本拓扑、PVC 挂载情况。
- 业务允许的停机窗口——这决定你走哪条路。
- 目标 PostgreSQL 18 的兼容性:应用驱动、扩展插件是否已适配。
# 先摸清现有 Crunchy PGO 集群状态
kubectl get postgrescluster -n postgres -o wide
kubectl get pods -n postgres -l postgres-operator.crunchydata.com/cluster=my-pg17
kubectl get pvc -n postgres -l postgres-operator.crunchydata.com/cluster=my-pg17
离线迁移:声明式 pg_dump 导入
离线迁移最简单,适合能接受几分钟到几十分钟停机的场景。CloudNativePG 内置了 pg_dump / pg_restore 导入机制,你只需要在 Cluster CRD 里声明数据来源,Operator 自动完成拉取和恢复。
核心思路:在旧集群上执行 pg_dump,把结果存到对象存储或 PVC,然后让新 CNPG 集群在启动时从该来源导入。
第一步:从 Crunchy PGO 集群导出数据
# 找到 Crunchy PGO 集群的主节点 Pod
PRIMARY_POD=$(kubectl get pods -n postgres \
-l postgres-operator.crunchydata.com/cluster=my-pg17,role=primary \
-o jsonpath='{.items[0].metadata.name}')
# 执行 pg_dump,输出自定义格式(压缩,适合 pg_restore)
kubectl exec -n postgres "$PRIMARY_POD" -c database -- \
pg_dump -Fc -d myappdb -f /tmp/myappdb.dump
# 把 dump 文件拷到本地,再上传到 S3 或目标 PVC
kubectl cp postgres/"$PRIMARY_POD":/tmp/myappdb.dump ./myappdb.dump
第二步:声明 CNPG 集群并导入
在 Cluster 定义里通过 .spec.bootstrap.initdb 指定导入来源。以下示例假设 dump 文件已上传到 S3:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-pg18
namespace: postgres
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:18
instances: 3
storage:
size: 10Gi
storageClass: standard
bootstrap:
initdb:
import:
type: microservice
source:
externalCluster: crunchypg17
externalClusters:
- name: crunchypg17
barmanObjectStore:
destinationPath: s3://my-bucket/pg-dumps/
s3Credentials:
accessKeyId:
name: s3-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: s3-creds
key: SECRET_ACCESS_KEY
注意:
import.type设为microservice时,CNPG 会用pg_dump+pg_restore流程。如果 dump 是整个实例级别,用monolith类型。根据你的导出范围选择。
创建集群后,Operator 会自动拉取 dump 并恢复,然后配置副本拓扑:
kubectl apply -f my-pg18-cluster.yaml -n postgres
# 观察导入进度
kubectl describe cluster my-pg18 -n postgres | grep -A5 "Bootstrap"
kubectl logs -n postgres -l cnpg.io/cluster=my-pg18 -c postgres --tail=50
停机窗口 = 导出时间 + 导入时间 + 应用切换连接串时间。对于几 GB 的数据库,通常在几分钟内完成。
在线迁移:逻辑复制近零停机切换
如果业务不允许长时间停机,走逻辑复制路线。PostgreSQL 自身的逻辑复制(pg_logical / publication + subscription)让你在旧集群持续服务的同时,把变更实时同步到新集群,最后做一次秒级切换。
在旧集群(Crunchy PGO PG 17)上创建 Publication
# 连到主节点
kubectl exec -n postgres "$PRIMARY_POD" -c database -- \
psql -d myappdb -c "
CREATE PUBLICATION cnpg_migration FOR ALL TABLES;
"
# 确认 publication
kubectl exec -n postgres "$PRIMARY_POD" -c database -- \
psql -d myappdb -c "\dp+"
在新集群(CNPG PG 18)上先做基线拷贝,再建 Subscription
基线拷贝可以用 pg_dump 做一次初始全量导入(同离线迁移的导出步骤),然后在新集群上建 subscription,逻辑复制会自动从基线之后的位置继续追增量。
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-pg18-online
namespace: postgres
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:18
instances: 3
storage:
size: 10Gi
storageClass: standard
# 先用 initdb 导入基线数据
bootstrap:
initdb:
import:
type: microservice
source:
externalCluster: crunchypg17
externalClusters:
- name: crunchypg17
connectionParameters:
host: my-pg17-primary.postgres.svc
port: 5432
user: replicator
dbname: myappdb
sslCert:
name: crunchy-tls
key: tls.crt
sslKey:
name: crunchy-tls
key: tls.key
sslRootCert:
name: crunchy-tls
key: ca.crt
集群启动并完成基线导入后,在新集群主节点上建 subscription:
# 找到新集群主节点
NEW_PRIMARY=$(kubectl get pods -n postgres \
-l cnpg.io/cluster=my-pg18-online,role=primary \
-o jsonpath='{.items[0].metadata.name}')
kubectl exec -n postgres "$NEW_PRIMARY" -c postgres -- \
psql -d myappdb -c "
CREATE SUBSCRIPTION cnpg_sub
CONNECTION 'host=my-pg17-primary.postgres.svc port=5432 dbname=myappdb user=replicator sslmode=verify-ca'
PUBLICATION cnpg_migration
WITH (copy_data = false, streaming = on);
"
copy_data = false 因为基线已经通过 pg_dump 导入过了,只需要追增量。streaming = on 开启流式逻辑复制,减少延迟。
监控同步进度
# 在新集群查看 subscription 状态
kubectl exec -n postgres "$NEW_PRIMARY" -c postgres -- \
psql -d myappdb -c "
SELECT subname, pid, received_lsn, latest_end_lsn
FROM pg_stat_subscription;
"
# 对比旧集群的发送位置
kubectl exec -n postgres "$PRIMARY_POD" -c database -- \
psql -d myappdb -c "
SELECT slot_name, confirmed_flush_lsn
FROM pg_replication_slots
WHERE slot_name LIKE '%cnpg_migration%';
"
当 received_lsn 和 confirmed_flush_lsn 持平且稳定,说明增量已追完。
切换:秒级 cutover
- 应用层准备新连接串(指向 CNPG 集群的 Service)。
- 在旧集群上断开 publication,停止写入:
kubectl exec -n postgres "$PRIMARY_POD" -c database -- \
psql -d myappdb -c "ALTER PUBLICATION cnpg_migration SET (publish = '');"
- 确认新集群已收到最后一批变更。
- 应用切换连接串到新集群 Service。
- 在新集群上断开 subscription,使其独立运行:
kubectl exec -n postgres "$NEW_PRIMARY" -c postgres -- \
psql -d myappdb -c "ALTER SUBSCRIPTION cnpg_sub DISABLE;"
kubectl exec -n postgres "$NEW_PRIMARY" -c postgres -- \
psql -d myappdb -c "DROP SUBSCRIPTION cnpg_sub;"
- 卸载 Crunchy PGO 集群。
整个切换窗口在秒级——从断 publication 到应用连新集群,通常不超过 10 秒。
两条路的取舍清单
| 维度 | 离线 pg_dump 导入 | 在线逻辑复制 |
|---|---|---|
| 停机时间 | 分钟级(取决于数据量) | 秒级 |
| 操作复杂度 | 低,声明式一步到位 | 中,需手动建 publication/subscription |
| 数据一致性 | 单次 dump,一致 | 逻辑复制,需监控 lag |
| 大对象/DDL | 全量拷贝无遗漏 | 逻辑复制不覆盖 DDL 和大对象,需额外处理 |
| 回滚难度 | 旧集群还在,回滚容易 | 切换后回滚需反向复制或重新导出 |
大对象和 DDL 的坑:逻辑复制只同步 INSERT/UPDATE/DELETE,不同步 ALTER TABLE、CREATE INDEX 等 DDL,也不同步 bytea 超过一定阈值的大对象。如果你的库有频繁 DDL 或大对象列,离线迁移反而更安全,或者在在线迁移前手动对齐 DDL。
实操建议
- 先在测试环境完整走一遍,无论选哪条路。尤其是逻辑复制路线,publication/subscription 的网络连通和 TLS 证书信任容易卡住。
- 数据量小于 10 GB 且能停机 5 分钟,直接走离线路线,省心。
- 数据量大或 SLA 要求高,走逻辑复制,但务必提前在测试环境验证 lag 监控和 cutover 脚本。
- 迁移完成后不要立即删旧集群,保留至少一个完整备份周期,确认新集群稳定后再清理。
- CNPG 的备份配置别忘:迁移成功后第一时间配上 Barman Cloud 备份,否则新集群就是裸跑。
# 迁移完成后立即加上备份配置
spec:
backup:
barmanObjectStore:
destinationPath: s3://my-bucket/cnpg-backups/
s3Credentials:
accessKeyId:
name: s3-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: s3-creds
key: SECRET_ACCESS_KEY
wal:
compression: gzip
data:
compression: gzip
immediateCheckpoint: true
从 Crunchy PGO 换到 CloudNativePG,本质上是从一个 Operator 生态换到另一个。数据迁移只是第一步,后续的备份策略、监控集成、证书轮转都需要按 CNPG 的声明式方式重新配置。但一旦跑稳,CNPG 的 Cluster CRD 一把声明全部拓扑和运维策略的体验,会让日常运维轻很多。