3.10. 存储扩展:OpenEBS、NFS CSI 与 MinIO

Pod 一重建,emptyDir 里的数据就没了;hostPath 又把数据钉死在某台机器上。生产集群需要的是动态供给、可快照、可扩容的持久卷。本章从 CSI 的 controller/node 分工讲起,依次落地 NFS CSI、OpenEBS 本地卷与 LVM 卷,最后用 MinIO 补上对象存储。

层级:

L3 · 平台

难度:

进阶

预计:

90 分钟

前置:

PV/PVC 与 StorageClass

3.10.1. 你将学到

  • CSI 的 controller/node 分工与一次 PVC 绑定流程,OpenEBS 4.x/3.x 的差异及 hostpath、LVM 的边界。

  • csi-driver-nfs 的部署方式与 dnsPolicy 坑,NFS StorageClass 的动态供给流程。

  • MinIO 的单机与分布式部署、对象存储的典型场景,以及快照与扩容的验证方法。

3.10.2. 为什么需要它

Kubernetes 的存储抽象只有三层:StorageClass 描述「怎么造卷」,PVC 描述「我要什么样的卷」,PV 是真正造出来的存储。静态供给要管理员手工建 PV,规模一大就不可维护;动态供给让 CSI 驱动在 PVC 出现时按参数自动创建 PV,这才是生产用法。

早期 in-tree 插件(如 kubernetes.io/aws-ebs)已全部迁出,v1.31 上新增能力只走 CSI。CSI 只定义 gRPC 接口,不定义实现,所以同样标 ReadWriteMany,NFS 能多节点同时写,LVM 本地卷只能单节点。

方案

访问模式

数据位置

快照

适用场景

hostPath / localpv

RWO

节点本地目录

依驱动

单节点缓存、日志

OpenEBS LVM

RWO

节点上的 LVM 逻辑卷

支持

数据库、需要快照的有状态服务

NFS CSI

RWX

集中式 NFS Server

支持

共享配置、多副本读写

MinIO

S3 API

对象桶

版本化

备份、数据湖、模型仓库

3.10.3. 核心概念

3.10.3.1. CSI 架构:controller 与 node

CSI 驱动分两组进程。controller 侧通常以 Deployment 运行,包含 external-provisioner、external-attacher、external-resizer、external-snapshotter 等 sidecar,它们监听 PV/PVC 事件,调用驱动实现的 CreateVolume、ControllerPublishVolume、CreateSnapshot。node 侧以 DaemonSet 运行,包含 node-driver-registrar 与驱动本体,负责在 kubelet 挂载时执行 NodeStageVolume、NodePublishVolume。

PVC 的绑定流程是:创建 PVC → provisioner 发现未绑定对象 → 调 CreateVolume → 建 PV 并绑定 → 创建 Pod → kubelet 在目标节点调 node 插件挂载。volumeBindingMode: WaitForFirstConsumer 把造卷推迟到调度之后,localpv 才知道该在哪台节点切空间。

架构图

图:动态供给从 PVC 到挂载的调用顺序,controller 造卷,node 挂载。

3.10.3.2. OpenEBS 4.x 与 3.x 的差异

OpenEBS 4.x 几乎完全重构,以 Helm umbrella chart 交付,引擎收敛为 local(hostpath、LVM、ZFS)与 replicated(Mayastor);3.x 被称为 Legacy,localpv-provisioner、Jiva、cstor、LVM Operator 各自独立部署。两者不能原地升级。

维度

OpenEBS 4.x

OpenEBS 3.x

交付方式

Helm chart 或聚合 YAML

Operator / 单组件 YAML

本地卷引擎

hostpath、LVM、ZFS

hostpath、LVM

复制卷引擎

Mayastor

Jiva、cstor

Jiva

不再提供

需要 iSCSI client

典型安装

openebs-localpv-lvm-4.1.yaml

openebs-operator.yaml

仓库推荐 4.1 的一键安装:

kubectl apply -f https://raw.githubusercontent.com/iKubernetes/learning-k8s/master/OpenEBS/deployment/openebs-localpv-lvm-4.1.yaml
kubectl -n openebs get pods

也可以走 Helm,并按需关掉用不到的重型引擎:

helm repo add openebs https://openebs.github.io/openebs
helm repo update
helm install openebs --namespace openebs openebs/openebs \
  --set engines.replicated.mayastor.enabled=false \
  --set engines.local.zfs.enabled=false \
  --create-namespace

3.10.3.3. hostpath localpv 与 LVM PV

hostpath 版 StorageClass 的 provisioner 是 openebs.io/local,用 cas.openebs.io/config 指定 StorageType 与 BasePath。它在节点上建目录并作为 local PV 暴露,WaitForFirstConsumer 保证卷与 Pod 同节点;代价是数据与节点绑定,Pod 漂移后看不到原数据。

LVM 版用 local.csi.openebs.io,参数里指定卷组。前置条件必须提前满足:所有节点装 lvm2、内核加载 dm-snapshot、事先建好卷组(如 lvmvg)。只有部分节点有 VG 时,必须用 allowedTopologies 限定节点,否则调度到无 VG 节点会一直挂载失败。

3.10.3.4. csi-driver-nfs 与 dnsPolicy 坑

csi-driver-nfs 的部署分三步:准备 NFS Server、部署驱动、创建 provisioner 为 nfs.csi.k8s.io 的 StorageClass。controller 与 node Pod 都用 hostNetwork: true,因为挂载 NFS 需要宿主机网络命名空间;但 v4.0–v4.2 的清单把 dnsPolicy 写成 Default,Pod 会使用宿主机 /etc/resolv.conf,解析不了 nfs-server.nfs.svc.cluster.local,表现为 PVC 一直 Pending 或挂载超时。

修法是改成 ClusterFirstWithHostNet,v4.3 及之后已默认如此;仓库的 deploy/04-csi-driver-nfs-4.6.0/ 就是修好的版本。StorageClass 参数只有 server 与 share,mountOptions 建议显式写 nfsvers=4.1。

3.10.3.5. MinIO 与对象存储

MinIO 提供兼容 S3 的 API,适合备份、日志归档、数据湖和模型文件。它不把桶挂成文件系统,而是用 SDK 或 mc 通过 HTTP 读写对象,天然支持多副本、跨节点访问。

部署形态有两种:单机 mode: standalone 适合实验,一个 Pod 加一个 PVC;分布式至少 4 个实例、每实例多卷,用纠删码提供冗余。仓库 values 用 Helm 单机形态部署(helm install minio minio/minio -n minio -f values.yaml --create-namespace),Console 走 LoadBalancer 或 Ingress。README 提到 MinIO 官方仓库已归档,生产选型要把维护状态纳入评估。

mc alias set myminio http://minio.example.com admin magedu.com
mc cp ./model.bin myminio/mybucket/ && mc admin info myminio

3.10.3.6. 快照与扩容

快照依赖 snapshot.storage.k8s.io 的 CRD 与 snapshot-controller:先建 VolumeSnapshotClass 指定驱动与删除策略,再创建引用 PVC 的 VolumeSnapshot;恢复时新建 PVC,把 dataSource 指向快照。OpenEBS LVM 的驱动是 local.csi.openebs.io。

扩容的前提是 StorageClass 开了 allowVolumeExpansion: true,然后改 PVC 的 spec.resources.requests.storage。CSI 先扩后端卷,再由 kubelet 扩文件系统,通常要求 Pod 重建。NFS 的容量只是声明值,驱动不会做配额限制,不要当成可精确扩容的块设备。

3.10.4. 动手实验

实验一:部署 NFS CSI 并跑通动态供给

步骤 1 · 准备 NFS Server 与驱动

kubectl create namespace nfs
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/deploy/example/nfs-provisioner/nfs-server.yaml -n nfs
kubectl apply -f csi-driver-nfs/deploy/04-csi-driver-nfs-4.6.0/
kubectl -n kube-system get pod -l 'app in (csi-nfs-node,csi-nfs-controller)'

每节点一个 csi-nfs-node(3/3),另一个 csi-nfs-controller(4/4),全部 Running 再继续。

步骤 2 · 创建 StorageClass

nfs-csi-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-csi
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: nfs.csi.k8s.io
parameters:
  server: nfs-server.nfs.svc.cluster.local
  share: /
reclaimPolicy: Delete
volumeBindingMode: Immediate
kubectl apply -f nfs-csi-storageclass.yaml

步骤 3 · 创建 PVC 并挂载到 Pod

nfs-pvc-pod.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs-dynamic
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
  storageClassName: nfs-csi
---
apiVersion: v1
kind: Pod
metadata:
  name: volumes-nfs-csi-demo
spec:
  containers:
  - name: redis
    image: redis:alpine
    volumeMounts:
    - mountPath: /data
      name: redisdata
  volumes:
  - name: redisdata
    persistentVolumeClaim:
      claimName: pvc-nfs-dynamic
kubectl apply -f nfs-pvc-pod.yaml
kubectl get pvc pvc-nfs-dynamic -w
kubectl exec volumes-nfs-csi-demo -- sh -c 'echo hello-nfs > /data/probe.txt'
kubectl -n nfs exec deploy/nfs-server -- ls /exports

预期能看到 probe.txt:

probe.txt

实验二:用 OpenEBS LVM 创建 PVC 并扩容

前置:节点已安装 lvm2、加载 dm-snapshot、创建了 lvmvg 卷组,并已应用 openebs-local-lvm-storageclass.yaml。

openebs-lvm-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: openebs-lvm-pvc
spec:
  storageClassName: openebs-lvmpv
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
kubectl apply -f openebs-lvm-pvc.yaml
kubectl patch pvc openebs-lvm-pvc -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
kubectl get pvc openebs-lvm-pvc

扩容后 CAPACITY 应变为 10Gi;再把 PVC 挂进 Pod,用 df -h /data 确认文件系统也扩展了。

3.10.4.1. 验证清单

  • nfs-csi 是默认 StorageClass,PVC 自动绑定;Pod 写入的文件在 NFS Server 可见。

  • OpenEBS LVM PVC 扩容后 CAPACITY 变为 10Gi,且 PVC 事件中没有扩容失败。

3.10.5. 常见问题与排错

现象

可能原因

处理方式

PVC 一直 Pending

没有默认 StorageClass,或 provisioner Pod 未就绪

kubectl describe pvc 看事件;确认 CSI controller 日志

PVC 等待 Pod 调度

StorageClass 是 WaitForFirstConsumer

先创建使用该 PVC 的 Pod,再看绑定结果

NFS PVC 创建成功但挂载超时

dnsPolicy 为 Default,解析不了 nfs-server.nfs.svc

改为 ClusterFirstWithHostNet;或直接用 NFS Server 的 IP

LVM 挂载失败

节点没有对应 VG、缺 dm-snapshot、未装 lvm2

vgs 查卷组、lsmod 查模块;用 allowedTopologies 限定节点

扩容后容量没变

StorageClass 未开 allowVolumeExpansion

先改 StorageClass,再重建 Pod 触发文件系统扩容

3.10.6. 进阶挑战

  1. 用 allowedTopologies 把 LVM StorageClass 限制在带 NVMe 的两台节点上,并验证调度行为。

  2. 用 MinIO 的 mc mirror 把 NFS 上的备份目录同步到桶里,设计一条从 PV 到对象存储的备份链路。

3.10.7. 小结

  • CSI 把「造卷」与「挂载」拆给 controller 和 node;OpenEBS 4.x 是重构后的 Helm 交付体系,3.x 是 Legacy,不能原地升级。

  • csi-driver-nfs 的 dnsPolicy 是典型部署坑,v4.3 之前必须手动改成 ClusterFirstWithHostNet。

  • 对象存储不是文件系统的替代品:需要 S3 API、多副本、版本化时选 MinIO,需要随机读写文件时选块或文件存储。

3.10.8. 延伸阅读