nodestagevolume 和 nodepublishvolume 必须分两步:前者映射远端存储为本地块设备并确保设备节点就绪,后者仅 bind mount;合并会导致设备未就绪而挂载失败。

为什么 NodeStageVolume 和 NodePublishVolume 必须分两步做
因为 Kubernetes 要求 CSI 插件严格区分“准备设备”和“挂载到目标路径”两个阶段,跳过或合并会导致 MountVolume.NodeAffinityMismatching 或 rpc error: code = Internal desc = device not staged。
典型错误是直接在 NodePublishVolume 里调用 mount,但底层块设备(比如 iSCSI LUN 或 NVMe SSD)还没被 NodeStageVolume 映射进节点命名空间 —— 此时 /dev/disk/by-path/... 根本不存在。
-
NodeStageVolume:负责将远端存储映射为本地块设备(如登录 iSCSI、attach EBS、nvme connect),然后调用mkfs(仅首次)、udevadm settle确保设备节点就绪,最后返回一个稳定的 staging path(如/var/lib/kubelet/plugins/kubernetes.io/csi/pv/<volume-id>/globalmount</volume-id>) -
NodePublishVolume:只做 bind mount,把 staging path 挂到 target path(如/var/lib/kubelet/pods/<pod-id>/volumes/kubernetes.io~csi/<vol-name>/mount</vol-name></pod-id>),不碰设备本身 - 如果使用文件系统后端(如 NFS、S3FS),可跳过
NodeStageVolume,但必须在GetPluginCapabilities中声明STAGE_UNSTAGE_VOLUME能力为 false,否则 kubelet 会强制调用它
如何让 Probe 和 Identity 服务通过 kubelet 健康检查
CSI 插件启动后,kubelet 会反复调用 Probe RPC 来确认插件存活,同时用 GetPluginInfo 验证插件身份。失败常见于监听地址、gRPC 超时或响应格式错位。
最常踩的坑是:用 localhost:9999 启动 gRPC server,但 kubelet 在宿主机网络命名空间里访问的是 127.0.0.1:9999 —— 如果插件跑在容器里且没设 hostNetwork: true,这个地址根本不通。
立即学习“go语言免费学习笔记(深入)”;
- 必须绑定
0.0.0.0:9999,不能只绑localhost或127.0.0.1 -
Probe响应里ready.status必须是true,且不能返回空status字段(Go 的 proto struct 默认零值会被序列化成false,要显式赋值) -
GetPluginInfo返回的name必须和CSIDriver对象的spec.name完全一致(包括大小写),否则 kubelet 拒绝注册 - 建议加个简单健康端点(如
/healthz)暴露在 HTTP 端口上,方便用curl快速验证进程是否响应
ControllerPublishVolume 的权限和幂等性怎么处理
这个接口负责在控制面将卷“发布”给某个节点(例如:为该节点授权访问某块云盘、创建 iSCSI target ACL、分配 FC WWN zone)。它不是纯读操作,失败重试极频繁,所以必须支持幂等。
常见错误是没校验是否已发布成功就直接调用云厂商 API,导致重复授权失败(如 AWS EBS AttachVolume 返回 VolumeInUse)或资源泄漏(如 iSCSI target 创建了多个同名 LUN)。
- 每次调用前先查目标节点是否已有该卷的访问权限(例如调用
DescribeVolumes看Attachments列表,或查 iSCSI target 的ACL条目) - 所有云厂商 API 调用必须带 context timeout(建议 ≤30s),避免阻塞整个 CSI controller
- 不要在
ControllerPublishVolume里做设备格式化或挂载 —— 这些属于 Node 端职责,控制面无权访问节点本地文件系统 - 如果后端是共享文件系统(如 GPFS、CephFS),此方法可空实现,但依然要返回 success,否则 PVC 无法进入 Bound 状态
调试时怎么看 NodeStageVolume 失败的真实原因
kubelet 日志只会显示 “failed to stage volume: rpc error: code = Internal desc = …”,真正线索藏在 CSI 插件自己的日志和 journalctl -u kubelet 的上下文里。
最容易被忽略的是:CSI 插件进程默认没有 CAP_SYS_ADMIN,而 NodeStageVolume 需要执行 iscsiadm、nvme、mkfs 等特权操作 —— 容器启动时漏掉 cap 或 seccomp profile 就会静默失败。
- 插件容器必须加
--cap-add=SYS_ADMIN --cap-add=NET_ADMIN(iSCSI/NVMe 场景),或至少SYS_ADMIN(用于 mount/unshare) - 在
NodeStageVolume开头加日志输出完整参数:req.GetVolumeId()、req.GetStagingTargetPath()、req.GetVolumeCapability(),避免靠猜判断是哪个卷出问题 - 用
strace -f -e trace=mount,openat,ioctl跟踪插件进程,能快速定位是mount系统调用失败,还是设备节点没生成出来 - 别依赖
os.IsNotExist判断设备是否存在 —— 应该用filepath.Glob("/dev/disk/by-path/*-lun-0")或lsblk -n -o NAME,TYPE,MOUNTPOINT主动探测
CSI 插件真正的复杂点不在协议实现,而在和 Linux 存储子系统(udev、multipath、iscsiadm、nvme-cli)的边界对齐 —— 这些工具的行为差异比 CSI spec 本身更难收敛。











