【K8S】Kubernetes CSI(Container Storage Interface)深度解析
一、为什么需要 CSI(背景速记)
- 早期 Kubernetes 使用 in-tree 存储驱动(内置在 kube 源码),扩展和维护成本高;CSI 将驱动移出内核/控制平面,以统一的 gRPC 接口让第三方存储厂商无需改动 k8s 源码就能提供插件。CSI 从 alpha 到 GA 的演进使其成为主流存储扩展方案。(GitHub)
二、总体架构(概念图 + 角色)

说明:CSI 驱动通常分为 Controller(控制端)与 Node(节点端) 两个进程/容器;外部的 sidecar(provisioner/attacher/resizer/snapshotter/node-registrar)负责与 k8s API 和 CSI driver 做 glue。详见官方 sidecar 设计与实现。(kubernetes-csi.github.io)
三、CSI 的“核心 RPC”(简明表)
CSI 的接口用 gRPC 定义(见 csi.proto),关键 RPC 与职责如下(控制器 vs 节点):
Controller RPC(只由 controller-side driver 实现)
CreateVolume/DeleteVolume— 创建/删除后端卷(动态供给)。(GitHub)ControllerPublishVolume/ControllerUnpublishVolume— 将卷“attach”/“detach”到某个 Node(云盘 attach 类操作)。(GitHub)ControllerExpandVolume— 控制面扩容(若驱动支持)。(kubernetes-csi.github.io)ListVolumes,ListSnapshots,CreateSnapshot,DeleteSnapshot等(视能力而定)。(GitHub)
Node RPC(只在节点上实现)
NodeStageVolume/NodeUnstageVolume— 在 node 级做“staging mount”(格式化/挂载到 global path)。NodePublishVolume/NodeUnpublishVolume— 把已经 staging 的卷 mount 到 Pod 的目录(或对 block device 做 bind)。NodeGetInfo,NodeGetCapabilities— 返回节点能力与拓扑标签(用于拓扑感知调度/绑定)。(GitHub)
重要:kubelet 在 Pod 调度到节点并需要卷时会发起
NodeStageVolume/NodePublishVolume(或仅NodePublishVolume用于 ephemeral inline 卷)。这些调用是 kubelet ↔ CSI Node plugin 的交互关键点。(Kubernetes)
四、常见功能与控制流程(Mermaid 流程图 + 说明)
1) 动态供给(PVC → PV → CreateVolume)
说明:external-provisioner 监听 PVC(匹配 StorageClass.provisioner),调用 CSI 的 CreateVolume,成功后创建 PV 对象并绑定 PVC。它带有重试/leader-election/超时机制(源码实现有重试回退与并行 worker 限制)。(GitHub)
2) Attach / Stage / Publish(Pod 启动、Pod 所属节点执行挂载)
说明:Attach(ControllerPublish)通常由 external-attacher 发起(如果驱动需要 “attach”),随后 kubelet 与 Node plugin 完成 NodeStage/NodePublish。VolumeAttachment CRD 是控制 Attach 的 k8s 对象。(GitHub)
3) 扩容与快照(简要)
- 扩容:用户编辑 PVC →
external-resizer触发ControllerExpandVolume(control-side),如需 node-level 扩容,kubelet 再调用NodeExpandVolume。(GitHub) - 快照:
external-snapshotter监听VolumeSnapshotCRD,调用CreateSnapshot/DeleteSnapshot。(GitHub)
五、sidecar(外部控制器)角色详解(为什么需要、各自职责)
CSI 设计中把与 Kubernetes API 的 glue 放在“sidecar”里,原因是:Kubernetes control-plane 原有控制循环(controller-manager)无法直接调用外部 gRPC driver,于是用外部进程做桥接。常见 sidecars 与要点:
- node-driver-registrar:把 driver socket 注册到 kubelet(让 kubelet 知道 driver 在哪个 unix socket 上),并促成
CSINode对象的填充。必须存在于 node-side pod。(GitHub) - external-provisioner(csi-provisioner):监听 PVC,调用
CreateVolume/DeleteVolume并创建/删除 PV。实现 leader-election、重试/backoff、capacity 对接等。源码位于kubernetes-csi/external-provisioner,实现了多线程 worker、超时策略与“selected node”机制(支持本地 PV 的distributed provisioning)。(GitHub) - external-attacher(csi-attacher):监听
VolumeAttachment,调用ControllerPublishVolume/ControllerUnpublishVolume(attach/detach)。实现周期性 re-sync(当 driver 支持 LIST_VOLUMES/… 能力时使用)。(GitHub) - external-resizer:监听 PVC 的 size 变更并调用
ControllerExpandVolume,并更新 PV/PVC 状态。(GitHub) - external-snapshotter:实现 VolumeSnapshot CRD 的控制逻辑(Create/Delete/List Snapshot)。(GitHub)
- livenessprobe:健康检查 driver socket,防止 kubelet/sidecar 因 driver 卡住导致故障隐蔽。
官方对这些 sidecar 的说明与推荐打包模式在 Kubernetes CSI docs 有完整说明。(kubernetes-csi.github.io)
六、源码级别入口与控制循环(阅读导航 + 关键实现点)
下面把你带到几个“起点”源文件(我不会贴太多逐字原文,但会指明函数/文件并概述逻辑),方便你直接跳进代码阅读并 trace 行为。
1) external-provisioner(动态供给)
-
仓库:
kubernetes-csi/external-provisioner。主逻辑在cmd/csi-provisioner与pkg/controller(或使用 sigs.k8s.io 的ProvisionController)。外面一层做 leader-election,内部ProvisionController.Run(ctx)启动 watch loops 并派 worker 去处理 PVC → CreateVolume。它处理的关键细节包括:- 对
ControllerCreateVolume的超时与幂等:timeout 被当成可能已经创建完成(因此会重试并检查结果),并带 exponential backoff。 - 支持 snapshot 作为数据源:会把 snapshot info 填入 CreateVolume 的 request。
- 支持 topology / capacity checks(用于本地 PV 分布式部署)。(GitHub)
- 对
如果你想看具体入口,打开
sigs.k8s.io/sig-storage-lib-external-provisioner/.../controller的Run/Provision实现(pkg.go.dev 上可查到ProvisionController.Run的 doc 与代码链接)。(Go Packages)
2) external-attacher(attach/detach)
- 仓库:
kubernetes-csi/external-attacher。主循环 watchVolumeAttachment对象,执行 ControllerPublish/Unpublish;实现重试、Probe、periodic reconcile(如果 driver 支持 ListVolumes 等)等。关键实现可在pkg/controller中找到(watch -> worker goroutines -> call CSI with timeout/backoff)。(GitHub)
3) node-driver-registrar(注册)
- 仓库:
kubernetes-csi/node-driver-registrar。它向 kubelet 注册 driver(通过 kubelet 的 plugin-registration socket),生成 CSINode/CSIDriver 元数据(告诉 kubelet driver 的 node socket 路径 & capabilities),其工作非常轻量但至关重要。(GitHub)
七、拓扑(Topology)、亲和性与调度
- CSI 支持Topology 信息(
TopologyRequirement/AccessibilityRequirements):驱动可在CreateVolume时接收拓扑要求(例如要求在某个 AZ/节点池创建卷),驱动的GetNodeInfo/NodeGetInfo也可上报节点标签用于调度。Kubernetes scheduler 可以使用CSINode/ 存储类的volumeBindingMode、Topology来实现就近调度。(kubernetes-csi.github.io)
八、常见问题、失败场景与调试要点(实战清单)
-
PVC 卡在
Pending/ “waiting for external provisioner”- 检查
external-provisionerpod 是否 Running、日志是否报错(权限、无法连到 CSI socket、CreateVolume 超时)。kubectl describe pvc会有事件日志。(GitHub)
- 检查
-
Pod 挂载失败(NodePublishVolume 错误)
- 查看对应 node 上的 csi-node pod(node plugin)日志与 kubelet 日志(kubelet 会记录 NodePublishVolume 调用与错误)。注意
requiresRepublish与节点重启带来的挂载刷新。(GitHub)
- 查看对应 node 上的 csi-node pod(node plugin)日志与 kubelet 日志(kubelet 会记录 NodePublishVolume 调用与错误)。注意
-
Attach/Detach 卡滞
- 查看
VolumeAttachment对象(kubectl get volumeattachment -o yaml),并检查external-attacher日志以及 driver 的 ControllerPublish 调用失败原因。(GitHub)
- 查看
-
扩容失败
- 确认 StorageClass 允许扩容(
allowVolumeExpansion),确认 driver 报告扩容 capability,查看external-resizer与 driver 的ControllerExpandVolume调用与结果。(kubernetes-csi.github.io)
- 确认 StorageClass 允许扩容(
-
Snapshot 创建/恢复失败
- 检查
VolumeSnapshotContent、VolumeSnapshot对象、external-snapshotter日志以及 driver 的CreateSnapshot返回。(GitHub)
- 检查
九、安全(Secrets / tokens / 权限注意)
- Secrets & tokens:StorageClass、PV、CSIDriver 可以配置 secretRef;当 kubelet 调用
NodePublishVolume时,可以把 pod 的 service-account token(通过tokenRequests)传给驱动以做后端认证(例如访问多租户存储)。此外,扩容时也支持nodeExpandSecretRef。必须管理好这些 secret 的 RBAC/密钥生命周期。(红帽文档) - 权限(RBAC):sidecar(provisioner/attacher/resizer)需要相应的 API 权限(创建 PV、更新
CSINode/VolumeAttachment等),部署 manifests 中通常包含 RBAC 配置(repo README 或 deploy/ 下有样例)。(GitHub)
十、设计/实现上的一些细节与陷阱(工程视角)
- 幂等与超时处理:后端存储可能在
CreateVolume返回之前就已经创建完成(或在 driver 内部异步执行),provisioner 要对 timeout 做“可能已经创建”假设并重试/查询,避免产生重复卷或漏删。external-provisioner 有相应逻辑(超时被认为“可能在后台创建”并会重复查询/重试)。(GitHub) - 并发限制:外部控制器用 worker threads;对后端并发能力要有配置(
--worker-threads、timeout、backoff)。过高并发会压垮 storage backend。(GitHub) - 本地存储(local PV):若驱动对每个节点有本地资源(local PV),external-provisioner 可以以 daemonset 方式在每个节点运行并做 node-local provisioning(有
--node-deployment支持及GetCapacity校验)。(GitHub) - CSI migration(in-tree -> CSI):Kubernetes 提供了 in-tree to CSI migration 方案,使现有 in-tree 驱动能无缝迁移到 CSI。社区在逐步移除 in-tree 插件(v1.25+ 相关里程碑)。如果你在维护集群迁移流程,请留意官方迁移文档和云厂商指南。(Kubernetes)
十一、快速参考(源码入口 & 文档)
- CSI Spec(proto + spec):
container-storage-interface/spec(csi.proto / spec.md)。(GitHub) - Kubernetes CSI docs(sidecars、开发者指南、测试):
kubernetes-csi.github.io/docs(sidecar 文档、volume-expansion、topology 等)。(kubernetes-csi.github.io) - external-provisioner(实现、参数、重试/leader 等):
kubernetes-csi/external-provisioner。(GitHub) - external-attacher / external-resizer / external-snapshotter repos:
kubernetes-csi/external-attacher、kubernetes-csi/external-resizer、kubernetes-csi/external-snapshotter。(GitHub) - Kubelet 与 NodePublish/NodeStage 的集成说明(kubelet 调用、ephemeral 行为):Kubernetes 官方文档 & blog。(Kubernetes)
十二、结论(要点回顾)
- CSI 把存储驱动移出 k8s 核心,以统一 gRPC 接口让厂商能快速发布驱动并与 k8s 集成。(GitHub)
- 核心组件等分工明确:Controller(Create/Attach/Expand)、Node(Stage/Publish)、以及多个 sidecar(provisioner/attacher/resizer/snapshotter/registrar)做 API ↔ driver 的 glue。(kubernetes-csi.github.io)
- 实际工程关注点:重试/幂等、拓扑感知、本地卷能力、RBAC/secret 管理与监控是生产级驱动部署必须考虑的。(GitHub)
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19513684

浙公网安备 33010602011771号