存储扩展:OpenEBS、NFS CSI 与 MinIO¶
Pod 一重建,emptyDir 里的数据就没了;hostPath 又把数据钉死在某台机器上。生产集群需要的是动态供给、可快照、可扩容的持久卷。本章从 CSI 的 controller/node 分工讲起,依次落地 NFS CSI、OpenEBS 本地卷与 LVM 卷,最后用 MinIO 补上对象存储。
你将学到¶
- 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
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
步骤 3 · 创建 PVC 并挂载到 Pod
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:
实验二:用 OpenEBS LVM 创建 PVC 并扩容
前置:节点已安装 lvm2、加载 dm-snapshot、创建了 lvmvg 卷组,并已应用 openebs-local-lvm-storageclass.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 触发文件系统扩容 |
进阶挑战¶
- 用
allowedTopologies把 LVM StorageClass 限制在带 NVMe 的两台节点上,并验证调度行为。 - 用 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,需要随机读写文件时选块或文件存储。
延伸阅读¶
- CSI 规范
- csi-driver-nfs
- 原仓库 OpenEBS 示例
- 原仓库 csi-driver-nfs
- 需要给 MinIO Console 配 HTTPS?读证书管理与 cert-manager。