2. 容器持久化基础

容器本身是“无状态”设计的:容器一旦被删除或重建,其可写层中的数据也随之消失。对于数据库、消息队列、日志归档、AI 训练数据集等有状态应用,必须把数据写到容器生命周期之外的存储上。Kubernetes 为此抽象出一套持久化对象模型,而 CSI(Container Storage Interface,容器存储接口)则负责把外部存储阵列(如 Dell PowerStore、PowerScale、PowerFlex、Unity XT)的能力接入这套模型。理解 PV、PVC、StorageClass 与 CSI 之间的分工,是后续在 Dell 企业存储上做持久化集成的前提。

一、四个核心对象的分工

Kubernetes 的持久化模型可以概括为“声明—绑定—供给—挂载”四层:

  • PV(PersistentVolume,持久卷):集群级别的存储资源对象,代表一块真实可用的存储(如一个 LUN、一个 NFS 导出、一个文件系统)。它由管理员预先创建,或由系统动态创建,生命周期独立于任何 Pod。
  • PVC(PersistentVolumeClaim,持久卷声明):命名空间级别的“存储申请单”。用户只描述“我要多大、什么访问模式、什么存储类”,不关心底层是哪台阵列、哪个卷。
  • StorageClass(存储类,SC):描述“用哪套存储、按什么参数供给”的模板。它引用一个 CSI Provisioner,并携带阵列侧参数(如存储池、协议、精简配置策略)。
  • CSI 驱动:运行在集群中的插件,把 Kubernetes 的卷操作翻译成 Dell 阵列的 REST API 调用,实现创建、挂载、扩容、快照等动作。

四者的关系可以用一句话概括:PVC 提出需求,StorageClass 决定由谁供给,CSI 驱动执行供给,PV 是供给出来的结果,最终被 Pod 挂载使用。

Kubernetes 持久化对象与 CSI 供给链路 应用 / Pod volumeMounts 引用 PVC (不感知底层阵列) PVC 持久卷声明 storage: 100Gi accessMode / storageClassName StorageClass 存储类 provisioner: csi.powerstore.dell.com parameters: pool / protocol 匹配 CSI 驱动(Controller + Node 插件) CreateVolume / ControllerPublish / NodeStage / NodePublish 调用 Provisioner Dell 企业存储阵列 PowerStore / PowerScale / PowerFlex / Unity XT REST API 创建卷 / 映射 PV 持久卷 Bound 到 PVC 生成 PV 与 PVC 绑定 静态供给: 管理员先手工创建 PV(指定 capacity / accessModes / csi.volumeHandle),PVC 再与之匹配绑定 动态供给: PVC 引用 StorageClass,CSI Provisioner 自动调用阵列 API 创建卷并生成 PV,无需人工干预

二、静态供给与动态供给的机制差异

静态供给(Static Provisioning)是“先有 PV,后有 PVC”。管理员先在 Dell 阵列上创建卷,再编写 PV YAML,把阵列卷的标识(如 volumeHandle)写进去,最后用户创建 PVC 去匹配。它的优点是可控性强,适合已有存储资源复用;缺点是每次申请都要人工介入,规模一大就难以维护。

动态供给(Dynamic Provisioning)是“先有 PVC,自动出 PV”。用户只需在 PVC 中写 storageClassName,CSI Provisioner 就会读取 StorageClass 中的参数,调用 Dell 阵列 API 创建卷,并自动生成对应的 PV 对象,随后完成绑定。这是生产环境的主流方式。

下面是一个动态供给的 PVC 示例,申请 100Gi 的块存储,使用 ReadWriteOnce 访问模式:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data
  namespace: prod
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: powerstore-block
  resources:
    requests:
      storage: 100Gi

对应的 StorageClass 需要提前创建,关键字段是 provisioner 和 parameters:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: powerstore-block
provisioner: csi.powerstore.dell.com
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
parameters:
  arrayIP: "10.10.20.30"
  storagePool: "SP_Block"
  protocol: "iSCSI"
  thinProvision: "true"

其中 reclaimPolicy: Delete 表示 PVC 删除后 PV 与阵列卷一并回收;allowVolumeExpansion: true 允许后续在线扩容;volumeBindingMode: WaitForFirstConsumer 表示延迟绑定,等 Pod 调度到具体节点后再创建卷,避免跨可用区挂载失败。

三、访问模式与绑定状态

PVC 的 accessModes 决定了卷能被多少个节点以何种方式挂载,常见取值如下:

访问模式缩写典型场景Dell 存储对应
ReadWriteOnceRWO单节点读写,如 MySQL 主库iSCSI / FC 块卷
ReadOnlyManyROX多节点只读共享NFS 只读导出
ReadWriteManyRWX多节点同时读写,如共享文件PowerScale NFS / PowerFlex File
ReadWriteOncePodRWOP单 Pod 独占读写块卷(K8s 1.29+)

PVC 与 PV 的绑定状态可通过 kubectl get pvc 观察:Pending 表示尚未匹配到 PV 或等待调度;Bound 表示已成功绑定;Lost 表示底层 PV 已丢失。动态供给场景下,PVC 从 Pending 变为 Bound 通常只需数秒到数十秒,具体取决于阵列 API 响应速度。

实践提示:在 Dell PowerStore 上做动态供给时,建议为不同业务划分独立 StorageClass,例如 powerstore-gold(高性能池)、powerstore-silver(容量池),通过 storagePool 参数隔离,避免所有业务挤在同一存储池中互相影响。

四、从 PVC 到挂载的完整链路

当 Pod 引用 PVC 后,实际发生的过程是:kubelet 调用 CSI Node 插件执行 NodeStageVolume(把卷挂到节点全局目录),再执行 NodePublishVolume(把卷绑定到 Pod 的 /var/lib/kubelet/pods/<uid>/volumes/ 路径)。对于 iSCSI 协议,Node 插件还会先登录 target、扫描 LUN、识别设备文件(如 /dev/sdb),再格式化为 ext4/xfs 并挂载。若挂载失败,可用 kubectl describe pod 查看事件,常见原因包括:阵列侧未开放 initiator IQN、CHAP 认证失败、多路径配置缺失。

理解这条链路后,就能明白为什么 CSI 驱动必须同时部署 Controller 和 Node 两类组件:Controller 负责与阵列 API 交互创建/删除卷,Node 负责在每台工作节点上完成实际的设备挂载。二者配合,才让“PVC 一提交,存储自动就绪”成为可能。