PVC Pending 与 Pod MountFailed 常被写成「存储坏了」。在 CSI 模型里,它们通常落在不同 RPC 与不同进程上:前者多半是 Controller 侧供给,后者多半是 Node 侧 Stage/Publish。没有这套阶段语言,Rook 日志与 Ceph 健康会对不上号。
本文钉三件事:CSI 规范里的服务与关键 RPC、K8s 如何用 sidecar 把 API 对象接到这些 RPC、以及一次 PVC 从创建到 Pod 可写的调用链。Ceph 后端细节外链;Rook 如何部署驱动见后续 Operator 篇。
本篇在系列中的位置
篇目 核心内容 第 1 篇 · 编排全景 五轴坐标系与 16 篇路线 第 2 篇 · CSI 规范与 K8s 接线 Controller/Node、sidecar、PVC→RPC 第 3 篇 · Rook Operator 与 CRD CephCluster 等 CRD 与 reconcile 边界
版本锚定:叙述对齐 CSI Spec 的 Controller/Node 模型与 K8s CSI GA(1.13)接线方式;工程落点为 Rook v1.20.4 + Ceph-CSI v3.17.0 + K8s v1.31–v1.36。无真实集群不粘贴伪造 Events。
一、问题:为什么必须分 Controller 与 Node
CSI 把「存储插件」拆成两类部署角色,而不是一个全能 Daemon:
| 角色 | 典型部署 | 职责直觉 |
|---|---|---|
| Controller 插件 | Deployment(可多副本,需选主) | 供给、删除、扩容、快照、部分 attach |
| Node 插件 | DaemonSet(每节点) | 节点上准备设备、挂载到 Pod 路径 |
这个拆分来自规范假设:集群级操作不必在每个节点上跑;节点级挂载必须靠近 kubelet。 K8s 在 GA 设计里进一步假设:kubelet 通过本机 Unix domain socket 调 Node 服务;Controller 侧则由集群里的 sidecar 监视 API 并调 CSI endpoint(见 Kubernetes CSI design proposal 与 2019 GA 博文)。
对 Ceph-CSI 而言:Controller 侧最终会对话 mon(创建 RBD
image / CephFS subvolume);Node 侧最终会
rbd map / 挂载 CephFS。中间隔着的是 RPC 与
sidecar,不是「kubectl 直接调 librados」。
二、CSI 三类服务与关键 RPC
CSI Spec 定义 gRPC 服务。实现必须通过 Identity 暴露能力;Controller 与 Node 按能力位声明自己支持哪些 RPC。下列是动态供给块/文件卷时最常碰到的调用(名称以 Spec 为准)。
Identity
GetPluginInfo:驱动名与版本。GetPluginCapabilities/ControllerGetCapabilities/NodeGetCapabilities:能力协商。Probe:就绪探测(常被 liveness sidecar 使用)。
驱动名会进入 StorageClass 的 provisioner
字段。在 Rook 默认命名空间下,RBD 常见为
rook-ceph.rbd.csi.ceph.com(前缀随 Operator
命名空间或自定义前缀变化,见 Rook Ceph CSI Drivers
文档)。
Controller 服务(节选)
| RPC | 典型触发者 | 含义 |
|---|---|---|
CreateVolume |
external-provisioner | 按容量与参数在后端创建卷 |
DeleteVolume |
external-provisioner | 回收 |
ControllerPublishVolume /
ControllerUnpublishVolume |
external-attacher(若能力声明需要) | 控制器侧「附加」语义 |
ControllerExpandVolume |
external-resizer | 扩容 |
CreateSnapshot /
DeleteSnapshot |
external-snapshotter | 快照 |
CreateVolume 的参数对 K8s 基本不透明:来自
StorageClass parameters 与 PVC 请求容量。对
RBD,这些参数最终映射到 pool、image features 等(第 6–7
篇);对 CephFS,映射到 filesystem / subvolume(第 8
篇)。
规范要求关键操作幂等:重试不应制造第二个真实卷或第二次破坏性删除。排障时「Events 里多次 Provisioning」不一定等于后端建了多个 image——要看驱动是否用 volume name / volume ID 做去重。
Node 服务(节选)
| RPC | 典型触发者 | 含义 |
|---|---|---|
NodeStageVolume |
kubelet | 把卷准备到节点级 staging 路径(每节点每卷通常一次) |
NodeUnstageVolume |
kubelet | 反向 |
NodePublishVolume |
kubelet | 从 staging(或直接)挂到 Pod 的
target_path |
NodeUnpublishVolume |
kubelet | 反向 |
NodeGetInfo |
node-driver-registrar / kubelet | 节点 ID 与拓扑信息 |
NodeExpandVolume |
kubelet | 节点侧扩容(文件系统伸展等) |
Stage 与 Publish 的分工是排障关键:Stage 失败常见于设备 map、多路径、密钥或内核模块;Publish 失败常见于目标路径、SELinux、bind-mount。RBD features 与节点 krbd 能力不一致时,失败可能落在 Stage,也可能表现为「挂上但 I/O 怪」——后者是第 7 篇的主题,本篇只钉阶段边界。
若驱动声明不需要 Stage,工作可能全部挤进 Publish;Ceph-CSI 的 RBD/CephFS 路径通常使用 Stage+Publish 两段式(以具体驱动能力为准)。
能力协商失败时,表象往往不是「规范写错了」,而是 sidecar
调用了一个驱动未声明的 RPC,或 kubelet 按旧假设走了
Stage。排障时应同时看:驱动 Get*Capabilities
结果(或启动日志中的 capability 集合)、sidecar
镜像版本、以及 K8s 小版本对 VolumeAttachment / 扩容 /
快照的要求。Rook 支持矩阵钉在 v1.31–v1.36,不意味着任意旧
sidecar 组合都在支持面内。
三、K8s sidecar:把 API 对象变成 RPC
Kubernetes 在引入 CSI 时选择:不把所有控制逻辑写进 kube-controller-manager / kubelet 的存储专用分支,而是发布一组可版本独立演进的 sidecar。2019-01-15 的 GA 博文明确列出 external-provisioner 等组件的职责。
| Sidecar | 监视的主要对象 | 调用的 CSI RPC(直觉) |
|---|---|---|
| external-provisioner | PersistentVolumeClaim(及 StorageClass) | CreateVolume /
DeleteVolume;成功后创建/绑定 PV |
| external-attacher | VolumeAttachment 等 | ControllerPublish /
ControllerUnpublish(若需要) |
| external-resizer | PVC 容量变大 | ControllerExpandVolume |
| external-snapshotter | VolumeSnapshot / VolumeSnapshotContent | CreateSnapshot /
DeleteSnapshot |
| node-driver-registrar | (向 kubelet 注册) | NodeGetInfo 等,完成插件登记 |
另外,Ceph-CSI / Rook 部署里常见 liveness 一类 sidecar,暴露驱动存活指标;这不属于 CSI Spec 的核心 RPC,但属于工程可观测面(第 12 篇)。
sequenceDiagram
participant User
participant API as K8s_API
participant Prov as external_provisioner
participant Ctrl as CSI_Controller
participant Att as external_attacher
participant KL as kubelet
participant Node as CSI_Node
User->>API: create PVC
Prov->>API: watch PVC
Prov->>Ctrl: CreateVolume
Ctrl-->>Prov: volume_id
Prov->>API: create PV bind PVC
User->>API: create Pod using PVC
API->>KL: schedule Pod
Att->>Ctrl: ControllerPublish if needed
KL->>Node: NodeStageVolume
KL->>Node: NodePublishVolume
Node-->>KL: target_path ready
要点:
- PVC Bound 只说明 provisioner 路径走通(或静态 PV 已绑定),不说明 NodePublish 已成功。
- Pod Running 且卷已挂才覆盖 Stage/Publish;中间任一 RPC 失败会以 Events、VolumeAttachment 状态或 kubelet 日志表现。
- sidecar 与 CSI 驱动镜像可以独立升级——这是灵活性,也是第 11 篇「版本错位」税的来源。
sidecar 的另一层工程现实是选主与副本:Controller 插件常以 Deployment 多副本运行,但供给类 RPC 需要领导者,避免双主重复建卷。external-provisioner 等组件用 lease/leader election 处理这件事。因此「ctrlplugin 有两个 Pod」不等于「供给吞吐线性加倍」;副本更多时,首先改善的是可用性与滚动升级时的中断窗口,而不是把 Ceph mon 的供给延迟抹掉。
对快照路径额外钉一句:VolumeSnapshot 相关对象是独立 CRD 面,snapshot-controller 与 external-snapshotter 必须与 CRD 版本对齐。只有 CSI 驱动「实现了 CreateSnapshot」而集群未装快照 CRD/controller 时,用户会在 API 层就失败——这仍属 sidecar 编排轴,不应先怪 Ceph 池。
四、PVC → PV → Pod 挂载调用链(机制)
下面按时间顺序写「动态供给 + 首次挂载」的默认叙事。不贴伪造输出;真实集群上应用 Events 与 CSI 日志核对。
4.1 供给
- 用户创建引用 StorageClass 的 PVC。
- external-provisioner 看到未绑定 PVC,向 CSI Controller
发
CreateVolume(容量、参数、可选快照源等)。 - Ceph-CSI Controller 联系 Ceph(密钥通常来自 Secret 引用),创建 RBD image 或 CephFS subvolume。
- provisioner 根据返回的
volume_id与能力创建 PV,并完成与 PVC 的绑定。
失败模式(语义级):
- StorageClass
provisioner名与集群中 Driver 名不一致 → provisioner 找不到驱动或无人认领。 - Secret / mon endpoint 错误 →
CreateVolume失败,PVC 停在 Pending。 - 后端配额/池不存在 → 同样 Pending,但应在 CSI Controller 日志中看到 Ceph 错误,而不是 kube-scheduler 错误。
4.2 调度与附加
- 创建使用该 PVC 的 Pod;调度器选节点(可能受拓扑/延迟绑定影响)。
- 若驱动需要 ControllerPublish,attacher 路径会创建/更新 VolumeAttachment 并调用对应 RPC。
4.3 节点挂载
- 目标节点 kubelet 调
NodeStageVolume:例如 RBD map 到设备,挂到 staging。 - kubelet 调
NodePublishVolume:把 staging 内容呈现到 Pod 卷目录(文件系统卷常为 bind-mount;块模式则是设备节点语义)。 - 容器启动后,应用 I/O 进入数据面——对 RBD 默认即 krbd → PG → BlueStore(ceph/12、ceph/01)。
卸载按相反顺序:NodeUnpublish →
NodeUnstage
→(可选)ControllerUnpublish;PVC 删除再走
DeleteVolume(取决于 reclaim policy)。
静态供给是一条并行故事:用户(或运维)预先创建指向已有
RBD image / CephFS 路径的 PV,PVC 绑定后仍走 Node
Stage/Publish,但跳过
CreateVolume。Rook 文档确认 RBD 与 CephFS
支持静态 PV/PVC。排障时若看到 Bound 却从未出现 provisioner
成功事件,要先问是不是静态卷,而不是怀疑 provisioner
静默成功。
块模式(volumeMode: Block)与文件系统模式在
Publish 语义上不同:前者把设备节点交给容器,后者完成
mkfs(若需要)与 mount。扩容时二者对
NodeExpandVolume 的依赖也不同。本系列第 9
篇会回到 expand;此处只需记住:RPC
名字相同,后端动作随 volumeMode 分叉。
延迟绑定(WaitForFirstConsumer)会把供给推迟到
Pod 调度之后,以便拓扑感知。此时 Pending
可能同时混合「还没调度」与「CreateVolume 失败」两种原因。读
Events 时要看清是 scheduler 还是 provisioner
在说话,否则会在错误的轴上改 StorageClass。
五、谱系:in-tree → CSI GA → 外部生态
K8s in-tree volume plugins / FlexVolume
→ CSI Spec(社区标准 gRPC 面)
→ K8s CSI alpha(1.9)→ beta(1.10)→ GA(1.13,2019-01)
→ 外部 sidecar 成为生产默认接线方式
→ 厂商驱动(含 Ceph-CSI)+ 可选 CSI-Addons 扩展面
奠基与工程化:
- CSI Spec 解决的问题是:存储驱动不应继续以「进 kube 树」为唯一分发路径;用稳定 RPC 面换独立发布节奏。
- Kubernetes CSI GA 博文(2019) 把用户故事钉在 PVC/PV/StorageClass 不变、底层换成 CSI;并公开 sidecar 分工。
- 迁移运动:后续多个版本推动 in-tree 云厂商插件 CSI Migration;动机是减少 kube 树负担,代价是集群必须同时管好驱动与 sidecar 版本。
工程间隙:论文式「标准接口」叙述很少讨论 sidecar 与 CRD(尤其 VolumeSnapshot)的版本耦合。生产上「规范 RPC 都实现了」仍可能因 snapshot controller / CRD 与 snapshotter sidecar 不匹配而整段快照 API 不可用——这是规范正确性之外的编排税。
六、争论:规范最小面 vs CSI-Addons
| 立场 | 主张 | 代价 |
|---|---|---|
| A:坚持 Spec 最小面 | 只依赖正式 CSI RPC,行为跨发行版可推理 | 复制、failover fencing、部分运维动作缺少标准 RPC |
| B:采用 CSI-Addons 等扩展 | 用额外 CRD + sidecar 表达 VolumeReplication、Network Fencing 等 | 又一层 CRD/控制器版本面;排障坐标系变长 |
Rook v1.20 文档将 CSI-Addons 描述为可选:需部署独立
controller,并在 OperatorConfig 或
Driver 上设
deployCsiAddons: true;Network Fencing
等还要额外字段。本系列立场是:先会走 Spec
主路径(本篇 + 第 6–9 篇),再在第 15 篇讨论
fencing/加密等扩展税——不要在 Pending
都归因不清时先开 Addons。
另一个仍开放的问题:sidecar 版本与集群 CRD
的可观测错位——能否在调度或供给前,用统一信号回答「当前
snapshotter 与 VolumeSnapshot CRD
是否兼容」?今日多依赖人工核对 release notes 与
kubectl 资源版本,缺少单一健康对象。这会影响第
11、12 篇的升级与观测设计。
七、与 Rook / Ceph-CSI 的接线预告
在 Rook v1.20.4 默认叙事里:
- Controller/Node 插件由 Ceph-CSI
Operator 根据
DriverCR 拉起(第 5 篇),不再由 Rook Operator「顺便」拥有全部 CSI 设置。 - 同一命名空间内通常能看到类似
*.rbd.csi.ceph.com-ctrlplugin与*-nodeplugin的工作负载(Quickstart 示例列出了这类 Pod 名);ctrlplugin 内包含多个 sidecar 容器,nodeplugin 含 registrar 与驱动。 - 应用仍只写 PVC/StorageClass;不必直接调 gRPC。排障时才下钻到 sidecar 容器日志与 CSI dump。
把本篇收成口诀:
- Pending → 先查 provisioner 与
CreateVolume。 - 已 Bound 但 Pod 起不来 → 先查 Stage/Publish 与 VolumeAttachment。
- 已挂载但慢/怪 → 先分清是 CSI features/内核,还是 RADOS 五轴(转 ceph/)。
与第 1 篇五轴的对应关系可以写成一张卡片:本篇覆盖的是 CSI RPC 轴 与 Sidecar 编排轴;Operator/CRD、数据面归属、升级闸门要到第 3–5、11 篇才展开。读完本篇仍不会「修好 Rook」,但应能拒绝一种常见误诊——把所有存储故障都写成 Ceph 集群红了。
工程上建议在平台文档里固定三列对照:Kubernetes 对象、CSI RPC、Ceph 后端对象(image / subvolume / snap)。缺任一列,跨团队复盘就会各说各话。第 6–9 篇会把 RBD/CephFS/快照的第三列补全;本篇只保证前两列的名字稳定。
八、小结
- CSI 用 Controller/Node 拆分集群级供给与节点级挂载;关键
RPC 包括
CreateVolume、NodeStageVolume、NodePublishVolume。 - K8s 用 external-provisioner / attacher / resizer / snapshotter 与 node-driver-registrar 把 API 对象接到 RPC;CSI GA 于 1.13。
- PVC Bound ≠ 挂载成功;挂载成功 ≠ I/O 问题在编排层。
- 谱系是 in-tree → CSI Spec → sidecar 生态;争论焦点之一是 Spec 最小面 vs CSI-Addons。
- 下一篇进入 Rook Operator:谁在调和
CephCluster,以及它不负责哪些 RADOS 内核语义。
九、参考资料
规范 / 官方文档(A)
- Container Storage Interface Spec —
github.com/container-storage-interface/spec(Identity / Controller / Node RPC)。 - Kubernetes Blog: Container Storage Interface (CSI) for Kubernetes GA(2019-01-15)— CSI GA in Kubernetes v1.13;sidecar 职责说明。
- Kubernetes design proposal: Container Storage
Interface —
github.com/kubernetes/design-proposals-archive(storage/container-storage-interface.md)。 - Rook v1.20 Ceph CSI Drivers —
rook.io/docs/rook/v1.20/Storage-Configuration/Ceph-CSI/ceph-csi-drivers/。
论文 / 设计脉络(A/B)
- CSI 本身是标准与工程设计,不是单一会议论文;谱系锚点以 Spec + K8s GA 博文 + design proposal 为准。
- 扩展面入口:CSI-Addons 项目与 Rook 文档中的 Network Fencing / VolumeReplication 叙述(B 级操作文档,细节第 15 篇再钉)。
站内对照
实验台账
- 本篇无集群输出;RPC 顺序为规范与 K8s 接线推导。
→ 上一篇:编排全景 · 系列目录 · 下一篇:Rook Operator 与 CRD
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
Rook / CSI:K8s 上的 Ceph 编排内核
补齐站内 Ceph RADOS 内核之上的编排层:CSI 规范与 sidecar、Rook Operator/CRD、Ceph-CSI Operator、RBD/CephFS 供给与挂载、VolumeSnapshot 语义差、升级闸门,以及排障与选型收束。
【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】排障坐标系:Pending、MountFailed、feature 静默失败与升级卡住
按六轴映射 Rook/CSI 常见故障:PVC Pending、MountFailed、RBD feature 静默失败、VolumeSnapshot 未就绪、升级卡住、mon endpoint/Secret 错配;每轴绑定 API/sidecar/CSI/Rook/Ceph 层,并回收 ceph/18 三条编排摩擦。