从 Crunchy PGO 迁移到 CloudNativePG:PostgreSQL 18 升级实战路线

2026-05-13 27 预计阅读时间:1 分钟
来源:postgr.es AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:10 分钟

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 PGOPostgresCluster CRD,配置偏过程式,备份/恢复逻辑内嵌较深。
  • CloudNativePGCluster CRD,一切声明式,从实例拓扑到备份策略都在同一个资源里写清楚。

这意味着迁移不只是"换个 Operator",而是换一种描述集群的方式。在做任何操作之前,先确认:

  1. 当前 Crunchy PGO 集群的版本、副本拓扑、PVC 挂载情况。
  2. 业务允许的停机窗口——这决定你走哪条路。
  3. 目标 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_lsnconfirmed_flush_lsn 持平且稳定,说明增量已追完。

切换:秒级 cutover

  1. 应用层准备新连接串(指向 CNPG 集群的 Service)。
  2. 在旧集群上断开 publication,停止写入:
kubectl exec -n postgres "$PRIMARY_POD" -c database -- \
  psql -d myappdb -c "ALTER PUBLICATION cnpg_migration SET (publish = '');"
  1. 确认新集群已收到最后一批变更。
  2. 应用切换连接串到新集群 Service。
  3. 在新集群上断开 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;"
  1. 卸载 Crunchy PGO 集群。

整个切换窗口在秒级——从断 publication 到应用连新集群,通常不超过 10 秒。

两条路的取舍清单

维度 离线 pg_dump 导入 在线逻辑复制
停机时间 分钟级(取决于数据量) 秒级
操作复杂度 低,声明式一步到位 中,需手动建 publication/subscription
数据一致性 单次 dump,一致 逻辑复制,需监控 lag
大对象/DDL 全量拷贝无遗漏 逻辑复制不覆盖 DDL 和大对象,需额外处理
回滚难度 旧集群还在,回滚容易 切换后回滚需反向复制或重新导出

大对象和 DDL 的坑:逻辑复制只同步 INSERT/UPDATE/DELETE,不同步 ALTER TABLE、CREATE INDEX 等 DDL,也不同步 bytea 超过一定阈值的大对象。如果你的库有频繁 DDL 或大对象列,离线迁移反而更安全,或者在在线迁移前手动对齐 DDL。

实操建议

  1. 先在测试环境完整走一遍,无论选哪条路。尤其是逻辑复制路线,publication/subscription 的网络连通和 TLS 证书信任容易卡住。
  2. 数据量小于 10 GB 且能停机 5 分钟,直接走离线路线,省心。
  3. 数据量大或 SLA 要求高,走逻辑复制,但务必提前在测试环境验证 lag 监控和 cutover 脚本。
  4. 迁移完成后不要立即删旧集群,保留至少一个完整备份周期,确认新集群稳定后再清理。
  5. 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 一把声明全部拓扑和运维策略的体验,会让日常运维轻很多。


相关推荐