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",避免出现两个默认类并存的窗口期。
动态 PV 创建流程
当用户提交一个 PVC 后,集群内部会经历一条清晰的链路:PVC 进入 Pending,控制器读取其 storageClassName,找到对应 StorageClass,取出 provisioner 和 parameters,调用存储插件创建后端卷,再生成一个 PV 对象并与 PVC 绑定,最后 PVC 变为 Bound,Pod 才能挂载使用。若 volumeBindingMode 为 WaitForFirstConsumer,则供应动作会推迟到 Pod 调度阶段,此时 PVC 会先停留在 Pending,直到有 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,这是设计行为而非故障。
Delete,后端卷会被一并删除,数据不可恢复。对有重要数据的业务,应使用 Retain 策略的 StorageClass,或在删除前先做快照/备份。参数配置的常见判断依据
| 参数 | 作用 | 选择建议 |
|---|---|---|
| provisioner | 决定由哪个插件创建卷 | 必须与集群已安装的 CSI/内置插件一致 |
| type / storagePool | 决定后端卷类型或存储池 | 按性能与成本档位划分,如 gp3、ssd、hdd |
| iops / throughput | 决定卷性能上限 | 数据库类业务取较高值,日志类可降低 |
| fsType | 卷格式化文件系统 | Linux 常用 ext4/xfs,需与业务兼容 |
| reclaimPolicy | PVC 删除后卷的去留 | 临时环境用 Delete,生产数据用 Retain |
| volumeBindingMode | 供应时机 | 多可用区集群优先 WaitForFirstConsumer |
| allowVolumeExpansion | 是否允许扩容 | 需要在线扩容的业务设为 true |
把 StorageClass 设计好之后,动态供应就把“申请存储”变成了应用清单里几行声明式字段。用户不再关心后端卷如何创建,只需要选择正确的存储类;管理员则通过 StorageClass 统一控制性能、回收和绑定策略,实现存储资源的可预期、可复用和可治理。