StorageClass 配了 PV 还 Pending?CSI 驱动没启动
场景:应用部署创建 PVC 后 PV 一直 Pending,StorageClass 配置检查了 3 遍没问题——问题不在 YAML,在 CSI 驱动侧 路径:PVC 状态 → Events → provisioner 日志 → CSI driver socket → 驱动修复
以下排查基于 K8s v1.28,CSI 组件使用 storage.k8s.io/v1 API
坐标 — PV 创建卡住的现场
上篇讲了 emptyDir 不设 sizeLimit 导致整节点 30 个 Pod 被驱逐,这篇我们来看另一个存储场景——PV 创建卡住了,根因不是 StorageClass 写错,是 CSI 驱动没起来。
新存储上线后 PVC 一直 Pending——"StorageClass 名字写错了吧"团队第一反应。kubectl describe pvc 一看,Events 段显示:
Warning ProvisioningFailed 3m external-provisioner
Failed to provision volume with StorageClass "csi-fast-sc":
rpc error: code = Unavailable desc = connection closed
StorageClass 名字没错——是 CSI 驱动侧 gRPC socket 没开,external-provisioner 连不上 driver,PV 没法创建。以后你发现 PVC Pending 且 StorageClass 配置没问题:先查 CSI 驱动组件状态,别在 YAML 字段上死磕。
表象:PVC 一直 Pending
创建一个 PVC 指定 StorageClass csi-fast-sc:
kubectl get pvc data-pvc
输出:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-pvc Pending csi-fast-sc 5m
PVC(PersistentVolumeClaim,Pod 使用的存储请求)停留在 Pending 状态,VOLUME 列为空——说明底层 PV(PersistentVolume,集群级存储资源)尚未创建。
如果你不熟悉 CSI 架构,第一反应是 StorageClass 写错了。检查了一遍 YAML:provisioner 名字对、parameters 格式对、reclaimPolicy 也设了——问题不在声明侧。
那问题在哪?
Events:provisioner 给出了明确错误
kubectl describe pvc data-pvc
Events 段:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning ProvisioningFailed 3m external-provisioner Failed to provision volume with StorageClass "csi-fast-sc": rpc error: code = Unavailable desc = connection closed
Warning ProvisioningFailed 2m external-provisioner Failed to provision volume with StorageClass "csi-fast-sc": rpc error: code = Unavailable desc = connection closed
关键线索在 From: external-provisioner 和 code = Unavailable desc = connection closed。
external-provisioner(K8s 的 CSI 边车容器,负责监听 PVC 变化并调用 CSI 驱动的 gRPC 接口创建卷)反复报 connection closed——这说明:
- StorageClass 的 provisioner 名字是对的(否则它连 driver 都找不到)
- external-provisioner 找到了 CSI driver 的注册信息
- 但它在建立 gRPC 连接时被拒绝了
gRPC 的 Unavailable 状态码表示服务端不可达——不是网络不通,是 Unix socket 文件不存在或进程没在监听。
K8s 版本
Server Version: v1.28.7
CSI 组件 API: storage.k8s.io/v1

分层 — CSI 存储链路拓扑
PVC 创 PV 不是单个组件的事,是一条完整调用链。分层看才能理解"断在哪":

这条链路上发生了什么
从用户创建 PVC 到 PV 绑定,K8s 依次走以下步骤:
- PVC 控制器看到新的未绑定 PVC,找到它引用的 StorageClass
- external-provisioner(运行在 kube-controller-manager 内部或作为独立 Pod)监听到 PVC 创建事件,根据 StorageClass 的 provisioner 字段找到对应的 CSI Driver 注册信息
- external-provisioner 通过 Unix socket(
/var/lib/kubelet/plugins/<driver>/csi.sock)向 CSI driver 发起 gRPC CreateVolume 调用 - CSI driver 收到请求后,调用后端存储 API 创建实际存储卷
- 卷创建成功后,CSI driver 返回 PV 对象信息,K8s 将 PV 绑定到 PVC
这条链路上任何一个环节断了,PV 就创建不出来。而 connection closed 说明断在第 3 步——external-provisioner 发出了 gRPC 调用,但 socket 对面没人接。
三层排查顺序
为什么提倡分层排查?因为不同层的根因对应不同的修复方式:
| 层级 | 查什么 | 关键命令 | 典型根因 |
|---|---|---|---|
| Pod 层 | PVC/PV 状态 + Events | kubectl describe pvc |
StorageClass 名字错、quota 不足 |
| CSI 层 | driver pod 状态 + socket + 日志 | kubectl get pods -n kube-system → kubectl logs |
driver 没启动、socket 目录未挂载 |
| 后端层 | 存储系统可达性 | 存储侧管理面检查 | 存储 API 不可达、容量耗尽 |
先查 Pod 层排除配置问题,再查 CSI 层看驱动状态,最后查后端——不跳层、不漏层。
路径 — 核心排查命令队列
第 1 步:确认 PVC 的 provisioner 和 StorageClass
kubectl get sc csi-fast-sc -o yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-fast-sc
provisioner: csi-fast.example.com # ← 这个 provisioner 决定了调哪个 CSI 驱动
parameters:
type: ssd
注意 provisioner 字段的值——external-provisioner 根据这个值找到对应的 CSI driver 注册信息。这个字段不是随便写的,它必须和 CSIDriver 对象的名字完全一致。
第 2 步:查 CSI driver 相关 Pod 状态
kubectl get pods -n kube-system -o wide | grep csi
输出:
csi-fast-controller-6b9d75c9d8-4z5m7 0/1 CrashLoopBackOff 5 10m
csi-fast-node-8h3j2 2/3 Running 0 2d
csi-fast-node-l9p4m 2/3 Running 0 2d
csi-fast-controller(CSI controller 组件,处理 CreateVolume/DeleteVolume 等控制面操作)CrashLoopBackOff——这是根因的信号。
注意这里有两种 CSI 组件: - Controller 组件:处理控制面操作,以 Deployment 形式运行,需要 Socket 目录来接受 gRPC 调用 - Node 组件:处理挂载/卸载操作,以 DaemonSet 形式运行在每个节点上
PVC 创建走的是 Controller 组件,所以即使 Node 组件都 Running,Controller 挂了也白搭。
第 3 步:检查 CSIDriver 对象
kubectl get csidriver
CSIDriver(CSI 驱动在集群中的注册对象,告诉 K8s 这个驱动支持哪些能力):
NAME ATTACHREQUIRED PODINFOONMOUNT MODES AGE
csi-fast.example.com true false Persistent 2d
CSIDriver 对象存在,说明驱动之前在集群中注册过。一个常见的排查误区是认为 CSIDriver 存在就等于 driver 在正常工作——不,它只是一个注册声明,不保证对应的 driver Pod 在跑。
第 4 步:看 CSI driver 日志(根因)
kubectl logs -n kube-system csi-fast-controller-6b9d75c9d8-4z5m7
I1023 10:15:23.001 csi-driver.go:42] Starting CSI driver...
I1023 10:15:23.002 identity.go:28] Opening gRPC server on unix:///csi/csi.sock
E1023 10:15:23.015 csi-driver.go:55] Failed to listen on socket: listen unix /csi/csi.sock: bind: no such file or directory
CSI driver 启动时试图在 /csi/csi.sock 监听 gRPC 请求,但 /csi/ 目录不存在——这是容器挂载卷(Volume)映射问题。
日志揭示了一个关键信息:CSI driver 用 Unix socket 通信。unix:// 协议意味着它需要一个实际的文件系统路径来创建 socket 文件。如果 /csi/ 目录没挂载进来,driver 进程一启动就卡死在 socket 创建这一步。
第 5 步:验证 socket 文件
kubectl exec -n kube-system csi-fast-controller-6b9d75c9d8-4z5m7 -- ls -la /csi/
ls: cannot access '/csi/': No such file or directory

socket 目录根本不存在——CSI driver Deployment 的 Pod spec 中没有声明 VolumeMount,容器内没有 /csi 这个目录,driver 进程无法创建 socket 文件,启动即崩溃,provisioner 连不上,PV 创建卡死。
这是一个 CSI driver 部署清单本身的质量问题。它出现在生产环境,通常是因为集群管理员从社区拉了一个 CSI Driver manifest,但 Helm Chart 升级时 volumes 字段被覆盖、或者手动编辑 Deployment 时漏掉了 volumeMounts。

定位 — 最常误判的方向
大多数人不熟悉 CSI 架构时会反复排查的错误方向——每一个我都见过真实案例。
❌ 错误方向 1:反复检查 StorageClass YAML
"provisioner 名字写对了吗?parameters 类型对吗?"——provisioner 字段只是一个标识符,external-provisioner 用它在集群中找到对应 CSIDriver 对象。名字正确不代表 CSI driver 在跑。
实际上,provisioner 名字错了 Events 根本不会出现 ProvisioningFailed——它是 StorageClassNotFound 或不触发任何 Event(因为 controller 找不到谁处理这个 provisioner)。
判断依据:如果你在 Events 中看到 external-provisioner 这个来源,说明 StorageClass 配置是通的。external-provisioner 能定位到说明名字正确,它的报错是它自己的工作和 CSI driver 通信,和你的 YAML 没关系。
❌ 错误方向 2:检查 PVC 的 accessModes
"ReadWriteOnce 还是 ReadWriteMany?会不会是 access mode 写错了?"——accessModes 不影响 PV 创建。它是挂载阶段的约束条件,告诉 K8s "这个卷应该以什么模式挂载到 Pod",不是 provisioning 阶段的检查项。
accessModes 错误会导致 Pod 启动时挂载失败(MountVolume.SetUp failed),不是 PV 创建失败。
❌ 错误方向 3:怀疑资源配额
"ResourceQuota 限制了吧?Namespace 资源用完了?"——配额限制会报 exceeded quota,Events 会明确写 "exceeded quota",不会写 connection closed。
ResourceQuota 检查发生在 Pod 创建阶段(Admission Controller),不是 PV provisioning 阶段。PV 是集群资源,不受 Namespace 级别的 ResourceQuota 限制。
✅ 正确排查思路
CSI 驱动的架构决定了排查顺序:
PVC Pending + ProvisioningFailed 事件 → 不用看 StorageClass 了,直接从 CSI 层入手:
- CSI driver Pod 在不在 Running(第 2 步)
- Driver 日志有没有 socket 错误(第 4 步)
- Socket 文件存不存在(第 5 步)
- VolumeMounts 配没配(看 Pod spec)
StorageClass 只是一个声明(告诉 K8s "找哪个 provisioner"),CSI driver 是一个运行中的组件(gRPC 服务端)。你在排查一个运行组件的问题,却一直在看一份静态 YAML——方向错了。
这条原则不仅适用 CSI,也适用于 K8s 中其他"配置+组件"分离的场景(Ingress Controller、CNI 插件、CoreDNS 等):配置正确不代表背后的控制器在正常工作。
标点 — 修复 + Check-list
修复
CSI controller 容器挂载卷配置缺失——CSI driver Deployment 的 Pod spec 中缺少 hostPath 卷映射 /csi:
apiVersion: apps/v1
kind: Deployment
metadata:
name: csi-fast-controller
namespace: kube-system
spec:
template:
spec:
containers:
- name: csi-driver
volumeMounts:
- name: socket-dir
mountPath: /csi # driver socket 目录
- name: csi-provisioner
volumeMounts:
- name: socket-dir
mountPath: /csi # provisioner 与 driver 共享 socket
volumes:
- name: socket-dir
emptyDir: {} # 容器间共享的临时目录
注意两个容器都需要挂载同一个 socket-dir——external-provisioner 和 CSI driver 通过这个共享的 emptyDir 卷交换 gRPC 消息。emptyDir 是 Pod 级别的临时目录,两个容器通过它实现本地 IPC。
修复后重启 CSI driver Pod,PV 创建成功:
kubectl get pvc data-pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-pvc Bound pvc-8f2b4e12-7c3d-4a5b-9e1f-6d2a3b4c5d6e 10Gi RWO csi-fast-sc 30s

预防措施
这类问题修复起来简单(加几行 YAML),但预防更重要。生产环境的 CSI 驱动部署应该做三件事:
- CSI driver 加 startupProbe:检查 socket 文件是否创建成功再 readiness——避免 deployment 滚动更新时短暂中断
- 监控告警:对
PersistentVolumeClaim ProvisioningFailed事件设置告警,第一时间发现 - 部署清单验收:CSI Driver 部署后自动检查 Pod spec 中
socket-dirvolumeMounts 是否存在——可以在 CI 中加个 kubectl 命令验证
金句
"排查 PVC 创建问题不要在 StorageClass YAML 上反复横跳——Events 已经告诉你是 provisioner 连不上 CSI driver,查 driver pod 才是正解。"
Check-list(每条对应一个 kubectl 命令)
PVC Pending 且疑似 CSI 问题时,按此队列排查:
| 序号 | 命令 | 预期输出 | 异常判断 |
|---|---|---|---|
| 1 | kubectl describe pvc <name> |
Events 段有 ProvisioningFailed 详情 | 看 Message 是否含 connection closed 或 Unavailable |
| 2 | kubectl get pods -n kube-system -o wide \| grep csi |
CSI controller + node 均 Running | controller CrashLoopBackOff 或 0/1 |
| 3 | kubectl logs -n kube-system <csi-controller> |
gRPC server started | Failed to listen on socket 或 no such file |
| 4 | kubectl exec -n kube-system <csi-controller> -- ls -la /csi/ |
socket 文件存在 (csi.sock) |
No such file or directory |
| 5 | kubectl get csidriver |
列出已注册的 CSI 驱动 | 目标 provisioner 对应的条目缺失 |
| 6 | kubectl describe pod -n kube-system <csi-controller> |
VolumeMounts 包含 socket 目录 | 缺少 /csi 挂载 |
C6 下篇预告
下篇我们聊另一个存储踩坑点——动态存储类 StorageClass 参数配置错误,看看 parameters 字段写错一个 key 会导致什么后果。
附:完整命令清单
# 1. PVC 状态
kubectl get pvc data-pvc -o wide
kubectl describe pvc data-pvc
# 2. StorageClass 配置
kubectl get sc csi-fast-sc -o yaml
# 3. CSI 组件 Pod 状态
kubectl get pods -n kube-system -o wide | grep csi
# 4. CSI driver 日志
kubectl logs -n kube-system csi-fast-controller-6b9d75c9d8-4z5m7
kubectl logs -n kube-system -l app=csi-fast-controller
# 5. CSIDriver 注册信息
kubectl get csidriver -o wide
kubectl describe csidriver csi-fast.example.com
# 6. CSI socket 文件验证
kubectl exec -n kube-system csi-fast-controller-6b9d75c9d8-4z5m7 \
-- ls -la /csi/
# 7. CSI driver 容器挂载卷配置
kubectl describe pod -n kube-system csi-fast-controller-6b9d75c9d8-4z5m7 \
| grep -A5 VolumeMounts