土法炼钢 · 系统与基础设施

【Rook / CSI】CSI 规范与 K8s 接线:Controller、Node 与 sidecar

文章导航

分类入口
storagekubernetes
标签入口
#csi#kubernetes#sidecar#provisioner#node-stage#volumesnapshot#rook

目录

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

驱动名会进入 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

要点:

  1. PVC Bound 只说明 provisioner 路径走通(或静态 PV 已绑定),不说明 NodePublish 已成功。
  2. Pod Running 且卷已挂才覆盖 Stage/Publish;中间任一 RPC 失败会以 Events、VolumeAttachment 状态或 kubelet 日志表现。
  3. 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 供给

  1. 用户创建引用 StorageClass 的 PVC。
  2. external-provisioner 看到未绑定 PVC,向 CSI Controller 发 CreateVolume(容量、参数、可选快照源等)。
  3. Ceph-CSI Controller 联系 Ceph(密钥通常来自 Secret 引用),创建 RBD image 或 CephFS subvolume。
  4. provisioner 根据返回的 volume_id 与能力创建 PV,并完成与 PVC 的绑定。

失败模式(语义级):

4.2 调度与附加

  1. 创建使用该 PVC 的 Pod;调度器选节点(可能受拓扑/延迟绑定影响)。
  2. 若驱动需要 ControllerPublish,attacher 路径会创建/更新 VolumeAttachment 并调用对应 RPC。

4.3 节点挂载

  1. 目标节点 kubelet 调 NodeStageVolume:例如 RBD map 到设备,挂到 staging。
  2. kubelet 调 NodePublishVolume:把 staging 内容呈现到 Pod 卷目录(文件系统卷常为 bind-mount;块模式则是设备节点语义)。
  3. 容器启动后,应用 I/O 进入数据面——对 RBD 默认即 krbd → PG → BlueStore(ceph/12ceph/01)。

卸载按相反顺序:NodeUnpublishNodeUnstage →(可选)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 扩展面

奠基与工程化

  1. CSI Spec 解决的问题是:存储驱动不应继续以「进 kube 树」为唯一分发路径;用稳定 RPC 面换独立发布节奏。
  2. Kubernetes CSI GA 博文(2019) 把用户故事钉在 PVC/PV/StorageClass 不变、底层换成 CSI;并公开 sidecar 分工。
  3. 迁移运动:后续多个版本推动 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,并在 OperatorConfigDriver 上设 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 默认叙事里:

把本篇收成口诀:

  1. Pending → 先查 provisioner 与 CreateVolume
  2. 已 Bound 但 Pod 起不来 → 先查 Stage/Publish 与 VolumeAttachment。
  3. 已挂载但慢/怪 → 先分清是 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/快照的第三列补全;本篇只保证前两列的名字稳定。


八、小结

  1. CSI 用 Controller/Node 拆分集群级供给与节点级挂载;关键 RPC 包括 CreateVolumeNodeStageVolumeNodePublishVolume
  2. K8s 用 external-provisioner / attacher / resizer / snapshotter 与 node-driver-registrar 把 API 对象接到 RPC;CSI GA 于 1.13。
  3. PVC Bound ≠ 挂载成功;挂载成功 ≠ I/O 问题在编排层。
  4. 谱系是 in-tree → CSI Spec → sidecar 生态;争论焦点之一是 Spec 最小面 vs CSI-Addons。
  5. 下一篇进入 Rook Operator:谁在调和 CephCluster,以及它负责哪些 RADOS 内核语义。

九、参考资料

规范 / 官方文档(A)

论文 / 设计脉络(A/B)

站内对照

实验台账


上一篇:编排全景 · 系列目录 · 下一篇:Rook Operator 与 CRD

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。

2026-08-15 · storage / kubernetes

Rook / CSI:K8s 上的 Ceph 编排内核

补齐站内 Ceph RADOS 内核之上的编排层:CSI 规范与 sidecar、Rook Operator/CRD、Ceph-CSI Operator、RBD/CephFS 供给与挂载、VolumeSnapshot 语义差、升级闸门,以及排障与选型收束。


By .