PVC 已经 Bound,Pod 也调度到了节点,业务却读写错乱或偶发
I/O 错误——这类事故很少停在「CreateVolume 失败」上。RBD CSI
的挂载路径把 image features、内核 krbd 能力、map
选项与节点生命周期 叠在一起;错位时常不是干净的
FailedMount,而是「看起来挂上了,行为不对」。
本文是「Rook / CSI」系列第 7 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 6 篇 · RBD 供给 StorageClass → image 第 7 篇 · 挂载与 features NodeStage/Publish;krbd;read affinity 第 8 篇 · CephFS CSI subvolume / RWX
版本锚定:Rook v1.20.4;Ceph-CSI v3.17.0;K8s v1.31–v1.36。Ceph:Squid v19.2.x / Tentacle v20.2.1+(v20.2.0 + read affinity 有已知损坏风险,标 Tentacle+ 警告)。krbd/features 语义外链 ceph/12。
一、Node 侧两段 RPC
K8s 对块设备卷的典型顺序(CSI 规范 + kubelet):
NodeStageVolume:在节点全局 staging 路径把 RBD image map 成块设备(默认 krbd →/dev/rbd*),必要时格式化/挂载到 staging。NodePublishVolume:把 staging 上的设备(或文件系统)bind-mount 进 Pod 可见路径。
sequenceDiagram
participant Kubelet
participant NodePlugin as CSI RBD node plugin
participant Kernel as krbd / nbd
participant Ceph as MON / OSD
Kubelet->>NodePlugin: NodeStageVolume
NodePlugin->>Kernel: rbd map (features check)
Kernel->>Ceph: libceph msgr
NodePlugin-->>Kubelet: staging path ready
Kubelet->>NodePlugin: NodePublishVolume
NodePlugin-->>Kubelet: target path in Pod
Controller 侧的 CreateVolume 已在第 6 篇完成。本篇失败模式几乎都落在 NodeStage 的 map 与后续 I/O 语义上。
二、map 路径:krbd 默认,nbd 兜底
Ceph-CSI RBD 默认走 krbd(内核
drivers/block/rbd.c +
libceph)。StorageClass 可设:
mounter: rbd-nbd— 强制用户态 nbd 路径(需节点具备模块与二进制)。mapOptions/unmapOptions— 传给 krbd 或 nbd 的逗号分隔选项(见 Cephrbd/rbd-nbdman page)。tryOtherMounters: "true"— 挂载时若 krbd 不支持当前 image features,可回退 nbd(ceph-csi 源码internal/rbd/nodeserver.go语义;参数名以所用 CSI 版本文档为准)。
异构集群(部分节点内核旧)是经典陷阱:CreateVolume 在「新内核节点」视角合法,Pod 却调度到「旧内核节点」——NodeStage 才暴露问题。
三、features 与 krbd:为何「挂上但怪」
3.1 能力谈判发生在何时
| 时机 | 行为 | 局限 |
|---|---|---|
| CreateVolume | 按 SC 的 imageFeatures 创建 image |
不知道将来哪个节点会 map |
| NodeStage | 读 image features,对照本机 krbd 支持集 | 检测失败才报错;部分组合历史上行为不一 |
ceph-csi 在 NodeStage 会检查 krbd
是否支持所需特性;不支持且未允许其他 mounter 时,返回
Internal(日志常见
unsupported krbd Feature)。这是干净失败路径。
ceph/18 点名、本系列要展开的摩擦是另一类:
- image 带
object-map/fast-diff/exclusive-lock等,节点内核「半支持」或工具链与运行时假设不一致; - map「成功」,文件系统也 mount 上了,但 exclusive-lock、object-map 更新或多客户端边界与预期不符;
- 业务看到的是校验和错误、偶发卡住、迁移后数据不对——排障语言若只会
FailedMount,会漏诊。
机制背景(不重写):Exclusive Lock、object-map 依赖链与 krbd map 闸门见 ceph/12 · features 与 krbd。编排层结论只有一条:StorageClass 的 features 上界 = 集群中最弱可调度节点的 krbd 上界(除非强制 nbd 并接受其运维面)。
3.2 工程核对顺序
rbd info {pool}/{image}看实际 features(toolbox)。- 对照节点内核版本与 Rook 文档分档(≥5.4 才考虑完整特性集示例)。
- 看 node plugin 日志中的 map 参数与 feature 检查结果。
- 确认是否误开
journaling等仅部分路径支持的特性。
不要先怀疑 BlueStore:挂载错位是客户端能力问题,不是 PG 降级问题。
四、read affinity:性能旋钮与 Tentacle 雷区
4.1 做什么
Rook 允许在 CephCluster 上打开 CSI read
affinity(默认关闭):
spec:
csi:
readAffinity:
enabled: true语义(Rook「Enable Read affinity」+ ceph-csi RBD
文档):按节点 topology 标签推导 CRUSH 位置,map 时附加 krbd
选项(如 read_from_replica=localize 与
crush_location=...),让读尽量打到「更近」的
OSD 副本。需要节点 topology 标签与内核 ≥
5.8(Rook 文档门槛)。
这是延迟优化,不是正确性特性。未理解 CRUSH 拓扑与标签一致性时,收益不稳定。
4.2 Tentacle v20.2.0:官方损坏警告(Tentacle+)
Rook Ceph Upgrades(v1.20)写明:
- 支持:Squid v19.2.0+、Tentacle v20.2.1+。
- Tentacle v20.2.0:不推荐。
- 若启用
csi.readAffinity.enabled: true,v20.2.0 存在已知数据损坏 / 丢失风险(社区 tracker / Rook issue #16839;ceph-csi 侧亦有对应报告,主影响叙述含 CephFS 路径)。官方建议:启用了 read affinity 时升到 v20.2.1+,文档部分段落进一步写 v20.2.2+——以你所跟的 Rook 版本文档为准,原则是避开 20.2.0。 - 若已在 v20.2.0 上打开该开关:立即关闭;Rook 会在内部尝试禁用 read affinity 以降低风险,但已有卷上的应用需重启,才能让禁用完整落到现有挂载。
站内纪律:凡 Tentacle 特有风险,正文标 Tentacle+,不把「默认关着所以没事」写成生产豁免——有人会打开它。
五、节点丢失与 network fencing(简述)
RBD 卷在节点宕机后不能「自动安全」地挂到另一节点:旧节点若仍持有 map/会话,双挂会损坏。
Rook v1.20 文档路径:
- 确认节点不可用后打上
node.kubernetes.io/out-of-service=nodeshutdown的 NoExecute/NoSchedule taint。 - 若启用 network fencing(CSI-Addons + Driver 配置,默认不启用):驱动从 image 元数据取客户端地址,对 Ceph blocklist,阻止旧会话。
- 在节点完整断电重启循环之前,不得提前去掉 taint——文档警告:过早摘 taint 可能留下陈旧 map/会话,造成严重一致性问题。
- 恢复后经冷却期,CSI-Addons 可自动解除 blocklist;亦可手工处理。
详细租户与安全面见第 15 篇;本篇只建立坐标:挂载生命周期包含 fencing,不只包含 mount 成功码。
六、排障时把「怪」拆成可证伪假设
遇到「卷已挂载但应用不对」时,按下面顺序收窄,避免一上来打 OSD:
- 确认挂载栈:节点上是否存在预期的
/dev/rbd*或 nbd 设备;Pod 内挂载选项是否来自本次 NodePublish。 - 确认 features 实际值:toolbox
rbd info与 StorageClass 声明是否一致;有无被人手工rbd feature enable过。 - 确认节点内核与模块:
rbd/libceph是否加载;内核是否低于 SC 设计基线。 - 确认 lock / blocklist:是否刚做过节点迁移或 fencing;旧客户端是否仍在 blocklist 上。
- 确认 read affinity:若刚升 Tentacle 或刚打开 localize 读,优先排除第 4 节雷区,再谈性能。
只有前四项都干净,才把问题推到 PG 降级、磁盘延迟或应用逻辑。第 13 篇会把这些收成排障坐标系;本篇先建立「怪 ≠ 存储慢」的默认怀疑顺序。
异构内核滚动升级(部分节点已升、部分未升)会放大第 2–3 项。平台若不能一次性升级所有可调度节点的内核,就应冻结 StorageClass features,而不是「先开 object-map 再慢慢升内核」。
七、与 librbd 路径的边界(容器默认不走这条)
ceph/12 花了整节讲 librbd / QEMU。Rook 上绝大多数应用容器走的是 kubelet → CSI node plugin → krbd → 文件系统 → bind 进 Pod,不是 Pod 内链 librbd。例外包括:虚拟化负载用 kubevirt 一类再挂 RBD、或特权实验直接 map。
因此:把 QEMU rbd_cache 争论原样搬到「普通
Deployment 挂 PVC」会误导。容器路径的正确性争论更集中在
krbd features、exclusive-lock 与节点
fencing。librbd
的缓存语义仍重要,但不在本篇默认事故面。
若强制
mounter: rbd-nbd,运维面变成用户态守护与 nbd
模块——排障日志与性能轮廓都变,需要单独 runbook,不能与 krbd
共用一份「看 dmesg」手册。
八、谱系、争论与开放问题
谱系:块设备挂载从本地 SCSI
语义外推到网络块时,能力协商问题反复出现(iSCSI 特性、NVMe
可选特性、RBD features)。CSI
把「供给」与「节点附着」拆开,放大了异构节点上的延迟暴露。krbd
与 librbd 双路径(ceph/12)是 Ceph
长期分叉;容器场景默认偏 krbd。网络 fencing
则是把经典「STONITH / 客户端 blocklist」接到 K8s 的
out-of-service taint 上——设计提案在 ceph-csi 的
non-graceful node shutdown 文档中可核对。
争论:要不要在 CreateVolume 时把
features
压到「集群最小内核」?保守派减少静默失败;激进派用新特性换
object-map 读放大收益,并依赖
tryOtherMounters。没有单一正确解——有内核矩阵与
SC
冻结流程的团队可以激进;多发行版混部的平台应保守。另一争论是
read
affinity:副本局部读在纸面上降低读延迟,却引入拓扑标签正确性与
Tentacle
修复版本依赖;默认关闭是务实默认,不是「功能不重要」。
开放问题:
- 调度器能否把「节点 krbd 能力」做成可扩展资源,使 PVC 与节点在绑定前对齐?
- read affinity 在副本局部读与 PG 降级/scrub 叠加时,尾延迟是否可预测?(需对照实验,本站无伪造数字。)
- fencing 冷却期与应用 RTO 之间如何给出可测试上限,而不是「大约五分钟」口语?
九、本篇收束
挂载成功码只证明 NodePublish 返回了 OK。features 错位、Tentacle+ read affinity 雷区、以及未 fencing 的双挂风险,都会在 OK 之后伤害数据。下一篇换到 CephFS:RWX 的税在 MDS/caps,不在 krbd。
十、把 Tentacle 与 Squid 的挂载假设写进变更单
站内机制叙述默认钉 Squid;进入 Tentacle 时,挂载相关变更单至少多写三行:
- 目标 Ceph 精确版本(禁止口头「升到 20」);
- 当前是否启用
csi.readAffinity,计划保持还是关闭; - 若曾经过 20.2.0,是否已完成「关特性 + 应用重启」闭环。
缺任一行,就把 Tentacle+ 风险从「已知文档」降成「口头传说」。Rook 升级文档把 20.2.0 标成不推荐,不是排版装饰。
对只读副本 localize 的性能期望,也应写成可证伪句:例如「在某拓扑标签齐全的节点上,读延迟分布是否相对关闭 affinity 时左移」。没有拓扑标签却打开开关,等于给 map 选项喂空约束,收益与风险不对称。
网络 fencing 变更单则要写清:谁有权限打
out-of-service taint、谁确认断电循环、谁负责解
blocklist。权限不清时,最常见的操作事故是「业务已迁走、旧节点未
fencing」或「未断电就摘 taint」。二者都比「忘记开
features」更接近数据损坏。
十一、NodePublish 之后还有谁能动这块盘
NodePublish 完成不代表只有应用容器在访问 image。节点上的 CSI 插件、可能的 CSI-Addons sidecar、kubelet 的挂载管理,以及运维在 toolbox 里对同一 image 的误操作,都会进入同一条一致性故事。Exclusive Lock 的协作与 blocklist 破锁(ceph/12)在「同一 image 被两个客户端路径打开」时被触发;容器场景下第二客户端常常是未 fencing 干净的旧节点,而不是第二个 Pod(RWO 已由 K8s 附着限制)。
因此排障「数据被写坏」时,时间线要拉到:节点 NotReady 时刻、taint 时刻、卷在新节点 Stage 时刻、旧节点是否完成断电。缺时间线就无法区分应用 bug 与双挂。第 15 篇会再谈多租户下谁有权触发 fencing;本篇要求值班至少会问这四个时间戳。
关于「静默失败」的可观测缺口:kubelet 事件擅长报告
MountFailed,不擅长报告「mount 成功但 feature
降级/行为降级」。若 ceph-csi 回退到 nbd
或跳过某条检查,需要从插件日志确认,而不是从
kubectl describe pod 的成功事件确认。把 node
plugin 日志纳入默认采集,是挂载篇对第 12 篇的具体订货。
性能问题上,mapOptions 里的队列深度一类旋钮可以改善延迟轮廓,但也会掩盖后端过载:把队列加长后 P99 表面变好,OSD 端却堆积更多飞行中请求。改 mapOptions 应与 OSD 延迟、慢请求计数对照,禁止只看应用侧均值。
参考资料
官方文档 / issue(A/B)
- Rook v1.20 — Block Storage · Node Loss / Network Fencing
- Rook v1.20 — Ceph CSI Drivers · Read affinity
- Rook v1.20 — Ceph Upgrades — v20.2.0 + read affinity 警告;HEALTH_ERR 拒升级
- Rook issue #16839;Ceph tracker / ceph-csi 相关报告(Tentacle+)
- Ceph-CSI —
docs/rbd/deploy.md;internal/rbd/nodeserver.go(NodeStage feature 检查)@ v3.17.0 语义
站内
→ 上一篇:RBD 供给 · 系列目录 · 下一篇:CephFS CSI
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Rook / CSI】RBD 供给路径:StorageClass 到 CreateVolume 到 image
拆解 Rook 默认 RBD CSI 如何把 StorageClass 参数与 Secret 转成 CreateVolume,再落到 Format 2 image;钉死 provisioner 命名前缀、池与 imageFeatures 边界,并外链 ceph/12 的对象命名——不重写 BlueStore。
【Rook / CSI】快照与克隆:VolumeSnapshot 语义、扩容与 CRD 错位
拆解 VolumeSnapshot/VolumeSnapshotClass/VolumeSnapshotContent 在 RBD 与 CephFS 上的保证;对照目录级 .snap 的语义差;覆盖 expand、静态 PV、clone/restore,以及 external-snapshotter 与 group snapshot CRD 版本错位失败模式。
【Rook / CSI】选型收束:排除树、开放问题与系列边界关闭
用机制排除树收束 Rook+Ceph-CSI 相对云 CSI、Longhorn、裸 Ceph 的选型;回收系列开放问题与 ceph/18 三条摩擦;标明何时不该跑 Rook;Tentacle+/Crimson 外链 Ceph 系列;以 ADR 友好语言关闭编排续作边界。
【Rook / CSI】编排全景:五轴坐标系与 16 篇路线
相对 ceph/ RADOS 内核与 storage/ 云块选型,钉清 Rook/CSI 编排层缺口;定义 CSI RPC、sidecar、Operator/CRD、数据面归属、升级闸门五条坐标系,给出 16 篇阅读路线与 PVC Bound 之后 I/O 仍走 krbd→PG→BlueStore 的边界。