一文理清K8s PVC挂载完整流程
很多人看到 PVC 变成 Bound,就以为存储已经好了。
实际生产里,最常见的画面是:
PVC:BoundPod:ContainerCreating事件:FailedAttachVolume / FailedMount
原因不复杂:PVC 绑定成功,只说明"卷分配给谁";真正让容器看到目录,还要经过调度、Attach、Stage、Publish、kubelet 挂载。
本文把这条链路拆开。遇到 StatefulSet 起不来、云盘多挂载冲突、Local PV 调度失败、CSI 插件异常时,可以按图排查。
先给结论:不要把 Bound 当成已挂载
PV、PVC、Pod 和容器内挂载点是四个不同阶段。
| 阶段 | 代表什么 | 不代表什么 |
|---|---|---|
| PVC Pending | 还没找到可用卷 | 不能判断应用问题 |
| PVC Bound | PV 与 PVC 已一对一绑定 | 卷还不一定在目标节点上 |
| VolumeAttachment attached | 控制面确认卷已附着到节点 | 节点侧不一定已挂载 |
| Pod Running | 容器已启动 | 应用数据不一定正确 |
| 容器内可读写目录 | 挂载链路完成 | 还需验证权限、数据与恢复能力 |
一句话记忆:
Bound 是"卷归属确定";Mount 成功才是"容器真正能用"。
一、PVC 到容器目录的全链路
以一个使用 CSI 块存储的数据库 Pod 为例:
用户创建 PVC ↓PV Controller 绑定或动态创建 PV ↓Pod 引用 PVC ↓Scheduler 为 Pod 选择节点 ↓Attach/Detach Controller 创建 VolumeAttachment ↓CSI external-attacher 调用 ControllerPublishVolume ↓存储系统把盘附着到目标节点 ↓kubelet 调用 NodeStageVolume ↓CSI Node 插件把设备挂到节点全局路径 ↓kubelet 调用 NodePublishVolume ↓卷被绑定挂载到 Pod volume 目录 ↓容器看到 volumeMounts 中的目录
这不是每一种驱动都完全一样。
例如 NFS、CephFS 这类网络文件系统,通常没有"把一块盘挂到某台节点"的概念,可以跳过 Attach;而云盘、SAN、块存储,Attach 往往是最容易出问题的一段。
二、第一段:PVC 如何绑定 PV
PVC 是应用声明:我要多大容量、什么访问模式、什么存储类型。
PV 是集群中的实际存储资源。它可以由管理员提前创建,也可以由 StorageClass 触发 CSI 动态创建。
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: mysql-data namespace: databasespec: accessModes: - ReadWriteOnce storageClassName: fast-csi resources: requests: storage: 100Gi
PVC Controller 会持续寻找匹配 PV。匹配要看:
容量是否够StorageClass 是否匹配accessModes 是否兼容volumeMode 是否一致selector 是否限制标签PV 是否已经被别的 PVC 占用
绑定关系是一对一,通过 claimRef 固化。一个 Bound 的 PV 不能再被另一个PVC随意抢走。
检查命令:
kubectl -n database get pvc mysql-datakubectl -n database describe pvc mysql-datakubectl get pv
如果 PVC 长期 Pending,先看 Event:
kubectl -n database get events --sort-by=.lastTimestamp | tail -30
常见原因:没有默认 StorageClass、请求容量过大、访问模式不匹配、Local PV 的 nodeAffinity 不符合条件。
你的集群没有默认 StorageClass 时尤其要注意:没有显式写 storageClassName 的 PVC 不会自动有盘,可能一直 Pending。
三、第二段:Pod 调度必须同时考虑卷的位置
Pod 引用了 PVC 后,Scheduler 不能只看 CPU 和内存。
对于有拓扑限制的卷,还要考虑:
卷能否挂到这个节点可用区是否匹配Local PV 是否只允许某个节点驱动是否支持多节点挂载节点是否已达到卷挂载数量上限
Local PV 最直观:数据就在 worker-1 本地磁盘,Pod 被调度到 worker-2 就没有意义。
一个 Local PV 通常会带 nodeAffinity:
spec: local: path: /data/local-pv/mysql-01 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - worker-1
如果 StatefulSet 的 Pod 因为资源、污点或亲和性被迫去别的节点,就会卡住。
此时优先看:
kubectl -n database describe pod mysql-0kubectl get pv <pv-name> -o yamlkubectl get nodes --show-labels
不要只看到 0/4 nodes are available 就盲目删 PVC。先判断是资源、污点,还是卷拓扑限制。
四、第三段:Attach 是控制面请求,真正动作由 CSI 完成
对需要附着的 CSI 驱动,Kubernetes 会创建 VolumeAttachment 对象。
它表达的是:
把 PV xxx 附着到 Node yyy
查看:
kubectl get volumeattachmentkubectl describe volumeattachment <name>
VolumeAttachment 是集群级资源,不属于某个 namespace。重点字段:
spec.source.persistentVolumeNamespec.nodeNamestatus.attachedstatus.attachError
CSI external-attacher 监听这个对象,再调用 CSI Driver 的:
ControllerPublishVolume
存储系统完成附着后,external-attacher 回写:
status: attached: true
注意:
VolumeAttachment.status.attached=true≠ 容器已经看到了数据目录
它只说明控制面认为"卷已到该节点"。后面还有节点侧的格式化、挂载、权限处理和Pod路径绑定。
Attach 常见故障
| 现象 | 常见原因 | 先查哪里 |
|---|---|---|
| Multi-Attach error | RWO卷仍挂在旧节点 | VolumeAttachment、旧Pod、节点状态 |
| attach timeout | 云盘API/存储网络异常 | external-attacher、云平台事件 |
| attachment不出现 | 驱动不需要Attach,或Pod尚未调度 | CSIDriver、Pod nodeName |
| attachment一直false | CSI Controller异常或权限不足 | controller插件日志 |
有些 CSI Driver 设置:
spec: attachRequired: false
比如部分网络文件系统。此时没有VolumeAttachment不一定是故障,不能拿块存储的标准去判断所有驱动。
五、第四段:NodeStage 和 NodePublish 才是节点侧真正挂载
CSI Driver 通常分两部分:
Controller 插件:跑在控制面或独立部署,负责创建、Attach、快照、扩容等Node 插件:跑在每个节点,负责设备发现、挂载、卸载
kubelet 通过 Unix Socket 和节点上的 CSI Driver 通信。
关键调用:
NodeStageVolumeNodePublishVolume
可以这样理解:
NodeStageVolume:把存储准备到节点的全局目录NodePublishVolume:把已准备好的卷挂到某个Pod的volume目录
最终 kubelet 再把它暴露给容器配置里的 volumeMounts.mountPath。
containers:- name: mysql image: mysql:8.0 volumeMounts: - name: data mountPath: /var/lib/mysqlvolumes:- name: data persistentVolumeClaim: claimName: mysql-data
如果 Pod 卡在 ContainerCreating,最有价值的是:
kubectl -n database describe pod mysql-0kubectl -n kube-system get pod -o wide | grep -i csi
然后到目标节点:
journalctl -u kubelet --since '30 min ago'mount | grep -i <pv-or-pvc-keyword>df -h
不同CSI驱动日志位置不同。不要一上来就删 VolumeAttachment;它可能让正在使用的卷进入更复杂的挂载状态。
六、排障路径:PVC Bound,但 Pod 还起不来
按以下顺序查,能避免来回跳:
1. 先确认 Pod 被调度到哪里
kubectl -n database get pod mysql-0 -o widekubectl -n database describe pod mysql-0
如果 NODE 为空,问题还在调度阶段,不要查CSI节点插件。
2. 确认 PVC、PV 绑定和访问模式
kubectl -n database get pvc mysql-data -o yamlkubectl get pv <pv-name> -o yaml
重点看:
status.phase: BoundaccessModes: RWO / RWXstorageClassNamenodeAffinity
3. 对需要Attach的卷看 VolumeAttachment
kubectl get volumeattachmentkubectl describe volumeattachment <name>
4. 看 CSI Controller 与 CSI Node 插件
kubectl -n kube-system get pods -o wide | grep -i csikubectl -n kube-system logs <csi-controller-pod> -c csi-attacherkubectl -n kube-system logs <csi-node-pod> -c <driver-container>
容器名必须以现场实际清单为准。不同驱动的Sidecar名字不同,不能照抄。
5. 最后确认容器内挂载和应用权限
kubectl -n database exec mysql-0 -- mount | grep /var/lib/mysqlkubectl -n database exec mysql-0 -- df -h /var/lib/mysqlkubectl -n database exec mysql-0 -- sh -c 'id && ls -ld /var/lib/mysql'
容器能启动却写数据失败,常见不是CSI没挂载,而是:
fsGroup不匹配底层文件系统只读UID/GID不匹配配额已满应用把数据写到了错误目录
七、RWO、RWX 和 Multi-Attach:最容易误判的地方
ReadWriteOnce 的核心不是"只能有一个Pod",而是同一时刻通常只能在一个节点上读写挂载。
同节点上的多个Pod是否能使用,取决于驱动实现;跨节点同时挂载则更容易触发 Multi-Attach。
| 模式 | 含义 | 常见用途 |
|---|---|---|
| RWO | 单节点读写 | 云盘、块存储、单实例数据库 |
| ROX | 多节点只读 | 静态内容、共享只读数据 |
| RWX | 多节点读写 | NFS、CephFS、共享文件系统 |
| RWOP | 单Pod读写 | 更严格的单写入者限制 |
StatefulSet 做数据库时,不要以为把副本数从1改成3,再让3个Pod共用一个RWO PVC就能形成集群。每个副本应该有自己的PVC,数据库复制由数据库自身处理。
八、生产建议:把存储故障切成四层处理
| 层级 | 责任对象 | 典型问题 |
|---|---|---|
| 声明层 | PVC / PV / StorageClass | Pending、绑定不匹配 |
| 调度层 | Scheduler / Node Affinity | Pod无法落到卷所在节点 |
| 控制器层 | external-provisioner / attacher | 创建卷、Attach失败 |
| 节点层 | kubelet / CSI Node | Mount失败、权限、设备异常 |
事故发生时,先给问题分类,再决定处理人和日志入口。
PVC Pending:先找平台/存储管理员和StorageClassPod Pending:先看调度与拓扑FailedAttachVolume:先看VolumeAttachment和CSI ControllerFailedMount:先看目标节点kubelet与CSI Node应用写入失败:先看容器内挂载、权限和文件系统
不要把"删Pod重建"当万能操作。对于RWO卷,旧Pod未完全释放、节点异常未脱离时,重建可能直接触发Multi-Attach,恢复时间反而更长。
总结
PVC申请存储 ↓PV绑定存储 ↓Scheduler选择合适节点 ↓CSI Controller创建/附着卷 ↓CSI Node在节点Stage/Publish ↓kubelet挂入Pod ↓容器从volumeMounts看到目录
Bound只是中间状态。
真正的需要确认应该是:
PVC Bound+ Pod Ready+ 容器内目录可读写+ 数据库或业务进程实际使用该目录+ 备份与恢复验证通过