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/ControllerUnpublishVolumePod 调度到节点
external-snapshotter监听 VolumeSnapshot 对象,调用 CreateSnapshot/DeleteSnapshotVolumeSnapshot 创建
external-resizer监听 PVC 扩容请求,调用 ControllerExpandVolumePVC 修改 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 的通信可以归纳为三条链路:

  1. Kubernetes API 链路:sidecar 通过 watch/list 与 kube-apiserver 通信,这是控制面的"事件源"。
  2. 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。
  3. 存储阵列 REST 链路:pure-csi 驱动通过 HTTPS 调用 FlashArray 的 /api/2.x/... 接口,完成卷创建、快照、连接主机等实际操作。

下面这张图展示了从 PVC 创建到 Pod 挂载完成的完整组件协作与通信路径:

控制面(Controller 侧) kube-apiserver PVC / PV / VolumeAttachment CSI Sidecar 容器组 provisioner attacher snapshotter resizer 监听 API 对象 → 翻译为 CSI gRPC 调用 pure-csi 驱动(Controller) Identity + Controller 服务 CreateVolume / DeleteVolume ControllerPublish / Snapshot Unix Socket: pluginproxy/csi.sock watch gRPC Pure FlashArray / FlashBlade REST API over HTTPS :443 API Token 认证 HTTPS 工作节点(Node 侧,DaemonSet) kubelet CSI 插件注册与调用 node-driver-registrar 注册 csi.sock 到 kubelet pure-csi 驱动(Node) NodeStageVolume / NodePublishVolume NodeUnpublish / NodeUnstage NodeGetInfo(拓扑上报) 挂载点:/var/lib/kubelet/pods/<uid>/volumes/... 文件系统:ext4 / xfs 注册 gRPC 调度后由 kubelet 触发 Node 侧 Sidecar pure-csi 驱动 存储阵列 API watch CSI gRPC REST API

五、一次完整挂载的时序与关键判断点

以"创建一个 20Gi 的 PVC 并挂载到 Pod"为例,组件间的实际调用顺序如下:

  1. 用户创建 PVC,apiserver 写入对象。external-provisioner watch 到该事件,调用 pure-csi 的 CreateVolume,参数包含 name=pvc-xxxx、capacity_range=20Gi、parameters(如 backend=flasharray、pod=fa-prod)。
  2. pure-csi 通过 REST API 在 FlashArray 上创建卷,返回卷的 volume_id(通常是阵列序列号 + 卷名)。provisioner 据此创建 PV 对象。
  3. Pod 被调度到某节点,kubelet 触发 attach/detach controller 创建 VolumeAttachment。external-attacher 调用 ControllerPublishVolume,pure-csi 在阵列上把卷连接到该节点对应的主机(通过 IQN 或 WWN 匹配)。
  4. kubelet 通过 node-driver-registrar 注册的 socket 调用 Node 侧 NodeStageVolume(全局挂载到 /var/lib/kubelet/plugins/kubernetes.io/csi/...),再调用 NodePublishVolume(bind mount 到 Pod 的 volumes 目录)。
  5. 容器启动,卷可用。

排查时最值得关注的判断点有三个:PV 是否被创建(provisioner 是否成功)、VolumeAttachment 是否为 Attached(attacher 是否成功)、节点上是否存在挂载点(Node 侧是否成功)。这三步分别对应三条通信链路,定位时按链路逐段检查即可快速收敛。

在 pure-csi 的 Controller Pod 中,可以用 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 这条链路逐段验证,从而把模糊的"挂载失败"转化为可定位的具体环节。