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

256 lines
9.6 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.

# 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 不支持用户自定义代码/模型/数据集隔离)。
2) **Stateless Ray Worker Node Pool**
- worker 容器启动时无需被平台告知 head 地址;
- worker 通过 GPFS 读取 **Head IP File** 自动连接 Ray head
- worker 内部 watchdog 监控 head 地址变化,发生变化时自动 `ray stop` + `ray start` 重连;
- worker 尽量不依赖本地持久化状态(宕机/替换后可无感重建)。
3) **保持 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易扩展
```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_ip``gcs_port` 改变,触发 `ray stop` + `ray start` 重连
### 5.2 Watchdog 策略(最小可用)
v2.5 推荐“简单且稳”的实现:
- polling 间隔 `watch_s=5`
- 对比 `head.json``updated_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_id`PK
- `display_name`
- `state`ACTIVE/DISABLED
- `created_at`
- `api_tokens`
- `token_hash`PK
- `user_id`FK
- `created_at`
- `last_used_at`
并在 `tasks` 表增加:
- `user_id`FK
### 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。
2) **Ray head 重启后 job server URL 变化**
- 对策head.json 内写入 `job_server_url`Ray Job Tool 可读取该文件更新 addressv2.6 可做动态 reload
3) **Worker 重连期间任务波动**
- 对策:服务侧调度器对齐 verl 的资源 fail-fast任务失败可归因并排队重试。