PV 与 PVC 存储管理
数据库文件和上传内容属于持久化数据,必须跨 Pod 重建、故障转移甚至集群迁移存活。本章聚焦 PV 与 PVC 这对解耦设计:应用只声明"要多大、怎么用",平台决定"用哪块存储"。
本篇目标:理解 PV/PVC 解耦设计解决什么问题,掌握生命周期与回收策略,分清访问模式,理解 volumeBindingMode 两种模式及跨 AZ 问题。
1. 存储架构演进与 PV/PVC 解耦设计
1.1 为什么需要解耦
早期容器挂载存储的做法是"把宿主机目录塞进容器"(volume),问题很多:路径不可移植、Pod 换节点数据不可达、容量和生命周期没人管。Kubernetes 的答案是把存储抽象为两个对象:
| 对象 | 比喻 | 职责 | 谁来创建 |
|---|---|---|---|
| PV(PersistentVolume) | 仓库里实际有的货 | 集群可用的一块具体存储资源 | 管理员或 StorageClass 动态供给 |
| PVC(PersistentVolumeClaim) | 租用申请单 | Namespace 内应用对容量和访问模式的申请 | 应用开发者 |
| StorageClass(第 13 章) | 缺货自动进货的供应商 | 动态供给、性能与回收策略模板 | 平台团队 |
应用 Pod ──挂载──▶ PVC(申请单)──绑定──▶ PV(实际存储)
▲
│ 动态供给时自动创建
StorageClass(供应商)
应用只写 PVC,不直接碰 PV——这样存储后端换掉时(比如从云盘换成 NFS),应用清单不用改。
1.2 绑定规则
- 一个 PVC 只能绑定一个 PV,绑定关系一对一;
- PV 的容量大于等于 PVC 的申请容量才能绑定(通常取最接近且能容纳的那一个);
- 访问模式需要兼容(见第 3 节)。
2. 生命周期与回收策略
2.1 PV 状态
kubectl get pv 的 STATUS 列直接反映 PV 的一生:
| 状态 | 含义 | 常见场景 |
|---|---|---|
Available | 已创建、未被任何 PVC 绑定 | 刚手动创建、或 PVC 删除后按 Retain 保留 |
Bound | 已被某个 PVC 绑定 | 正常使用中 |
Released | 绑定的 PVC 已被删除,但数据未处理 | 删除 PVC 后、管理员清理前的中间态 |
Failed | 回收失败或存储不可用 | 底层云盘被误删、reclaim 出错 |
kubectl get pv
kubectl get pvc -A
kubectl describe pvc uploads -n learning
2.2 回收策略(reclaimPolicy)
reclaimPolicy 决定 PVC 删除后 PV 怎么处理:
| 策略 | 删除 PVC 后 | 适用 |
|---|---|---|
Delete | PV 和底层存储一并删除 | 开发/测试,存储跟着 PVC 走 |
Retain | PV 保留(状态 Released),需管理员手动确认、复用或删除 | 生产首选,避免误删数据 |
Recycle(已废弃) | 清空数据后重新 Available | 已废弃,勿在新集群使用 |
2.3 PVC 保留策略(StatefulSet 相关)
StatefulSet 场景下,PVC 的生命周期由 persistentVolumeClaimRetentionPolicy 决定(详见第 6 章):
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain # 删除 StatefulSet 时保留 PVC
whenScaled: Delete # 缩容时删除对应 PVC(数据丢失!)
生产上建议 whenScaled: Retain,缩容后手动确认数据不再需要再删除 PVC。
3. 访问模式(AccessModes)
accessModes 定义卷能被多少节点、以什么方式挂载,是选择存储类型时最容易踩坑的地方:
| 模式 | 含义 | 谁支持 |
|---|---|---|
ReadWriteOnce(RWO) | 只能被一个节点挂载为读写 | 云盘(EBS/GCE PD/云硬盘) |
ReadOnlyMany(ROX) | 可被多个节点以只读挂载 | 云盘、NFS、CephFS |
ReadWriteMany(RWX) | 可被多个节点读写 | NFS、CephFS、GFS 等网络文件系统 |
ReadWriteOncePod(RWOP,1.22+) | 只能被一个 Pod挂载(比 RWO 更严格) | 需要严格单写者的场景 |
选型速查:
单副本数据库(云盘) → ReadWriteOnce
多副本共享上传目录(NFS) → ReadWriteMany
配置/证书只读共享(NFS) → ReadOnlyMany
日志采集只读挂载 → ReadOnlyMany
4. 卷绑定模式(VolumeBindingMode)
volumeBindingMode 决定卷何时被创建/绑定,直接影响跨可用区问题:
| 模式 | 行为 | 风险/适用 |
|---|---|---|
Immediate | PVC 一创建就立即创建并绑定卷 | 可能跨 AZ 绑定:卷在 AZ-A,Pod 被调度到 AZ-B 后无法挂载 |
WaitForFirstConsumer | 等第一个使用它的 Pod 被调度后,在 Pod 所在可用区创建卷 | 生产首选,避免跨 AZ 挂载问题 |
WaitForFirstConsumer 的代价是:PVC 会一直 Pending 直到有 Pod 引用它——这不是故障,是预期行为。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
5. 挂载到 Pod
PVC 创建好后,在工作负载里通过 volumes + volumeMounts 两步挂载:
apiVersion: apps/v1
kind: Deployment
metadata:
name: upload-api
namespace: learning
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: upload-api
template:
metadata:
labels:
app.kubernetes.io/name: upload-api
spec:
containers:
- name: api
image: registry.example.com/upload-api:1.4.2
volumeMounts:
- name: uploads
mountPath: /data/uploads
subPath: uploads
volumes:
- name: uploads
persistentVolumeClaim:
claimName: uploads
要点:
claimName引用的 PVC 必须在同一个 Namespace;mountPath是容器内路径,目录不存在时 Kubernetes 会自动创建;subPath可让多个容器共享同一个卷但各挂各的子目录;- 非 root 应用写卷目录遇到
Permission denied,先检查文件系统属主,或在securityContext里设置fsGroup。
6. 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| PVC 一直 Pending | StorageClass 不存在、配额不足、无匹配 PV | kubectl describe pvc 看事件(详见第 13 章) |
| Pod 调度后挂载失败 | Immediate 模式跨 AZ 绑定 | 改用 WaitForFirstConsumer |
| 多副本共享 RWO 卷 | 访问模式不兼容多节点 | 换 RWX 网络存储或改 StatefulSet 独立卷 |
| 删除 PVC 后 PV 卡 Released | 未确认数据、手动清理未做 | 确认后手动删除或重新创建 PV |
7. 练习
练习 A(PVC 持久化验证)
- 创建一个 PVC(1Gi),挂载到一个 busybox Pod 的
/data目录 - 在
/data中写入文件,删除 Pod,重建 Pod 挂载同一 PVC,确认数据还在 - 创建
emptyDir卷(不要 PVC),重复上述操作,确认删除 Pod 后数据丢失
练习 B(生命周期与状态观察)
- 查看集群里的 StorageClass,确认默认 StorageClass 是哪一个
- 创建一个 PVC 并观察状态从 Pending 变为 Bound,
kubectl get pv看绑定情况 - 删除 PVC,观察 PV 状态变化(取决于 reclaimPolicy 是 Delete 还是 Retain)
- 手动创建一个静态 PV(容量 5Gi,
ReadWriteOnce),再用容量匹配的 PVC 绑定它
下一篇:StorageClass 动态存储。