K8s 的存储体系有三个核心概念:

  • PV 是什么?为什么要手动创建?
  • PVC 又是什么?和 PV 什么关系?
  • StorageClass 怎么做到自动创建 PV?

很多人分不清它们谁是谁、怎么配合。这篇文章一次讲透。

一、先看懂三个角色

K8s 把存储抽象成了一套「供需匹配」模型,类比租房市场特别好理解:

角色 K8s 概念 租房类比 谁创建的
房源 PV(PersistentVolume) 可出租的房子 管理员 / StorageClass
求租 PVC(PersistentVolumeClaim) 我要一套两室一厅 开发者(写在 Pod 配置里)
中介 StorageClass 房产中介,按需建房 管理员

关键设计理念:开发人员不需要知道底层存储是什么。你只要声明"我要 10Gi 存储",剩下的 K8s 自动匹配。

[root@master-1 ~]# cat pvc.yaml 
apiVersion: v1
kind: PersistentVolumeClaim  # 资源类型为持久卷声明(PVC)
metadata:
  name: nginx-pvc    # PVC 对象名称,需在命名空间内唯一
spec:
  storageClassName: ""   # 空字符串表示禁用动态存储,只匹配静态 PV
  accessModes:
    - ReadWriteMany
  resources:  # 声明存储资源需求
    limits:
      storage: 20G   # 存储资源上限(不可超过 PV 容量)
    requests:
      storage: 10Gi  # 实际请求的存储空间(PV 需满足此值)

至于这 10Gi 来自 NFS、Ceph、还是云盘,开发者不用关心。

二、PV 和 PVC 是怎么绑定的

1. 静态供给:管理员先建好 PV

[root@master-1 ~]# cat pv.yaml 
# 管理员提前建好一个 20Gi 的 NFS PV
apiVersion: v1
kind: PersistentVolume     #资源类型为持久卷(PV)
metadata:
  name: nginx-pv
spec:
   accessModes:
   - ReadWriteMany   #适合 NFS 共享存储
   # 声明存储卷的类型为nfs
   nfs:
     path: /ifs/kubernetes/data/pv001
     server: 192.168.91.19
   persistentVolumeReclaimPolicy: Retain
   # 声明存储的容量
   capacity:
     storage: 20Gi

当 PVC 请求 10Gi ReadWriteMany 时,K8s 会在所有 PV 里找一个「容量够 + 访问模式匹配」的,绑上去。

绑定的规则很死:

  1. 容量:PV 容量 ≥ PVC 请求(20Gi 的 PV 可以给 10Gi 的 PVC,反之不行)
  2. 访问模式:必须兼容(RWO → RWO,RWX → RWX,RWO 不能给 ROX)
  3. 一对一:一个 PV 只能绑一个 PVC,绑完其他 PVC 就看不到了
[root@master-1 ~]# kubectl apply -f pv.yaml
persistentvolume/nginx-pv created
[root@master-1 ~]# kubectl apply -f pvc.yaml 
persistentvolumeclaim/nginx-pvc created
# 查看绑定状态
[root@master-1 ~]# kubectl get pv,pvc

2. 动态供给:StorageClass 自动化建 PV

静态供给的问题是:管理员得提前把所有规格 PV 建好,太累了。

StorageClass 解决的就是这个:当 PVC 来了,自动帮你建 PV。

# 定义一个 StorageClass 
apiVersion: storage.k8s.io/v1
# 指定使用的 Kubernetes API 版本,storage.k8s.io/v1 是存储相关资源的稳定版
kind: StorageClass
metadata:
  name: managed-nfs-storage # StorageClass 的名称,PVC 可以通过指定这个名字来使用该存储类
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
# 关键注解:将当前 StorageClass 设置为集群的**默认存储类**。
# 当 PVC 未指定 storageClassName 时,会自动使用这个默认类来动态创建 PV。
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
# 指定存储供应商(provisioner)的名称。
# 这里使用的是 NFS 子目录外部供应器,它会根据 PVC 请求在 NFS 服务器上自动创建子目录并绑定。
parameters:
  archiveOnDelete: "false"
# 参数:当 PVC 被删除时,是否自动归档(保留)对应的后端存储数据。
# false 表示直接删除 NFS 上对应的子目录及数据;true 表示会重命名(归档)而不删除。
reclaimPolicy: Delete
# PV 的回收策略。Delete 表示当 PVC 被释放后,动态创建的 PV 和其后端存储(如 NFS 子目录)会被一起删除。
# 其他可选值有 Retain(保留数据供手动处理)。
volumeBindingMode: Immediate
# 卷绑定模式。Immediate 表示 PVC 创建时立即进行绑定,不考虑 Pod 调度约束。
# 另一个常见值是 WaitForFirstConsumer,会等待第一个使用该 PVC 的 Pod 被调度后再绑定,以感知节点可用区等。

然后在 PVC 里引用它:

[root@master-1 ~]# cat pvc.yaml 
apiVersion: v1
kind: PersistentVolumeClaim  # 资源类型为持久卷声明(PVC)
metadata:
  name: nginx-pvc    # PVC 对象名称,需在命名空间内唯一
spec:
  storageClassName: managed-nfs-storage   # 指定用哪个 StorageClass
  accessModes:
    - ReadWriteMany
  resources:  # 声明存储资源需求
    limits:
      storage: 20G   # 存储资源上限(不可超过 PV 容量)
    requests:
      storage: 10Gi  # 实际请求的存储空间(PV 需满足此值)
      
[root@master-1 ~]# kubectl apply -f pvc.yaml

K8s 收到这个 PVC,自动创建一个 PV 并绑定。全程管理员零介入。

三、一张图看清完整链路

[Pod] → 引用 PVC → PVC 找 PV → PV 对接实际存储(NFS/Ceph/云盘)
                                    ↑
                          StorageClass 自动创建 PV(动态供给)

写成配置就是三层调用:

# Layer 1: Pod 引用 PVC
spec:
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: my-pvc         # 指向 PVC

# Layer 2: PVC 引用 StorageClass
spec:
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 20Gi

# Layer 3: StorageClass 定义后端供给
provisioner: disk.csi.azure.com  # 或 NFS / Ceph / 云盘 CSI

四、三个必须知道的关键参数

1. accessModes(访问模式)

模式 含义 典型场景
ReadWriteOnce (RWO) 单节点读写 数据库(MySQL、PostgreSQL)
ReadOnlyMany (ROX) 多节点只读 静态资源、配置文件分发
ReadWriteMany (RWX) 多节点读写 共享文件存储、日志采集

注意:RWO 不等于单 Pod。同一 Node 上多个 Pod 都可以读写同一个 RWO 卷。

2. reclaimPolicy(回收策略)

reclaimPolicy: Retain    # 推荐:删除 PVC 后 PV 保留,手动清理
reclaimPolicy: Delete    # 危险:删除 PVC 后自动删底层存储
reclaimPolicy: Recycle   # 已废弃:rm -rf 后重新可用

生产环境建议一律用 RetainDelete 策略在测试环境省事,但在生产上一个误删 PVC 就可能导致数据永久丢失。

3. volumeBindingMode(卷绑定模式)

volumeBindingMode: Immediate              # PVC 一创建就绑(可能跨可用区)
volumeBindingMode: WaitForFirstConsumer   # 等 Pod 调度后再绑(推荐)

WaitForFirstConsumer 的好处:PV 创建的云盘和 Pod 在同一个可用区,避免跨 AZ 延迟。云环境下强烈推荐。

五、排坑实战:最常见的 3 个问题

1. PVC 一直 Pending

[root@master-1 ~]# kubectl get pvc nginx-pvc
NAME        STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS           AGE
nginx-pvc   Pending                                      managed-nfs-storage1   53s

排查三步走:

# 1. 看 PVC 详情,最下面会告诉你原因
kubectl describe pvc nginx-pvc

# 2. 常见报错:
#    "no persistent volumes available" → 没有匹配的 PV 或 StorageClass 没配
#    "storageclass.storage.k8s.io "standard" not found" → StorageClass 名称写错了

# 3. 看 StorageClass 是否存在
kubectl get storageclass

2. Pod 挂载超时,一直 ContainerCreating

kubectl describe pod nginx
# Events:
#   Warning  FailedMount  Unable to attach or mount volumes

通常是两个原因:

  • 云盘跨可用区挂载:Pod 在 Zone A,云盘在 Zone B → 用 WaitForFirstConsumer
  • NFS 服务器不可达:检查 NFS Server IP 和防火墙

3. 删了 PVC,数据还在吗?

取决于 reclaimPolicy

策略 删 PVC 删 PV 云盘/实际存储
Retain PV 变 Released 需手动删 保留
Delete PV 被删 自动删 一起删 ⚠️

所以在 Delete 策略下,谁手滑删了 PVC 谁就可能背锅。

六、总结

一句话:PV 是管理员准备的存量房,PVC 是开发者的租房请求,StorageClass 是自动建房的中介。

落地清单

# 动作 为什么
1 生产环境配 StorageClass,不要手动建 PV 减少人工操作,避免规格不匹配
2 reclaimPolicy 用 Retain 防止误删 PVC 导致数据丢失
3 volumeBindingMode 用 WaitForFirstConsumer 避免云盘跨可用区挂载
4 PVC 命名规范:{app}-{环境}-data 一眼认出是谁的卷
5 定期 kubectl get pv | grep Released 清理僵尸 PV,释放资源

K8s 存储说难不难,三个概念搞清楚了,剩下的就是选好 CSI 驱动、配好回收策略。出问题的时候,对着 PV → PVC → StorageClass 这条链逐层排查,基本都能定位。

你是手动建 PV 还是用 StorageClass 动态供给?评论区聊聊你的生产实践。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐