跳转至

存储扩展:OpenEBS、NFS CSI 与 MinIO

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

难度 · 进阶 预计 90 分钟 前置:PV/PVC 与 StorageClass

你将学到

  • CSI 的 controller/node 分工与一次 PVC 绑定流程,OpenEBS 4.x/3.x 的差异及 hostpath、LVM 的边界。
  • csi-driver-nfs 的部署方式与 dnsPolicy 坑,NFS StorageClass 的动态供给流程。
  • MinIO 的单机与分布式部署、对象存储的典型场景,以及快照与扩容的验证方法。

为什么需要它

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 对象桶 版本化 备份、数据湖、模型仓库

核心概念

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 才知道该在哪台节点切空间。

sequenceDiagram
  participant U as 用户
  participant A as API Server
  participant P as external-provisioner
  participant S as 存储系统
  participant N as node plugin
  U->>A: 创建 PVC
  A->>P: 监听到未绑定 PVC
  P->>S: CreateVolume
  S-->>P: 返回 volumeId
  P->>A: 创建 PV 并完成绑定
  U->>A: 创建使用 PVC 的 Pod
  A->>N: 调度到节点后触发挂载
  N->>S: NodeStage 与 NodePublish

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

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

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 节点会一直挂载失败。

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。

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

快照与扩容

快照依赖 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 的容量只是声明值,驱动不会做配额限制,不要当成可精确扩容的块设备。

动手实验

实验一:部署 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 确认文件系统也扩展了。

验证清单

  • nfs-csi 是默认 StorageClass,PVC 自动绑定;Pod 写入的文件在 NFS Server 可见。
  • OpenEBS LVM PVC 扩容后 CAPACITY 变为 10Gi,且 PVC 事件中没有扩容失败。

常见问题与排错

现象 可能原因 处理方式
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 触发文件系统扩容

进阶挑战

  1. 用 allowedTopologies 把 LVM StorageClass 限制在带 NVMe 的两台节点上,并验证调度行为。
  2. 用 MinIO 的 mc mirror 把 NFS 上的备份目录同步到桶里,设计一条从 PV 到对象存储的备份链路。

小结

  • CSI 把「造卷」与「挂载」拆给 controller 和 node;OpenEBS 4.x 是重构后的 Helm 交付体系,3.x 是 Legacy,不能原地升级。
  • csi-driver-nfs 的 dnsPolicy 是典型部署坑,v4.3 之前必须手动改成 ClusterFirstWithHostNet。
  • 对象存储不是文件系统的替代品:需要 S3 API、多副本、版本化时选 MinIO,需要随机读写文件时选块或文件存储。

延伸阅读