3. 存储集成核心对象
在OpenShift中,有状态业务能否稳定运行,很大程度上取决于对存储对象模型的理解是否清晰。很多初学者会把PV、PVC、StorageClass、VolumeAttachment和CSI驱动混为一谈,结果在排查“Pod一直Pending”“PVC一直Pending”“卷挂载失败”时无从下手。本节的目标,是把这五个对象放回它们真实的位置上,讲清楚谁定义能力、谁提出需求、谁负责绑定、谁执行挂载,以及CSI驱动在其中扮演什么角色。
一、五个对象的分工
可以把OpenShift的存储集成理解成一条“需求—匹配—绑定—挂载—使用”的链路,但这条链路并不是简单的线性流程,而是由不同控制器和组件协作完成的状态机。
- StorageClass:描述“存储能力”。它声明某类存储由哪个Provisioner提供、使用什么参数、是否允许动态供给、绑定模式是Immediate还是WaitForFirstConsumer、回收策略是Delete还是Retain。它是集群管理员预先定义的“存储菜单”。
- PVC:描述“用户需求”。业务开发者只关心“我要5Gi、ReadWriteOnce、访问模式、指定某个StorageClass”,不需要知道后端是Ceph、iSCSI还是云盘。PVC是命名空间级别的对象。
- PV:描述“实际存储资源”。它可以是管理员静态创建的,也可以由StorageClass对应的Provisioner动态创建。PV是集群级别对象,记录容量、访问模式、回收策略、后端标识(如Ceph image名、iSCSI IQN、云盘ID)。
- VolumeAttachment:描述“卷与节点的挂载关系”。当Pod调度到某个节点后,AttachDetach控制器会创建VolumeAttachment,通知CSI驱动把卷挂载到目标节点。它解决的是“卷在哪个节点上可用”的问题。
- CSI驱动:真正执行存储操作的组件。它通常以DaemonSet形式运行在节点上,负责NodeStageVolume、NodePublishVolume等操作;同时还有Controller部分负责CreateVolume、DeleteVolume、ControllerPublishVolume。CSI驱动是OpenShift与存储后端之间的翻译层。
二、从PVC到Pod的完整链路
下面这张图展示了五个对象之间的真实关系。注意:PVC并不直接“连接”到PV,而是通过绑定关系关联;VolumeAttachment也不是PVC的属性,而是由控制器根据Pod调度结果动态创建。
三、动态供给与静态供给的差异
理解这五个对象后,就能区分两种典型模式。动态供给时,管理员只创建StorageClass,开发者创建PVC,Provisioner自动创建PV并完成绑定。静态供给时,管理员先手动创建PV,再让PVC去匹配。两者的关键差异在于:动态供给的PV由CSI驱动创建,volumeHandle由驱动生成;静态供给的PV由管理员填写后端细节,CSI驱动只负责挂载。
在OpenShift中,默认StorageClass会影响未指定storageClassName的PVC。如果集群存在多个默认StorageClass,PVC可能绑定到非预期存储。因此生产环境通常只保留一个默认StorageClass,其余显式指定。
四、VolumeAttachment与WaitForFirstConsumer
当StorageClass的bindingMode为WaitForFirstConsumer时,PVC不会立即绑定PV,而是等到Pod被调度后才触发。这样做的好处是:PV可以在Pod所在节点附近创建,避免跨可用区挂载失败。此时VolumeAttachment的创建时机与Pod调度结果强相关。如果Pod一直Pending,需要检查PVC是否处于WaitForFirstConsumer状态、调度器是否无法找到满足卷拓扑的节点。
五、一个可参考的PVC与StorageClass示例
下面给出一个教学示例,假设集群已部署CSI驱动,StorageClass名为fast-ssd,后端为Ceph RBD。参数仅为示例,实际值需根据环境调整。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: rbd.csi.ceph.com
parameters:
clusterID: openshift-storage
pool: replicapool
imageFeatures: layering
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-data
namespace: app-db
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 5Gi
创建后,可以用以下命令观察状态变化:
oc get pvc -n app-db
oc get pv
oc get volumeattachment
oc describe pvc db-data -n app-db
预期现象是:PVC先处于Pending,Pod调度后变为Bound,同时出现对应的PV和VolumeAttachment。如果PVC长时间Pending,describe输出中的Events会给出具体原因。
六、五个对象的职责边界
| 对象 | 作用域 | 核心职责 | 谁创建 |
|---|---|---|---|
| StorageClass | 集群级 | 定义存储类型与供给策略 | 管理员 |
| PVC | 命名空间级 | 声明容量、访问模式、存储类 | 开发者 |
| PV | 集群级 | 承载实际存储资源与后端标识 | 管理员或Provisioner |
| VolumeAttachment | 集群级 | 记录卷与节点的挂载关系 | AttachDetach控制器 |
| CSI驱动 | 集群级+节点级 | 执行创建、删除、挂载、卸载 | 存储厂商或社区 |
把这五个对象的关系理顺后,后续学习动态供给、卷扩容、快照、拓扑感知调度时,就能快速定位问题发生在哪一层。存储集成的核心不是记住命令,而是理解每个对象在链路中的位置和职责边界。