容器天生是「无状态」的,一旦 Pod 被销毁或重建,容器内部写入的文件都会随之消失。但在真实的业务场景中,数据库、消息队列、日志系统、文件上传服务等应用,都必须把数据持久地保存下来,即使 Pod 重建、节点宕机,数据也不能丢。Kubernetes 为此提供了一套完整的持久化存储体系,核心就是 PV(PersistentVolume)、PVC(PersistentVolumeClaim)和 StorageClass。今天我们就从「为什么需要持久化存储」出发,一步步把这套机制讲清楚、用起来。

为什么容器需要持久化存储
先看一个最直观的例子:我们在 Pod 里写入一个文件,然后重建 Pod,看看数据还在不在。
# 进入一个临时 Pod 写入文件
kubectl run busybox --image=busybox --restart=Never -- sh -c "echo hello > /data/test.txt && sleep 3600"
kubectl exec busybox -- cat /data/test.txt # 输出 hello
# 删除 Pod 后重建
kubectl delete pod busybox
kubectl run busybox --image=busybox --restart=Never -- sh -c "sleep 3600"
kubectl exec busybox -- ls /data # 文件不见了
结果很清楚:容器自身的文件系统生命周期与 Pod 绑定,Pod 没了,数据也就没了。要解决这个问题,就需要把「数据的生命周期」和「容器的生命周期」解耦,这正是 PV / PVC 机制存在的意义。
核心概念:PV、PVC 与 StorageClass
Kubernetes 把持久化存储抽象成了三个层次的角色,各司其职:
- PV(PersistentVolume):集群级的存储资源,相当于「管理员预先准备好的存储空间」。它由管理员创建,或由 StorageClass 动态生成,独立于任何 Pod 存在。
- PVC(PersistentVolumeClaim):用户(应用)发起的存储申请,相当于「我要一个多大、什么类型的存储」。Pod 通过引用 PVC 来使用存储,而不直接接触 PV。
- StorageClass:存储的「配方」。它定义了用什么后端(如 NFS、云盘、Ceph)、什么参数去动态创建 PV,实现按需供给。
这套设计实现了申请与供给的分离:开发者只需要声明 PVC(要多少、什么模式),无需关心底层是本地磁盘还是云硬盘;而管理员负责定义 StorageClass 和 PV 池。这样职责清晰,也便于在多租户环境下统一管理。
访问模式与回收策略
PV / PVC 有两个关键属性需要理解。
访问模式(accessModes)主要有三种:
accessModes:
- ReadWriteOnce # RWO:单个节点读写
- ReadOnlyMany # ROX:多节点只读
- ReadWriteMany # RWX:多节点读写
回收策略(persistentVolumeReclaimPolicy)决定了 PVC 删除后 PV 的去向:
persistentVolumeReclaimPolicy: Retain # 保留数据,需手动清理
# 或 Delete:删除 PVC 时同时删除底层存储
# 或 Recycle:已废弃,不推荐使用
实战步骤:创建 PV、PVC 并挂载到 Pod
下面我们用一个 NFS 类型的静态 PV 完整演示一遍流程。假设已经有一台 NFS 服务器 192.168.1.100,共享目录为 /data/k8s。
第一步:创建 PV
# pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-5g
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/k8s
server: 192.168.1.100
kubectl apply -f pv.yaml
kubectl get pv
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
# pv-nfs-5g 5Gi RWX Retain Available
可以看到 PV 处于 Available 状态,等待被绑定。
第二步:创建 PVC 并绑定
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nginx-data
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
kubectl apply -f pvc.yaml
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES
# pvc-nginx-data Bound pv-nfs-5g 5Gi RWX
当 PVC 的请求(5Gi、RWX)与某个 PV 的规格匹配时,二者自动绑定,PVC 状态变为 Bound。
第三步:Pod 挂载 PVC
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-with-pv
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: pvc-nginx-data
kubectl apply -f pod.yaml
kubectl exec nginx-with-pv -- sh -c "echo persisted > /usr/share/nginx/html/index.html"
此时无论 Pod 如何重启,/usr/share/nginx/html 下的数据都会持久化到 NFS 上,不再随 Pod 消失。
动态供给:StorageClass 实战
手动创建 PV 在存储规模大时会非常繁琐,实际生产环境更常用 StorageClass 动态供给——用户创建 PVC 时,由系统自动创建对应的 PV。下面是一个使用 NFS 的动态供给示例(需要先部署 nfs-subdir-external-provisioner):
# storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: nfs.storage.k8s.io/nfs
parameters:
archiveOnDelete: "false"
# dynamic-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-data
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
kubectl apply -f storageclass.yaml
kubectl apply -f dynamic-pvc.yaml
kubectl get pv # 会自动生成一个 10Gi 的新 PV
动态供给大幅降低了运维成本:应用只需声明需求,存储资源按需自动创建、绑定。
常见问题
1. PVC 一直处于 Pending 状态怎么办?
通常是三个原因:一是没有匹配的 PV,二是没有对应的 StorageClass 或 provisioner 未就绪,三是访问模式不匹配。用 kubectl describe pvc <name> 查看 Events 定位原因。
2. PV 删除不了,一直卡在 Terminating?
这说明 PV 还绑定着 PVC,或底层存储存在保护机制。先删除引用它的 PVC,若仍卡住,可尝试给 PV 打补丁强制删除终结器:
kubectl patch pv <pv-name> -p '{"metadata":{"finalizers":null}}'
3. 多副本应用如何共享存储?
需要存储支持 ReadWriteMany(RWX)访问模式,例如 NFS、CephFS、GlusterFS。本地磁盘(如 hostPath、local)只支持单节点读写,无法用于多副本共享。
4. Pod 挂载报 mount 失败?
检查 NFS 服务器网络与权限、导出目录是否可访问,以及节点上是否安装了对应存储插件。查看 kubectl describe pod 的 Events 是最直接的排查手段。
总结
持久化存储是 Kubernetes 上运行有状态应用的基础。今天我们从容器数据易失的问题出发,理解了 PV、PVC、StorageClass 三个核心概念的分工,并通过「静态 PV + PVC + Pod 挂载」和「StorageClass 动态供给」两个实战场景,完整走通了数据持久化的流程。掌握这套机制后,你就能在集群上稳定地运行数据库、消息队列等有状态服务。存储还只是开始,后续我们会继续深入 CSI 驱动、备份恢复等进阶话题,敬请期待。
下期预告:第 8 天我们将进入「调度机制」,讲解节点选择(nodeSelector / nodeAffinity)、污点(taint)与容忍度(toleration),让 Pod 精确调度到合适的节点上。


















暂无评论内容