前言
终于等来了国庆长假!宅在家折腾自己的 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 早就超时了,于是:
- OSD 1 被 autoout,CRUSH weight 归零;
- 它的数据被迁移到其余 3 个 OSD;
- 集群恢复到
HEALTH_OK,81 个 PG 全部active+clean。
单看这一步,这是设计行为,不是故障。真正的问题在后面:节点回来了,OSD 1 却没有自愈。
判据一:Deployment 消失 ≠ Pod CrashLoop
我第一个动作是看 Pod:
kubectl-nrook-ceph get deploy|greposd输出里只有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 作业的日志里。
kubectl-nrook-ceph logs job/rook-ceph-osd-prepare-n100-cheshi-0关键几行(已简化):
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-monSecret | .data.fsid | f568f7c3-5603-4108-99ff-3742cf008a83(过期) |
三行凑到一起,答案就出来了:
- 集群真值 = 磁盘元数据 =
abb2c4e2-…✅ - 只有
rook-ceph-monSecret 里的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不动。
NEW_FSID=$(ceph fsid)kubectl-nrook-ceph patch secret rook-ceph-mon--typemerge\-p"{\"data\":{\"fsid\":\"$(echo-n"$NEW_FSID"|base64-w0)\"}}"然后让 operator 重新加载ClusterInfo:
kubectl-nrook-ceph rollout restart deploy/rook-ceph-operator🐾注意:千万别图省事把
rook-ceph-monSecret 删了重建。它挂着DisasterProtectionFinalizer,删掉很可能被 operator 当成「新集群 bootstrap」,那才是真的灾难。
验证:从 0 devices 到 1 devices
重启 operator 后,再看 prepare 日志:
1 ceph-volume raw osd devices configured on this node从0变成1,就是采纳成功的信号。接着:
kubectl-nrook-ceph get deploy rook-ceph-osd-1# 1/1 Readyceph osd tree# osd.1 up/inceph osddf# crush weight 0.86850OSD 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:
ceph pg deep-scrub$(ceph pgls|awk'NR>1{print $1}')ceph health detail结果:全部deep-scrub ok,0 inconsistent objects,HEALTH_OK。至此才算真正收工。
预防措施
踩完坑总结几条,供各位参考:
- 升级前
ceph osd set noout,升级完再unset noout,避免 autoout 触发全量数据迁移; - 给节点升级准备 UPS,另外不要手贱强制关机,几百块的 UPS 比几 TB 的重平衡便宜多了, 另外这次就是断电的教训;
- 备份
rook-ceph-monSecret,出问题时才有基线可比对; - 恢复后必须 deep-scrub,别只看到
HEALTH_OK就关电脑。
总结
回头看,这次故障的「魔鬼」藏在一个从没被人注意过的字段里。磁盘完好、数据完好、PG 完好,坏的是rook-ceph-monSecret 里那个永远不会自动纠正的过期 FSID。而 Rook 上游源码里的「只读不写」,决定了它只能靠人工修。
Deployment 消失 ≠ Pod CrashLoop这个判据,帮我省了至少一晚上的瞎折腾。三方 FSID 比对加源码级验证,则是把「玄学」变成「确定」的关键两步。
纸上得来终觉浅,绝知此事要躬行。OSD 1 其实压根没病,只是被错认了户口。搞清楚这一点,修复就只是一行kubectl patch- 精简,优雅。
以上。
📚️ 参考文档
- Rook 官方文档 - Ceph OSD Management
- Ceph 文档 - OSD autoout 与 mon_osd_down_out_interval
- Ceph 文档 - Deep Scrubbing
- Rook 源码
pkg/operator/ceph/controller/cluster_info.go(fsidSecretNameKey定义与读写位置)