返回首页

5.2:PV / PVC / StorageClass

1. 为什么 Kubernetes 要把“存储提供”和“存储使用”拆开

在 Kubernetes 里,计算资源和存储资源不是同一类问题。Pod、Deployment 负责应用运行,但存储往往来自云盘、NAS、本地盘、分布式块存储等外部系统。官方文档对 PersistentVolume 子系统的定义非常明确:它通过一组 API,把“存储如何被提供”与“存储如何被消费”解耦。::cite[212]

这件事非常重要。因为对应用开发者来说,我只想说“给我一块 20Gi 的可读写存储”;但对平台管理员来说,我关心的是“这块存储来自什么类型的后端、性能级别如何、是否支持扩容、删除后是否回收”。PV / PVC / StorageClass 就是 Kubernetes 为这两个视角提供的协作机制。::cite[212]

2. 三个核心对象先记住

对象 站在谁的视角 主要职责
PV(PersistentVolume) 集群/平台管理员 描述一块可用存储资源
PVC(PersistentVolumeClaim) 应用使用者 申请一块符合条件的存储
StorageClass 平台管理员 定义某类存储能力与动态供给方式

一句话理解:

  • PV 是“存储本体”
  • PVC 是“存储申请单”
  • StorageClass 是“存储套餐说明书”::cite[212]

3. PV 与 PVC 的角色分工

3.1 PV:平台侧准备好的存储资源

PV 是集群中的一类资源对象,它描述容量、访问模式、回收策略、底层卷类型等信息。它并不直接绑定某个 Pod,而是等待 PVC 来匹配。::cite[212]

常见字段包括:

  • capacity.storage:容量大小
  • accessModes:访问模式,比如 ReadWriteOnce
  • persistentVolumeReclaimPolicy:回收策略
  • storageClassName:所属存储类
  • csi / nfs / hostPath 等:底层存储实现

3.2 PVC:应用侧发起的存储请求

PVC 由应用侧提交,它不关心底层具体是哪个云盘、哪台存储阵列,只描述“我要多大、我需要什么访问模式、最好属于哪个 StorageClass”。Kubernetes 会把 PVC 与合适的 PV 绑定起来。::cite[212]

PVC 常见字段包括:

  • resources.requests.storage:申请容量
  • accessModes:期望访问模式
  • storageClassName:期望的存储类
  • volumeName:如果你想显式绑定某个 PV,也可以指定

3.3 Pod 如何使用 PVC

Pod 不直接挂载 PV,而是挂载 PVC。也就是说,应用永远通过“声明需求”来拿存储,而不是直接操作底层卷对象。这种设计让应用 YAML 更稳定,也让平台可以在后端自由切换实现。::cite[212]

4. 静态供给:先有 PV,再来 PVC

静态供给(Static Provisioning)适合这样一种场景:管理员已经提前准备好存储,并显式创建了 PV;开发者只需要通过 PVC 去认领它。

4.1 静态供给的工作流程

  1. 管理员创建 PV
  2. 开发者创建 PVC
  3. 控制器根据容量、访问模式、StorageClass 等条件进行匹配
  4. Pod 通过 PVC 使用存储

4.2 完整示例:静态 PV + PVC + Pod

下面用 hostPath 做一个最小化演示。注意,它只是为了帮助理解 PV/PVC 绑定机制,不代表生产推荐方案。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: demo-pv-static
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /data/k8s/demo-pv
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo-pvc-static
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: manual
---
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod-static
spec:
  containers:
    - name: nginx
      image: nginx:1.27
      volumeMounts:
        - name: app-data
          mountPath: /usr/share/nginx/html
  volumes:
    - name: app-data
      persistentVolumeClaim:
        claimName: demo-pvc-static

执行步骤:

kubectl apply -f pv-pvc-static-demo.yaml
kubectl get pv
kubectl get pvc
kubectl describe pvc demo-pvc-static

如果看到 PV 和 PVC 都进入 Bound 状态,就说明绑定成功。::cite[212]

5. 动态供给:PVC 一提交,系统自动创建卷

随着云原生场景普及,管理员不可能为每一个业务都手工准备 PV。于是 Kubernetes 引入了动态供给(Dynamic Provisioning)机制:只要 PVC 指定了某个 StorageClass,系统就可以通过对应的 Provisioner 自动创建实际存储,再生成 PV 并完成绑定。官方文档把这视为 StorageClass 的核心能力之一。::cite[214]

5.1 动态供给的工作流程

  1. 管理员定义 StorageClass
  2. 应用创建引用该 StorageClass 的 PVC
  3. 外部或内置 Provisioner 创建底层卷
  4. Kubernetes 自动生成 PV
  5. PVC 与新 PV 自动绑定
  6. Pod 挂载 PVC 使用该卷

5.2 为什么动态供给更符合现代平台化

因为它把“存储申请”变成了自助式体验:

  • 开发者不需要知道底层存储实现
  • 平台可以通过不同 StorageClass 暴露不同存储档位
  • 自动化程度更高,更适合多租户集群

6. StorageClass:如何定义存储能力

官方文档对 StorageClass 的定义很清楚:它用来描述管理员提供的存储类别,不同类别可以映射到不同 QoS、备份策略或其他平台策略。::cite[214]

6.1 一个 StorageClass 里通常会定义什么

  • provisioner:由谁来创建卷
  • parameters:后端存储相关参数
  • reclaimPolicy:PVC 删除后怎么处理卷
  • allowVolumeExpansion:是否支持扩容
  • volumeBindingMode:何时绑定卷

6.2 示例:定义一个 StorageClass

下面是一个典型的 CSI 场景写法。provisioner 的名称需要根据你的实际驱动来调整。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: csi.example.com
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: ssd
  fsType: ext4

这里可以重点看三件事:

  • Delete 表示卷跟随 PVC 生命周期自动删除
  • allowVolumeExpansion: true 表示该类卷允许扩容
  • WaitForFirstConsumer 表示等 Pod 真正被调度时再完成卷绑定,有利于拓扑感知存储正确落位::cite[214]

7. 动态供给完整示例:StorageClass + PVC + Pod

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: demo-csi-sc
provisioner: csi.example.com
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: standard
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo-pvc-dynamic
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: demo-csi-sc
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod-dynamic
spec:
  containers:
    - name: app
      image: nginx:1.27
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: demo-pvc-dynamic

执行步骤:

kubectl apply -f dynamic-storage-demo.yaml
kubectl get sc
kubectl get pvc
kubectl get pv
kubectl describe pvc demo-pvc-dynamic

如果你的集群里已经安装并配置好对应的 CSI 驱动,这个 PVC 会触发动态创建底层卷,再自动生成 PV 完成绑定。::cite[214]

8. 存储绑定:PVC 到底是怎么找到 PV 的

绑定过程可以理解为“需求匹配供给”。PVC 会根据下面几项条件寻找合适 PV:

  • 容量是否满足申请
  • 访问模式是否兼容
  • storageClassName 是否一致
  • 是否已经被其他 PVC 占用

一旦绑定成功,这个绑定关系通常具有排他性,尤其在 ReadWriteOnce 的场景下更明显。官方文档建议把 PV/PVC 视为一组面向存储的声明式匹配机制,而不是手工挂载动作。::cite[212]

8.1 volumeBindingMode 为什么重要

StorageClass 中的 volumeBindingMode 会影响“何时做出绑定决策”。其中 WaitForFirstConsumer 很关键,因为它允许调度器先综合 Pod、节点拓扑、卷可用区等信息,再做更合理的绑定与供给。对于跨可用区集群,这往往比“立刻绑定”更安全。::cite[214]

9. 回收策略:PVC 删掉以后,卷怎么办

PV 的 persistentVolumeReclaimPolicy 决定资源释放后的处理方式。常见策略如下:

策略 含义 适用场景
Retain 保留数据,人工处理 重要业务数据、审慎删除
Delete 自动删除底层卷 临时环境、自动化资源回收

在生产里,数据库等关键系统常常更偏向 Retain,避免误删 PVC 导致真实数据立即消失;而测试环境、临时环境更适合 Delete,减少资源残留。官方文档也强调,回收策略属于平台能力设计的一部分。::cite[212]

10. 扩容机制:卷满了以后怎么办

v1.32 文档中,StorageClass 可以通过 allowVolumeExpansion 声明是否支持卷扩容。如果底层驱动支持,用户只需修改 PVC 的 requests.storage,系统即可触发扩容流程。::cite[214]

10.1 PVC 扩容示例

先创建一个支持扩容的 PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: resize-demo-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: demo-csi-sc
  resources:
    requests:
      storage: 10Gi

当你需要扩容时,直接修改为更大容量:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: resize-demo-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: demo-csi-sc
  resources:
    requests:
      storage: 20Gi

执行命令:

kubectl apply -f resize-demo-pvc.yaml
kubectl describe pvc resize-demo-pvc

扩容能否成功,取决于两个条件:

  1. StorageClass 允许扩容
  2. 底层 CSI 或存储后端真正支持扩容::cite[214]

11. 实战中最容易踩的坑

11.1 申请了 PVC,却一直 Pending

常见原因有:

  • 没有可匹配的 PV
  • storageClassName 不一致
  • 集群没有可用的默认 StorageClass
  • 对应 Provisioner/CSI 驱动未安装或异常
  • 容量、访问模式不匹配

排查顺序建议:

kubectl get pvc
kubectl describe pvc <pvc-name>
kubectl get pv
kubectl get sc

11.2 误把 hostPath PV 当生产持久化

这种写法在单机测试环境很方便,但在真正的多节点集群中缺乏高可用、调度弹性和可靠恢复能力。它更适合做教学与实验,不适合作为平台标准存储方案。

11.3 误以为删除 PVC 就一定删除数据

不一定。真正决定是否删除底层卷的,是 PV 或 StorageClass 上的回收策略。如果是 Retain,PVC 删除后数据可能仍然保留,需要人工清理。::cite[212]

12. 记忆方法:把三者串起来

你可以把整个过程记成一句话:

开发者通过 PVC 提出需求,平台通过 StorageClass 定义规则,系统最终用 PV 表示实际存储结果。

如果是静态供给,PV 先存在;如果是动态供给,PVC 会驱动系统自动创建 PV。理解了这一层,后面再看 StatefulSet、数据库、CSI,就会非常顺。

13. 小结

本章的重点不是背 YAML,而是理解 Kubernetes 存储抽象的设计思想:

  • PV 表示“已有或将被创建的存储资源”
  • PVC 表示“应用侧声明的存储需求”
  • StorageClass 表示“平台暴露给租户的存储能力模板”
  • 静态供给适合手工管理资源
  • 动态供给适合平台化和规模化使用
  • 绑定、回收、扩容是生产环境必须理解的三件事

下一章我们继续进入有状态工作负载的核心对象:StatefulSet


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

9.5 实战故障复盘

下一篇

6.3:Pod 安全标准