DeepSeek总结的从 Crunchy PGO 迁移到使用 CloudNativePG 管理的 PostgreSQL 18

来源:https://www.gabrielebartolini.it/articles/2026/05/cnpg-recipe-24-migrating-from-crunchy-pgo-to-postgresql-18-with-cloudnativepg/

CNPG 食谱 24 - 从 Crunchy PGO 迁移到使用 CloudNativePG 管理的 PostgreSQL 18

作者: Gabriele Bartolini
日期: 2026年5月13日

目录

  • 先决条件
  • 设置本地环境
  • 部署源 PostgreSQL 17 集群
  • 加载示例数据
  • 检查扩展兼容性
  • 离线迁移
  • 在线迁移
    • 部署目标集群
    • 设置逻辑复制
    • 验证与切换
  • 清理
  • 镜像体积与安全态势
  • 结论

从 Crunchy PGO v6 管理的 PostgreSQL 17 集群迁移到 CloudNativePG 下 PostgreSQL 18 的分步指南。涵盖两条路径:使用 CloudNativePG 内置的 pg_dump 导入的完全声明性离线迁移,以及使用原生 PostgreSQL 逻辑复制实现接近零停机切换的在线迁移。

自从 Crunchy Data 大约一年前被 Snowflake 收购以来,我在 EDB 的工作中,与潜在客户和评估 Kubernetes 上 PostgreSQL 的团队交谈时,反复听到同样的担忧:对 Crunchy PGO 未来的不确定性。问题各不相同(围绕开源 operator 的长期承诺、发布节奏、社区活动),但潜在的担忧是一致的。我应该坦诚地说:鉴于 Crunchy operator 的架构与 CloudNativePG 的根本不同,我对它的直接了解有限,我对其未来可能提供的任何意见充其量只是推测。这里重要的是实际问题:如果你正在考虑你的选择,迁移路径是什么样的?如果你正在运行 Crunchy PGO v6 集群并考虑你的选择,这个食谱将准确地向你展示如何迁移到 CloudNativePG,并在同一操作中升级到 PostgreSQL 18。涵盖两条路径:使用 CloudNativePG 内置的 pg_dump 导入的离线路径,以及使用原生 PostgreSQL 逻辑复制实现接近零停机切换的在线路径。

从 CloudNativePG 的角度来看,迁移的源只是一个通过网络可访问的 PostgreSQL 端点。无论该端点是由 Crunchy PGO、Zalando、Patroni、RDS 还是其他任何东西管理,都无关紧要。重要的是数据库可访问,存在具有足够权限的用户,并且(对于在线路径)源支持逻辑复制。其他一切都是标准的 PostgreSQL 和 CloudNativePG 机制。

本食谱建立在 CNPG 食谱 5 和 CNPG 食谱 15 中引入的声明性逻辑复制的基础上。Crunchy PGO 集群被视为一个黑盒:我们应用一个清单来启动它,记下服务端点和它创建的凭据 secret,并将该信息传递给 CloudNativePG。不需要了解 PGO 内部结构。

两条路径仅在创建 CloudNativePG 集群时有所不同。离线路径是两者中较简单的:整个迁移表示为一个集群清单,端到端完全声明性,无需设置复制。需要一个维护窗口,其时间与数据集大小成比例。

在线路径使用原生 PostgreSQL 逻辑复制来保持数据从源连续流向目标,无论数据集大小如何,都将切换窗口减少到几秒钟,代价是增加几个设置和拆除复制对象的步骤。

如果 pg_dump 窗口在你的工作负载可容忍的范围内,请阅读离线部分并停止。如果不是,请继续阅读在线部分。

以下步骤使用 Kind 作为本地 Kubernetes 环境,但这些清单是纯 YAML,在任何兼容集群上都可以不变地工作。如果你已经有一个运行着 PGO 部署的集群,请直接跳到迁移部分。

先决条件

CNPG 食谱 1 涵盖了完整的本地实验环境设置,并逐步指导安装下面列出的所有工具。如果这是你第一次使用 CloudNativePG,请从那里开始。

  • Docker
  • Git
  • Kind
  • 带有 cnpg 插件的 kubectl

设置本地环境

创建一个 Kind 集群并安装 CloudNativePG:

bash 复制代码
kind create cluster --name cnpg-migration
kubectl config use-context kind-cnpg-migration

使用 operator 清单安装 CloudNativePG。从安装页面获取最新稳定版本的 URL,然后应用它:

bash 复制代码
kubectl apply --server-side -f \
  https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.29/releases/cnpg-1.29.1.yaml
  # 替换为安装页面中的最新发布 URL

等待 operator 变为可用:

bash 复制代码
kubectl rollout status deployment \
  -n cnpg-system cnpg-controller-manager

然后应用 CloudNativePG 扩展镜像目录。该目录提供 pgaudit 和其他扩展作为 OCI 镜像,通过 Kubernetes ImageVolume 功能交付给每个 Pod(从 Kubernetes 1.35 开始默认可用;在 1.33 和 1.34 上必须显式启用 ImageVolume 特性门控):

bash 复制代码
kubectl apply -f \
  https://raw.githubusercontent.com/cloudnative-pg/artifacts/refs/heads/main/image-catalogs-extensions/catalog-minimal-trixie.yaml

检查目录以查看哪些 PostgreSQL 版本和扩展镜像可用:

bash 复制代码
kubectl describe clusterimagecatalog postgresql-minimal-trixie

部署源 PostgreSQL 17 集群

虽然本食谱使用 PGO v6,但从 CloudNativePG 方面来看,PGO v5 的迁移步骤是相同的:两个版本都使用相同的服务和 secret 命名约定,CloudNativePG 直接连接到 PostgreSQL 端点,无需了解管理它的 operator。如果你已经在运行 v5 集群,请完全跳过本节,直接使用你现有的端点和凭据进入迁移步骤。

按照官方快速入门安装 Crunchy Postgres for Kubernetes (PGO)。克隆 operator 仓库,检出新发布标签,并应用 Kustomize 目标:

bash 复制代码
git clone https://github.com/CrunchyData/postgres-operator.git
cd postgres-operator
git checkout v6.0.1
kubectl apply -k config/namespace
kubectl apply --server-side -k config/default
cd ..

请查看发布页面以获取当前标签,替换上面的 v6.0.1

现在部署源 PostgresCluster。该清单创建一个 app 数据库,带有一个常规应用程序用户 (app) 和一个专用的迁移用户 (cnpg),CloudNativePG 将使用后者进行连接。wal_level: logical 已包含在内;它是在线路径所必需的,对离线路径无害。不需要显式的镜像标签:PGO v6 会根据 postgresVersion 自动解析正确的容器镜像。

在应用之前需要注意数据库名称。这里的名称 app 是象征性的;请将其替换为你正在迁移的数据库的名称。CloudNativePG 推荐的模式是每个集群一个数据库(微服务模型)。如果源包含多个数据库,请为每个数据库单独运行此过程。在考虑偏离该模式之前,请参阅 CloudNativePG 常见问题解答。

postgres-cluster-source.yaml

yaml 复制代码
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
  name: crunchy
spec:
  postgresVersion: 17
  patroni:
    dynamicConfiguration:
      postgresql:
        parameters:
          wal_level: logical
  instances:
  - name: instance1
    replicas: 1
    dataVolumeClaimSpec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 5Gi
  backups:
    pgbackrest:
      repos:
      - name: repo1
        volume:
          volumeClaimSpec:
            accessModes:
            - ReadWriteOnce
            resources:
              requests:
                storage: 5Gi
  users:
  - name: app
    databases:
    - app
  - name: cnpg
    databases:
    - app
    options: "SUPERUSER"

此处授予 cnpg 用户 SUPERUSER 权限是为了使本食谱自包含。在生产环境中,仅授予 CloudNativePG 将在源上执行的操作所需的权限。

一旦 PGO 协调了集群,本食谱其余部分相关的两个值是:

  • 主端点 : crunchy-primary.default.svc (端口 5432)
  • 迁移凭据 : secret crunchy-pguser-cnpg,键 password

检查集群状态,直到所有实例报告健康:

bash 复制代码
kubectl describe postgresclusters.postgres-operator.crunchydata.com crunchy

加载示例数据

以下 Job 创建一个带有 SERIAL 主键的 orders 表并插入 1000 行。这模拟了本地环境中的现有工作负载;在实际迁移中,数据已经存在,此步骤应完全跳过。

sample-data-init.yaml

yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: sample-data-init
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: psql
        image: ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie
        env:
        - name: PGPASSWORD
          valueFrom:
            secretKeyRef:
              name: crunchy-pguser-cnpg
              key: password
        command:
        - psql
        - -h
        - crunchy-primary.default.svc
        - -U
        - cnpg
        - app
        - -c
        - |
          CREATE TABLE orders (
            id SERIAL PRIMARY KEY,
            description TEXT NOT NULL,
            created_at TIMESTAMPTZ DEFAULT now()
          );
          INSERT INTO orders (description)
          SELECT 'Order ' || g FROM generate_series(1, 1000) AS g;

验证数据存在并检查当前序列值:

bash 复制代码
kubectl run psql-check --rm -it --restart=Never \
  --image=ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie \
  --env="PGPASSWORD=$(kubectl get secret crunchy-pguser-cnpg \
    -o jsonpath='{.data.password}' | base64 -d)" \
  -- psql -h crunchy-primary.default.svc -U cnpg app \
     -c "SELECT count(*) FROM orders;" \
     -c "SELECT last_value FROM orders_id_seq;"

源已就绪:一个在 crunchy-primary.default.svc 可访问的、运行中的 PostgreSQL 17 实例,带有数据、一个值为 1000 的序列和一个名为 cnpg 的超级用户。

检查扩展兼容性

Crunchy PostgreSQL 镜像默认安装 pgaudit。目标集群也必须具备 pgaudit,否则当 pg_restore 在转储中遇到 CREATE EXTENSION pgaudit 时会失败。

本食谱中的两个集群清单都使用 imageCatalogRef 引用之前安装的 postgresql-minimal-trixie 目录,并在 spec.postgresql.extensions 中声明 pgaudit。CloudNativePG 通过 Kubernetes ImageVolume 功能将 pgaudit 扩展镜像作为只读卷挂载到每个 Pod 上,使扩展可用于 PostgreSQL 18,而无需将其嵌入基础操作镜像中。pgaudit GUC 参数在 spec.postgresql.parameters 中设置,CloudNativePG 会自动管理这些参数。在导入过程中,扩展将被干净地创建。

对于实际迁移,请先在源上审计完整的扩展列表(SELECT extname FROM pg_extension),并在开始前确认每个扩展要么在目标镜像中可用,要么可以通过目录中的扩展镜像提供。

离线迁移

这是完全声明性的路径。应用以下集群清单:

cluster-offline.yaml

yaml 复制代码
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-app
spec:
  instances: 1
  imageCatalogRef:
    apiGroup: postgresql.cnpg.io
    kind: ClusterImageCatalog
    name: postgresql-minimal-trixie
    major: 18
  storage:
    size: 5Gi
  postgresql:
    extensions:
      - name: pgaudit
    parameters:
      pgaudit.log: "all, -misc"
      pgaudit.log_catalog: "off"
      pgaudit.log_parameter: "on"
      pgaudit.log_relation: "on"
  bootstrap:
    initdb:
      import:
        type: microservice
        databases:
        - app
        source:
          externalCluster: crunchy
  externalClusters:
  - name: crunchy
    connectionParameters:
      host: crunchy-primary.default.svc
      port: "5432"
      user: cnpg
      dbname: app
    password:
      name: crunchy-pguser-cnpg
      key: password

CloudNativePG 连接到源,在 app 数据库上运行 pg_dump,并将完整的模式和数据结构恢复到新的 PostgreSQL 18 集群中。导入仅在引导时运行一次。迁移本身无需配置其他任何东西。

等待集群变为就绪状态:

bash 复制代码
kubectl wait --for=condition=Ready cluster/pg-app --timeout=600s

验证行数和序列值与源匹配:

bash 复制代码
# 源
kubectl run psql-count --rm -it --restart=Never \
  --image=ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie \
  --env="PGPASSWORD=$(kubectl get secret crunchy-pguser-cnpg \
    -o jsonpath='{.data.password}' | base64 -d)" \
  -- psql -h crunchy-primary.default.svc -U cnpg app \
     -c "SELECT count(*) FROM orders;" \
     -c "SELECT last_value FROM orders_id_seq;"

# 目标
kubectl cnpg psql pg-app -- app \
  -c "SELECT count(*) FROM orders;" \
  -c "SELECT last_value FROM orders_id_seq;"

一旦计数匹配,将 pg-app 扩展到所需的副本数,并将你的应用程序重定向到 pg-app-rw.default.svc。迁移完成。

在线迁移

当数据集大到 pg_dump 窗口不可接受时,使用此路径。逻辑复制在生产环境旁边连续运行,将切换时间减少到排空复制队列所需的几秒钟。它要求源端使用 PostgreSQL 10 或更高版本,这涵盖了所有当前受支持的 PostgreSQL 版本。

部署目标集群

清单与离线路径相同,但有一处更改:schemaOnly: true 指示 CloudNativePG 在引导时仅导入模式。行数据通过下一步设置的订阅到达。

cluster-online.yaml

yaml 复制代码
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-app
spec:
  instances: 1
  imageCatalogRef:
    apiGroup: postgresql.cnpg.io
    kind: ClusterImageCatalog
    name: postgresql-minimal-trixie
    major: 18
  storage:
    size: 5Gi
  postgresql:
    extensions:
      - name: pgaudit
    parameters:
      pgaudit.log: "all, -misc"
      pgaudit.log_catalog: "off"
      pgaudit.log_parameter: "on"
      pgaudit.log_relation: "on"
  bootstrap:
    initdb:
      import:
        type: microservice
        schemaOnly: true
        databases:
        - app
        source:
          externalCluster: crunchy
  externalClusters:
  - name: crunchy
    connectionParameters:
      host: crunchy-primary.default.svc
      port: "5432"
      user: cnpg
      dbname: app
    password:
      name: crunchy-pguser-cnpg
      key: password

等待集群变为就绪状态:

bash 复制代码
kubectl wait --for=condition=Ready cluster/pg-app --timeout=300s

确认模式已到达但表为空:

bash 复制代码
kubectl cnpg psql pg-app -- app -c '\dt+'

设置逻辑复制

使用 cnpg 插件在源上创建发布(publication)。它从 pg-app 的 spec 中的 crunchy 外部集群条目派生连接详细信息:

bash 复制代码
kubectl cnpg publication create pg-app \
  --external-cluster crunchy \
  --publication migration \
  --all-tables

在 CloudNativePG 中,声明性的 Subscription 资源处理此操作:

subscription.yaml

yaml 复制代码
apiVersion: postgresql.cnpg.io/v1
kind: Subscription
metadata:
  name: pg-app-migration
spec:
  cluster:
    name: pg-app
  dbname: app
  name: migration
  externalClusterName: crunchy
  publicationName: migration
  subscriptionReclaimPolicy: delete

CloudNativePG 创建订阅并立即开始初始表同步。确认它已启动:

bash 复制代码
kubectl logs \
  -l cnpg.io/cluster=pg-app \
  --follow \
  | grep -i "logical replication"

你应该会看到类似 logical replication apply worker for subscription "migration" has started 的消息。现在,行数据将从源持续流向 pg-app

验证与切换

在实际切换之前,至少运行一次预演来测量复制延迟并练习切换流程。检查源上的复制槽:

bash 复制代码
kubectl run psql-lag --rm -it --restart=Never \
  --image=ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie \
  --env="PGPASSWORD=$(kubectl get secret crunchy-pguser-cnpg \
    -o jsonpath='{.data.password}' | base64 -d)" \
  -- psql -h crunchy-primary.default.svc -U cnpg app -c "
    SELECT slot_name,
           confirmed_flush_lsn,
           pg_current_wal_lsn(),
           pg_current_wal_lsn() - confirmed_flush_lsn AS lag_bytes
    FROM pg_replication_slots
    WHERE slot_name = 'migration';"

lag_bytes 持续接近零时,表明订阅已追赶上。此时行数据已在目标中,但逻辑复制不会复制序列。在同步之前检查目标序列值:

bash 复制代码
kubectl cnpg psql pg-app -- app \
  -c "SELECT last_value FROM orders_id_seq;"

无论复制了多少行,该值都将是 1(未推进序列的默认值)。在切换前同步序列:

bash 复制代码
kubectl cnpg subscription sync-sequences pg-app \
  --subscription migration

在维护窗口前作为预演运行一次,并在重定向流量前立即再运行一次。再次检查目标序列以确认它现在与源匹配:

bash 复制代码
kubectl cnpg psql pg-app -- app \
  -c "SELECT last_value FROM orders_id_seq;"

需要注意的是,PostgreSQL 19 预计将引入通过 CREATE PUBLICATIONCREATE SUBSCRIPTION 对象复制序列状态的原生支持,这将使此手动步骤变得不必要。该能力是未来 CloudNativePG 集成的有力候选。

当准备上线时,停止对源的写入。等待 lag_bytes 变为零并运行最后一次 sync-sequences。将 pg-app 扩展到所需的副本数,并将你的应用程序重定向到 pg-app-rw.default.svc

一旦确认应用程序正常运行,清理复制对象:

bash 复制代码
# 删除订阅资源
# (subscriptionReclaimPolicy: delete 会删除底层的 SQL 订阅)
kubectl delete subscription pg-app-migration

# 删除源上的发布
kubectl cnpg publication drop pg-app \
  --external-cluster crunchy \
  --publication migration

清理

当应用程序在 pg-app 上稳定运行后,停用源集群:

bash 复制代码
kubectl delete postgrescluster crunchy

一旦所有数据库都迁移完毕,就可以删除 PGO operator 及其命名空间。

bootstrap.initdb.import 块和 crunchyexternalClusters 条目仅在初始引导期间被参考,对运行中的集群没有影响。迁移完成后,你可以从集群清单中删除这两个部分并应用更改。CloudNativePG 将进行协调,不会造成任何中断。

要拆除本食谱中使用的本地 Kind 环境:

bash 复制代码
kind delete cluster --name cnpg-migration

镜像体积与安全态势

迁移到 CloudNativePG 也会改变你拉取和操作的镜像栈。下表量化了这种变化。拉取大小是从 OCI 清单层数据测量的压缩后大小;漏洞计数来自 docker scout quickview

压缩后的拉取大小

镜像 角色 压缩后拉取大小
registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi9-17.9-2610 PGO 源集群 ~346 MB
ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie CNPG 目标(本食谱) ~87 MB
pgaudit 扩展镜像 pgaudit OCI 镜像卷 ~44 KB
ghcr.io/cloudnative-pg/plugin-barman-cloud:v0.12.0 CNPG 备份插件 ~40 MB
CNPG 最小 + pgaudit + Barman Cloud 插件 目标总计 ~127 MB

CVE 暴露情况 (docker scout quickview)

镜像 包数量 严重 高危 中危 低危
crunchy-postgres:ubi9-17.9-2610 625 2 156 1053 201
postgresql:18-minimal-trixie 140 0 4 6 39

CNPG minimal-trixie 镜像是一个 Debian Trixie Slim 基础镜像,仅包含 PostgreSQL 18,扩展作为 OCI 镜像卷交付。完整的目标栈(操作镜像、pgaudit 扩展镜像、Barman Cloud 插件)总计约 127 MB,而仅 Crunchy 源操作镜像就为 346 MB。CVE 减少更为显著:140 个包对比 625 个,零个严重漏洞对比两个,四个高危漏洞对比 156 个。包数量不仅仅关乎这些宏观数字:更少的包意味着任何未来安全披露的爆炸半径更小。CNPG 最小镜像还附带完整的 SBOM 来源证明,使得审计镜像中确切包含的内容变得简单直接。

结论

两种迁移路径都归结为一个集群清单和源的连接详细信息。离线路径是两者中较短的:整个迁移是一个单一的声明性资源,应用一次。在线路径增加了发布、订阅和 sync-sequences 步骤,但使切换窗口独立于数据集大小。相同的方法同样适用于 Percona Operator for PostgreSQL,后者使用相同的服务和 secret 命名约定。

本食谱中的集群清单故意保持最小化:一个实例,没有备份配置,没有资源限制。它们仅用于教学目的。在生产环境中,你应至少运行三个实例,在重定向流量之前通过 Barman Cloud 插件配置 WAL 归档和备份,并设置适当的资源请求和限制。CloudNativePG 文档涵盖了所有这些内容;请将这里的清单视为起点,而非生产模板。

请继续关注即将发布的食谱!如需最新更新,请考虑订阅我的 LinkedIn 和 Twitter 频道。

如果你觉得这篇文章信息丰富,请随时使用下面提供的链接在你的社交媒体网络中分享。非常感谢你的支持!

本文在 Claude (Anthropic) 的协助下起草和完善。所有技术内容、更正和编辑方向均由作者本人负责。

封面图片:"大象与河马"。

作者

Gabriele Bartolini

EDB 副总裁、Kubernetes 首席架构师 | PostgreSQL 贡献者 | DoK 大使 | CloudNativePG 维护者 | 前 2ndQuadrant(联合创始人)

相关推荐
夜雪闻竹5 小时前
Claude Code 对话自动导入完全指南
数据库·数据挖掘·copilot
云祺vinchin6 小时前
云祺x鼎捷,为制造企业ERP打造双保险
数据库·安全·制造
我滴老baby6 小时前
2026年AI Agent将走向何方?十大趋势深度解析:从多模态融合到自主决策,从端侧部署到具身智能,提前布局下一个万亿级市场
数据库·人工智能·知识图谱
AC赳赳老秦6 小时前
OpenClaw与思维导图工具联动:自动生成工作规划脑图、拆解任务节点,适配职场管理
java·大数据·服务器·数据库·python·php·openclaw
zhishijike7 小时前
全国行政区划sql(省市区)
数据库·sql·mysql
KaMeidebaby7 小时前
卡梅德生物技术快报|单 B 细胞抗体技术:全犬源单抗制备流程、关键参数与性能验证
前端·数据库·其他·百度·新浪微博
KG_LLM图谱增强大模型7 小时前
scHilda:大模型与知识图谱分层融合,突破单细胞分型瓶颈
数据库·人工智能·知识图谱
凯瑟琳.奥古斯特7 小时前
力扣3654:二维矩阵连续空位统计
数据结构·数据库·算法·职场和发展
满昕欢喜7 小时前
SQL Server的概述与安装
数据库·sqlserver