argus-cluster/doc/storage_deployment.md

315 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 关于MinIO + JuiceFS 在H20部署
这是一个架构设计的关键决策点。在 **H20 节点运行 K3s** 的前提下,我建议采取 **“存储组件独立部署Outside客户端插件化接入Inside”** 的混合策略。
简单来说:**MinIO 和 Redis 跑在 K3s 外面(用 DockerJuiceFS Client 通过 CSI 跑在 K3s 里面。**
以下是详细的架构拆解和理由:
### 1\. 架构图解
```mermaid
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` 管理。
**理由**
1. **爆炸半径隔离(最重要)**AI 实验初期K3s 集群可能会因为 CNI 网络插件配置错误、Device Plugin 崩溃、或者各种误操作导致集群不可用Crash。如果存储后端MinIO跑在 K3s 里K3s 一挂,你的数据就“锁”在里面了,连日志都拷不出来。
2. **资源竞争保护**K8s 的调度器虽然有 QoS但在只有 2 个节点的情况下H20 的高负载很容易导致控制面不稳。把 MinIO 独立出来,保证存储服务的 CPU/内存资源不受 K3s 内部 Pod 挤压。
3. **网络直通**:宿主机直接部署 MinIO使用的是 `host` 网络,少了一层 K8s Service/Ingress 转发,对于大带宽吞吐稍微有一点点性能优势。
#### B. JuiceFS Client建议 **集成在 K3s 内部 (Inside)**
**方式**:使用 **JuiceFS CSI Driver**
**理由**
1. **符合“平台化”愿景**:你的目标是做一个 "AI Infra 平台"。对于上层用户(算法工程师),他们不应该关心宿主机挂载了什么目录。他们提交的 YAML 只需要写:
```yaml
volumes:
- name: data
persistentVolumeClaim:
claimName: pvc-juicefs
```
这是标准的云原生体验。
2. **权限与隔离**CSI Driver 可以更好地处理容器内的挂载点权限问题。如果手动在宿主机 `mount` 然后用 `hostPath` 映射进容器,经常会遇到 `Permission Denied` 或者 `root` 用户归属问题,非常头疼。
3. **自动缓存管理**CSI Driver 可以通过配置 `StorageClass`,自动帮你处理缓存路径的挂载和清理。
-----
### 3\. 实操配置指南 (关键点)
既然你只有两台 H20 并且要上 K3s以下是落地的关键配置
#### 第一步:在宿主机启动 MinIO 和 Redis (后端)
不要用 Helm直接写一个 `docker-compose.yml` 放在 `/opt/ai-infra/` 下运行:
```yaml
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 (前端)
1. **安装 CSI Driver**
```bash
helm repo add juicefs https://juicedata.github.io/charts/
helm install juicefs-csi-driver juicefs/juicefs-csi-driver -n kube-system
```
2. **创建 Secret (连接信息)**
```yaml
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"
```
3. **创建 StorageClass (核心中的核心)**
这里要配置**本地缓存**,让 JuiceFS 能够利用 H20 的 NVMe。
```yaml
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”** 模式:
1. **Outside**: 在宿主机直接运行 `juicefs mount ... /mnt/ai-data`。
2. **Inside**: 在 Pod YAML 里直接映射:
```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` 示例(需要在三台机器上都运行):
```yaml
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 客户端**。
1. **安装 `mc` (MinIO Client)** 命令行工具。
2. **配置别名**
```bash
mc alias set oldm http://192.168.1.10:9000 minioadmin minioadmin
mc alias set newm http://192.168.1.10:9100 admin strongpassword
```
3. **全量镜像 (Mirror)**
```bash
# 创建新桶
mc mb newm/mybucket
# 开始同步数据 (将旧桶数据镜像到新桶)
# --watch 参数可以持续监听增量数据,适合不停机迁移
mc mirror --watch oldm/mybucket newm/mybucket
```
#### 路径 BJuiceFS 层面迁移(适合要换云厂商/存储类型)
如果你想顺便整理数据碎片,或者从 MinIO 迁移到 阿里云 OSS可以用这个。但在你的场景下路径 A 更快。
* 命令:`juicefs sync minio://... minio://...` (不推荐,因为需要解密再加密,消耗 CPU)。
-----
### 第三步:停机切换 (Cutover)
为了保证 100% 数据一致性建议申请一个短时间的维护窗口10-20分钟
1. **停止训练任务**Scale down 所有的 Training Job。
2. **停止旧 MinIO 写入**
* 确保 `mc mirror` 已经追平了数据(没有 pending
* 你可以把旧 MinIO 设为只读,或者直接停止旧容器。
3. **最后一次 Sync**
* 运行 `mc mirror --overwrite` 确保最后一点差异也同步了。
4. **修改 K8s Secret**
* 这是 JuiceFS 的魔力所在。你**不需要**重新格式化 JuiceFS也**不需要**修改元数据引擎Redis。你只需要告诉 JuiceFS“底下的对象存储换地方了”。
* 编辑 K8s 里的 Secret
<!-- end list -->
```bash
kubectl edit secret juicefs-sc-secret
```
* 修改 `bucket` 和 `access-key/secret-key`
<!-- end list -->
```yaml
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"
```
5. **重启 CSI Driver**
* 删除 JuiceFS CSI 的 Pod让它们重建并加载新的 Secret。
* 删除使用该 PVC 的 Pod。
-----
### 第四步:验证与回收
1. **验证**
* 启动一个测试 Pod挂载 PVC。
* `ls` 一下目录,确认文件都在。
* `cat` 一个文件确认能读JuiceFS 会去新 MinIO 拿数据块)。
2. **回收**
* 确认一切正常运行几天后。
* 下线旧的单节点 MinIO 容器。
* 释放旧的磁盘空间。
### 专家建议 (Pro Tips)
1. **负载均衡 (Load Balancer)**
* 到了 3 节点分布式阶段,建议在 3 台 MinIO 前面架设一个 **Nginx** 或者 **HAProxy**。
* 这样 JuiceFS Client 连接的是 `http://nginx:9000`Nginx 轮询转发给后端 3 台 MinIO。
* **好处**:如果某台 MinIO 挂了Nginx 自动剔除,训练任务完全无感。如果直连某台 IP那台挂了训练就断了。
2. **元数据备份**
* 在做这一切之前,**务必备份 Redis 的 dump.rdb**。
* 只要 Redis 在JuiceFS 的文件结构就在。对象存储里的数据块丢了还能通过文件名找回来(虽然内容坏了),但 Redis 丢了,对象存储里那一堆 `chunk-xxx` 的文件就是一堆毫无意义的二进制垃圾,神仙难救。
3. **拓扑限制**
* MinIO 扩容通常是“倍增”或者“对等扩容”。比如 4 节点扩容,通常是再加 4 节点(变成 2 个 Server Pool
* 所以,规划 3 节点时,最好磁盘大小一致,网络环境一致。