Rook-Ceph OSD 1 down/out 修复记录:mon secret FSID 失配
事故日期:2026-10-02 影响节点:
n100-cheshi-0(Ubuntu 24.04 → 26.04.1 升级,升级过程中发生断电) 结果:OSD 1 已恢复up/in,集群HEALTH_OK,深度校验确认无数据损坏
1. 结论摘要(TL;DR)
本次故障不是磁盘或数据损坏,而是 Rook operator 的 rook-ceph-mon Secret 中 fsid 字段过期,导致 operator 拒绝采纳磁盘上完好的 OSD 1。
| 项目 | 修复前 | 修复后 |
|---|---|---|
ceph -s |
HEALTH_OK(OSD 1 已 out,PG 全 clean) | HEALTH_OK |
| OSD 状态 | 4 osds: 3 up, 3 in;osd.1 down out weight 0 |
4 osds: 4 up, 4 in;osd.1 up in weight 1 |
osd.1 crush weight |
0(autoout 清零) |
0.86850(恢复原值) |
rook-ceph-mon secret fsid |
f568f7c3-… ❌ 错误 |
abb2c4e2-… ✅ 正确 |
| OSD 1 Deployment | 不存在 | 1/1 Ready |
| PG | 81 active+clean |
81 active+clean,deep-scrub 全部 ok |
关键点:rook-ceph-osd-1 的 Deployment 不是"崩溃",而是被 operator 主动删除/未创建。 磁盘上的 BlueStore 元数据自始至终完好。
2. 环境信息
| 组件 | 版本 |
|---|---|
| Kubernetes | k3s v1.36.5+k3s1 |
| Rook operator | docker.io/rook/ceph:v1.20.7 |
| Ceph | v20.2.4 (Tentacle),quay.io/ceph/ceph:v20.2.4 |
| 集群 FSID | abb2c4e2-12a3-4b93-8d90-f2eaf2290901 |
| 节点 | n100-cheshi-0(OSD 1)、n100-jumper-0(OSD 2)、n100-jumper-1(OSD 0)、n100-jumper-2(OSD 3) |
| OSD 1 设备 | /dev/nvme0n1p3(ZHITAI TiPro7000 1TB,889 GiB,bluestore,非加密) |
3. 症状
升级 + 断电后,n100-cheshi-0 上的 OSD 1 处于 down/out:
$ ceph osd tree
-5 0.86850 host n100-cheshi-0
1 ssd 0.86850 osd.1 down 0 1.00000
$ ceph osd dump | grep 'osd.1 '
osd.1 down out weight 0 up_from 404052 up_thru 404055 down_at 404060 \
last_clean_interval [403278,404046) [...] autoout,exists 84b684bc-6ab4-4033-a75f-fec3b0b426cc
两个异常信号:
autoout,exists—— OSD 存在但被自动 out(crush weight 被清零)。ceph osd df中osd.1的SIZE为0 B—— 完全没有容量上报,说明 OSD 守护进程根本没在运行。
$ ceph status
osd: 4 osds: 3 up (since 2h), 3 in (since 4h)
pgs: 81 active+clean # 数据未受影响
3.1 关键线索:Deployment 缺失
$ kubectl -n rook-ceph get deploy | grep osd
rook-ceph-osd-0 1/1
rook-ceph-osd-2 1/1
rook-ceph-osd-3 1/1
# rook-ceph-osd-1 不存在!
rook-ceph-osd-1 的 Deployment 完全不存在。这不是"Pod CrashLoop",而是 operator 从未创建它(或已将其删除)。
4. 根因分析
4.1 决定性证据:osd-prepare 作业日志
kubectl -n rook-ceph logs job/rook-ceph-osd-prepare-n100-cheshi-0
I | cephosd: old lsblk can't detect bluestore signature, so try to detect here
D | exec: Running command: stdbuf -oL ceph-volume --log-path /tmp/ceph-log raw list --format json
D | cephosd: {
"84b684bc-6ab4-4033-a75f-fec3b0b426cc": {
"ceph_fsid": "abb2c4e2-12a3-4b93-8d90-f2eaf2290901",
"device": "/dev/nvme0n1p3",
"osd_id": 1,
"osd_uuid": "84b684bc-6ab4-4033-a75f-fec3b0b426cc",
"type": "bluestore"
}
}
I | cephosd: skipping osd.1: "84b684bc-…" belonging to a different ceph cluster "abb2c4e2-…"
I | cephosd: 0 ceph-volume raw osd devices configured on this node
W | cephosd: skipping OSD configuration as no devices matched the storage settings for this node "n100-cheshi-0"
磁盘上的 OSD 元数据完好无损(合法的 osd_id: 1、type: bluestore、ceph_fsid),但 operator 判定它"属于另一个 Ceph 集群"并跳过,因此不生成 OSD Deployment。
4.2 FSID 三方比对
| 来源 | FSID | 判定 |
|---|---|---|
运行中集群 ceph fsid |
abb2c4e2-12a3-4b93-8d90-f2eaf2290901 |
✅ 真值 |
| OSD 1 磁盘 BlueStore 元数据 | abb2c4e2-12a3-4b93-8d90-f2eaf2290901 |
✅ 与真值一致 |
rook-ceph-mon Secret .data.fsid |
f568f7c3-5603-4108-99ff-3742cf008a83 |
❌ 过期 |
$ kubectl -n rook-ceph get secret rook-ceph-mon -o jsonpath='{.data.fsid}' | base64 -d
f568f7c3-5603-4108-99ff-3742cf008a83 # 与真实集群不符
OSD 磁盘与真实集群是一致的;错的只有 Rook 用作"期望值"的那个 Secret 字段。
4.3 Rook 源码级验证
在 Rook 上游源码中确认了该字段的读写路径(pkg/operator/ceph/controller/cluster_info.go):
// L52 —— 字段名
fsidSecretNameKey = "fsid"
// L110-154 —— 读取路径:仅从 Secret 读取,不做任何校验/纠正
secrets, err := clusterdContext.Clientset.CoreV1().Secrets(namespace).Get(context, AppName, ...)
...
clusterInfo = &cephclient.ClusterInfo{
Namespace: namespace,
FSID: string(secrets.Data[fsidSecretNameKey]), // ← 唯一来源
...
}
// L417-441 —— 写入路径:仅在 Secret「不存在」时创建
func createClusterAccessSecret(...) error {
secrets := map[string][]byte{
fsidSecretNameKey: []byte(clusterInfo.FSID), // ← 仅此一处写入
...
}
...
clientset.CoreV1().Secrets(namespace).Create(...)
}
整个仓库中 fsidSecretNameKey 只出现 3 次:
| 位置 | 作用 |
|---|---|
cluster_info.go:52 |
常量定义 |
cluster_info.go:154 |
读(构造 ClusterInfo) |
cluster_info.go:423 |
写(仅在 Secret 不存在、创建新集群时) |
另有 cluster_info.go:167 一处 Secrets().Update(),但只在旧版 admin-secret 键迁移时触发(当前 Secret 已含 ceph-username,不会触发)。
结论:该字段无任何自动纠正逻辑,也不会被后续调和覆盖 —— 手工修正后是稳定、持久的。
4.4 事件链(推断,与证据一致)
n100-cheshi-0执行 Ubuntu 24.04 → 26.04.1 升级,过程中断电。- 节点长时间离线,4 个 OSD 中 OSD 1 所在节点不可达。
- Ceph 达到
mon_osd_down_out_interval,将 OSD 1 标记out并把 crush weight 置 0(autoout),数据迁往其余 3 个 OSD,PG 恢复active+clean。 - Rook operator 在节点反复离线期间重新引导集群访问凭据,重新生成了
rook-ceph-monSecret 的fsid(resourceVersion: 4),但该值未与运行中的 mon 数据/OSD 对齐。 - 节点恢复后,operator 用错误的
fsid作期望值扫描 OSD 磁盘 → 判定"属于其他集群" → 跳过 → 不创建rook-ceph-osd-1Deployment。 - OSD 1 因此永久停留在
down/out,且不会自愈。
4.5 为什么其他 Ceph 组件没坏
rook-ceph-mon Secret 中另外两个字段是可用且正确的:
$ kubectl -n rook-ceph get secret rook-ceph-mon -o jsonpath='{.data.ceph-username}' | base64 -d
client.admin
用该 Secret 里的 ceph-secret 直接向集群认证 —— 成功:
$ ceph -s --name client.admin --keyring /tmp/t.keyring
cluster:
id: abb2c4e2-12a3-4b93-8d90-f2eaf2290901
health: HEALTH_OK
mon 端点通过 rook-ceph-mon-endpoints ConfigMap 加载(与 fsid 无关),因此 mon/mgr/mds 一直正常运行,只有 OSD 采纳流程因 FSID 校验而失败。
5. 修复方案
5.1 修复前检查清单
执行前确认(避免在真正需要重建时误操作):
-
ceph -s为HEALTH_OK,PG 全部active+clean(无数据丢失风险) - OSD 磁盘元数据可读且
ceph_fsid与真实集群一致 - Secret 中的
ceph-secret仍能成功认证(证明凭据未被破坏,只需修fsid) - 全集群无其他资源引用错误 FSID(已扫描所有 ConfigMap/Secret/StorageClass)
# 扫描错误 FSID 是否泄漏到其他资源(结果:无)
WRONG=f568f7c3-5603-4108-99ff-3742cf008a83
kubectl get configmap,secret -A -o json | grep -c "$WRONG" # → 仅 rook-ceph-mon 自身
kubectl get sc -o json | grep -c "$WRONG" # → 0
5.2 步骤 1:修正 Secret 中的 FSID
# 以运行中集群的真实 FSID 为准
FSID=$(kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph fsid | tr -d '\r\n')
# → abb2c4e2-12a3-4b93-8d90-f2eaf2290901
# 仅替换 fsid 字段,保留 ceph-secret / ceph-username / mon-secret
kubectl -n rook-ceph patch secret rook-ceph-mon \
--type merge \
-p "{\"data\":{\"fsid\":\"$(printf '%s' "$FSID" | base64 -w0)\"}}"
# → secret/rook-ceph-mon patched
验证:
$ kubectl -n rook-ceph get secret rook-ceph-mon -o json | python3 -c "
import json,sys,base64
d=json.load(sys.stdin)
for k in sorted(d['data']):
v=base64.b64decode(d['data'][k]).decode('utf8','replace')
print(f' {k:14} = {v if k in (\"fsid\",\"ceph-username\") else v[:12]+\"...(masked)\"}')
"
ceph-secret = AgAag5JqppFL...(masked)
ceph-username = client.admin
fsid = abb2c4e2-12a3-4b93-8d90-f2eaf2290901 # ✅
mon-secret = AgB6g5Jq+6Yf...(masked)
⚠️ 只改
fsid。不要删除 Secret 重建 —— 删除会触发DisasterProtectionFinalizer并可能被 operator 当作新集群重新 bootstrap,风险极高。
5.3 步骤 2:重启 operator 以重新加载 ClusterInfo
Secret 在 operator 启动时读取进内存缓存,需重启使其生效:
kubectl -n rook-ceph delete pod -l app=rook-ceph-operator --wait=false
kubectl -n rook-ceph wait --for=condition=Ready pod -l app=rook-ceph-operator --timeout=180s
operator 启动初期的
Error initializing cluster client: ObjectNotFound('RADOS object not found (error calling conf_read_file)')属正常引导过程,随后会自行消失。
5.4 步骤 3:等待调和并验证采纳
Rook 每约 10 分钟调和一次;OSD 阶段位于 mgr 阶段之后。
# 观察 CephCluster 状态
kubectl -n rook-ceph get cephcluster rook-ceph -o jsonpath='{.status.phase}{"\n"}'
# Progressing → Ready
验证点 —— prepare 作业不再跳过 OSD 1:
$ kubectl -n rook-ceph logs job/rook-ceph-osd-prepare-n100-cheshi-0 | grep -E 'different ceph cluster|raw osd devices configured'
I | cephosd: 1 ceph-volume raw osd devices configured on this node # ✅ 采纳成功
# 且不再出现 "belonging to a different ceph cluster"
I | cephosd: devices = [{ID:1 Cluster:ceph UUID:84b684bc-6ab4-4033-a75f-fec3b0b426cc \
DeviceClass:nvme BlockPath:/dev/nvme0n1p3 ... CVMode:raw Store:bluestore}]
验证点 —— Deployment 与 Pod 已创建:
$ kubectl -n rook-ceph get deploy rook-ceph-osd-1
NAME READY UP-TO-DATE AVAILABLE
rook-ceph-osd-1 1/1 1 1
$ kubectl -n rook-ceph get pods -l ceph_daemon_id=1 -o wide
NAME READY STATUS NODE
rook-ceph-osd-1-678d4fff96-lbtpk 1/1 Running n100-cheshi-0
5.5 步骤 4:确认 OSD 已回到集群(无需手工 osd in)
OSD 守护进程启动后自动向 mon 重新注册。本次无需手工执行 ceph osd in,Rook 重建 Deployment 时同时恢复了 crush weight:
$ ceph osd tree
-5 0.86850 host n100-cheshi-0
1 ssd 0.86850 osd.1 up 1.00000 1.00000
$ ceph osd dump | grep 'osd.1 '
osd.1 up in weight 1 up_from 404241 up_thru 404273 down_at 404060 \
last_clean_interval [404052,404059) [...] exists,up 84b684bc-…
$ ceph osd df
1 ssd 0.86850 1.00000 889 GiB ... up # SIZE 已从 0 B 恢复为 889 GiB
若某次修复后 OSD 仍为
out或 weight 为 0,再用以下命令手工恢复:历史上ceph osd in 1 # 重新标记 in ceph osd crush reweight osd.1 0.86850 # 恢复 crush weight 为原值autoout会把 weight 置 0,因此恢复 weight 这一步是必要的(本次由 Rook 自动完成)。
6. 验证结果
6.1 集群终态
$ ceph -s
cluster:
id: abb2c4e2-12a3-4b93-8d90-f2eaf2290901
health: HEALTH_OK
services:
mon: 3 daemons, quorum c,e,g
mgr: a(active), standbys: b
mds: 1/1 daemons up, 1 hot standby
osd: 4 osds: 4 up (since 2m), 4 in (since 4m) # ✅ 4/4 up & in
data:
volumes: 1/1 healthy
pools: 4 pools, 81 pgs
objects: 22.32k objects, 81 GiB
pgs: 81 active+clean # ✅ 无降级
ceph osd tree 全部 4 个 OSD 均为 up / in / 1.00000,crush weight 均为 0.86850(与故障前一致)。
6.2 数据完整性校验(重点)
由于故障起因是"升级期间断电",必须排除静默数据损坏。对全部 81 个 PG 发起深度校验:
kubectl -n rook-ceph exec deploy/rook-ceph-tools -- bash -c '
for pg in $(ceph pg ls | awk "NR>1{print \$1}"); do ceph pg deep-scrub "$pg"; done'
结果:
$ ceph health detail
HEALTH_OK # ✅ 无 inconsistent / scrub error
$ kubectl -n rook-ceph logs -l ceph_daemon_id=1 | grep deep-scrub | tail -3
1.7 deep-scrub ok
1.f deep-scrub ok
1.12 deep-scrub ok # 每个 OSD 的 PG 均为 ok
结论:所有 81 个 PG 深度校验通过,0 inconsistent objects,磁盘数据无损坏。
7. 修复过程中的副作用(重要)
重启 operator 后,Rook 下发了新的 OSD 配置(Deployment 模板变更),触发全部 4 个 OSD 的滚动重启:
滚动过程(旧 Pod → 新 Pod):
| OSD | 修复前 Pod | 修复后 Pod |
|---|---|---|
| 0 | rook-ceph-osd-0-78c8b4998c-tnndx |
rook-ceph-osd-0-56cb79d8b7-t8hbv |
| 1 | 不存在 | rook-ceph-osd-1-678d4fff96-lbtpk |
| 2 | rook-ceph-osd-2-69b8758db8-bjm84 |
rook-ceph-osd-2-6895c7d8d8-xphrn |
| 3 | rook-ceph-osd-3-59c577779d-btd67 |
rook-ceph-osd-3-59fc9b6f66-jxhj8 |
(OSD 3 分两轮完成滚动,最终 4 个 Pod 均在约 5 分钟内重建并 Ready。)
期间集群短暂出现:
health: HEALTH_WARN
1 osds down
Degraded data redundancy: 11648/44646 objects degraded (26.090%), 23 pgs degraded
这是正常的滚动重启过渡态,OSD 2 的替换 Pod 处于 Init:1/4(ceph-volume raw activate → expand-bluefs → cephx-keyring-update 依次完成),约 1~2 分钟后全部恢复 Ready,集群回到 HEALTH_OK / 81 active+clean。
此项不构成数据风险:所有 PG 在过渡期仍满足副本要求,未出现
undersized/incomplete。
8. 关键判据速查(下次遇到同类问题)
| 观察到的现象 | 含义 |
|---|---|
| OSD Deployment 不存在(而非 CrashLoop) | operator 主动跳过,多半是采纳/校验失败,而非设备故障 |
ceph osd df 中该 OSD SIZE = 0 B |
守护进程未运行,非磁盘故障 |
autoout 且 weight 0 |
由 mon_osd_down_out_interval 自动 out,可通过 restore weight 复原 |
prepare 日志 belonging to a different ceph cluster |
rook-ceph-mon Secret 的 fsid 与真实集群不符 |
ceph fsid == OSD 元数据 ceph_fsid ≠ Secret fsid |
三者中 Secret 过期 → 修 Secret |
Secret 的 ceph-secret 仍可认证 |
凭据完好,只需改 fsid,切勿删除重建 Secret |
9. 预防措施
- 升级前先
ceph osd set noout,避免节点离线期间 OSD 被autoout并触发全量数据迁移:ceph osd set noout # ... 节点维护/升级 ... ceph osd unset noout - 为节点升级准备 UPS,避免断电导致升级中断与集群元数据/凭据不一致。
- 备份关键 Secret(尤其是
rook-ceph-mon),以便事后比对fsid:kubectl -n rook-ceph get secret rook-ceph-mon -o yaml > rook-ceph-mon.secret.bak.yaml - 集群恢复后,始终以深度校验确认数据完整性,不要仅凭
HEALTH_OK判断(HEALTH_OK只表示 PG 元数据一致,静默位翻转需 scrub 才能发现):for pg in $(ceph pg ls | awk 'NR>1{print $1}'); do ceph pg deep-scrub $pg; done ceph health detail | grep -i inconsistent
10. 附:本次事件时间线
| 时间(本地) | 事件 |
|---|---|
| 升级期间 | n100-cheshi-0 Ubuntu 24.04 → 26.04.1,过程中断电 |
| ~升级后 | OSD 1 被 autoout,weight → 0,数据迁至其余 3 OSD |
| operator 重启期间 | rook-ceph-mon Secret 的 fsid 被重新生成为过期值 f568f7c3-…(resourceVersion 4) |
| 14:14 | osd-prepare 作业判定 OSD 1 "属于不同集群"并跳过;rook-ceph-osd-1 Deployment 缺失 |
| 14:21 | 开始诊断:确认磁盘元数据完好、Secret 凭据可用、仅 fsid 过期 |
| 14:24:35 | 修正 Secret fsid → abb2c4e2-…;重启 operator |
| 14:27 | 新 prepare 作业采纳 OSD 1(1 ceph-volume raw osd devices configured) |
| 14:27:49 | rook-ceph-osd-1 Deployment 创建,Pod 运行 |
| 14:28–14:30 | operator 滚动重启全部 OSD;短暂 HEALTH_WARN(26% degraded) |
| 14:29 | osd.1 up in weight 1,crush weight 恢复 0.86850 |
| 14:30 | 集群回到 HEALTH_OK,4 osds up/in,81 active+clean |
| 14:31 | 全部 81 个 PG 深度校验完成:deep-scrub ok,无损坏 |
11. 参考
- Rook
CreateOrLoadClusterInfo/createClusterAccessSecret(pkg/operator/ceph/controller/cluster_info.go)—— 说明fsid仅在 Secret 创建时写入 - Rook Discussion #13946 how to load OSD belonging to different Ceph cluster —— "different ceph cluster" 报错的社区讨论
- 本仓库相关文档:Rook-Ceph v1.20.0 CSI ServiceAccount 命名不匹配 Bug 及修复方案
- 升级背景:Ubuntu 24.04 LTS 升级到 26.04 LTS