K8s存储卷全解析:PV/PVC/StorageClass 关系

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

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

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

一、先看懂三个角色

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

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

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

yaml 复制代码
[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

yaml 复制代码
[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 就看不到了
bash 复制代码
[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。

yaml 复制代码
# 定义一个 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 里引用它:

yaml 复制代码
[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(动态供给)

写成配置就是三层调用:

yaml 复制代码
# 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(回收策略)

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

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

3. volumeBindingMode(卷绑定模式)

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

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

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

1. PVC 一直 Pending

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

排查三步走:

bash 复制代码
# 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

bash 复制代码
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`

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

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

相关推荐
江华森4 小时前
从零搭建 Kubernetes 集群并部署 Kuboard v3 管理面板 —— 国内环境完整实战教程
容器·kubernetes
xiaogg367811 小时前
Rancher2.0搭建kubernetes(K8S)集群
云原生·容器·kubernetes
成为你的宁宁11 小时前
【基于 Prometheus Operator 监控 K8s控制器、调度器、代理组件】
kubernetes·prometheus
IT策士12 小时前
第 44篇 k8s之实战:将 Web 应用迁移到 Kubernetes(上)
前端·容器·kubernetes
Zhu75813 小时前
在k8s环境部署elasticsearch+kibana
elasticsearch·kubernetes·jenkins
张青贤13 小时前
k8s驱逐节点和加入节点
kubernetes·kubeadm
IT策士14 小时前
第 42 篇 k8s之日志管理:使用 EFK 或 Loki 采集日志
云原生·容器·kubernetes
Plastic garden15 小时前
k8s(4)Kubernetes Pod 管控例子
云原生·容器·kubernetes