CloudNativePG PostgreSQL 大版本升级完全指南:从 Minor 滚动更新到离线 In-Place 就地升级
【免费下载链接】cloudnative-pgThe most popular Kubernetes Operator for PostgreSQL.项目地址: https://gitcode.com/GitHub_Trending/cl/cloudnative-pg
本文以 CloudNativePG 操作符为背景,系统讲解 PostgreSQL 版本升级的两大路径:小版本(minor)升级的滚动更新机制,以及大版本(major)升级的三种策略,并重点深入剖析由操作符自动编排的离线就地(offline in-place)升级——包括pg_upgrade --link的底层执行流程、Job 编排、扩展(extensions)处理、update_extensions.sql脚本、备份与 WAL 归档的跨版本边界问题,以及完整的可运行示例。读完本文,你将掌握在 Kubernetes 上安全、可回滚地完成 PostgreSQL 大版本升级的完整实战方案。
升级分类总览
PostgreSQL 的升级工作可分为两大类,二者在兼容性、停机方式和操作复杂度上完全不同:
- 小版本升级(Minor version upgrades):例如从 18.0 升级到 18.1,属于同一大版本内的补丁更新,通常通过滚动更新即可完成。
- 大版本升级(Major version upgrades):例如从 16.x 升级到 18.0,会改变数据内部存储格式,需要结构化的升级流程。
小版本升级:滚动更新
PostgreSQL 版本号遵循major.minor格式。以版本 18.1 为例,18是大版本号,1是小版本号。同一大版本内的所有小版本之间完全兼容——它们只包含缺陷修复(bug fixes)和安全更新,不会改变内部存储格式。
小版本升级操作步骤
在 CloudNativePG 中升级小版本非常简单:只需更新集群定义中的 PostgreSQL 容器镜像引用即可,既可以直接修改.spec.imageName,也可以通过 镜像目录(image catalog) 管理镜像版本变化。
操作符检测到镜像变化后,会触发集群的滚动更新:
- 一个接一个地替换实例,从副本(replicas)开始;
- 所有副本更新完成后,对主实例(primary)执行 switchover 或重启,完成整个流程。
整个过程中集群始终保持可用,应用几乎无感知。这也是小版本升级与大版本升级在体验上的本质区别。
大版本升级:三种策略与权衡
PostgreSQL 大版本发布会改变数据内部存储格式,因此必须采用结构化的升级流程。CloudNativePG 支持三种大版本升级方法:
| 方法 | 部署模式 | 在线/离线 | 核心工具 |
|---|---|---|---|
| 逻辑导出/导入(Logical dump/restore) | Blue/green | 离线 | pg_dump/pg_restore,详见数据库导入 |
| 原生逻辑复制(Native logical replication) | Blue/green | 在线 | 逻辑复制槽,详见逻辑复制在线迁移与大版本升级示例 |
物理升级(Physical withpg_upgrade) | In-place | 离线 | pg_upgrade --link,即本文后续讲解的"离线就地大版本升级" |
三种方法在停机时长、复杂度、数据量处理能力上各有取舍:
- 逻辑导出/导入:灵活但耗时与数据量成正比,离线进行;
- 原生逻辑复制:可在旧库运行期间完成大部分数据迁移,实现近乎零停机的在线升级,但需要预先搭建逻辑复制拓扑;
- 物理
pg_upgrade:速度最快、最适合大数据量,但集群必须整体停机,采用就地替换磁盘数据的方式。
最佳方案取决于你的升级策略与运维约束。
重要提示:强烈建议在生产环境升级前,先在受控的测试环境中完整演练上述所有候选方法。
离线就地大版本升级(Offline In-Place)
当集群以声明式方式请求一个PostgreSQL 大版本号更高的 operand 容器镜像时,CloudNativePG 会自动执行离线就地大版本升级。
触发升级的两种方式
你可以通过以下任一种方式触发就地大版本升级:
- 通过
.spec.imageName更新镜像标签中的大版本号(如16→18); - 使用镜像目录(image catalog) 来管理版本变更。
关于受支持镜像标签的详细要求,请参阅镜像标签要求。
前置约束与注意事项
1. 操作系统发行版必须一致
大版本升级只支持基于同一操作系统发行版的镜像。例如,如果旧版本使用的是
bullseye镜像,则不能升级到bookworm镜像。
2. PostgreSQL 17.0–17.5 的已知缺陷
PostgreSQL 17.0 至 17.5 存在一个缺陷:当
max_slot_wal_keep_size参数被设置为除-1之外的任何值时,大版本升级会失败,并报出与复制槽(replication slot)配置相关的错误。该问题已在 PostgreSQL 17.6 及 18 或更高版本中修复。如果你正在使用 PostgreSQL 17.0–17.5,请先升级到至少 PostgreSQL 17.6 再执行大版本升级,或者临时将max_slot_wal_keep_size设置为-1。
这一约束在源码中也有对应处理:internal/cmd/manager/instance/upgrade/execute/cmd.go中针对 PostgreSQL 17.6 之前的版本专门绕过了该参数,确保pg_upgrade能正常执行。
3. 扩展(extensions)兼容性由用户负责
CloudNativePG 不负责 PostgreSQL 扩展的兼容性。你必须确保源镜像中的扩展与目标镜像兼容,且升级路径受支持,并提前充分测试升级过程。操作符的数据库扩展管理功能可以帮助你以声明式方式管理扩展升级。
4. 就地升级具有固有风险,务必先备份
就地升级是一种特殊操作,存在固有风险。强烈建议在发起升级前对集群进行完整备份。关于
pg_upgrade的详细官方指南,可参考 PostgreSQL 官方文档。
升级流程(Upgrade Process)
CloudNativePG 的就地大版本升级由控制器驱动。在源码层面,该逻辑位于 pkg/reconciler/majorupgrade/reconciler.go,并由 internal/controller/cluster_controller.go 在reconcileResources中、且早于 running-jobs 守护检查调用(因为升级 Job 的生命周期由它全权管理)。
整体流程如下:
- 关闭所有集群 Pod:确保数据一致性。对应源码中
deleteAllPodsInMajorUpgradePreparation,会删除全部实例 Pod 与残留 Job,等待删除完成后继续。 - 记录旧版本信息:将之前的 PostgreSQL 版本和镜像记录到集群状态
.status.pgDataImageInfo中。源码中同时使用乐观锁将集群 phase 置为PhaseMajorUpgrade,并将目标镜像信息写入Status.TargetPGDataImageInfo。 - 发起新的升级 Job,该 Job 负责:
- 校验:确认镜像中的二进制文件与数据文件与"大版本升级请求"相符(否则拒绝执行);
- 准备新目录:为新的
PGDATA、WAL 文件(如适用)和表空间创建新目录; - 执行升级:使用
pg_upgrade --link完成升级; - 替换目录:升级成功后,用升级后的新目录替换原目录。
升级 Job 的内部结构
从 pkg/reconciler/majorupgrade/job.go 的源码可以看出,升级 Job 的命名与结构相当讲究:
- Job 名称:以主实例 Pod 名称 +
-major-upgrade后缀命名,并通过utils.SetInstanceRole(..., primary)标记为主角色,直接挂载主实例的持久卷组。 - init 容器
prepare:使用旧版本镜像(cluster.Status.PGDataImageInfo.Image)运行/controller/manager instance upgrade prepare /controller/old,把旧版本二进制目录记录下来,供后续执行阶段使用——这是pg_upgrade同时需要新旧两套二进制的前提。 - 主容器:运行
/controller/manager instance upgrade execute /controller/old/bindir.txt,即internal/cmd/manager/instance/upgrade/execute子命令,实际调用pg_upgrade执行升级。 BackoffLimit = 0:失败的pg_upgrade重试也不会成功,因此 Job 失败即终止,不自动重试。
升级期间的停机声明
警告:升级期间,整个 PostgreSQL 集群(包括副本)对应用不可用。在继续之前,请确保你的系统能够容忍这段时间的停机。
升级后的处理(Post-Upgrade Actions)
升级成功时的动作
如果升级成功,CloudNativePG 会:
- 销毁副本的 PVC(如可用):源码
majorVersionUpgradeHandleCompletion会遍历所有 PVC,删除除主实例之外的所有 PVC; - 按需扩容副本:随后按 spec 中声明的
instances数量从升级后的主实例重新克隆副本。
同时,源码还会在升级完成后重置Status.TimelineID为 1(与pg_upgrade的行为一致),并清理升级 Job。
警告:重新克隆副本可能非常耗时,尤其是对于超大数据库。请预留足够的时间。升级完成后,尽快做一次新的基础备份(base backup)。升级前的备份与 WAL 文件不能用于跨大版本边界的基于时间点恢复(PITR),详见下文备份与 WAL 归档考虑。
警告:
pg_upgrade不会迁移优化器统计信息。升级后建议在你的数据库上运行ANALYZE以更新统计信息。
升级失败时的回滚
如果升级失败,只需将集群配置中的镜像回退到之前的大版本。操作符会检测到回滚并自动删除失败的升级 Job,让集群以原版本重新启动。
重要提示:这一流程保护现有数据库免受数据丢失——因为升级过程中不修改任何数据。如果升级失败,通常无需从备份做完整恢复即可回滚。请密切监控整个过程,并在需要时采取纠正措施。
源码中对应handleRollbackIfNeeded:只要用户回退的镜像大版本不再高于.status.pgDataImageInfo.majorVersion(等于也算回滚,即使升级 Job 正在运行也会被终止),操作符就会删除升级 Job,并重置Status.Image为旧镜像、清除TargetPGDataImageInfo,同时发出MajorUpgradeRollback事件。
大版本升级期间的扩展处理(Extensions)
当集群声明了镜像卷扩展(image-volume extensions)时,升级 Job 会同时挂载源版本与目标版本的扩展镜像:
- 源版本扩展:旧服务器启动时需要其已有的共享库,因此必须保留;
- 目标版本扩展:为新的 PostgreSQL 大版本构建,
pg_upgrade需要它们才能正确执行到新大版本的升级。
具体挂载布局如下:
- 源版本扩展挂载在
/extensions/<name>(即稳态路径),这样旧PGDATA中的dynamic_library_path和extension_control_path两个 GUC 保持原值不变。这确保了大版本升级失败时,回退到旧大版本可以无缝进行。 - 目标版本扩展仅在升级 Job 期间临时挂载在
/new-extensions/<name>,并为新大版本的PGDATA相应配置dynamic_library_path和extension_control_path。
同时,LD_LIBRARY_PATH和PATH会被扩展为同时包含两套路径,使得任一版本的二进制与共享对象都可达。通过extensions[].env声明的环境变量遵循优先级与冲突解决规则。
注意:目标版本条目在源版本条目之后应用。当同一个变量在两者中都定义时,目标版本的值优先生效。
从源码看,扩展的解析发生在创建 Job 之前(resolveExtensionsForMajorVersion):若集群使用imageCatalogRef,则从镜像目录解析新大版本的扩展集合;否则要求扩展在集群 spec 中完整声明。解析失败时集群进入PhaseImageCatalogError阶段,且不会触碰任何 Pod。此外,升级完成后使用 Job 启动时已解析并持久化在TargetPGDataImageInfo中的扩展集合(而非重新查询目录),以保证与pg_upgrade实际构建的新PGDATA一致。
当pg_upgrade生成update_extensions.sql时
当目标集群包含某些扩展,且其default_version比pg_upgrade遗留安装的版本更新时,pg_upgrade会在PGDATA内生成一个update_extensions.sql脚本。在该脚本执行之前,pg_catalog中的 SQL 级扩展元数据与运行中服务器实际加载的共享库是不一致的。
该脚本位于主实例 Pod 的PGDATA内。你可以使用数据库超级用户(postgres)在所有数据库中执行它来更新这些扩展:
PRIMARY=$(kubectl get cluster <name> -o jsonpath='{.status.currentPrimary}') SCRIPT=/var/lib/postgresql/data/pgdata/update_extensions.sql kubectl exec -i "$PRIMARY" -- psql -f "$SCRIPT"注意:上述脚本路径假设使用 CloudNativePG operand 镜像的默认
PGDATA位置。当pg_upgrade生成该脚本时,操作符也会在日志中记录解析后的实际路径;如果您的 operand 镜像使用了不同的PGDATA,请查看操作符日志获取准确路径。
备份与 WAL 归档考虑(Backup and WAL Archive Considerations)
执行大版本升级时,pg_upgrade会创建一个带有新 System ID的全新数据库系统,并将 PostgreSQL 时间线重置为 1。这对备份和 WAL 归档有直接影响:
- 时间线文件冲突:新的时间线 1 文件可能会覆盖原集群的时间线 1 文件;
- 混合版本归档:不加干预的话,归档中将同时包含两个 PostgreSQL 版本的 WAL 文件和备份。
警告:跨大版本边界不支持基于时间点恢复(PITR)。你不能用升级前的备份恢复到升级后的某个时间点。升级后应尽快做新的基础备份,为新大版本建立恢复基线。
备份系统如何处理大版本升级取决于插件实现:部分插件会自动管理升级期间的归档分离,另一些则要求手动配置,为大版本使用不同的归档路径。请查阅你的备份插件文档了解其在升级期间的具体行为。
示例:使用 Barman Cloud 插件手动分离归档路径
Barman Cloud 插件在升级期间不会自动分离归档。为了保留升级前的备份并保持归档干净,需要在触发升级时同时修改serverName参数。
升级前(PostgreSQL 16):
spec: imageName: ghcr.io/cloudnative-pg/postgresql:16-minimal-trixie plugins: - name: plugin-barman-cloud enabled: true parameters: destinationPath: s3://my-bucket/ serverName: cluster-example-pg16触发升级时,同时修改imageName与serverName:
spec: imageName: ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie plugins: - name: plugin-barman-cloud enabled: true parameters: destinationPath: s3://my-bucket/ serverName: cluster-example-pg18采用该配置后,旧的cluster-example-pg16归档完整保留,可用于升级前恢复;升级后的集群则写入cluster-example-pg18,互不干扰。
注意:已弃用的内置
barmanObjectStore实现同样需要在升级期间手动修改serverName来分离归档。
完整示例:执行一次大版本升级
以一个运行版本 16 的 PostgreSQL 集群为例:
apiVersion: postgresql.cnpg.io/v1 kind: Cluster metadata: name: cluster-example spec: imageName: ghcr.io/cloudnative-pg/postgresql:16-minimal-trixie instances: 3 storage: size: 1Gi第 1 步:确认当前版本
使用以下命令查看当前 PostgreSQL 版本:
kubectl cnpg psql cluster-example -- -qAt -c 'SELECT version()'输出类似:
PostgreSQL 16.x ...第 2 步:修改imageName触发升级
将大版本标签从16改为18:
apiVersion: postgresql.cnpg.io/v1 kind: Cluster metadata: name: cluster-example spec: imageName: ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie instances: 3 storage: size: 1Gi第 3 步:观察升级过程
提交修改后,操作符会自动执行以下步骤:
- 集群停机(Cluster shutdown)——终止所有集群 Pod,确保升级时数据一致;
- 升级 Job 执行(Upgrade job execution)——创建名为主实例 Pod +
-major-upgrade后缀的 Job,在主实例的持久卷组上运行pg_upgrade; - 升级后步骤(Post-upgrade steps):
- 删除副本(
cluster-example-2与cluster-example-3)的 PVC 组; - 重启主实例 Pod;
- 从升级后的主实例重新克隆两个新副本(
cluster-example-2与cluster-example-3)。
- 删除副本(
提示:与源码对应,pkg/reconciler/majorupgrade/reconciler_test.go 与 job_test.go 覆盖了 Job 创建、副本 PVC 删除、回滚清理等关键路径的单元测试,可作为理解流程行为的补充参考。
第 4 步:验证新版本
升级完成后,再次运行同一命令:
kubectl cnpg psql cluster-example -- -qAt -c 'SELECT version()'输出应类似:
PostgreSQL 18.x ...第 5 步:更新统计信息
由于pg_upgrade不迁移优化器统计信息,请在app数据库上运行ANALYZE:
kubectl cnpg psql cluster-example -- app -c 'ANALYZE'同时,如前所述,尽快执行一次新的基础备份,为 18 大版本建立 PITR 恢复基线。
小结
CloudNativePG 将 PostgreSQL 升级提炼为一条清晰的分层路径:小版本升级通过镜像更新 + 滚动更新零停机完成;大版本升级则根据在线/离线需求,在逻辑导入导出、逻辑复制和物理pg_upgrade之间选择。其中离线就地升级由操作符全自动编排——从 Pod 停机、旧版本信息记录(.status.pgDataImageInfo)、pg_upgrade --linkJob 执行、副本 PVC 重建,到失败时的自动回滚,全程声明式驱动且对数据零修改。掌握这一机制,配合扩展兼容性检查、update_extensions.sql执行、归档路径分离和升级后新基线备份,即可在生产环境中可靠地推进 PostgreSQL 大版本演进。
延伸阅读
- 滚动更新
- 镜像目录(Image Catalog)
- 容器镜像与镜像标签要求
- 数据库导入(逻辑 dump/restore)
- 逻辑复制与大版本在线升级
- 数据库扩展管理
- 镜像卷扩展(Image Volume Extensions)
【免费下载链接】cloudnative-pgThe most popular Kubernetes Operator for PostgreSQL.项目地址: https://gitcode.com/GitHub_Trending/cl/cloudnative-pg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考