argus-cluster/specs/mvp/v2.5/v2.5_design.md

9.6 KiB
Raw Blame History

MVP v2.5 详细设计方案User Management + Stateless Ray Node Pool

本文目标:把 mvp_roadmap_v2.md 中 v2.5 的思路落到可工程化实现的设计层,包括:

  • API Server 内新增 user management
  • Ray node pool 变为无状态worker 自发现 head、自动加入、watchdog 自愈);
  • 仍保持 v2.0 的“任务管理层”语义Task/Attempt、队列、资源判断、Ray Job 提交与状态同步;
  • 所有共享数据/状态统一落在 GPFSdev 环境可先用 NFS容器内路径统一为 /private/

术语说明文中“GPFS”代表生产共享存储dev 环境可用 NFS但容器内仍以 /private/ 访问。


1. 目标与非目标

1.1 v2.5 目标Must

  1. User Management最小多租户
  • 支持创建/禁用用户;
  • 为每个用户签发内部 tokenAPI key用于认证与隔离
  • 用户隔离v2.5 先做最小闭环,仅隔离 jobs 输出 与 API 可见性):
    • 用户只能看到/操作自己的 Task
    • 训练输出job root、checkpoints、日志归档等按 user 目录落盘;
    • 训练输入verl 代码、HF cache、datasets统一使用 common/v2.5 不支持用户自定义代码/模型/数据集隔离)。
  1. Stateless Ray Worker Node Pool
  • worker 容器启动时无需被平台告知 head 地址;
  • worker 通过 GPFS 读取 Head IP File 自动连接 Ray head
  • worker 内部 watchdog 监控 head 地址变化,发生变化时自动 ray stop + ray start 重连;
  • worker 尽量不依赖本地持久化状态(宕机/替换后可无感重建)。
  1. 保持 v2.0 的 Task 管理行为
  • Task/Attempt 模型不变(或向后兼容扩展);
  • 对齐 verl 的 fail-fast 行为:资源不足时服务侧 pending + 重试;
  • Ray Job 提交仍通过 Ray Python SDKJobSubmissionClient

1.2 v2.5 非目标Not Now

  • 完整 WebUI留到 v3.0)。
  • 公平调度/配额/优先级(留到 v3.x
  • 完整生产级 IAM留到 v4+v2.5 仅内部 token。
  • K8s 原生编排(本阶段不要求,但设计需能适配“算力平台拉起容器,只能 ssh 进去纳管”的模式)。

2. 总体架构(对应 roadmap v2.5

2.1 组件划分

控制面Control Plane

  • API Server
    • user management
    • task management队列/调度/重试/状态聚合)
    • Ray Job ToolRay Client
    • VerlTaskSpecTaskSpec YAML沿用 v2.0/v2.1 格式)
    • 与 Ray head 在同一台/同一容器是推荐形态(便于访问 dashboard / job server
  • Ray Head有状态
    • 启动后把 head 地址写入 GPFS 的 Head IP File用于 worker 服务发现

数据面Data Plane

  • Ray Workers无状态节点池
    • stateless bootstrap从 GPFS 读取 head 地址自动加入集群
    • watchdog持续 watch head 地址文件变化并自愈重连

共享存储GPFS

  • 统一数据路径:数据、模型 cache、代码、任务输出、以及 head 服务发现文件。

2.2 v2.5 的控制反转IoC

与 v2.0/手工集群的关键差异:

  • v2.0:平台脚本/运维显式启动 worker 并指定 --address=<head>
  • v2.5worker 自己从 GPFS 读取 head_ip_file,无需平台维持 worker 列表与 SSH 连接池。

3. GPFS 目录结构(容器内 /private

建议在 v2.5 固化以下目录(与现有 v2.0 兼容扩展):

/private/
  ray/
    discovery/
      <cluster_name>/
        head.json              # Head IP File服务发现
        head.json.lock         # 可选写入锁v2.5 可先不实现)
  users/
    <user_id>/
      jobs/                    # /private/users/<uid>/jobs/<ray_submission_id>/*
      outputs/                 # 训练输出聚合(按需要)
  common/
    code/                      # 平台/公共代码快照verl code snapshot 等)
    datasets/                  # 公共数据集
    hf/                        # 公共 HF cachedev 复用)
    db/                        # sqlite
    logs/                      # API 日志、平台日志

说明:

  • common/平台默认目录v2.5 先默认所有用户可写;后续再加 ACL/只读)。
  • users/<user_id>/...:用户隔离主边界(最小多租户的关键)。

4. Head IP File服务发现设计

4.1 文件路径

  • head_ip_file = /private/ray/discovery/<cluster_name>/head.json
  • <cluster_name>:由配置指定(例如 argus-ray),允许同一 GPFS 上存在多个环境/集群。

4.2 文件内容JSON

建议采用 JSON易扩展

{
  "cluster_name": "argus-ray",
  "head_ip": "10.0.0.12",
  "gcs_port": 6379,
  "dashboard_port": 8265,
  "job_server_url": "http://10.0.0.12:8265",
  "updated_at": "2025-12-25T17:00:00Z",
  "expires_at": "2025-12-25T17:01:00Z"
}

关键点:

  • updated_at:便于排障与可观测;
  • expires_at:避免 worker 读取到“陈旧 head 地址”后无限重连;
  • job_server_url:对外可直接用于 Ray Job Tool 配置(便于无脑接入)。

4.3 写入策略(原子更新)

Head 写入时必须保证 worker 读取不会读到半文件:

  • 写临时文件 head.json.tmp
  • fsync(可选);
  • rename(head.json.tmp -> head.json)(原子替换)。

4.4 心跳与 TTL

Head 进程需周期性刷新 head.json

  • 建议 ttl_s=60,刷新周期 refresh_s=10
  • 若 head 进程异常退出worker 读取到过期文件可进入“等待模式”而非无限重连。

5. Stateless Worker Bootstrap + Watchdog

5.1 启动序列worker 容器内)

  1. 启动脚本读取 head.json
    • 若文件不存在sleep + 重试(直到存在)
    • 若存在但 expires_at 已过期sleep + 重试(直到变为新鲜)
  2. 解析 head_ip:gcs_port 并执行:
    • ray stop --force || true
    • ray start --address=<head_ip>:<gcs_port> --resources='{"worker_node": 100, ...}' ...
  3. 启动 watchdog 进程(同容器):
    • 轮询/监听 head.json 的内容变化
    • 一旦 head_ipgcs_port 改变,触发 ray stop + ray start 重连

5.2 Watchdog 策略(最小可用)

v2.5 推荐“简单且稳”的实现:

  • polling 间隔 watch_s=5
  • 对比 head.jsonupdated_at 或 hash
  • 若发现变更:执行重连;
  • 若连续多次重连失败指数退避v2.5 可先固定退避v2.6 再增强)。

5.3 资源标签driver 强制落 worker

继续沿用 v2.0 的思路:

  • worker 启动时 --resources='{"worker_node": 100}'
  • head 不包含 worker_node 资源
  • Ray job submit 时设置 entrypoint_resources{"worker_node": 1}

5.4 GPU/CPU 的“无状态”约束

  • worker 是否有 GPU 由底层算力平台决定(生产上平台会为容器挂载 GPU
  • worker 启动脚本不应硬编码 GPU 编号,只依赖 NVIDIA_VISIBLE_DEVICES/平台注入;
  • head 推荐 --num-gpus=0 --num-cpus=0,避免训练调度到 head。

6. User Management 设计(最小多租户)

6.1 数据模型SQLite

新增两张表(示意):

  • users
    • user_idPK
    • display_name
    • stateACTIVE/DISABLED
    • created_at
  • api_tokens
    • token_hashPK
    • user_idFK
    • created_at
    • last_used_at

并在 tasks 表增加:

  • user_idFK

6.2 鉴权策略

内部 token 模式:

  • Authorization: Bearer <token>
  • 服务端将 token 映射到 user_id
  • 后续所有 task 查询/取消/日志默认 scope 到该 user_id

管理员能力v2.5 最小实现):

  • 额外配置一个 admin token或把特定 user 标记为 admin
  • admin 可 list all users/tasks用于运维排障

6.3 用户目录隔离(路径约束)

核心原则v2.5 版):

  • 输出:必须落在 /private/users/<uid>/jobs/...(服务端统一计算,不允许用户任意指定输出根)
  • 输入:统一使用 /private/common/...v2.5 不支持用户自定义 verl 代码、也不做 hf/datasets 的用户隔离)

服务端处理策略(最小可用):

  • 解析 TaskSpec 后,对输入路径字段做白名单前缀校验(必须是 /private/common/...;拒绝 ../ 与越界路径);
  • 输出目录统一由服务端计算:job_root = /private/users/<uid>/jobs/<ray_submission_id>/

7. TaskSpecVerlTaskSpec YAML在 v2.5 的扩展点

v2.5 不扩展 TaskSpec:保持与 v2.0/v2.1 的 YAML 结构化字段与语义一致。

v2.5 的“用户语义”仅体现在服务端的补齐/约束:

  • user_id 由 token 推导(用户不需要在 YAML 里写 user_id
  • 服务端派生 ray_submission_id(由 task_id/attempt 派生);
  • 服务端统一计算输出目录 job_root=/private/users/<uid>/jobs/<ray_submission_id>/...
  • v2.5 不支持用户自定义 verl 代码路径(因此 runtime_env 不需要注入用户 code 目录)。

8. 迁移与兼容性

v2.5 设计需满足:

  • 现有 v2.0 的“手工启动 worker”仍可运行作为 dev fallback
  • 在不改镜像的前提下worker watchdog 可以以“容器启动命令/entrypoint”方式注入dev 用 scripts生产由算力平台指定 command

9. 风险与对策v2.5

  1. GPFS 上 head.json 一致性/延迟
  • 对策:原子 rename + TTLwatchdog polling。
  1. Ray head 重启后 job server URL 变化
  • 对策head.json 内写入 job_server_urlRay Job Tool 可读取该文件更新 addressv2.6 可做动态 reload
  1. Worker 重连期间任务波动
  • 对策:服务侧调度器对齐 verl 的资源 fail-fast任务失败可归因并排队重试。