3. 容器存储接口CSI架构解析
在 Kubernetes 中,容器本身是无状态的,真正需要持久化的数据必须落到外部存储系统。CSI(Container Storage Interface)就是 Kubernetes 与外部存储之间的标准“插座”:存储厂商只需实现一套 CSI 接口,就能被所有支持 CSI 的编排系统(Kubernetes、Nomad 等)调用,而不必为每个平台单独写插件。理解 CSI 的架构,是理解 ONTAP CSI 驱动如何把 NetApp 存储能力接入容器平台的前提。
一、CSI 规范:把“存储操作”抽象成一组 gRPC 接口
CSI 规范由三组 gRPC 服务组成,它们分别对应存储生命周期的不同阶段:
- Identity 服务:提供
GetPluginInfo、GetPluginCapabilities、Probe,用于让 kubelet 和外部组件识别插件身份、能力与健康状态。 - Controller 服务:提供
CreateVolume、DeleteVolume、ControllerPublishVolume、ControllerUnpublishVolume、CreateSnapshot、ListVolumes等,负责卷的创建、删除、挂载到节点前的“控制器侧”操作。 - Node 服务:提供
NodeStageVolume、NodePublishVolume、NodeUnpublishVolume、NodeGetInfo等,负责在具体节点上把卷挂载到容器可见的路径。
规范还定义了 VolumeCapability(访问模式:单节点读写、多节点只读、多节点读写等)、Volume(卷 ID、容量、拓扑约束)、TopologyRequirement(拓扑要求)等数据结构。这些结构决定了 CSI 驱动必须“听懂”哪些参数,也决定了上层调度器如何把 Pod 调度到能访问该卷的节点上。
二、控制器插件与节点插件:两种部署形态
CSI 驱动通常以两种插件形态部署:
- Controller 插件:以 Deployment 或 StatefulSet 形式运行在控制平面或专用节点上,通常只有 1~3 个副本。它调用存储后端 API 完成卷的创建、删除、扩容、快照等操作,并处理
ControllerPublishVolume把卷“绑定”到某个节点。 - Node 插件:以 DaemonSet 形式运行在每个工作节点上,负责在节点本地执行
NodeStageVolume(把卷挂载到全局暂存目录)和NodePublishVolume(把卷 bind mount 到 Pod 的/var/lib/kubelet/pods/<podUID>/volumes/路径下)。
两者通过 Unix Domain Socket 与 kubelet 通信:kubelet 通过 --csi-address 指向 Node 插件的 socket,而 Controller 插件则通过 external-provisioner、external-attacher、external-resizer、external-snapshotter 等 sidecar 容器与 Kubernetes API 交互。这种“驱动 + sidecar”的组合,是 CSI 生态的典型模式。
三、动态供应:从 PVC 到真实卷的自动创建
动态供应(Dynamic Provisioning)是 CSI 最核心的能力之一。当用户创建一个 PersistentVolumeClaim(PVC)并指定 storageClassName 时,Kubernetes 的 external-provisioner sidecar 会监听该 PVC,并调用 CSI Controller 的 CreateVolume 接口。请求中携带容量、访问模式、拓扑要求以及 StorageClass 中的 parameters(例如 ONTAP 的 SVM、聚合、QoS 策略等)。
Controller 插件收到请求后,调用 ONTAP 的 REST API 或 ZAPI 在指定 SVM 中创建 FlexVol 卷,并返回卷 ID。随后 external-provisioner 创建对应的 PersistentVolume(PV)对象,Kubernetes 将 PVC 与 PV 绑定。整个过程无需管理员手工创建卷,PVC 从 Pending 变为 Bound 即表示供应成功。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ontap-nas
provisioner: csi.trident.netapp.io
parameters:
backendType: "ontap-nas"
storagePool: "aggr1"
svm: "svm_k8s"
qosPolicy: "k8s-gold"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
其中 volumeBindingMode: WaitForFirstConsumer 表示延迟绑定:PV 不会在 PVC 创建时立即供应,而是等到 Pod 被调度、确定节点后再创建卷。这一模式与拓扑感知紧密相关。
四、拓扑感知:让卷创建在“对的位置”
在 ONTAP 环境中,一个集群可能包含多个节点、多个聚合(aggregate),甚至多个 SVM。如果卷创建在节点无法访问的聚合上,Pod 就无法挂载。CSI 通过拓扑感知解决这个问题:
- Node 插件通过
NodeGetInfo上报节点的拓扑键,例如topology.csi.netapp.io/region、topology.csi.netapp.io/zone或自定义的topology.csi.netapp.io/aggregate。 - Controller 插件在
CreateVolume时接收TopologyRequirement,其中包含requisite(必须满足)和preferred(优先满足)的拓扑段。 - 调度器(kube-scheduler)结合 PV 的节点亲和性(nodeAffinity)与 Pod 的资源请求,把 Pod 调度到能访问该卷的节点上。
以 ONTAP 为例,如果两个工作节点分别位于不同可用区,而卷只能创建在某个聚合上,那么拓扑感知会确保 Pod 被调度到能访问该聚合的节点。若使用 WaitForFirstConsumer,供应动作会等到 Pod 调度决策完成后才发生,从而避免“先创建卷、再发现节点不可达”的尴尬。
五、ONTAP CSI 驱动在 CSI 架构中的位置
ONTAP CSI 驱动(如 NetApp Trident 的 CSI 实现)同时实现了 Controller 服务和 Node 服务,并作为 CSI 插件注册到 Kubernetes。它的位置可以这样理解:
- 在 控制平面侧,它通过
external-provisioner等 sidecar 接收 PVC/PV 事件,调用 ONTAP REST API 完成卷的创建、删除、扩容和快照。 - 在 工作节点侧,它通过 DaemonSet 中的 Node 插件执行 iSCSI/NFS 挂载、bind mount,把 ONTAP 卷呈现给容器。
- 在 拓扑层,它把 ONTAP 的聚合、SVM、节点等信息映射为 CSI 拓扑键,参与调度决策。
因此,ONTAP CSI 驱动并不是一个“独立于 CSI 之外”的组件,而是 CSI 规范在 NetApp ONTAP 存储上的具体实现。它把 ONTAP 的 FlexVol、FlexGroup、Snapshot、QoS 等能力,翻译成 Kubernetes 能理解的 PVC、PV、StorageClass 和 VolumeSnapshot 对象。
从图中可以看到,CSI 把存储操作拆成“控制平面”和“节点平面”两条路径:控制平面负责卷的生命周期管理,节点平面负责把卷真正挂到容器里。ONTAP CSI 驱动同时占据这两个位置,并通过拓扑感知与调度器协同,确保卷创建在 Pod 能访问的 ONTAP 资源上。理解这张图,就理解了 ONTAP 容器持久化存储集成的骨架。