12 KiB
关于MinIO + JuiceFS 在H20部署
这是一个架构设计的关键决策点。在 H20 节点运行 K3s 的前提下,我建议采取 “存储组件独立部署(Outside),客户端插件化接入(Inside)” 的混合策略。
简单来说:MinIO 和 Redis 跑在 K3s 外面(用 Docker),JuiceFS Client 通过 CSI 跑在 K3s 里面。
以下是详细的架构拆解和理由:
1. 架构图解
graph TD
subgraph "物理层 / Host OS (H20 Node)"
NVMe[NVMe SSD (物理盘)]
end
subgraph "K3s 集群 (容器层)"
Pod[训练任务 Pod]
CSI[JuiceFS CSI Driver (DaemonSet)]
Pod -- "PVC 挂载" --> CSI
end
subgraph "独立 Docker 容器 (非 K3s)"
Redis[Redis 容器]
MinIO[MinIO 容器]
end
%% 关键数据流
CSI -- "1. 读写缓存" --> NVMe
CSI -- "2. 网络 IO" --> MinIO
CSI -- "3. 元数据 IO" --> Redis
%% 避免环路
MinIO -. "数据持久化" .- NVMe
2. 组件部署位置详解
A. MinIO & Redis:建议 独立于 K3s 部署 (Outside)
方式:直接在宿主机(Host OS)上使用 docker run 或 docker-compose 启动,或者使用 systemd 管理。
理由:
- 爆炸半径隔离(最重要):AI 实验初期,K3s 集群可能会因为 CNI 网络插件配置错误、Device Plugin 崩溃、或者各种误操作导致集群不可用(Crash)。如果存储后端(MinIO)跑在 K3s 里,K3s 一挂,你的数据就“锁”在里面了,连日志都拷不出来。
- 资源竞争保护:K8s 的调度器虽然有 QoS,但在只有 2 个节点的情况下,H20 的高负载很容易导致控制面不稳。把 MinIO 独立出来,保证存储服务的 CPU/内存资源不受 K3s 内部 Pod 挤压。
- 网络直通:宿主机直接部署 MinIO,使用的是
host网络,少了一层 K8s Service/Ingress 转发,对于大带宽吞吐稍微有一点点性能优势。
B. JuiceFS Client:建议 集成在 K3s 内部 (Inside)
方式:使用 JuiceFS CSI Driver。
理由:
- 符合“平台化”愿景:你的目标是做一个 "AI Infra 平台"。对于上层用户(算法工程师),他们不应该关心宿主机挂载了什么目录。他们提交的 YAML 只需要写:
这是标准的云原生体验。volumes: - name: data persistentVolumeClaim: claimName: pvc-juicefs - 权限与隔离:CSI Driver 可以更好地处理容器内的挂载点权限问题。如果手动在宿主机
mount然后用hostPath映射进容器,经常会遇到Permission Denied或者root用户归属问题,非常头疼。 - 自动缓存管理:CSI Driver 可以通过配置
StorageClass,自动帮你处理缓存路径的挂载和清理。
3. 实操配置指南 (关键点)
既然你只有两台 H20 并且要上 K3s,以下是落地的关键配置:
第一步:在宿主机启动 MinIO 和 Redis (后端)
不要用 Helm,直接写一个 docker-compose.yml 放在 /opt/ai-infra/ 下运行:
version: '3'
services:
minio:
image: minio/minio
network_mode: "host" # 直接用宿主机网络,性能最好
volumes:
- /mnt/data1/minio_data:/data # 挂载到大容量HDD或SATA SSD
command: server /data --console-address ":9001"
redis:
image: redis:7
network_mode: "host"
volumes:
- /mnt/data1/redis_data:/data
command: redis-server --appendonly yes
第二步:在 K3s 中安装 JuiceFS CSI Driver (前端)
-
安装 CSI Driver:
helm repo add juicefs https://juicedata.github.io/charts/ helm install juicefs-csi-driver juicefs/juicefs-csi-driver -n kube-system -
创建 Secret (连接信息):
apiVersion: v1 kind: Secret metadata: name: juicefs-sc-secret namespace: default type: Opaque stringData: name: "myjfs" metaurl: "redis://<宿主机IP>:6379/1" # 注意这里填宿主机内网IP storage: "minio" bucket: "http://<宿主机IP>:9000/mybucket" access-key: "minioadmin" secret-key: "minioadmin" -
创建 StorageClass (核心中的核心): 这里要配置本地缓存,让 JuiceFS 能够利用 H20 的 NVMe。
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: juicefs-sc provisioner: csi.juicedata.com parameters: csi.storage.k8s.io/node-publish-secret-name: juicefs-sc-secret csi.storage.k8s.io/node-publish-secret-namespace: default csi.storage.k8s.io/provisioner-secret-name: juicefs-sc-secret csi.storage.k8s.io/provisioner-secret-namespace: default # === 关键性能参数 === cache-dir: /mnt/nvme0/juicefs-cache # H20 宿主机上的 NVMe 路径 cache-size: "2048000" # 2TB 缓存上限 buffer-size: "512" # 写缓冲 prefetch: "2" # 预读并发度
4. 备选方案:如果 CSI 搞不定怎么办?
如果 M1 阶段你发现 CSI Driver 调试太麻烦(有时候 K3s 的路径和标准 K8s 不一样,导致 CSI 挂载失败),可以退回到 “Host Mount + HostPath” 模式:
- Outside: 在宿主机直接运行
juicefs mount ... /mnt/ai-data。 - Inside: 在 Pod YAML 里直接映射:
volumes: - name: data hostPath: path: /mnt/ai-data type: Directory
结论: 优先尝试 CSI Driver 方案,这才是正规军的做法,能为你的“平台化”愿景打下基础。MinIO/Redis 请务必保持独立,不要让 K3s 的波动影响到底层数据安全。
关于Minio扩容
这是一个非常关键的运维问题。
首先要泼一盆冷水(也是为了避坑):MinIO 不支持从“单机模式(Standalone)”无缝平滑扩容到“分布式模式(Distributed)”。
- 单机模式下,MinIO 是直接把文件存成普通文件。
- 分布式模式下,MinIO 会启用纠删码(Erasure Coding),把文件切片打散存储。
底层数据结构完全不同,因此你不能简单地把新节点加入旧集群。你必须通过**“建立新集群 -> 数据迁移 -> 切换流量”**的方式来完成。
鉴于你使用了 JuiceFS,这个迁移过程其实比纯 MinIO 迁移要灵活得多。以下是针对你 H20/K3s 环境的最佳实战方案:
方案概览:蓝绿部署迁移法
我们不修改旧的 MinIO,而是搭建一个新的 3 节点集群,同步数据,然后改一下 K8s Secret 指向新集群。
前置条件
假设你现有的环境是:
- Old MinIO: 跑在 Node A (192.168.1.10)
- New Target: 准备在 Node A, Node B, Node C 上跑 3 节点分布式 MinIO。
第一步:搭建全新的 3 节点 MinIO 集群
由于旧的 MinIO 还在通过 host 网络运行(占用 9000 端口),新集群如果不关掉旧的,需要用不同端口(比如 9100)或者部署在不同机器上。
假设你新增了机器,或者错开端口。以下是 3 节点分布式 MinIO 的 docker-compose.yml 示例(需要在三台机器上都运行):
version: '3'
services:
minio-distributed:
image: minio/minio
network_mode: "host"
hostname: "node1" # 另外两台改为 node2, node3
# 关键:分布式启动命令,必须列出所有节点
command: server http://192.168.1.10:9100/data http://192.168.1.11:9100/data http://192.168.1.12:9100/data --console-address ":9101"
volumes:
- /mnt/data_new:/data # 挂载新的数据盘(或者旧盘的新目录)
environment:
MINIO_ROOT_USER: "admin"
MINIO_ROOT_PASSWORD: "strongpassword"
注意:3 节点 MinIO 允许挂掉 1 台机器而不丢失数据。
第二步:数据迁移 (两种路径)
鉴于你用的是 JuiceFS,这里有两条路可选:
路径 A:底座迁移(推荐,速度快,原汁原味)
直接搬运 MinIO 里的对象块(Block)。因为 JuiceFS 把数据切成了固定的 Block 存在 MinIO 里,我们只需要把这些 Block 从旧 MinIO 搬到新 MinIO,不需要经过 JuiceFS 客户端。
- 安装
mc(MinIO Client) 命令行工具。 - 配置别名:
mc alias set oldm http://192.168.1.10:9000 minioadmin minioadmin mc alias set newm http://192.168.1.10:9100 admin strongpassword - 全量镜像 (Mirror):
# 创建新桶 mc mb newm/mybucket # 开始同步数据 (将旧桶数据镜像到新桶) # --watch 参数可以持续监听增量数据,适合不停机迁移 mc mirror --watch oldm/mybucket newm/mybucket
路径 B:JuiceFS 层面迁移(适合要换云厂商/存储类型)
如果你想顺便整理数据碎片,或者从 MinIO 迁移到 阿里云 OSS,可以用这个。但在你的场景下,路径 A 更快。
- 命令:
juicefs sync minio://... minio://...(不推荐,因为需要解密再加密,消耗 CPU)。
第三步:停机切换 (Cutover)
为了保证 100% 数据一致性,建议申请一个短时间的维护窗口(10-20分钟)。
- 停止训练任务:Scale down 所有的 Training Job。
- 停止旧 MinIO 写入:
- 确保
mc mirror已经追平了数据(没有 pending)。 - 你可以把旧 MinIO 设为只读,或者直接停止旧容器。
- 确保
- 最后一次 Sync:
- 运行
mc mirror --overwrite确保最后一点差异也同步了。
- 运行
- 修改 K8s Secret:
- 这是 JuiceFS 的魔力所在。你不需要重新格式化 JuiceFS,也不需要修改元数据引擎(Redis)。你只需要告诉 JuiceFS:“底下的对象存储换地方了”。
- 编辑 K8s 里的 Secret:
kubectl edit secret juicefs-sc-secret- 修改
bucket和access-key/secret-key:
stringData: # Redis 配置保持不变!元数据还在那里 metaurl: "redis://192.168.1.10:6379/1" # 修改 bucket 地址为新集群地址 # 注意:MinIO 分布式集群通常前面会加一个 Nginx 做负载均衡 # 或者你可以填任意一个节点的 IP (MinIO 会自动转发) bucket: "http://192.168.1.10:9100/mybucket" access-key: "admin" secret-key: "strongpassword" - 重启 CSI Driver:
- 删除 JuiceFS CSI 的 Pod,让它们重建并加载新的 Secret。
- 删除使用该 PVC 的 Pod。
第四步:验证与回收
- 验证:
- 启动一个测试 Pod,挂载 PVC。
ls一下目录,确认文件都在。cat一个文件,确认能读(JuiceFS 会去新 MinIO 拿数据块)。
- 回收:
- 确认一切正常运行几天后。
- 下线旧的单节点 MinIO 容器。
- 释放旧的磁盘空间。
专家建议 (Pro Tips)
-
负载均衡 (Load Balancer):
- 到了 3 节点分布式阶段,建议在 3 台 MinIO 前面架设一个 Nginx 或者 HAProxy。
- 这样 JuiceFS Client 连接的是
http://nginx:9000,Nginx 轮询转发给后端 3 台 MinIO。 - 好处:如果某台 MinIO 挂了,Nginx 自动剔除,训练任务完全无感。如果直连某台 IP,那台挂了训练就断了。
-
元数据备份:
- 在做这一切之前,务必备份 Redis 的 dump.rdb。
- 只要 Redis 在,JuiceFS 的文件结构就在。对象存储里的数据块丢了还能通过文件名找回来(虽然内容坏了),但 Redis 丢了,对象存储里那一堆
chunk-xxx的文件就是一堆毫无意义的二进制垃圾,神仙难救。
-
拓扑限制:
- MinIO 扩容通常是“倍增”或者“对等扩容”。比如 4 节点扩容,通常是再加 4 节点(变成 2 个 Server Pool)。
- 所以,规划 3 节点时,最好磁盘大小一致,网络环境一致。