argus-cluster/doc/storage_deployment.md

12 KiB
Raw Blame History

关于MinIO + JuiceFS 在H20部署

这是一个架构设计的关键决策点。在 H20 节点运行 K3s 的前提下,我建议采取 “存储组件独立部署Outside客户端插件化接入Inside 的混合策略。

简单来说:MinIO 和 Redis 跑在 K3s 外面(用 DockerJuiceFS 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 rundocker-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 只需要写:
    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/ 下运行:

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

    helm repo add juicefs https://juicedata.github.io/charts/
    helm install juicefs-csi-driver juicefs/juicefs-csi-driver -n kube-system
    
  2. 创建 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"
    
  3. 创建 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” 模式:

  1. Outside: 在宿主机直接运行 juicefs mount ... /mnt/ai-data
  2. 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 客户端

  1. 安装 mc (MinIO Client) 命令行工具。
  2. 配置别名
    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)
    # 创建新桶
    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
    kubectl edit secret juicefs-sc-secret
    
    • 修改 bucketaccess-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"
    
  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:9000Nginx 轮询转发给后端 3 台 MinIO。
    • 好处:如果某台 MinIO 挂了Nginx 自动剔除,训练任务完全无感。如果直连某台 IP那台挂了训练就断了。
  2. 元数据备份

    • 在做这一切之前,务必备份 Redis 的 dump.rdb
    • 只要 Redis 在JuiceFS 的文件结构就在。对象存储里的数据块丢了还能通过文件名找回来(虽然内容坏了),但 Redis 丢了,对象存储里那一堆 chunk-xxx 的文件就是一堆毫无意义的二进制垃圾,神仙难救。
  3. 拓扑限制

    • MinIO 扩容通常是“倍增”或者“对等扩容”。比如 4 节点扩容,通常是再加 4 节点(变成 2 个 Server Pool
    • 所以,规划 3 节点时,最好磁盘大小一致,网络环境一致。