Appearance
持久化存储:PVC 到 Linux VFS
容器中的一个目录不是天然持久的。Kubernetes 用 PVC/PV/StorageClass 表达“我需要什么存储”;CSI 把这个声明连接到具体存储系统;Linux 最后把设备、网络文件系统或内存对象挂进容器的 mount namespace。
text
PVC 声明容量、访问模式、StorageClass
-> PV binding 或动态 provision
-> scheduler 选择能使用该卷的 Node
-> CSI controller 创建或 attach 卷
-> kubelet / CSI node plugin stage、publish、mount
-> 容器看到 volumeMount 路径
-> write() -> VFS -> 文件系统 / 块设备 / 网络存储这条链的每一步都有独立状态。PVC=Bound 只表示声明与 PV 已绑定;它不证明目标 Node 已挂载,也不证明应用数据已经持久化到介质。
1. 四个对象分别说什么
| 对象 | 归属与含义 | 不承诺什么 |
|---|---|---|
| PVC | 工作负载请求的容量、访问模式、StorageClass | 某个云盘或目录已经存在 |
| PV | 可绑定的具体存储资源及其声明状态 | 已经可被任意 Node 挂载 |
| StorageClass | 动态供给器和参数、绑定策略、回收策略 | 某次 provision 已成功 |
| VolumeAttachment | 某些 CSI 驱动的控制面 attach 状态 | kubelet 已完成 mount |
PVC 是应用的稳定契约,PV 是集群侧资源。动态供给时,external-provisioner 通过 CSI CreateVolume 创建后端资源并创建或更新 PV;静态供给时,管理员事先提供 PV,binder 负责匹配 PVC 与 PV。
2. 先放置,还是先建卷
StorageClass.volumeBindingMode 决定一项很重要的时序:
text
Immediate:
PVC 创建后尽早 provision / bind
WaitForFirstConsumer:
等待有使用该 PVC 的 Pod 被调度
-> 根据候选 Node 的拓扑再 provision / bind后者解决拓扑耦合问题,例如云盘只能挂到同一可用区的 Node。这里的架构取舍是:把卷绑定推迟到拥有消费位置的信息之后,避免先创建一个 scheduler 根本无法使用的资源。
3. 从 Pod 调度到容器可见路径
Pod 被调度到某个 Node 后,kubelet 的 volume manager 负责让声明中的 volume 对该 Pod 可用。CSI 的常见分工如下,具体调用组合取决于驱动能力和卷类型:
text
Controller 侧:
CreateVolume -> ControllerPublishVolume(需要 attach 的驱动)
Node 侧:
NodeStageVolume -> NodePublishVolume
-> kubelet 将 volume 挂到 Pod volume 目录
-> 容器 runtime 将其 bind mount 到 volumeMountNodeStageVolume 通常把卷准备到 Node 上的全局 staging 路径;NodePublishVolume 再把它暴露给某个 Pod。对不需要 attach 的网络存储、驱动直接挂载或内联卷,调用细节不同,但“控制面准备资源”和“Node 本地让 Pod 可用”的边界仍然存在。
容器有自己的 mount namespace。runtime 常用 bind mount 把 kubelet 已准备好的路径带入容器,因此容器内的 /data 看似普通目录,背后可能是 ext4/XFS 块设备、NFS、CephFS、tmpfs 或驱动的其他实现。应用不能通过路径名字判断持久性、延迟、原子性或多写者语义。
4. 写入真正经过 Linux 的哪一层
卷挂载成功后,Kubernetes 不会参与每次文件写入。应用的系统调用进入 Linux:
text
write(fd, bytes)
-> VFS 根据 mount 与 inode 找到文件系统实现
-> page cache / 文件系统元数据与日志
-> 块层与设备驱动,或网络文件系统客户端
-> 本地磁盘、云块设备、远端存储集群write() 返回、数据进入 page cache、文件系统 journal 提交、设备完成 flush 是不同完成点。数据库或关键业务日志不能把“函数返回”直接当作断电后一定可恢复;需要依据文件系统、挂载选项、存储系统与应用自身的 fsync/WAL 策略定义持久化语义。
这与前文的 GPU fence、Service endpoint 是同一类架构问题:上游接受请求、下游完成工作、最终对外可见,都是不同状态,必须用明确契约连接。
5. 访问模式与回收策略的边界
PV 的 ReadWriteOnce、ReadOnlyMany、ReadWriteMany 描述 Kubernetes 期望的访问方式和驱动能力。它们不是一套跨存储后端完全相同的锁协议;特别是 RWO 通常约束可读写挂载的 Node,而不必然等价于“该 Node 上只能有一个 Pod 使用”。应以所选 CSI 驱动的实际能力与 Kubernetes 版本文档为准。
persistentVolumeReclaimPolicy 则决定 PVC 释放后如何处理后端资源:Delete 通常让供给器删除资源,Retain 保留给管理员复核和手动回收。它不是备份策略,也不能替代应用级删除保护。
6. 按阶段排查“数据卷不可用”
| 阶段 | 证据 | 常见断点 |
|---|---|---|
| 声明与绑定 | PVC/PV phase、StorageClass、events | class 不存在、容量/访问模式不匹配 |
| 拓扑与调度 | Pod node、WaitForFirstConsumer、Node affinity | 卷在错误可用区,Pod 无可用 Node |
| 控制面准备 | CSI controller 日志、VolumeAttachment、后端卷状态 | provision 或 attach 失败 |
| Node 挂载 | kubelet、CSI node plugin、mount 事件 | 设备未出现、凭据/格式/挂载失败 |
| 应用写入 | 容器内 mount、权限、I/O 错误、持久化语义 | 路径只读、UID/GID 不符、空间满、延迟或数据一致性错误 |
不要只用 kubectl get pvc 收尾。它最多覆盖第一段;应用看见路径、能写入、数据在故障模型下如何保存,属于后续不同的证据层。
7. 从 CSI 学到的架构规则
- Kubernetes API 只表达可移植的存储需求,CSI 把需求适配到厂商和后端差异。
- 控制器和 Node plugin 分工:前者管理全局资源,后者处理与机器有关的 attach、mount 和生命周期。
- 延迟绑定把不可逆资源创建推迟到拓扑信息充分之后。
- “请求被接受”“资源已绑定”“Node 已挂载”“数据已持久化”必须作为不同验收点。
资料与源码入口
- Kubernetes:Persistent Volumes、Storage Classes、CSI Volumes,访问于 2026-08-03。
- CSI:Container Storage Interface Specification,访问于 2026-08-03。RPC 的支持范围由具体驱动能力决定。
- Kubernetes 源码:
pkg/controller/volume/persistentvolume/、pkg/kubelet/volumemanager/、pkg/volume/csi/,访问于 2026-08-03。 - Linux:VFS documentation、Linux block layer,访问于 2026-08-03。
本文解释的是 PVC/PV/CSI 和 Linux VFS 的职责关系。任何存储后端的性能、故障域、快照、一致性和多写者语义,都必须以目标 CSI 驱动和实际存储系统文档、压测与故障演练为准。