上一篇【第67篇】CNI深度解析——容器网络接口标准二十行代码就能写一个网络插件下一篇【第69篇】K8s Event机制——集群的黑匣子摘要CRI管容器运行时、CNI管网络那存储呢答案是CSIContainer Storage Interface——K8s存储的解耦标准。在CSI出现之前K8s的存储插件是in-tree的——代码直接写进K8s源码里AWS EBS、GCE PD、vSphere……各写各的。想加个新存储得改K8s核心代码、等发版。CSI把这一切解耦任何存储厂商只要实现CSI接口打个Sidecar就能接入K8s。这篇文章讲清CSI的三组gRPC服务Identity/Controller/Node、那堆Sidecar组件provisioner/attacher/snapshotter/node-driver-registrar各自干啥以及从PVC到Pod挂载卷的完整调用链。第042篇讲过CSI全景这篇是深入。一、为什么需要CSI1.1 in-tree的痛【CSI 之前的黑暗时代(in-tree插件)】 存储插件代码直接写在K8s源码里 • pkg/volume/aws_ebs/ • pkg/volume/gce_pd/ • pkg/volume/vsphere_volume/ ... 痛点 • 加新存储 改K8s核心 等发版(几个月) • 存储厂商的bug要K8s发版才能修 • K8s核心越来越臃肿 • 存储厂商不能自己迭代 → CSI 把存储逻辑外移成独立驱动二、CSI的三组服务2.1 gRPC接口【CSI 的三组 gRPC 服务】 ┌──────────────────────────────────────────┐ │ 1. Identity Service (身份) │ │ • GetPluginInfo: 我是谁(名字/版本) │ │ • GetPluginCapabilities: 我支持啥能力 │ │ • Probe: 我活着吗? │ └──────────────────────────────────────────┘ ┌──────────────────────────────────────────┐ │ 2. Controller Service (控制面, 在Master) │ │ • CreateVolume / DeleteVolume │ │ • ControllerPublishVolume (挂到某Node) │ │ • CreateSnapshot / ListSnapshots │ │ • 管卷的生老病死(不管容器) │ └──────────────────────────────────────────┘ ┌──────────────────────────────────────────┐ │ 3. Node Service (节点面, 在每个Node) │ │ • NodePublishVolume: 把卷挂进容器 │ │ • NodeUnpublishVolume: 卸载 │ │ • NodeStageVolume: 挂载到Node全局目录 │ │ • 管卷和节点/容器的对接 │ └──────────────────────────────────────────┘2.2 Controller vs Node 的分工维度Controller ServiceNode Service运行位置Master侧(或独立)每个Node管什么卷的创建/删除/快照卷挂到具体Node/容器例子在云上申请一块云盘把云盘格式化挂进Pod调用者external-provisioner等Sidecarkubelet要点Controller和Node的分工就像盖房子和装修入伙——Controller负责在存储系统里把卷创建出来比如云上申请块存储Node负责把这个卷真正挂到某个Node的Pod里格式化、mount。两者通过gRPC通信职责清晰。三、CSI Sidecar组件3.1 那堆辅助进程光有CSI驱动还不够——K8s需要一些翻译官把K8s的API事件转成CSI调用。这些翻译官就是Sidecar【CSI 的 Sidecar 全家福】 ┌────────────────────────────────────────────┐ │ external-provisioner │ │ • 监听 PVC 创建 → 调 Controller.CreateVolume│ │ • 动态创建PV │ ├────────────────────────────────────────────┤ │ external-attacher │ │ • 监听 VolumeAttachment → 调Controller. │ │ ControllerPublishVolume │ │ • 把卷挂到目标Node │ ├────────────────────────────────────────────┤ │ external-snapshotter │ │ • 管 VolumeSnapshot 的创建/删除 │ ├────────────────────────────────────────────┤ │ external-resizer │ │ • 管卷的扩容(ExpandVolume) │ ├────────────────────────────────────────────┤ │ node-driver-registrar │ │ • 向kubelet注册这个CSI驱动 │ │ • kubelet才知道有这个存储可用 │ └────────────────────────────────────────────┘ 这些Sidecar 你的CSI驱动 一个完整CSI部署四、完整调用链PVC到挂载4.1 从一句话到挂载【用户 kubectl apply pvc → Pod里看到卷 的完整链路】 1. 用户创建 PVC (要10Gi存储) │ 2. external-provisioner 看到新PVC → 调 CSI Controller.CreateVolume → 云上/存储系统创建真实卷 │ 3. provisioner 自动创建 PV 绑定这个PVC │ 4. 用户创建 Pod 引用这个PVC → Scheduler 把Pod调度到 Node-X │ 5. external-attacher 看到 VolumeAttachment → 调 Controller.ControllerPublishVolume → 把卷附着到 Node-X (云盘挂载到虚拟机) │ 6. kubelet (在Node-X) 看到Pod要用卷 → 调 CSI Node.NodeStageVolume (格式化挂到全局目录) → 调 CSI Node.NodePublishVolume (绑定进Pod的mount namespace) │ 7. Pod里的 /data 能读了✅# 用户视角极其简单背后是上面一整套apiVersion:v1kind:PersistentVolumeClaimmetadata:name:my-pvcspec:accessModes:[ReadWriteOnce]resources:requests:storage:10GistorageClassName:fast-ssd# 指向某CSI驱动的StorageClass---apiVersion:v1kind:Podspec:containers:-name:appimage:nginxvolumeMounts:-name:datamountPath:/datavolumes:-name:datapersistentVolumeClaim:claimName:my-pvc要点用户只写了PVCPOD两小段YAML背后却触发了一整套跨组件协作provisioner建卷、attacher附着、kubelet挂载。CSI Sidecar把K8s的声明式API翻译成CSI的gRPC调用存储厂商只需实现三组服务。这就是为什么现在几百种存储都能轻松接入K8s——标准解耦的威力。五、CSI vs in-tree 现状【迁移状态】 旧in-tree插件: 已废弃(CSI Migration)如: • AWSElasticBlockStore → ebs.csi.aws.com • GCEPersistentDisk → pd.csi.storage.gke.io • vSphereVolume → csi.vsphere.vmware.com 新存储: 一律用CSI(没有in-tree了)本篇小结CSI是K8s存储的解耦标准用gRPC定义三组服务Identity身份、Controller卷的创建/删除/快照在控制面、Node卷挂进容器在节点面。那堆Sidecar组件是翻译官——provisioner把PVC变成CreateVolume调用、attacher把卷附着到Node、node-driver-registrar向kubelet注册驱动。从PVC到挂载的完整链路跨了provisioner→attacher→kubelet三方协作但用户只写了两段YAML。CSI和CRI、CNI一起构成了K8s面向接口、即插即用的三大标准支柱。下篇讲K8s Event机制——集群的黑匣子。上一篇【第67篇】CNI深度解析——容器网络接口标准二十行代码就能写一个网络插件下一篇【第69篇】K8s Event机制——集群的黑匣子