Rook 集群升级机制深度解析:从升级控制器设计到自动化滚动升级实践
【免费下载链接】rookStorage Orchestration for Kubernetes项目地址: https://gitcode.com/gh_mirrors/roo/rook
Rook 作为 Kubernetes 上的存储编排框架,将升级能力视为存储运维自动化的核心环节。本文以 Rook 仓库中的集群升级设计文档为主线,完整梳理其"零停机滚动升级、健康校验门禁、失败回滚、迁移与同步"四大设计目标,并结合仓库中升级相关的源码实现与当前实操指南,说明这一设计如何落地为今天 operator 中"改镜像即触发、逐组件校验推进"的自动化升级流程。读完本文,你将掌握 Rook 升级的完整设计脉络、每类 Ceph 组件的升级顺序与健康判据,以及如何在真实集群中执行、监控和验证一次升级。
一、设计背景与目标:为什么升级要"自动且可靠"
任何长期运行的存储集群都无法避免软件迭代。当 Rook 发布新版本后,已部署集群需要平滑升级到新版本,而"保持部署软件最新"是保障集群健康的重要运维手段。设计文档在开头即点明主旨:升级过程应当既自动又可靠,让存储管理员从繁琐的手工操作中解放出来。
为此,设计文档提出了四个长期目标(该文档写作时以 v0.6 为时间参考,但这些目标是长期愿景):
- 自动(Automatic):当新版本发布且管理员决定开始升级后,运行中的集群应能在无进一步人工干预的情况下完成全部组件的版本更新。
- 零停机(No downtime):升级窗口内集群功能零中断。升级必须以滚动(rolling)方式执行,避免所有组件同时更新;整个过程中集群应始终保持健康状态。
- 迁移(Migrations):破坏性变更、schema 变更与数据格式变更,应通过自动化的迁移流程处理。
- 回滚(Rollback):若升级失败,Rook 应回滚到上一版本并恢复集群健康。
从今天的仓库实现看,这一设计蓝图大部分已落地:当前
rook-ceph集群的升级完全由 operator 自动驱动,仅在涉及权限、CRD 或不兼容问题时需要管理员手动介入(见 rook-upgrade.md)。
二、升级的承担者:运行在 operator 进程内的升级控制器
设计文档对"谁来做升级"给出了明确的架构决策:
执行与编排升级的职责由一个**升级控制器(upgrade controller)**承担,它作为 Rook operator 的一部分,运行在 operator 的同一个 Pod、同一个进程中(类似于 Rook volume provisioner 的运行方式)。
升级控制器负责两件事:
- 按既定顺序逐组件执行更新(更新镜像 → 终止旧 Pod → 等待新 Pod 就绪);
- 在升级过程中持续监控集群与组件健康,采取纠正措施恢复健康,必要时执行整体回滚。
从源码结构看,这一设计体现在pkg/operator/ceph/cluster的多个模块中:集群级版本检测与升级判定位于 pkg/operator/ceph/cluster/version.go,Ceph 镜像版本探测与"当前版本 vs 期望版本"对比逻辑位于 pkg/operator/ceph/controller/version.go,而 OSD 的批量滚动更新队列实现在 pkg/operator/ceph/cluster/osd/update.go。升级逻辑确实与 operator 主流程同进程运行,通过 reconcile 循环驱动。
升级的前置条件
升级控制器开始升级前必须满足两个条件:
- 集群健康:集群必须处于健康状态(依据下文"升级健康验证"定义的检查项)。集群不健康时,升级控制器不得开始升级。
- 元数据持久化:Pod 的元数据必须持久化。如果配置文件等元数据只存在于 Pod 的临时 emptyDir 中(即未设置
dataDirHostPath),升级控制器将拒绝执行升级。
该"健康门禁"在当前实现中依然严格保留:pkg/operator/ceph/cluster/version.go的validateCephVersion中,当检测到镜像版本与运行版本不一致时会调用daemonclient.IsCephHealthy,若集群不健康且未设置SkipUpgradeChecks,则直接报错:
ceph status in namespace %s is not healthy, refusing to upgrade. Either fix the health issue or force an update by setting skipUpgradeChecks to true in the cluster CR这也与官方文档 ceph-upgrade.md 的警告一致:"当请求更新时,operator 会检查 Ceph 状态,若处于HEALTH_ERR状态将拒绝继续升级"。
三、通用升级顺序:从系统命名空间到集群组件
设计文档将升级划分为两个层面:先升级 Rook 系统命名空间(operator 与 agents),再升级各个 Rook 集群。
3.1 升级 Rook 系统命名空间
Rook 系统命名空间承载环境中所有 Rook 集群的唯一控制平面,必须先于任何单个集群升级。
Operator(第一个被升级的组件):operator Pod 是升级控制器的宿主,若存在新的升级逻辑或需要执行迁移,新版本的升级控制器才知道如何操作,因此它必须最先更新。这一步由管理员手工执行,以确保管理员已准备好开始升级:
kubectl set image deployment/rook-operator rook-operator=rook/rook:v0.6.1该命令更新 operator Pod 模板的 image 字段,随后管理 operator Pod 的 Deployment 会终止旧 Pod 并启动运行新版本的新 Pod。对照当前实操指南 rook-upgrade.md,这一步骤演变为:
kubectl -n $ROOK_OPERATOR_NAMESPACE set image deploy/rook-ceph-operator rook-ceph-operator=rook/ceph:v1.20.0Agents:Rook agents 同样运行在系统命名空间中。当 operator Pod 以比 agents 更新的版本启动后,它会通过 Kubernetes API 更新 agent Pod 模板的 image 字段,然后以滚动方式逐个终止 agent Pod,由管理它们的 DaemonSet 用新版本 Pod 替换。当 operator 与所有 agent Pod 都以新版本健康运行时,管理员就可以开始各 Rook 集群的升级。
3.2 升级 Rook 集群(Cluster CRD)
设计文档给出了集群层面的完整升级序列:
- operator 启动时遍历集群:升级后的 operator 在启动时遍历每个 Cluster CRD 实例并核对期望状态。若系统命名空间升级尚未完成,operator 会推迟该集群的升级——operator 绝不允许集群版本高于自身版本。
- 开始 reconcile:升级控制器执行 reconciliation,使集群实际版本与期望版本(即 operator Pod 的容器版本)达成一致。每步开始/结束时,Cluster CRD 的 status 字段会更新以指示升级进度(当前步骤),这有助于升级被打断后续传;同时每个步骤必须是幂等的,重复执行不会产生意外副作用。
- Mons(监控器):monitor Pod 以滚动方式升级。对每个monitor:
- 更新 Pod 模板的 image 字段,终止 Pod,由管理它的 ReplicaSet 以新版本 Pod 替换;
- 控制器验证新 Pod 处于新版本、
Running状态,且 monitor 重新进入in quorum、Ceph 状态为OK; - 在进入下一个 monitor 前,整体验证集群健康。
- Ceph Managers:通过更新 Pod 模板 image 字段升级 mgr Pod,Deployment 会终止旧 Pod 并启动新版本 Pod。控制器验证新 Pod 为新版本、
Running,且 mgr 实例在 Ceph 状态输出中显示为Active。 - OSDs:monitor 之后以滚动方式升级 OSD Pod。对每个OSD:
- 更新 Pod 模板 image 字段到新版本;
- OSD 的生命周期管理既可以由一个 DaemonSet 整体管理,也可以由每个 OSD 一个 ReplicaSet 分别管理,无论哪种方式,每个 OSD Pod 都会被终止并由其管理控制器以新版本重新拉起;
- 控制器验证每个 OSD 运行新版本并恢复
UP与IN状态,且所有 PG 恢复active+clean后才继续。
- 可选组件(RGW / MDS):若用户安装了对象存储(RGW)或共享文件系统(MDS)等可选组件,它们同样会被升级。两者均由 Deployment 管理,升级控制器更新 Pod 模板 image 后由 Deployment 完成新旧替换,并在进入下一实例前验证集群健康与对象/文件功能。
从源码印证:当前实现中 OSD 的更新由
pkg/operator/ceph/cluster/osd/update.go中的updateExistingOSDs完成。它会先用updateQueue收集所有需要更新的 OSD ID,然后通过cephclient.OSDOkToStop检查某个 OSD 是否可以安全停止,只有通过检查的 OSD 才会被批量更新(UpdateMultipleDeploymentsAndWait),并在每个批次后等待 Deployment 就绪——这正是设计文档"逐步验证再前进"思想的工程化落地。
四、升级健康验证:以"金丝雀"方式逐组件推进
设计文档指出,升级用户指南中的手工健康检查步骤会被升级控制器以自动化方式复用,确保在继续升级前集群是健康的。这种"升级一个组件 → 验证健康与稳定 → 再升级下一个组件"的方式,本质上是一种金丝雀部署(canary deployment)。
升级控制器应执行的标准健康检查摘要:
- 所有 Pod 处于
Running状态,重启次数尽量少;不得出现 Pod 崩溃循环回退(crash loop backoff); - 总体状态:集群整体状态为
OK,无警告或错误状态消息; - Monitors:所有 monitor 处于
in quorum且各自状态为OK; - OSDs:所有 OSD 为
UP且IN; - MGRs:所有 Ceph manager 处于
Active状态; - Placement groups:所有 PG 处于
active+clean状态。
这些检查与今天的健康验证指南 health-verification.md 一一对应。实操中可用ceph status验证:
TOOLS_POD=$(kubectl -n $ROOK_CLUSTER_NAMESPACE get pod -l "app=rook-ceph-tools" -o jsonpath='{.items[*].metadata.name}') kubectl -n $ROOK_CLUSTER_NAMESPACE exec -it $TOOLS_POD -- ceph status健康输出应包含:health: HEALTH_OK、mon: 3 daemons, quorum b,c,a、mgr: a(active)、osd: 6 osds: 6 up, 6 in、pgs: 900 active+clean。若 MDS 存在则全部active,若 RGW 存在则所有守护进程active。
关于"不健康集群升级":当前 Rook 提供了两个强制开关——
skipUpgradeChecks: true与continueUpgradeAfterChecksEvenIfNotHealthy: true(详见 ceph-cluster-crd.md)。前者跳过健康检查直接升级,后者在检查不通过时仍继续推进,二者都带有潜在风险提示。
Pod 就绪/存活探针
为补充升级控制器的健康判定能力,同时利用 Kubernetes 内建的升级能力,Rook Pod 应尽可能实现 liveness 与 readiness 探针。对实现了探针的 Pod,升级控制器可将其作为"是否健康、可否继续"的又一数据点。
五、回滚:升级失败的兜底策略
若升级过程中升级控制器观察到集群进入无法自行恢复的不健康状态,就需要将组件回滚到上一稳定版本。滚动/金丝雀式升级使这一点成为可能:只需将 Pod 模板的 image 字段改回上一版本,再逐个终止 Pod,让管理控制器用旧版本 Pod 替换即可。
设计文档同时坦诚指出:回滚到旧版本后,集群健康与稳定大概率能够恢复,但并非所有升级期间出现的集群不稳定都能靠简单回滚解决;需要更多真实集群升级经验来同时改进升级可靠性与回滚有效性。当前实现在升级失败重试方面同样谨慎——例如 OSD 更新失败后不会立即重试,而是等待下一次 reconcile(pkg/operator/ceph/cluster/osd/update.go中注释说明:若是 k8s/etcd 瞬时问题,下次 reconcile 应能成功;若是其他问题,则会持续报错)。
六、升级工具:让升级进度可见
设计文档提出应实现帮助用户监控与验证升级进度的状态命令,例如:
rook versions:返回集群中所有 Rook 组件的版本,让用户一眼看出哪些组件已完成升级(类似ceph versions命令)。rook status --upgrade:从升级控制器获取当前升级已完成步骤与状态的摘要。
在今天的实践中,"版本可视化"通过 Kubernetes 标签机制实现:rook-version标签标记在 Ceph 资源上,使用下面的命令即可观察各 Deployment 的期望副本数/已更新副本数/就绪副本数及 Rook 版本:
watch --exec kubectl -n $ROOK_CLUSTER_NAMESPACE get deployments -l rook_cluster=$ROOK_CLUSTER_NAMESPACE -o jsonpath='{range .items[*]}{.metadata.name}{" \treq/upd/avl: "}{.spec.replicas}{"/"}{.status.updatedReplicas}{"/"}{.status.readyReplicas}{" \trook-version="}{.metadata.labels.rook-version}{"\n"}{end}'判断升级是否全部完成的最简单方式,是检查整个集群是否只剩一个rook-version:
kubectl -n $ROOK_CLUSTER_NAMESPACE get deployment -l rook_cluster=$ROOK_CLUSTER_NAMESPACE -o jsonpath='{range .items[*]}{"rook-version="}{.metadata.labels.rook-version}{"\n"}{end}' | sort | uniq同理,Ceph 数据层的升级进度通过ceph-version标签观察(见 ceph-upgrade.md)。从源码看,operator 也会在升级完成后通过daemonclient.GetAllCephDaemonVersions统计运行中的 Ceph 版本数量,只有全部统一时才记录"成功升级到版本 X"(见 pkg/operator/ceph/cluster/version.go 的printOverallCephVersion)。
七、迁移与破坏性变更:慎之又慎
当出现破坏性变更或数据格式变更时,升级控制器有能力在升级过程中自动执行必要的迁移步骤。但设计文档强调:迁移虽可行却绝不受欢迎,因为迁移需要编写和测试额外升级逻辑,还会带来新的失败路径。Rook 项目应提升对引入破坏性变更的纪律性,对任何需要迁移的新代码保持极度谨慎。
当前版本的做法与之呼应:官方升级文档会为每个大版本单独列出 breaking changes(如 v1.20 起 CSI 驱动改由 ceph-csi-operator 管理、最小 Kubernetes 版本升至 v1.32),并配套给出从 ConfigMap 设置到 csi-operator 资源的迁移指引,参见 rook-upgrade.md 的 "Breaking changes in v1.20" 一节。
八、利用 Kubernetes 内建滚动更新能力
Kubernetes 为滚动更新提供了内建支持(如kubectl rolling-update)。Rook 可以借助该能力处理拥有多个无状态 Pod 的 ReplicationController,例如 RGW。但对 monitor 这类需要谨慎观察(确保健康维持、quorum 重建)的关键组件,内建支持并不合适。
即使升级控制器对某些无状态组件使用内建滚动更新,在推进到下一组组件前,它仍应通过全部集群健康检查——这一约束在当前 OSD 更新实现中体现得尤为明显:updateExistingOSDs在更新前会先检查 PG 健康(IsClusterClean),不干净则推迟更新。
九、同步与锁定:升级期间禁止其他变更
升级过程必须被精确编排以保证可靠与成功,因此需要一定的锁定/同步机制,确保升级进行期间集群不能被其他变更干扰。例如升级控制器正在滚动发布新版本时,不应允许通过修改 Cluster CRD 做其他变更(如从集群中移除节点)。实现方式可以是 operator 在升级期间停止对所有 CRD 的 watch,或直接从 CRD 事件处理中立即返回。
Ceph 侧也有配合机制:可在集群中设置noout标志,表示 OSD 因升级被暂时下线时不应被标记为 out,从而避免触发不必要的数据恢复操作(该建议源自 Ceph Luminous 升级指南)。这正好呼应设计文档"让升级以受控方式进行"的意图。
十、可扩展性:从逐个升级到分批推进
- 小集群:一次升级一个 Pod 足够。
- 大型集群(100+ 节点):逐个升级会导致升级窗口过长。升级控制器应能批量升级多个 Pod 以按时完成升级,但不能跨组件类型批处理(如同时升级 mons 与 OSDs),这些"验证整体健康"的边界必须保留;monitor 也不建议批量,因为整个集群通常只有少数 monitor 服务,不推荐同时宕掉多个 monitor。
批量的正确姿势是逐步增大批大小:比如按金丝雀方式先升级单个 OSD 并验证健康,再同时更新两个、四个……直到合理上限,避免过多 Pod 同时下线影响集群健康。
从源码印证:当前 OSD 更新逻辑中,
defaultOSDMaxUpdatesInParallel默认为20(见 pkg/operator/ceph/cluster/osd/update.go),且每批次通过OSDOkToStop判定可以安全下线的 OSD 数量;若集群无法进行 ok-to-stop 检查(如少于 3 个 OSD 的 CI 环境),则退化为一次只处理一个 OSD。这正是设计文档"canary 批量 + 健康验证"思想的可配置实现。
十一、升级失败排查:如何取回已消失 Pod 的调试信息
升级本质上是"终止旧 Pod、启动新 Pod",因此需要策略调查那些可能已不存在的 Pod。设计文档给出了几种从已停止运行 Pod 获取调试产物的技术:
kubectl logs --previous ${POD_NAME} ${CONTAINER_NAME}:获取 Pod 上一实例的日志(例如已崩溃但尚未终止的 Pod);kubectl get pods --show-all=true:列出所有 Pod,包括为替换为更新版本而被终止的旧版本 Pod;- Rook operator 日志(升级控制器输出的宿主)应详尽记录:
- 升级期间执行的动作序列;
- 修改过的控制器(DaemonSet / ReplicaSet / Deployment)与被终止的 Pod 名称;
- 遇到的所有健康检查状态与输出。
十二、结语:设计如何演化为今天的自动化升级
设计文档的结尾指出,手工流程已证明 Rook 可升级,完全自动化的升级能力将按迭代方式实现,并从预生产环境的实战中积累经验;优先实现"happy path"(按序列自动更新所有组件,健康检查失败且集群无法恢复时立即停止),回滚、迁移与破坏性变更处理则在后续里程碑中补齐。
对照仓库现状,这一演进路线已基本走完:
- 手工部分:operator 与集群命名空间的前置操作(更新 common 资源、CRDs、operator 镜像)仍需管理员执行,路径与命令详见 rook-upgrade.md;
- 自动化部分:Ceph 数据层的升级(mons、mgrs、OSDs、MDS、RGW)完全由 operator 驱动——修改 CephCluster CR 的
spec.cephVersion.image即触发,逐个守护进程检查、滚动替换、健康验证,详见 ceph-upgrade.md:
ROOK_CLUSTER_NAMESPACE=rook-ceph NEW_CEPH_IMAGE='quay.io/ceph/ceph:v20.2.4-20260818' kubectl -n $ROOK_CLUSTER_NAMESPACE patch CephCluster $ROOK_CLUSTER_NAMESPACE --type=merge -p "{\"spec\": {\"cephVersion\": {\"image\": \"$NEW_CEPH_IMAGE\"}}}"- 版本判定:
diffImageSpecAndClusterRunningVersion(pkg/operator/ceph/cluster/version.go)对比期望镜像版本与集群运行版本,一致则无事可做,期望高于运行则触发升级,期望低于运行则明确告警"downgrading is not supported"(不支持降级)——这一实现细节正是对设计文档"回滚"边界的严谨补充。
设计文档中面向社区的三个开放问题(除回滚外还有哪些恢复健康的步骤、回滚失败怎么办、Pod 能实现哪些有意义的探针)至今仍是升级可靠性研究的持续话题。对于存储管理员而言,牢记本文的核心结论即可:升级前务必确保集群健康、元数据持久化;升级中相信 operator 的滚动编排与健康门禁;升级后通过rook-version/ceph-version标签与ceph status双重确认版本统一与健康恢复。
【免费下载链接】rookStorage Orchestration for Kubernetes项目地址: https://gitcode.com/gh_mirrors/roo/rook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考