4. Pure CSI插件架构解析
Pure Storage 的 CSI 驱动(pure-csi)并不是一个单体进程,而是由多个遵循 CSI 规范的独立组件协同工作。理解这些组件的职责边界与通信路径,是排查卷挂载失败、快照创建超时、拓扑调度异常等问题的前提。本节从部署形态入手,逐层拆解各组件的职责,并给出它们之间真实的通信机制。
一、部署形态:Controller 与 Node 两类工作负载
pure-csi 在 Kubernetes 集群中通常以两种形态部署:
- Controller 侧:以 Deployment 或 StatefulSet 形式运行,通常 1~3 副本,承载所有"集群级"操作,如创建/删除卷、挂载到节点前的 Attach、创建快照、扩容、克隆等。它不直接接触节点上的文件系统。
- Node 侧:以 DaemonSet 形式运行,每个工作节点一个 Pod,承载所有"节点级"操作,如把卷格式化为 ext4/xfs、挂载到
/var/lib/kubelet/pods/...下的目标路径、卸载、上报节点拓扑信息。
这种划分不是 Pure 特有的设计,而是 CSI 规范强制的:CSI 定义了 Identity、Controller、Node 三组 gRPC 服务,其中 Node 服务必须运行在卷最终被挂载的节点上,Controller 服务则可以在任意位置集中运行。
二、Controller 侧组件职责
Controller 侧 Pod 内通常包含多个容器,每个容器承担一个明确的角色:
| 组件 | 职责 | 典型触发来源 |
|---|---|---|
| external-provisioner | 监听 PVC 事件,调用 CSI 的 CreateVolume/DeleteVolume,并把结果写入 PV 对象 | PVC 创建/删除 |
| external-attacher | 监听 VolumeAttachment 对象,调用 ControllerPublishVolume/ControllerUnpublishVolume | Pod 调度到节点 |
| external-snapshotter | 监听 VolumeSnapshot 对象,调用 CreateSnapshot/DeleteSnapshot | VolumeSnapshot 创建 |
| external-resizer | 监听 PVC 扩容请求,调用 ControllerExpandVolume | PVC 修改 storage 大小 |
| csi-provisioner 侧驱动容器(pure-csi) | 实现 CSI Identity 与 Controller 服务,真正与 Pure FlashArray/FlashBlade 的 REST API 通信 | 被上述 sidecar 调用 |
关键点在于:external-* 这些 sidecar 并不直接与存储阵列通信。它们只负责"监听 Kubernetes API 对象 → 翻译成 CSI gRPC 调用 → 把结果写回 API 对象"。真正与 FlashArray 交互的是 pure-csi 驱动容器,它通过 HTTPS 调用阵列的 REST API(默认 443 端口),使用 API Token 认证。
三、Node 侧组件职责
Node 侧 Pod 内一般包含两个容器:
- node-driver-registrar:把 pure-csi 的 Node 服务注册到 kubelet 的插件目录(
/var/lib/kubelet/plugins/pure-csi/csi.sock),使 kubelet 能通过 CSI 接口调用它。 - pure-csi 驱动容器(Node 模式):实现
NodeStageVolume、NodePublishVolume、NodeUnpublishVolume、NodeUnstageVolume、NodeGetInfo等方法。它需要访问宿主机的/dev、/var/lib/kubelet、/sys等路径,因此以 privileged 或特定 capabilities 运行。
Node 侧不感知 PVC/PV 对象,它只接收来自 kubelet 的 gRPC 请求,参数中携带卷的 IQN/WWN、目标路径、文件系统类型等。这也是为什么 Node 侧 Pod 重启通常不会影响已挂载卷——挂载点由内核维护,Pod 只是执行者。
四、通信机制:三条主要链路
把上述组件串起来,pure-csi 的通信可以归纳为三条链路:
- Kubernetes API 链路:sidecar 通过 watch/list 与 kube-apiserver 通信,这是控制面的"事件源"。
- CSI gRPC 链路:sidecar 与 pure-csi 驱动之间通过 Unix Domain Socket 通信。Controller 侧 socket 通常位于
/var/lib/csi/sockets/pluginproxy/csi.sock,Node 侧位于/var/lib/kubelet/plugins/pure-csi/csi.sock。 - 存储阵列 REST 链路:pure-csi 驱动通过 HTTPS 调用 FlashArray 的
/api/2.x/...接口,完成卷创建、快照、连接主机等实际操作。
下面这张图展示了从 PVC 创建到 Pod 挂载完成的完整组件协作与通信路径:
五、一次完整挂载的时序与关键判断点
以"创建一个 20Gi 的 PVC 并挂载到 Pod"为例,组件间的实际调用顺序如下:
- 用户创建 PVC,apiserver 写入对象。external-provisioner watch 到该事件,调用 pure-csi 的
CreateVolume,参数包含name=pvc-xxxx、capacity_range=20Gi、parameters(如backend=flasharray、pod=fa-prod)。 - pure-csi 通过 REST API 在 FlashArray 上创建卷,返回卷的
volume_id(通常是阵列序列号 + 卷名)。provisioner 据此创建 PV 对象。 - Pod 被调度到某节点,kubelet 触发 attach/detach controller 创建 VolumeAttachment。external-attacher 调用
ControllerPublishVolume,pure-csi 在阵列上把卷连接到该节点对应的主机(通过 IQN 或 WWN 匹配)。 - kubelet 通过 node-driver-registrar 注册的 socket 调用 Node 侧
NodeStageVolume(全局挂载到/var/lib/kubelet/plugins/kubernetes.io/csi/...),再调用NodePublishVolume(bind mount 到 Pod 的 volumes 目录)。 - 容器启动,卷可用。
排查时最值得关注的判断点有三个:PV 是否被创建(provisioner 是否成功)、VolumeAttachment 是否为 Attached(attacher 是否成功)、节点上是否存在挂载点(Node 侧是否成功)。这三步分别对应三条通信链路,定位时按链路逐段检查即可快速收敛。
kubectl logs <pod> -c csi-provisioner 查看 sidecar 日志,用 kubectl logs <pod> -c pure-csi 查看驱动日志。Node 侧同理,-c node-driver-registrar 与 -c pure-csi 分别对应注册与挂载操作。日志中的 gRPC 方法名(如 /csi.v1.Controller/CreateVolume)能直接告诉你请求走到了哪一层。
六、Snapshotter 与 Resizer 的补充说明
external-snapshotter 除了调用 CreateSnapshot,还会创建 VolumeSnapshotContent 对象,并在恢复时调用 CreateVolume 的 volume_content_source 参数实现从快照克隆。external-resizer 则在 PVC 扩容时调用 ControllerExpandVolume,pure-csi 会通过 REST API 调整阵列卷大小,随后由 Node 侧执行文件系统扩容(NodeExpandVolume)。这两个组件与 provisioner 共享同一个 Controller 侧 socket,因此它们的失败往往表现为同一类 gRPC 超时,需要结合驱动日志区分是阵列侧限流还是参数校验失败。
理解这套架构的核心,是记住"sidecar 只做翻译,驱动才做实事,阵列才是最终执行者"。任何一次卷操作,都可以沿着 API → gRPC → REST 这条链路逐段验证,从而把模糊的"挂载失败"转化为可定位的具体环节。