Kubernetes PV 与 PVC:持久化存储基础入门
理解 PersistentVolume 与 PersistentVolumeClaim 的绑定关系、生命周期以及 Kubernetes 存储排障思路。
Kubernetes 一旦进入存储主题,难度就会明显上升。原因很简单:Pod 天生可以被替换,但数据通常不行。PV 和 PVC 这套模型,就是 Kubernetes 用来把“应用需要什么存储”和“底层怎么提供存储”分开的方式。
PV 和 PVC 分别是什么
- PV:集群里可供使用的一块存储资源。
- PVC:工作负载发出的存储申请。
- 绑定:Kubernetes 把一个 PVC 匹配到一个合适的 PV。
- StorageClass:描述存储供给策略的规则。
你可以把它理解成:应用只说“我需要什么”,集群决定“具体怎么给”。
这套抽象为什么重要
如果没有 PV / PVC,应用 YAML 就得直接依赖底层存储实现,业务和基础设施会耦合得很死。Kubernetes 把需求和实现分开,是为了让应用声明容量、访问模式、类别,而具体的卷创建、绑定和回收由平台处理。
但这也意味着:一旦存储出问题,Pod 往往只是表面现象,真正的根因常常藏在 PVC、PV、StorageClass 或 provisioner 里。
基本工作流
- 应用通过 PVC 提出存储需求。
- Kubernetes 找一个匹配的 PV,或者通过 StorageClass 动态创建一个。
- PVC 进入
Bound状态。 - Pod 挂载这个 claim。
- 应用开始对挂载路径读写数据。
如果你使用的是动态供给,那么真正串起这条链路的是 StorageClass -> provisioner:PVC 只负责声明 storageClassName,StorageClass 再决定让哪个 provisioner 或 CSI 驱动去创建底层卷。
动态供给和静态供给
动态供给
这是大多数现代集群的默认方式。PVC 指向一个 StorageClass,由后端自动创建卷。
换句话说,PVC 本身并不会直接“造盘”,而是通过 StorageClass 把请求交给对应的 provisioner 去完成。
适合场景:
- 集群里已经有可用的存储 provisioner
- 希望减少人工干预
- 希望开发和测试环境流程更统一
静态供给
管理员先创建 PV,应用再用 PVC 去绑定。
适合场景:
- 必须预先准备好特定存储
- 正在迁移已有卷
- 需要对某些存储卷做更严格的人工控制
最小示例
下面是最值得先理解的一组最小 YAML:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: standard
---
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: nginx:1.25
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
先看懂这组 YAML,再去看 StatefulSet 或数据库类工作负载会轻松很多。
访问模式到底意味着什么
ReadWriteOnce(RWO):通常允许一个节点以读写方式挂载。ReadWriteMany(RWX):多个节点可以共享读写。ReadOnlyMany(ROX):多个节点可以共享只读。
访问模式不是随便填的偏好项,而是底层存储能力的真实映射。你填了 RWX,不代表后端就一定支持。
回收策略:删除后会发生什么
这个细节很多人直到出事才会重视。
Delete:PVC 删除后,底层卷也跟着删除。Retain:PVC 删除后,底层卷保留,等待人工处理或复用。
如果你的数据不能轻易丢,Retain 就不是一个可有可无的参数,而是很重要的安全边界。
扩容与快照
很多存储后端支持 PVC 扩容,但“改大容量”不一定等于“应用马上就能看到更大文件系统”。有时候文件系统本身还要扩展。
快照则是另外一回事。卷能持久化,不等于你有备份体系。备份、恢复、演练,仍然要有自己独立的流程。
用一句话理解 StorageClass
StorageClass 就是集群里关于存储的默认策略描述。它可能决定:
- 用哪个 provisioner
- 用什么性能等级
- 如何回收卷
- 用什么绑定模式
- 是否受可用区拓扑约束
如果说 PV/PVC 是存储契约,那 StorageClass 就是契约背后的策略引擎。
所以这篇最好和 kubernetes-quickstart-storageclass.md 对着看:那篇更偏平台视角,会讲清楚 provisioner、默认类、回收策略和社区里常见的 CSI 驱动是怎么串到 PVC 动态供给里的。
最常见的失败模式
PVC 一直 Pending
最常见原因包括:
- StorageClass 不存在
- provisioner 没有正常工作
- 请求的容量或访问模式不被支持
- 可用区 / 拓扑约束不满足
Pod 因为 claim 没绑定而 Pending
这类问题最容易误导人,因为表面上看到的是 Pod 没起来,实际上根因可能完全在存储层。
卷已经存在,但挂载失败
这时候问题可能已经从绑定阶段进入节点执行阶段,比如 CSI 挂载失败、权限不对、文件系统异常等。
一套实用的排障顺序
遇到存储问题时,别先盯着 Pod,先走这套顺序:
kubectl get pvc -n demo
kubectl describe pvc app-data -n demo
kubectl get pv
kubectl describe pv <pv-name>
kubectl get storageclass
kubectl get events -n demo --sort-by=.metadata.creationTimestamp
然后依次问:
- PVC 是不是
Bound? - 引用的 StorageClass 对不对?
- 后端是否支持这个容量和访问模式?
- 是否被 zone / topology 约束卡住?
- 如果已经绑定,是否在节点挂载阶段失败?
拓扑与可用区问题
很多存储后端是按可用区工作的。如果 Pod 被调度到错误的 zone,卷可能永远挂不上。这也是为什么一旦进入多可用区集群,存储就不再只是“容量问题”,而变成了“调度问题”。
性能与成本取舍
存储并不只是“够不够大”的问题:
- 本地盘或 SSD 更快,但灵活性通常差一些
- 网络存储更容易统一管理,但延迟可能更高
- 高性能类存储很容易把成本抬上去
选择 StorageClass 时,最好基于真实 IO 模式,而不是凭直觉“多买一点保险”。
什么场景下别急着上 PVC
并不是所有数据都值得一开始就做持久化。
- 可重建缓存可以不持久化
- 临时工作目录可能用
emptyDir就够 - 一些无状态服务的数据本就不需要跨 Pod 保留
持久化很重要,但它也意味着你要承担备份、迁移、恢复和清理这些额外责任。
FAQ
Q: Pod 一直 Pending,是不是先看 Pod 就够了?
A: Pod 要看,但如果涉及存储,PVC 往往更值得优先看。很多问题本质上是 claim 没绑定,而不是 Pod YAML 本身出错。
Q: PV 在不在,就等于我有备份了吗? A: 不是。PV 只解决持久化,不解决备份、恢复和灾备演练。
Q: 什么时候最该关心 Retain 和 Delete?
A: 只要你在意误删后的数据命运,就应该明确评估回收策略。它会直接影响删除后的恢复空间。
下一步阅读
- 接着读
kubernetes-quickstart-storageclass.md - 再读
kubernetes-quickstart-statefulset.md - 如果你要跑数据库,再继续读 MySQL 相关 QuickStart
收个尾
存储问题表面上常常表现为“Pod 没起来”,但真正的关键往往在 claim、class、provisioner 和拓扑约束。先把存储链路看清,再去怀疑应用本身,通常效率更高。