来源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请从那里开始。DockerGitKind带有cnpg插件的kubectl设置本地环境创建一个 Kind 集群并安装 CloudNativePGkind create cluster--namecnpg-migration kubectl config use-context kind-cnpg-migration使用 operator 清单安装 CloudNativePG。从安装页面获取最新稳定版本的 URL然后应用它kubectl apply --server-side-f\https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.29/releases/cnpg-1.29.1.yaml# 替换为安装页面中的最新发布 URL等待 operator 变为可用kubectl rollout status deployment\-ncnpg-system cnpg-controller-manager然后应用 CloudNativePG 扩展镜像目录。该目录提供pgaudit和其他扩展作为 OCI 镜像通过 Kubernetes ImageVolume 功能交付给每个 Pod从 Kubernetes 1.35 开始默认可用在 1.33 和 1.34 上必须显式启用 ImageVolume 特性门控kubectl apply-f\https://raw.githubusercontent.com/cloudnative-pg/artifacts/refs/heads/main/image-catalogs-extensions/catalog-minimal-trixie.yaml检查目录以查看哪些 PostgreSQL 版本和扩展镜像可用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 目标gitclone https://github.com/CrunchyData/postgres-operator.gitcdpostgres-operatorgitcheckout v6.0.1 kubectl apply-kconfig/namespace kubectl apply --server-side-kconfig/defaultcd..请查看发布页面以获取当前标签替换上面的v6.0.1。现在部署源PostgresCluster。该清单创建一个app数据库带有一个常规应用程序用户 (app) 和一个专用的迁移用户 (cnpg)CloudNativePG 将使用后者进行连接。wal_level: logical已包含在内它是在线路径所必需的对离线路径无害。不需要显式的镜像标签PGO v6 会根据postgresVersion自动解析正确的容器镜像。在应用之前需要注意数据库名称。这里的名称app是象征性的请将其替换为你正在迁移的数据库的名称。CloudNativePG 推荐的模式是每个集群一个数据库微服务模型。如果源包含多个数据库请为每个数据库单独运行此过程。在考虑偏离该模式之前请参阅 CloudNativePG 常见问题解答。postgres-cluster-source.yamlapiVersion:postgres-operator.crunchydata.com/v1beta1kind:PostgresClustermetadata:name:crunchyspec:postgresVersion:17patroni:dynamicConfiguration:postgresql:parameters:wal_level:logicalinstances:-name:instance1replicas:1dataVolumeClaimSpec:accessModes:-ReadWriteOnceresources:requests:storage:5Gibackups:pgbackrest:repos:-name:repo1volume:volumeClaimSpec:accessModes:-ReadWriteOnceresources:requests:storage:5Giusers:-name:appdatabases:-app-name:cnpgdatabases:-appoptions:SUPERUSER此处授予cnpg用户SUPERUSER权限是为了使本食谱自包含。在生产环境中仅授予 CloudNativePG 将在源上执行的操作所需的权限。一旦 PGO 协调了集群本食谱其余部分相关的两个值是主端点:crunchy-primary.default.svc(端口 5432)迁移凭据: secretcrunchy-pguser-cnpg键password检查集群状态直到所有实例报告健康kubectl describe postgresclusters.postgres-operator.crunchydata.com crunchy加载示例数据以下 Job 创建一个带有SERIAL主键的orders表并插入 1000 行。这模拟了本地环境中的现有工作负载在实际迁移中数据已经存在此步骤应完全跳过。sample-data-init.yamlapiVersion:batch/v1kind:Jobmetadata:name:sample-data-initspec:template:spec:restartPolicy:Nevercontainers:-name:psqlimage:ghcr.io/cloudnative-pg/postgresql:18-minimal-trixieenv:-name:PGPASSWORDvalueFrom:secretKeyRef:name:crunchy-pguser-cnpgkey:passwordcommand:-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;验证数据存在并检查当前序列值kubectl run psql-check--rm-it--restartNever\--imageghcr.io/cloudnative-pg/postgresql:18-minimal-trixie\--envPGPASSWORD$(kubectl get secret crunchy-pguser-cnpg\-ojsonpath{.data.password}|base64-d)\-- psql-hcrunchy-primary.default.svc-Ucnpg app\-cSELECT count(*) FROM orders;\-cSELECT 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而无需将其嵌入基础操作镜像中。pgauditGUC 参数在spec.postgresql.parameters中设置CloudNativePG 会自动管理这些参数。在导入过程中扩展将被干净地创建。对于实际迁移请先在源上审计完整的扩展列表SELECT extname FROM pg_extension并在开始前确认每个扩展要么在目标镜像中可用要么可以通过目录中的扩展镜像提供。离线迁移这是完全声明性的路径。应用以下集群清单cluster-offline.yamlapiVersion:postgresql.cnpg.io/v1kind:Clustermetadata:name:pg-appspec:instances:1imageCatalogRef:apiGroup:postgresql.cnpg.iokind:ClusterImageCatalogname:postgresql-minimal-trixiemajor:18storage:size:5Gipostgresql:extensions:-name:pgauditparameters:pgaudit.log:all, -miscpgaudit.log_catalog:offpgaudit.log_parameter:onpgaudit.log_relation:onbootstrap:initdb:import:type:microservicedatabases:-appsource:externalCluster:crunchyexternalClusters:-name:crunchyconnectionParameters:host:crunchy-primary.default.svcport:5432user:cnpgdbname:apppassword:name:crunchy-pguser-cnpgkey:passwordCloudNativePG 连接到源在app数据库上运行pg_dump并将完整的模式和数据结构恢复到新的 PostgreSQL 18 集群中。导入仅在引导时运行一次。迁移本身无需配置其他任何东西。等待集群变为就绪状态kubectlwait--forconditionReady cluster/pg-app--timeout600s验证行数和序列值与源匹配# 源kubectl run psql-count--rm-it--restartNever\--imageghcr.io/cloudnative-pg/postgresql:18-minimal-trixie\--envPGPASSWORD$(kubectl get secret crunchy-pguser-cnpg\-ojsonpath{.data.password}|base64-d)\-- psql-hcrunchy-primary.default.svc-Ucnpg app\-cSELECT count(*) FROM orders;\-cSELECT last_value FROM orders_id_seq;# 目标kubectl cnpg psql pg-app -- app\-cSELECT count(*) FROM orders;\-cSELECT last_value FROM orders_id_seq;一旦计数匹配将pg-app扩展到所需的副本数并将你的应用程序重定向到pg-app-rw.default.svc。迁移完成。在线迁移当数据集大到pg_dump窗口不可接受时使用此路径。逻辑复制在生产环境旁边连续运行将切换时间减少到排空复制队列所需的几秒钟。它要求源端使用 PostgreSQL 10 或更高版本这涵盖了所有当前受支持的 PostgreSQL 版本。部署目标集群清单与离线路径相同但有一处更改schemaOnly: true指示 CloudNativePG 在引导时仅导入模式。行数据通过下一步设置的订阅到达。cluster-online.yamlapiVersion:postgresql.cnpg.io/v1kind:Clustermetadata:name:pg-appspec:instances:1imageCatalogRef:apiGroup:postgresql.cnpg.iokind:ClusterImageCatalogname:postgresql-minimal-trixiemajor:18storage:size:5Gipostgresql:extensions:-name:pgauditparameters:pgaudit.log:all, -miscpgaudit.log_catalog:offpgaudit.log_parameter:onpgaudit.log_relation:onbootstrap:initdb:import:type:microserviceschemaOnly:truedatabases:-appsource:externalCluster:crunchyexternalClusters:-name:crunchyconnectionParameters:host:crunchy-primary.default.svcport:5432user:cnpgdbname:apppassword:name:crunchy-pguser-cnpgkey:password等待集群变为就绪状态kubectlwait--forconditionReady cluster/pg-app--timeout300s确认模式已到达但表为空kubectl cnpg psql pg-app -- app-c\dt设置逻辑复制使用cnpg插件在源上创建发布publication。它从pg-app的 spec 中的crunchy外部集群条目派生连接详细信息kubectl cnpg publication create pg-app\--external-cluster crunchy\--publicationmigration\--all-tables在 CloudNativePG 中声明性的Subscription资源处理此操作subscription.yamlapiVersion:postgresql.cnpg.io/v1kind:Subscriptionmetadata:name:pg-app-migrationspec:cluster:name:pg-appdbname:appname:migrationexternalClusterName:crunchypublicationName:migrationsubscriptionReclaimPolicy:deleteCloudNativePG 创建订阅并立即开始初始表同步。确认它已启动kubectl logs\-lcnpg.io/clusterpg-app\--follow\|grep-ilogical replication你应该会看到类似logical replication apply worker for subscription migration has started的消息。现在行数据将从源持续流向pg-app。验证与切换在实际切换之前至少运行一次预演来测量复制延迟并练习切换流程。检查源上的复制槽kubectl run psql-lag--rm-it--restartNever\--imageghcr.io/cloudnative-pg/postgresql:18-minimal-trixie\--envPGPASSWORD$(kubectl get secret crunchy-pguser-cnpg\-ojsonpath{.data.password}|base64-d)\-- psql-hcrunchy-primary.default.svc-Ucnpg 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持续接近零时表明订阅已追赶上。此时行数据已在目标中但逻辑复制不会复制序列。在同步之前检查目标序列值kubectl cnpg psql pg-app -- app\-cSELECT last_value FROM orders_id_seq;无论复制了多少行该值都将是 1未推进序列的默认值。在切换前同步序列kubectl cnpg subscription sync-sequences pg-app\--subscriptionmigration在维护窗口前作为预演运行一次并在重定向流量前立即再运行一次。再次检查目标序列以确认它现在与源匹配kubectl cnpg psql pg-app -- app\-cSELECT last_value FROM orders_id_seq;需要注意的是PostgreSQL 19 预计将引入通过CREATE PUBLICATION和CREATE SUBSCRIPTION对象复制序列状态的原生支持这将使此手动步骤变得不必要。该能力是未来 CloudNativePG 集成的有力候选。当准备上线时停止对源的写入。等待lag_bytes变为零并运行最后一次sync-sequences。将pg-app扩展到所需的副本数并将你的应用程序重定向到pg-app-rw.default.svc。一旦确认应用程序正常运行清理复制对象# 删除订阅资源# (subscriptionReclaimPolicy: delete 会删除底层的 SQL 订阅)kubectl delete subscription pg-app-migration# 删除源上的发布kubectl cnpg publication drop pg-app\--external-cluster crunchy\--publicationmigration清理当应用程序在pg-app上稳定运行后停用源集群kubectl delete postgrescluster crunchy一旦所有数据库都迁移完毕就可以删除 PGO operator 及其命名空间。bootstrap.initdb.import块和crunchy的externalClusters条目仅在初始引导期间被参考对运行中的集群没有影响。迁移完成后你可以从集群清单中删除这两个部分并应用更改。CloudNativePG 将进行协调不会造成任何中断。要拆除本食谱中使用的本地 Kind 环境kind delete cluster--namecnpg-migration镜像体积与安全态势迁移到 CloudNativePG 也会改变你拉取和操作的镜像栈。下表量化了这种变化。拉取大小是从 OCI 清单层数据测量的压缩后大小漏洞计数来自docker scout quickview。压缩后的拉取大小镜像角色压缩后拉取大小registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi9-17.9-2610PGO 源集群~346 MBghcr.io/cloudnative-pg/postgresql:18-minimal-trixieCNPG 目标本食谱~87 MBpgaudit扩展镜像pgauditOCI 镜像卷~44 KBghcr.io/cloudnative-pg/plugin-barman-cloud:v0.12.0CNPG 备份插件~40 MBCNPG 最小 pgaudit Barman Cloud 插件目标总计~127 MBCVE 暴露情况 (docker scout quickview)镜像包数量严重高危中危低危crunchy-postgres:ubi9-17.9-261062521561053201postgresql:18-minimal-trixie14004639CNPGminimal-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 BartoliniEDB 副总裁、Kubernetes 首席架构师 | PostgreSQL 贡献者 | DoK 大使 | CloudNativePG 维护者 | 前 2ndQuadrant联合创始人
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请从那里开始。DockerGitKind带有cnpg插件的kubectl设置本地环境创建一个 Kind 集群并安装 CloudNativePGkind create cluster--namecnpg-migration kubectl config use-context kind-cnpg-migration使用 operator 清单安装 CloudNativePG。从安装页面获取最新稳定版本的 URL然后应用它kubectl apply --server-side-f\https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.29/releases/cnpg-1.29.1.yaml# 替换为安装页面中的最新发布 URL等待 operator 变为可用kubectl rollout status deployment\-ncnpg-system cnpg-controller-manager然后应用 CloudNativePG 扩展镜像目录。该目录提供pgaudit和其他扩展作为 OCI 镜像通过 Kubernetes ImageVolume 功能交付给每个 Pod从 Kubernetes 1.35 开始默认可用在 1.33 和 1.34 上必须显式启用 ImageVolume 特性门控kubectl apply-f\https://raw.githubusercontent.com/cloudnative-pg/artifacts/refs/heads/main/image-catalogs-extensions/catalog-minimal-trixie.yaml检查目录以查看哪些 PostgreSQL 版本和扩展镜像可用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 目标gitclone https://github.com/CrunchyData/postgres-operator.gitcdpostgres-operatorgitcheckout v6.0.1 kubectl apply-kconfig/namespace kubectl apply --server-side-kconfig/defaultcd..请查看发布页面以获取当前标签替换上面的v6.0.1。现在部署源PostgresCluster。该清单创建一个app数据库带有一个常规应用程序用户 (app) 和一个专用的迁移用户 (cnpg)CloudNativePG 将使用后者进行连接。wal_level: logical已包含在内它是在线路径所必需的对离线路径无害。不需要显式的镜像标签PGO v6 会根据postgresVersion自动解析正确的容器镜像。在应用之前需要注意数据库名称。这里的名称app是象征性的请将其替换为你正在迁移的数据库的名称。CloudNativePG 推荐的模式是每个集群一个数据库微服务模型。如果源包含多个数据库请为每个数据库单独运行此过程。在考虑偏离该模式之前请参阅 CloudNativePG 常见问题解答。postgres-cluster-source.yamlapiVersion:postgres-operator.crunchydata.com/v1beta1kind:PostgresClustermetadata:name:crunchyspec:postgresVersion:17patroni:dynamicConfiguration:postgresql:parameters:wal_level:logicalinstances:-name:instance1replicas:1dataVolumeClaimSpec:accessModes:-ReadWriteOnceresources:requests:storage:5Gibackups:pgbackrest:repos:-name:repo1volume:volumeClaimSpec:accessModes:-ReadWriteOnceresources:requests:storage:5Giusers:-name:appdatabases:-app-name:cnpgdatabases:-appoptions:SUPERUSER此处授予cnpg用户SUPERUSER权限是为了使本食谱自包含。在生产环境中仅授予 CloudNativePG 将在源上执行的操作所需的权限。一旦 PGO 协调了集群本食谱其余部分相关的两个值是主端点:crunchy-primary.default.svc(端口 5432)迁移凭据: secretcrunchy-pguser-cnpg键password检查集群状态直到所有实例报告健康kubectl describe postgresclusters.postgres-operator.crunchydata.com crunchy加载示例数据以下 Job 创建一个带有SERIAL主键的orders表并插入 1000 行。这模拟了本地环境中的现有工作负载在实际迁移中数据已经存在此步骤应完全跳过。sample-data-init.yamlapiVersion:batch/v1kind:Jobmetadata:name:sample-data-initspec:template:spec:restartPolicy:Nevercontainers:-name:psqlimage:ghcr.io/cloudnative-pg/postgresql:18-minimal-trixieenv:-name:PGPASSWORDvalueFrom:secretKeyRef:name:crunchy-pguser-cnpgkey:passwordcommand:-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;验证数据存在并检查当前序列值kubectl run psql-check--rm-it--restartNever\--imageghcr.io/cloudnative-pg/postgresql:18-minimal-trixie\--envPGPASSWORD$(kubectl get secret crunchy-pguser-cnpg\-ojsonpath{.data.password}|base64-d)\-- psql-hcrunchy-primary.default.svc-Ucnpg app\-cSELECT count(*) FROM orders;\-cSELECT 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而无需将其嵌入基础操作镜像中。pgauditGUC 参数在spec.postgresql.parameters中设置CloudNativePG 会自动管理这些参数。在导入过程中扩展将被干净地创建。对于实际迁移请先在源上审计完整的扩展列表SELECT extname FROM pg_extension并在开始前确认每个扩展要么在目标镜像中可用要么可以通过目录中的扩展镜像提供。离线迁移这是完全声明性的路径。应用以下集群清单cluster-offline.yamlapiVersion:postgresql.cnpg.io/v1kind:Clustermetadata:name:pg-appspec:instances:1imageCatalogRef:apiGroup:postgresql.cnpg.iokind:ClusterImageCatalogname:postgresql-minimal-trixiemajor:18storage:size:5Gipostgresql:extensions:-name:pgauditparameters:pgaudit.log:all, -miscpgaudit.log_catalog:offpgaudit.log_parameter:onpgaudit.log_relation:onbootstrap:initdb:import:type:microservicedatabases:-appsource:externalCluster:crunchyexternalClusters:-name:crunchyconnectionParameters:host:crunchy-primary.default.svcport:5432user:cnpgdbname:apppassword:name:crunchy-pguser-cnpgkey:passwordCloudNativePG 连接到源在app数据库上运行pg_dump并将完整的模式和数据结构恢复到新的 PostgreSQL 18 集群中。导入仅在引导时运行一次。迁移本身无需配置其他任何东西。等待集群变为就绪状态kubectlwait--forconditionReady cluster/pg-app--timeout600s验证行数和序列值与源匹配# 源kubectl run psql-count--rm-it--restartNever\--imageghcr.io/cloudnative-pg/postgresql:18-minimal-trixie\--envPGPASSWORD$(kubectl get secret crunchy-pguser-cnpg\-ojsonpath{.data.password}|base64-d)\-- psql-hcrunchy-primary.default.svc-Ucnpg app\-cSELECT count(*) FROM orders;\-cSELECT last_value FROM orders_id_seq;# 目标kubectl cnpg psql pg-app -- app\-cSELECT count(*) FROM orders;\-cSELECT last_value FROM orders_id_seq;一旦计数匹配将pg-app扩展到所需的副本数并将你的应用程序重定向到pg-app-rw.default.svc。迁移完成。在线迁移当数据集大到pg_dump窗口不可接受时使用此路径。逻辑复制在生产环境旁边连续运行将切换时间减少到排空复制队列所需的几秒钟。它要求源端使用 PostgreSQL 10 或更高版本这涵盖了所有当前受支持的 PostgreSQL 版本。部署目标集群清单与离线路径相同但有一处更改schemaOnly: true指示 CloudNativePG 在引导时仅导入模式。行数据通过下一步设置的订阅到达。cluster-online.yamlapiVersion:postgresql.cnpg.io/v1kind:Clustermetadata:name:pg-appspec:instances:1imageCatalogRef:apiGroup:postgresql.cnpg.iokind:ClusterImageCatalogname:postgresql-minimal-trixiemajor:18storage:size:5Gipostgresql:extensions:-name:pgauditparameters:pgaudit.log:all, -miscpgaudit.log_catalog:offpgaudit.log_parameter:onpgaudit.log_relation:onbootstrap:initdb:import:type:microserviceschemaOnly:truedatabases:-appsource:externalCluster:crunchyexternalClusters:-name:crunchyconnectionParameters:host:crunchy-primary.default.svcport:5432user:cnpgdbname:apppassword:name:crunchy-pguser-cnpgkey:password等待集群变为就绪状态kubectlwait--forconditionReady cluster/pg-app--timeout300s确认模式已到达但表为空kubectl cnpg psql pg-app -- app-c\dt设置逻辑复制使用cnpg插件在源上创建发布publication。它从pg-app的 spec 中的crunchy外部集群条目派生连接详细信息kubectl cnpg publication create pg-app\--external-cluster crunchy\--publicationmigration\--all-tables在 CloudNativePG 中声明性的Subscription资源处理此操作subscription.yamlapiVersion:postgresql.cnpg.io/v1kind:Subscriptionmetadata:name:pg-app-migrationspec:cluster:name:pg-appdbname:appname:migrationexternalClusterName:crunchypublicationName:migrationsubscriptionReclaimPolicy:deleteCloudNativePG 创建订阅并立即开始初始表同步。确认它已启动kubectl logs\-lcnpg.io/clusterpg-app\--follow\|grep-ilogical replication你应该会看到类似logical replication apply worker for subscription migration has started的消息。现在行数据将从源持续流向pg-app。验证与切换在实际切换之前至少运行一次预演来测量复制延迟并练习切换流程。检查源上的复制槽kubectl run psql-lag--rm-it--restartNever\--imageghcr.io/cloudnative-pg/postgresql:18-minimal-trixie\--envPGPASSWORD$(kubectl get secret crunchy-pguser-cnpg\-ojsonpath{.data.password}|base64-d)\-- psql-hcrunchy-primary.default.svc-Ucnpg 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持续接近零时表明订阅已追赶上。此时行数据已在目标中但逻辑复制不会复制序列。在同步之前检查目标序列值kubectl cnpg psql pg-app -- app\-cSELECT last_value FROM orders_id_seq;无论复制了多少行该值都将是 1未推进序列的默认值。在切换前同步序列kubectl cnpg subscription sync-sequences pg-app\--subscriptionmigration在维护窗口前作为预演运行一次并在重定向流量前立即再运行一次。再次检查目标序列以确认它现在与源匹配kubectl cnpg psql pg-app -- app\-cSELECT last_value FROM orders_id_seq;需要注意的是PostgreSQL 19 预计将引入通过CREATE PUBLICATION和CREATE SUBSCRIPTION对象复制序列状态的原生支持这将使此手动步骤变得不必要。该能力是未来 CloudNativePG 集成的有力候选。当准备上线时停止对源的写入。等待lag_bytes变为零并运行最后一次sync-sequences。将pg-app扩展到所需的副本数并将你的应用程序重定向到pg-app-rw.default.svc。一旦确认应用程序正常运行清理复制对象# 删除订阅资源# (subscriptionReclaimPolicy: delete 会删除底层的 SQL 订阅)kubectl delete subscription pg-app-migration# 删除源上的发布kubectl cnpg publication drop pg-app\--external-cluster crunchy\--publicationmigration清理当应用程序在pg-app上稳定运行后停用源集群kubectl delete postgrescluster crunchy一旦所有数据库都迁移完毕就可以删除 PGO operator 及其命名空间。bootstrap.initdb.import块和crunchy的externalClusters条目仅在初始引导期间被参考对运行中的集群没有影响。迁移完成后你可以从集群清单中删除这两个部分并应用更改。CloudNativePG 将进行协调不会造成任何中断。要拆除本食谱中使用的本地 Kind 环境kind delete cluster--namecnpg-migration镜像体积与安全态势迁移到 CloudNativePG 也会改变你拉取和操作的镜像栈。下表量化了这种变化。拉取大小是从 OCI 清单层数据测量的压缩后大小漏洞计数来自docker scout quickview。压缩后的拉取大小镜像角色压缩后拉取大小registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi9-17.9-2610PGO 源集群~346 MBghcr.io/cloudnative-pg/postgresql:18-minimal-trixieCNPG 目标本食谱~87 MBpgaudit扩展镜像pgauditOCI 镜像卷~44 KBghcr.io/cloudnative-pg/plugin-barman-cloud:v0.12.0CNPG 备份插件~40 MBCNPG 最小 pgaudit Barman Cloud 插件目标总计~127 MBCVE 暴露情况 (docker scout quickview)镜像包数量严重高危中危低危crunchy-postgres:ubi9-17.9-261062521561053201postgresql:18-minimal-trixie14004639CNPGminimal-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 BartoliniEDB 副总裁、Kubernetes 首席架构师 | PostgreSQL 贡献者 | DoK 大使 | CloudNativePG 维护者 | 前 2ndQuadrant联合创始人