一文理清K8s PVC挂载完整流程

一文理清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+ 容器内目录可读写+ 数据库或业务进程实际使用该目录+ 备份与恢复验证通过
相关推荐
青柠之夏cc1 小时前
Laravel服务容器底层原理揭秘
javascript·rpc·laravel
Coisinier2 小时前
docker命令管理镜像及容器
linux·运维·nginx·docker·容器·操作系统
小猿姐2 小时前
KubeBlocks Enterprise v2.3 发布!
redis·kubernetes·aiops
东风微鸣5 小时前
如何发现 K8s 集群里的无用资源?
云原生·容器·kubernetes
flytalei6 小时前
从YAML到Nginx服务访问:动手理解 Kubernetes Pod、Deployment 与 Service
运维·nginx·kubernetes
木雷坞6 小时前
Docker Compose部署Docmost:数据库、附件持久化与首次协作
数据库·docker·容器
cg.family7 小时前
K8S:scale 与 delete pod 核心区别
运维·docker·容器
wei_shuo7 小时前
极空间部署1Panel实战:应用商店装AList、Docker管理与远程访问
运维·docker·容器
朗尊软件技术团队7 小时前
电商系统Docker容器化部署实践:从Dockerfile镜像瘦身到K8s滚动上线
运维·docker·云原生·容器·kubernetes