Rook-Ceph 数据修复实战

前言

终于等来了国庆长假!宅在家折腾自己的 HomeLab、 K8s、 NAS 服务器集群的感觉真爽!这次长假真的是折腾了好多东西,感兴趣的读者请期待后续的爆更,等不及的话也可以直接看我的 repo - east4ming/homelab2 的 PR 和 Commits。

今天是第一篇。

事情是这样的:我家 HomeLab 的 Rook-Ceph 集群,前阵子趁着国庆给全部节点从 Ubuntu 24.04 升级到 26.04.1。结果其中一台 n100-cheshi-0 升级出现问题,我多次重启均无法恢复,🧠一热+一急直接断电,直接 GG😭,一躺就是好几个小时。好在最后还是修复了。(也是一篇文章,敬请期待)

节点重新上线之后,我本以为 Ceph 会自己缓过来,毕竟盘都还在,PG 也没报错。结果一看 ceph -s,好家伙,4 个 OSD 只有 3 个在线,OSD 1 躺在 down/out 里装死,ceph osd df 里它的 SIZE 是 0 B。

说实话,一开始我脑子里全是「磁盘挂了」「BlueStore 元数据损坏」「要重建 OSD 了」这些最坏的剧本。折腾了半宿才发现,磁盘上那 4TB 数据一点事没有,是 Rook operator 自己「不认」它了。

这篇文章记录从排查到修复的完整链路。重点在排查思路:用三方 FSID 比对加源码验证,把一个看起来像数据损坏的问题,定位成 Secret 里某个字段过期。

📝 声明:

  • 本文非广告、非推广,纯属踩坑记录。
  • 环境为 4 节点 K3s + Rook-Ceph(4 OSD),homelab 环境,不是生产环境,仅为读者提供思路,别照抄。

背景:autoout 是罪魁祸首吗?

先说清楚 OSD 为什么会被踢出去。Ceph 有个参数 mon_osd_down_out_interval,默认 600 秒。OSD 失联超过这个时间,monitor 就会把它标记为 out,同时把它的 CRUSH weight 清零,这就是所谓的 autoout。

节点断电好几个小时,OSD 1 早就超时了,于是:

  1. OSD 1 被 autoout,CRUSH weight 归零;
  2. 它的数据被迁移到其余 3 个 OSD;
  3. 集群恢复到 HEALTH_OK,81 个 PG 全部 active+clean。

单看这一步,这是设计行为,不是故障。真正的问题在后面:节点回来了,OSD 1 却没有自愈。

判据一:Deployment 消失 ≠ Pod CrashLoop

我第一个动作是看 Pod:

bash 复制代码
kubectl -n rook-ceph get deploy | grep osd

输出里只有 rook-ceph-osd-0/2/3,根本没有 rook-ceph-osd-1 这个 Deployment。

这个信号很说明问题。Rook 管理 OSD 的姿势是这样的:

现象 含义
Deployment 存在,Pod CrashLoop / Pending operator 认了这块盘,问题出在运行时、调度或设备上
Deployment 根本不存在 operator 在准备阶段就没采纳这块盘,Pod 从没被创建过

也就是说,OSD 1 不是运行时故障,是 operator 主动跳过。这个区别直接把我从「磁盘坏了」拉到「operator 为什么不认它」。

ceph osd df 里 OSD 1 的 SIZE 显示 0 B 也是同一个逻辑:不是盘没了,是压根没被纳管。

判据二:osd-prepare 日志才是决定性证据

Rook 纳管 OSD 的流程是:rook-ceph-osd-prepare 作业先扫盘、判断能不能用,然后才由 operator 创建对应的 Deployment。所以真正的答案在 prepare 作业的日志里。

bash 复制代码
kubectl -n rook-ceph logs job/rook-ceph-osd-prepare-n100-cheshi-0

关键几行(已简化):

log 复制代码
osd_id: 1
type: bluestore
ceph_fsid: abb2c4e2-12a3-4b93-8d90-f2eaf2290901
...
skipping osd.1 ... belonging to a different ceph cluster
0 ceph-volume raw osd devices configured on this node

看到 belonging to a different ceph cluster 这句,我当时有点懵:我哪来的第二个 Ceph 集群?

不过日志也告诉我,磁盘上的元数据是合法且完整的:osd_id 1、type bluestore、ceph_fsid abb2c4e2-...。问题不在盘,而在 Rook 认为「当前集群的 FSID」和盘上的 FSID 对不上。

三方 FSID 比对

顺着这条线,我把三个来源的 FSID 摆到一起:

来源 命令 / 位置 FSID
运行中集群真值 ceph fsid abb2c4e2-12a3-4b93-8d90-f2eaf2290901
OSD 1 磁盘 BlueStore 元数据 prepare 日志 ceph_fsid abb2c4e2-...(一致)
rook-ceph-mon Secret .data.fsid f568f7c3-5603-4108-99ff-3742cf008a83(过期)

三行凑到一起,答案就出来了:

  • 集群真值 = 磁盘元数据 = abb2c4e2-... ✅
  • 只有 rook-ceph-mon Secret 里的 fsid 是旧的 f568f7c3-... ❌

operator 拿着 Secret 里的过期 FSID 去跟磁盘比对,自然判定「这不是我的盘」,于是跳过采纳。磁盘、数据、PG,全都是好的。

源码级验证:为什么这个字段不会自动纠正?

这里我多了个心眼:万一只是巧合,下次会不会又漂?于是我翻了 Rook 上游源码 pkg/operator/ceph/controller/cluster_info.go,搜 fsidSecretNameKey,只有三处:

  • L52:常量定义
  • L154:读取(构造 ClusterInfo 时用)
  • L423:写入,且仅在 Secret 不存在、创建新集群时才写

换句话说,fsid 没有任务在「发现不一致时自动纠正」。它一旦写歪,就会一直歪下去。这也解释了为什么节点恢复后 OSD 1 永远无法自愈:这不是等一等就能好的故障。

📝 Notes:这个结论让我对整个修复方案有了信心。改掉 Secret 里的值就够了,磁盘不用动。

修复:patch 而非 delete

修复动作只有一行命令。原则是只改 fsid 字段 ,保留 ceph-secret、ceph-username、mon-secret 不动。

bash 复制代码
NEW_FSID=$(ceph fsid)
kubectl -n rook-ceph patch secret rook-ceph-mon --type merge \
  -p "{\"data\":{\"fsid\":\"$(echo -n "$NEW_FSID" | base64 -w0)\"}}"

然后让 operator 重新加载 ClusterInfo:

bash 复制代码
kubectl -n rook-ceph rollout restart deploy/rook-ceph-operator

🐾 注意 :千万别图省事把 rook-ceph-mon Secret 删了重建。 它挂着 DisasterProtectionFinalizer,删掉很可能被 operator 当成「新集群 bootstrap」,那才是真的灾难。

验证:从 0 devices 到 1 devices

重启 operator 后,再看 prepare 日志:

log 复制代码
1 ceph-volume raw osd devices configured on this node

从 0 变成 1,就是采纳成功的信号。接着:

bash 复制代码
kubectl -n rook-ceph get deploy rook-ceph-osd-1   # 1/1 Ready
ceph osd tree                                      # osd.1 up/in
ceph osd df                                        # crush weight 0.86850

OSD 1 自动重新注册,CRUSH weight 恢复,全程没有手工执行 ceph osd in 或 ceph osd crush reweight。

副作用:全部 OSD 滚动重启

operator 下发新 OSD 配置时,触发了我全部 4 个 OSD 的滚动重启。集群短暂进入 HEALTH_WARN:

指标 数值
osds down 1
objects degraded 11648 / 44646 ≈ 26.090%
pgs degraded 23

看到 26% degraded 那一瞬间,心跳快了一下 😅。不过这属于正常过渡态:期间所有 PG 仍满足副本要求,没有出现 undersized 或 incomplete。大约 1~2 分钟后集群自己恢复了。

闭环:HEALTH_OK ≠ 数据无损

断电场景下,最不能信的就是 HEALTH_OK。PG 状态正常只代表「副本数够」,不代表「数据没被写坏」。

所以我对全部 81 个 PG 跑了一遍 deep-scrub:

bash 复制代码
ceph pg deep-scrub $(ceph pg ls | awk 'NR>1{print $1}')
ceph health detail

结果:全部 deep-scrub ok,0 inconsistent objects,HEALTH_OK。至此才算真正收工。

预防措施

踩完坑总结几条,供各位参考:

  1. 升级前 ceph osd set noout,升级完再 unset noout,避免 autoout 触发全量数据迁移;
  2. 给节点升级准备 UPS,另外不要手贱强制关机,几百块的 UPS 比几 TB 的重平衡便宜多了, 另外这次就是断电的教训;
  3. 备份 rook-ceph-mon Secret,出问题时才有基线可比对;
  4. 恢复后必须 deep-scrub,别只看到 HEALTH_OK 就关电脑。

总结

回头看,这次故障的「魔鬼」藏在一个从没被人注意过的字段里。磁盘完好、数据完好、PG 完好,坏的是 rook-ceph-mon Secret 里那个永远不会自动纠正的过期 FSID。而 Rook 上游源码里的「只读不写」,决定了它只能靠人工修。

Deployment 消失 ≠ Pod CrashLoop 这个判据,帮我省了至少一晚上的瞎折腾。三方 FSID 比对加源码级验证,则是把「玄学」变成「确定」的关键两步。

纸上得来终觉浅,绝知此事要躬行。OSD 1 其实压根没病,只是被错认了户口。搞清楚这一点,修复就只是一行 kubectl patch - 精简,优雅。

以上。

📚️ 参考文档

相关推荐
imDwAaY2 小时前
WebSocket 和 SSE 有什么区别?从实时通信到 AI 流式输出
后端·websocket
三千星2 小时前
Java开发者转型AI工程化Week 7:从散落知识点到可上线的应用
后端
胡写代码2 小时前
雪花 ID 传到前端就变了个数?我用全局 Long 转 String 一次收口
前端·后端
SimonKing2 小时前
一只离线鼠鼠,干翻了一堆在线格式转换网站
java·后端·程序员
武子康2 小时前
两台 A6000,LingBot 应该先验哪条路径?
人工智能·后端·agent
Thneonl3 小时前
值班第一年:最先要学的不是排障,是叫人
后端·程序员
codigger3 小时前
Redis 正式接入 AI:当"最懂速度的数据库"开始解决"记忆问题"
redis·分布式·后端·ai·向量检索
沐言人生3 小时前
82.4k 星!把十几万行代码变成知识图谱,新人终于不用硬啃了
前端·后端·github
Thneonl3 小时前
让 LLM 值第一班岗:只读的活放手交,变更的活一条别给
后端·架构