PVC 卡在 Pending
时,值班最常问的是「调度坏了还是存储坏了」。对 Rook 上的 RBD
来说,真正要回答的是:声明式的
StorageClass 参数,如何经
external-provisioner 变成 Ceph-CSI 的
CreateVolume,再变成池里的 Format 2
image。 挂载与 features
错位留给下一篇;本篇只钉供给路径。
本文是「Rook / CSI」系列第 6 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 5 篇 · Ceph-CSI Operator OperatorConfig/Driver第 6 篇 · RBD 供给 StorageClass → CreateVolume → image 第 7 篇 · 挂载与 features NodeStage/Publish;krbd 错位
版本锚定:Rook v1.20.4;Ceph-CSI v3.17.0;K8s v1.31–v1.36。后端机制叙述对齐站内 ceph/12 · RBD(Squid);Ceph 集群支持 Squid v19.2.x / Tentacle v20.2.1+。无真实 Rook 集群时不粘贴伪造
kubectl输出;配置片段取自 Rook v1.20 官方示例语义。
一、供给路径的坐标系
一次动态供给最少穿过四层:
| 层 | 对象 | 失败时常见表象 |
|---|---|---|
| API | PVC / StorageClass | Pending,Events 无 CSI 日志 |
| Sidecar | external-provisioner |
一直重试、leader 选主异常 |
| CSI Controller | Ceph-CSI RBD CreateVolume |
mon 不可达、Secret 错误、池不存在 |
| RADOS | Format 2 image + header OID | toolbox 里 rbd ls 看不到期望 image |
flowchart LR
pvc["PVC Bound request"] --> sc["StorageClass\nprovisioner + params"]
sc --> ep["external-provisioner\nsidecar"]
ep --> cv["CSI CreateVolume"]
cv --> img["RBD Format 2 image\nin pool"]
img --> pv["PV + volumeHandle"]
I/O 尚未开始:PVC Bound 只保证「有一块可挂的
image」。对象如何按 order 切 OID、写如何进
PG/BlueStore,见 ceph/12 与 ceph/08。本篇不重写
BlueStore。
二、provisioner 命名:命名空间前缀不是装饰
Rook 默认把 CSI 驱动名做成「Operator 命名空间 +
驱动后缀」。官方块存储示例(deploy/examples/csi/rbd/storageclass.yaml,Rook
v1.20 文档):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
clusterID: rook-ceph
pool: replicapool
imageFormat: "2"
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
csi.storage.k8s.io/controller-publish-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/controller-publish-secret-namespace: rook-ceph
csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
csi.storage.k8s.io/fstype: ext4
reclaimPolicy: Delete
allowVolumeExpansion: true要点:
provisioner: rook-ceph.rbd.csi.ceph.com:前缀rook-ceph必须与 Rook Operator / CSI 所在命名空间一致。Operator 装在my-namespace时,应写成my-namespace.rbd.csi.ceph.com(Rook「Ceph CSI Drivers」文档硬条件)。- v1.20:驱动名由 Ceph-CSI Operator 的
DriverCRmetadata.name决定,不再靠改rook-ceph-operator-config里的 CSI 键来「偷偷改名」。Helm 路径用ceph-csi-driverschart 的推荐values.yaml,显式写出带命名空间前缀的驱动名。 - VolumeSnapshotClass 的
driver字段必须与 StorageClass 的provisioner同名。只改一边会导致快照 RPC 找不到驱动。
把前缀当成可随便起的 DNS 名,是多集群联邦或「复制粘贴 SC」时最常见的 Pending 根因之一:API 对象合法,但集群里根本没有该名的 CSIDriver。
三、StorageClass 参数如何映射到 CreateVolume
3.1 必填语义
| 参数 | 含义 | 错了会怎样 |
|---|---|---|
clusterID |
Rook 文档口径:通常为 CephCluster 所在命名空间 | mon endpoint / CSI config 对不上 |
pool |
image 所在的 CephBlockPool 名 |
CreateVolume 失败:池不存在 |
| Secret 四元组 | provisioner / expand / publish / node-stage | auth 失败或挂载时无 node 凭据 |
imageFormat |
默认 "2" |
Format 1 已弃用;生产只谈 Format 2 |
imageFeatures |
Format 2 特性位的符号列表 | 供给成功但后续 map 失败或行为怪(见第 7 篇) |
CephBlockPool 必须先存在。示例里
replicapool 的 failureDomain: host
与 replicated.size: 3 要求至少三个不同节点上的
OSD;这是池放置约束,不是 CSI
本身的问题。池创建失败时,PVC 会在 CreateVolume
阶段报错,而不是在调度阶段。
3.2 Secret 分工
Rook Operator 在集群命名空间生成:
rook-csi-rbd-provisioner:Controller 侧 Create/Delete/Expand/ControllerPublish 用。rook-csi-rbd-node:NodeStage 映射 image 用。
凭据对应 Ceph 客户端 cap(通常是 profile rbd
一类)。Secret 命名空间写错、或手工轮换 keyring 后未同步 CSI
Pod,表现为间歇性 CreateVolume / map 失败。排障时先对 Secret
存在性与命名空间,再怀疑 mon。
3.3 fstype 与扩容
csi.storage.k8s.io/fstype:未指定时 provisioner 侧常默认ext4。Rook 文档明确:超融合场景不推荐xfs(与 OSD 同节点挂载时有死锁风险叙述)。allowVolumeExpansion: true只打开 K8s 扩容入口;还需 expand Secret。真正改 image 大小与文件系统 online resize 的路径在第 9 篇。
四、CreateVolume 落成什么
Ceph-CSI RBD 在 CreateVolume 中(语义对齐
ceph-csi RBD 文档与 Rook 块存储章):
- 用 provisioner Secret 连 mon,确认
pool可用。 - 按 PVC 请求容量创建 Format 2 image(名称通常由 CSI
生成,带
csi-vol-一类前缀;以实际rbd ls/ PVvolumeHandle为准)。 - 写入 StorageClass 声明的
imageFeatures。 - 返回
volume_id/ 上下文,供 K8s 写成 PV 的 CSIvolumeHandle与volumeAttributes。
image 一经创建,RADOS 侧就有:
rbd_header.{image_id}:大小、order、features、snap 列表。rbd_data.{image_id}.{object_number:016x}:数据对象命名规则。
偏移到对象序号的公式、striping、与 Exclusive Lock 的关系,见 ceph/12 · image 到对象映射。编排层只需记住:PVC 容量是 image 逻辑大小;实际占用随写入与快照 COW 增长,不是「一创建就占满 GiB」。
五、imageFeatures:供给时就要为挂载买单
Rook 官方示例对内核版本给出两档建议:
| 内核 | 建议 imageFeatures |
对应社区叙述 |
|---|---|---|
| ≥ 5.4 | layering,fast-diff,object-map,deep-flatten,exclusive-lock(位掩码叙述为
63) |
krbd 支持较完整特性集 |
| ≤ 5.3 | 仅 layering(位掩码叙述为 2) |
避免 map 时特性闸门失败 |
ceph/18
点名的摩擦之一是:CSI 声明的 features 与节点 krbd
能力不一致。供给阶段往往成功——Controller
节点不一定是将来挂载的节点,异构内核集群尤其如此。失败被推迟到
NodeStage:有时是干净的
unsupported krbd Feature,有时表现为「挂上但怪」(第
7 篇展开)。
工程纪律:
- 以最旧可调度节点的内核能力为 StorageClass 上限,而不是以构建机内核为准。
- 需要
journaling(镜像)等特性时,显式声明并确认 mounter 路径(krbd vs rbd-nbd)支持面。 - 不要在生产 SC 上默默打开「文档里出现过的所有 feature 名」。
六、Pending 归因:先分清层再下刀
| 观察 | 更可能落点 |
|---|---|
| PVC Events 无 provisioner 相关信息 | StorageClass 名错、无默认 SC、调度器未处理到供给 |
| provisioner 日志反复 CreateVolume | mon endpoint、clusterID、Secret、池名 |
| CreateVolume 成功但 Pod 不调度 | 与存储无关的调度约束 |
| Bound 后 MountVolume 失败 | 第 7 篇:map / features / Secret node |
无集群截图时,仍可用命令语义自检(需可调度集群与 Rook toolbox):
kubectl get sc,pvc -A
kubectl -n rook-ceph get pods -l app=csi-rbdplugin
# toolbox 内:
# rbd ls -p replicapool
# rbd info replicapool/<image>不要用「Ceph 慢」概括所有 Pending:供给路径上多数是配置与身份,不是 BlueStore 延迟。
七、谱系、争论与开放问题
谱系:块设备作为「RADOS
上的对象序列」可追溯到 Weil 的 Ceph 设计叙述与 RBD Format
2;K8s 侧则从 in-tree plugin / FlexVolume 迁到
CSI(规范定义 Controller
CreateVolume 与 Node 服务的最小面,见
container-storage-interface/spec)。Rook 把
CephBlockPool + StorageClass
钉成声明式入口;v1.20 再把驱动配置所有权交给 Ceph-CSI
Operator(第 5、11 篇)。
争论:StorageClass 应暴露多少 Ceph
细节?一端要求「应用只看见
storageClassName」;另一端要求运维能直接读到
pool、features、fstype。Rook
选择显式参数——可读、可
diff,也更容易复制粘贴出错。没有文献证明「隐式默认永远更安全」;生产上更稳的是:少数经过内核矩阵验证的
SC,而不是每个租户一份私货 features。
开放问题:
- 调度前能力探测:能否在 CreateVolume
或调度器扩展里拒绝「将来无法在候选节点 map」的 PVC,而不是
Bound 后再挂载失败?(ceph-csi 有挂载时 feature 检测与
tryOtherMounters;调度前闭环仍弱。) - 多 CephCluster / 多前缀:同一 API
Server 上多个 Rook 命名空间时,provisioner 前缀与
clusterID的可观测错位如何做成准入策略,而不是靠文档提醒?
八、与 CephBlockPool、回收策略的交接
供给路径上还有两个常被当成「CSI 细节」、实际是 RADOS 放置与生命周期 的开关。
池的 failureDomain 与副本数决定
CreateVolume 成功之后,image
的对象能不能按预期分散故障域。三副本却只备了两个节点的 OSD
时,池可能一直不干净或根本建不出来;PVC 侧只会看到
CreateVolume 重试。这时应回到 CephBlockPool /
ceph osd tree 一类坐标系,而不是调大
provisioner 并发。
reclaimPolicy:
Delete:删 PVC 后走 DeleteVolume,image 按驱动逻辑删除(仍可能受 trash/延迟删除影响,机制见 ceph/12 trash 叙述)。Retain:PV/PVC 删了,RBD image 仍留在池里;Rook 文档提醒需运维手工rbd rm(或等价流程)清理,否则池被「遗忘 image」占满。
多租户平台若默认 Retain
却没有回收工单,半年后会出现「容量监控显示池将满,但活跃 PVC
并不多」——根因在编排回收策略,不在 BlueStore 碎片。
EC 池作 RBD 数据池时,Rook 文档要求独立 replicated
元数据池 + dataPool
参数,且挂载节点内核有下限叙述。本篇不展开 EC
条带;要点是:StorageClass
参数必须与池拓扑一致,否则 CreateVolume 或后续 I/O
在错误层失败。
九、工程间隙:论文式「动态供给」与生产异构
CSI 规范把 CreateVolume 写成相对干净的 RPC:给容量与参数,回 volume id。生产 Rook 集群里,同一 StorageClass 会被:
- 不同内核的节点挂载(features 谈判推迟到 NodeStage);
- 不同命名空间的复制粘贴(provisioner 前缀漂移);
- GitOps 与手工
kubectl混改 Secret(身份漂移)。
规范假设「集群内驱动与节点能力同质」;异构是默认现实。因此第 6 篇的验收不应停在「示例 Wordpress PVC Bound」,而应包括:用计划中的最旧节点可调度类跑一次完整挂载(完整路径见第 7 篇)。只在新内核跳板机上验收供给,是把失败外包给上线当晚。
相对云盘 CSI:云厂商把 features 谈判收进控制面黑盒;Rook 把谈判摊开成 YAML。摊开可读,也要求平台自己维持内核矩阵——这是自建税,不是文档缺陷。
十、本篇边界
不写:BlueStore 写路径、PG peering、完整 Helm values
百科、云厂商托管块 API。
下一篇从 Bound 之后的 NodeStageVolume /
NodePublishVolume 讲起,专钉 features 与
krbd。
把本篇收成一句话:Pending 时先问 provisioner 名、Secret、池与 CreateVolume 日志;Bound 只说明 image 在,不说明节点能 map。 对象命名与 COW 账单去 ceph/12;编排层到此交棒。
十一、供给并发、限流与「假死」
external-provisioner 与 CSI Controller 对 CreateVolume 有并发与重试行为(具体旗标随 sidecar 版本变化,以部署清单为准)。池或 mon 短暂抖动时,大量 PVC 同时 Pending 会放大重试风暴:日志看起来像「存储彻底坏了」,实际是控制面自激。
工程上可做的分层:
- 发布窗口限制同时创建的 PVC 批量;
- 区分「永久错误」(池不存在、provisioner 名错误)与「可重试错误」(超时),避免把永久错误留在无限重试里;
- 观察 provisioner 领导者是否频繁切换——选主抖动会让供给进度像随机停顿。
这些都不改变 RADOS 语义,却决定「人为故障」是否被误报成 Ceph 事故。第 12–13 篇会把指标与事件字段收成坐标;本篇先强调:供给路径的尾延迟包含 API Server 与 sidecar 队列,不是只有 OSD commit。
与云盘对照:托管控制面通常把重试与配额错误翻译成明确的 PVC Event 原因码;Rook 自建则要求值班会读 CSI 容器日志。编制不足时,这本身就是选型负分(见系列第 14–16 篇排除树方向)。
再补一条身份轮换纪律:Ceph 客户端 key 轮换后,必须确认 provisioner/node 两类 Secret 与 CSI Pod 已加载新内容,再宣布「轮换完成」。只改 mon 侧用户、不重启/不滚动 CSI,会造成「旧卷还能挂、新卷 CreateVolume 失败」的撕裂态,排障时极像随机故障。
十二、从系列阅读路径看本篇位置
必读核心路径(见 系列目录)把第 6 篇放在「知道 Operator 与 CSI Operator 之后、真正碰 PVC 之前」的位置。没有第 2 篇的 RPC 坐标系,CreateVolume 只是黑盒函数名;没有第 5 篇,你会在 v1.20 上继续改已经失效的 ConfigMap 键。本篇往下交给第 7 篇,是因为供给成功是挂载失败的必要非充分条件。
若读者只运维 RBD、不碰 CephFS,仍建议至少浏览第 9 篇的 VolumeSnapshot 与 CRD 错位:备份链路坏掉时,症状常被误报成「Rook 块存储坏了」。第 11 篇的升级闸门则决定你是否会在跳版本后同时失去供给能力与快照能力。
站内 ceph/12 与本篇的分工再强调一次:那边回答 image 如何变成 OID 与 COW 如何触发;这边回答 K8s 如何决定「该创建一个多大、带哪些 features 的 image」。两边都读完,才能在「挂上但怪」时判断该看内核还是该看 PG。
容量规划上,动态供给会让「池里有多少 image」变成 API
速率的函数。缺少配额(Namespace ResourceQuota /
存储配额策略)时,一个 CI Namespace 就可以用循环 PVC
打满池。这不是 Ceph 特有问题,但在自建 Rook
上没有云账单熔断,崩溃来得更沉默。平台侧应把 PVC
数量与请求容量的上限写成与 StorageClass
绑定的策略,而不是靠事后 rbd ls | wc。
最后,关于「示例配置能否直接生产」:Rook 官方
imageFeatures: layering
示例偏保守,适合作为默认起点;把注释里的完整特性集复制进生产
SC
而不做内核矩阵,是把文档里的「可选优化」误读成「推荐必开」。保守默认
+ 按工作负载开第二套 SC,通常比一套激进 SC 更安全。
参考资料
规范 / 官方文档(A)
- CSI Spec — Controller
CreateVolume/ 卷生命周期(container-storage-interface/spec) - Rook v1.20 — Block
Storage Overview — StorageClass
示例、
imageFeatures与内核分档、Secret 字段 - Rook v1.20 — Ceph CSI Drivers — 命名空间前缀、扩容前提
- Rook v1.20 — CSI
Configuration —
Driver名即 provisioner 名 - Ceph-CSI — RBD StorageClass
参数说明(
docs/rbd/deploy.md@ v3.17.0 语义)
站内
- ceph/12 · RBD — OID 命名、features、快照 COW
- ceph/18 · 选型终章 — features/krbd 摩擦点名
- 第 2 篇 · CSI 规范与 K8s 接线
- 第 5 篇 · Ceph-CSI Operator
→ 上一篇:Ceph-CSI Operator · 系列目录 · 下一篇:挂载与 features
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Rook / CSI】挂载与 features:NodeStage、krbd 错位与 read affinity
拆解 RBD 的 NodeStageVolume/NodePublishVolume、map 路径与 imageFeatures 对 krbd 的依赖;说明失败为何常表现为挂上但怪;钉死 csi.readAffinity 与 Tentacle v20.2.0 损坏警告,并简述 network fencing。
【Rook / CSI】CephFS CSI:subvolume、RWX 与编排侧 MDS 税
拆解 Rook 上 CephFS CSI 如何用 subvolume 兑现 ReadWriteMany;钉死 provisioner 命名、StorageClass 与配额边界,并说明编排侧为 MDS/caps 付出的税——外链 ceph/13,不重写 journal 内核。
【Rook / CSI】快照与克隆:VolumeSnapshot 语义、扩容与 CRD 错位
拆解 VolumeSnapshot/VolumeSnapshotClass/VolumeSnapshotContent 在 RBD 与 CephFS 上的保证;对照目录级 .snap 的语义差;覆盖 expand、静态 PV、clone/restore,以及 external-snapshotter 与 group snapshot CRD 版本错位失败模式。
【Rook / CSI】对象与 NFS:RGW 暴露边界与实验性 NFS CSI
划清 CephObjectStore/RGW 对 K8s 应用的暴露边界;说明 Rook NFS CSI 的 experimental 地位与依赖(CephFilesystem+CephNFS,非 RGW);本系列不写成 S3 教程——机制外链 ceph/14。