说明:本文由 Codex 根据作者提供的主题、素材与公开资料辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。
为什么需要 PV 和 PVC#
Pod 是短生命周期资源。
Pod 被删除、重建、迁移到其他节点时,容器里的临时文件系统通常也会跟着消失。
如果应用需要保存状态,例如:
- 数据库数据。
- 用户上传文件。
- 消息队列数据目录。
- 构建缓存。
- 需要跨 Pod 重启保留的业务状态。
就需要把存储从 Pod 生命周期里抽象出来。
Kubernetes 用三类对象完成这个抽象:
| 对象 | 角色 |
|---|---|
PersistentVolume | 集群里的真实存储资源 |
PersistentVolumeClaim | 用户对存储资源的申请 |
StorageClass | 动态创建存储资源的模板和策略 |
简单理解:
PV 是存储资源;
PVC 是申请单;
StorageClass 是供应规格。PV 是什么#
PersistentVolume 简称 PV。
它是集群级别资源,不属于某个 namespace。
PV 描述的是一块已经准备好,或者可以被 Kubernetes 管理的存储资源。
这块资源可能来自:
- 云厂商块存储。
- NFS。
- iSCSI。
- Ceph。
- CSI 驱动提供的存储。
- 本地磁盘。
一个静态 PV 示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-demo
spec:
capacity:
storage: 20Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs-retain
nfs:
server: 192.0.2.10
path: /exports/app-dataPV 里通常会描述:
- 容量。
- 访问模式。
- 回收策略。
- 存储类别。
- 真实后端存储信息。
- 节点亲和性。
- 卷模式:文件系统或块设备。
PVC 是什么#
PersistentVolumeClaim 简称 PVC。
它是 namespace 级别资源。
应用通常不会直接声明自己要使用哪个 PV,而是创建 PVC 来表达需求:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
namespace: app
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard这表示:
我需要一块 10Gi 的存储;
访问模式是 ReadWriteOnce;
希望使用 standard 这个 StorageClass。控制面会寻找匹配的 PV,或者通过 StorageClass 动态创建 PV,然后把 PVC 和 PV 绑定。
绑定关系是一对一的:
一个 PVC 绑定一个 PV;
一个 PV 同一时间只绑定一个 PVC。StorageClass 是什么#
StorageClass 用来描述一类存储。
例如:
- 普通磁盘。
- 高性能 SSD。
- 多副本高可用存储。
- 支持快照和扩容的存储。
- 特定区域或拓扑的存储。
示例:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: csi.example.com
parameters:
type: ssd
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer几个关键字段:
| 字段 | 含义 |
|---|---|
provisioner | 谁来创建真实存储 |
parameters | 传给 provisioner 的参数 |
reclaimPolicy | PVC 删除后如何处理 PV 和后端存储 |
allowVolumeExpansion | 是否允许扩容 PVC |
volumeBindingMode | 什么时候绑定和创建卷 |
如果 PVC 没有指定 storageClassName,并且集群有默认 StorageClass,Kubernetes 会使用默认 StorageClass。
如果想明确禁用动态供应,可以写:
storageClassName: ""这和不写 storageClassName 不是一回事。
生命周期#
PV 和 PVC 的生命周期可以概括为:
Provisioning -> Binding -> Using -> ReclaimingProvisioning#
供应方式有两种。
静态供应:
管理员提前创建 PV;
用户创建 PVC;
控制面把 PVC 绑定到匹配 PV。动态供应:
用户创建 PVC;
PVC 指定 StorageClass;
CSI 或其他 provisioner 创建真实存储和 PV;
控制面绑定 PVC 和 PV。现代集群更常见的是动态供应。
Binding#
控制面会根据 PVC 的需求匹配 PV:
storageClassName。- 请求容量。
accessModes。volumeMode。- selector。
如果没有匹配的 PV,PVC 会保持 Pending。
查看:
kubectl get pvc -n app
kubectl describe pvc -n app app-data
kubectl get pvUsing#
Pod 通过 PVC 使用存储:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: app
spec:
replicas: 1
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: app-dataPod 不需要知道底层是云盘、NFS 还是别的存储。它只关心 PVC。
Reclaiming#
PVC 删除后,PV 如何处理取决于回收策略。
| 策略 | 行为 |
|---|---|
Retain | 保留 PV 和真实数据,需要人工处理 |
Delete | 删除 PV,并尝试删除后端存储 |
Recycle | 已废弃,不建议使用 |
动态供应的 PV 通常继承 StorageClass 的 reclaimPolicy,默认常见是 Delete。
生产环境里,如果数据非常重要,应认真确认回收策略,避免误删 PVC 后连底层数据一起删除。
Access Modes#
常见访问模式:
| 模式 | 含义 |
|---|---|
ReadWriteOnce | 可被一个节点以读写方式挂载 |
ReadOnlyMany | 可被多个节点以只读方式挂载 |
ReadWriteMany | 可被多个节点以读写方式挂载 |
ReadWriteOncePod | 只允许一个 Pod 以读写方式使用 |
访问模式表达的是存储卷挂载能力,不等于应用层并发写一定安全。
例如某些文件系统支持 ReadWriteMany,但应用是否能多副本同时写,还要看应用自己的数据一致性设计。
volumeBindingMode#
StorageClass 里的 volumeBindingMode 很关键。
常见两个值:
Immediate
WaitForFirstConsumerImmediate 表示 PVC 创建后就立即创建或绑定 PV。
WaitForFirstConsumer 表示等到 Pod 被调度时再创建或绑定 PV。
对于有拓扑限制的存储,比如只能挂载到某个可用区的云盘,WaitForFirstConsumer 更安全。它可以让调度器先结合 Pod 调度位置,再决定卷应该在哪个区域创建。
扩容#
如果 StorageClass 设置了:
allowVolumeExpansion: true就可以通过修改 PVC 请求容量来扩容:
kubectl patch pvc app-data -n app \
-p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'注意:
- 不是所有存储驱动都支持扩容。
- 文件系统扩容可能需要 Pod 重新挂载或由 kubelet 在线完成,取决于驱动和文件系统。
- 不要直接改 PV 的容量来假装扩容,这可能让控制面误判状态。
常见问题#
PVC 一直 Pending#
常见原因:
- 没有默认 StorageClass。
- PVC 指定的
storageClassName不存在。 - 静态 PV 不满足容量或访问模式。
- CSI provisioner 没有正常运行。
- 拓扑限制导致无法创建卷。
WaitForFirstConsumer下还没有 Pod 使用这个 PVC。
排查:
kubectl describe pvc -n app app-data
kubectl get storageclass
kubectl get pv
kubectl get events -n app --sort-by=.metadata.creationTimestamp
kubectl get pod -n kube-systemPod 挂载失败#
常见原因:
- PV/PVC 未绑定。
- 节点无法访问后端存储。
- CSI node plugin 异常。
- 权限、文件系统、挂载参数错误。
- 多节点挂载和访问模式冲突。
排查:
kubectl describe pod -n app <pod-name>
kubectl describe pvc -n app app-data
kubectl describe pv <pv-name>
kubectl get csinode
kubectl get pods -A | grep -i csi删除 PVC 后数据没了#
先看回收策略:
kubectl get pv
kubectl describe pv <pv-name>如果策略是 Delete,删除 PVC 可能触发底层存储删除。
如果策略是 Retain,PV 和底层数据会保留,但需要管理员手动清理或重新绑定。
一个实践建议#
生产环境里,建议把存储策略显式化:
重要数据:使用 Retain 或明确的备份策略;
临时缓存:可以使用 Delete;
有区域限制的块存储:优先考虑 WaitForFirstConsumer;
需要扩容:确认 CSI 和 StorageClass 支持 allowVolumeExpansion;
多副本同时读写:确认底层存储和应用都支持。不要只看 PVC 申请成功,还要看数据生命周期:
谁创建?
谁挂载?
谁扩容?
谁备份?
谁删除?
删除后数据是否保留?小结#
PV/PVC 的本质是把“应用怎么消费存储”和“集群怎么提供存储”解耦。
管理员或 StorageClass 提供 PV;
应用通过 PVC 申请存储;
Pod 通过 PVC 挂载使用;
回收策略决定 PVC 删除后的数据命运。理解这条链路后,排查存储问题时就能把问题定位到 PVC 申请、PV 绑定、CSI 创建、节点挂载、权限和数据回收中的某一段。