4. 动态存储供应

在 OpenShift 中,静态存储供应要求管理员先手工创建 PV,再让 PVC 去匹配,这种方式在节点规模扩大、应用数量增长后很快会变成负担。动态存储供应(Dynamic Provisioning)把这一步反过来:用户只提交 PVC,集群根据 PVC 所引用的 StorageClass 自动调用底层存储插件创建后端卷,并生成与之绑定的 PV。理解 StorageClass 的参数、默认存储类的选择规则以及 PV 自动创建的完整链路,是让有状态业务真正“开箱即用”的关键。

StorageClass 是什么,参数从哪里来

StorageClass 是一个集群级资源,它描述“用哪一类存储、以什么参数去创建卷”。它本身不存储数据,而是把 PVC 的请求翻译成底层存储系统能理解的创建指令。一个 StorageClass 的核心字段包括:

  • provisioner:负责创建卷的存储插件,例如 kubernetes.io/aws-ebs、ebs.csi.aws.com、csi.vsphere.vmware.com、rook-ceph.rbd.csi.ceph.com 等。它决定了后续 parameters 的合法取值范围。
  • parameters:传给 provisioner 的键值对,例如磁盘类型、IOPS、文件系统、复制因子、池名称等。不同 provisioner 的参数完全不同,必须以对应插件的文档为准。
  • reclaimPolicy:PV 回收策略,Delete 表示 PVC 删除后自动删除后端卷,Retain 表示保留卷供人工处理。
  • volumeBindingMode:Immediate 表示 PVC 一创建就立刻供应卷;WaitForFirstConsumer 表示等到有 Pod 调度时才供应,从而让卷创建在 Pod 所在可用区,避免跨区挂载失败。
  • allowVolumeExpansion:是否允许后续扩容 PVC。

下面是一个面向云盘类存储的 StorageClass 示例,参数均为教学示例值,实际使用时需替换为你的存储后端真实取值:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "6000"
  throughput: "250"
  fsType: ext4
  encrypted: "true"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

这里 type: gp3 决定云盘类型,iops 与 throughput 决定性能档位,encrypted 决定是否加密,fsType 决定卷被格式化成什么文件系统。把这些参数写进 StorageClass,而不是写进每个 PVC,是为了让同类应用复用同一套存储策略,减少重复和出错。

默认存储类:让 PVC 不写 storageClassName 也能绑定

如果集群里存在一个被标记为默认的 StorageClass,那么用户创建 PVC 时即使不写 storageClassName,也会自动使用它。设置默认存储类的方式是给它打上注解:

kubectl annotate storageclass fast-ssd \
  storageclass.kubernetes.io/is-default-class="true"

# 查看当前默认存储类
kubectl get storageclass

输出中名字后面带 (default) 的那一行就是当前默认类。需要注意两点:第一,一个集群最好只保留一个默认 StorageClass,否则行为不确定;第二,如果 PVC 显式写了 storageClassName: "",表示“不要用默认类,我要静态匹配”,这与不写该字段的含义不同。切换默认类时,应先把旧默认类的注解改为 "false",再给新类打上 "true",避免出现两个默认类并存的窗口期。

默认存储类只影响“未指定 storageClassName 的新 PVC”,不会改变已经绑定完成的 PVC 和 PV。修改默认类后,已有业务不受影响,新提交的 PVC 才会走新的默认类。

动态 PV 创建流程

当用户提交一个 PVC 后,集群内部会经历一条清晰的链路:PVC 进入 Pending,控制器读取其 storageClassName,找到对应 StorageClass,取出 provisioner 和 parameters,调用存储插件创建后端卷,再生成一个 PV 对象并与 PVC 绑定,最后 PVC 变为 Bound,Pod 才能挂载使用。若 volumeBindingMode 为 WaitForFirstConsumer,则供应动作会推迟到 Pod 调度阶段,此时 PVC 会先停留在 Pending,直到有 Pod 引用它并完成调度决策。

动态存储供应:从 PVC 到 Bound 的完整链路 PVC 提交 storageClassName: fast-ssd resources: 20Gi StorageClass provisioner: ebs.csi.aws.com parameters: type=gp3, iops=6000 bindingMode: WaitForFirstConsumer 存储插件 Provisioner 调用后端 API 创建卷 设置容量 / 类型 / 加密 返回卷标识 volumeID 后端存储 云盘 / Ceph RBD / NFS / vSphere 实际数据落盘位置 PV 自动生成 capacity: 20Gi reclaimPolicy: Delete PVC 变为 Bound STATUS: Bound VOLUME: pvc-xxxx Pod 挂载使用 volumeMounts /data 持久化目录 WaitForFirstConsumer 模式: PVC 先保持 Pending → 调度器为 Pod 选定节点/可用区 → 再触发卷创建 → PV 与 PVC 绑定 → Pod 启动挂载。可避免卷与 Pod 跨可用区不匹配。

观察这条链路时,最常用的判断命令是 kubectl get pvc 和 kubectl describe pvc <name>。如果 PVC 长时间处于 Pending,通常要看 Events 区域:出现 waiting for first consumer to be created before binding 属于正常等待;出现 no persistent volumes available for this claim and no storage class is set 说明没有默认类且 PVC 未指定类;出现 provisioner 相关报错则要检查 StorageClass 参数是否被后端接受。

实操:创建 StorageClass 并验证动态供应

下面给出一段可参考的验证流程,参数为教学示例,实际执行时请替换为你的存储后端信息:

# 1. 创建 StorageClass
kubectl apply -f fast-ssd.yaml

# 2. 设为默认类
kubectl annotate storageclass fast-ssd \
  storageclass.kubernetes.io/is-default-class="true"

# 3. 创建 PVC(不写 storageClassName,验证默认类生效)
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo-pvc
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 20Gi
EOF

# 4. 观察状态变化
kubectl get pvc demo-pvc -w
kubectl get pv | grep demo-pvc

预期结果是:PVC 先短暂 Pending,随后变为 Bound;同时出现一个由 provisioner 自动创建的 PV,其容量为 20Gi,回收策略继承 StorageClass 的 Delete。如果使用 WaitForFirstConsumer,在创建引用该 PVC 的 Pod 之前,PVC 会一直 Pending,这是设计行为而非故障。

删除 PVC 时,若 StorageClass 的 reclaimPolicy 为 Delete,后端卷会被一并删除,数据不可恢复。对有重要数据的业务,应使用 Retain 策略的 StorageClass,或在删除前先做快照/备份。

参数配置的常见判断依据

参数作用选择建议
provisioner决定由哪个插件创建卷必须与集群已安装的 CSI/内置插件一致
type / storagePool决定后端卷类型或存储池按性能与成本档位划分,如 gp3、ssd、hdd
iops / throughput决定卷性能上限数据库类业务取较高值,日志类可降低
fsType卷格式化文件系统Linux 常用 ext4/xfs,需与业务兼容
reclaimPolicyPVC 删除后卷的去留临时环境用 Delete,生产数据用 Retain
volumeBindingMode供应时机多可用区集群优先 WaitForFirstConsumer
allowVolumeExpansion是否允许扩容需要在线扩容的业务设为 true

把 StorageClass 设计好之后,动态供应就把“申请存储”变成了应用清单里几行声明式字段。用户不再关心后端卷如何创建,只需要选择正确的存储类;管理员则通过 StorageClass 统一控制性能、回收和绑定策略,实现存储资源的可预期、可复用和可治理。