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

203 lines
10 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 — Stateless Ray Node Pool 容器固化设计
目标:把 v2.5 的 **stateless poolhead discovery + worker watchdog** 能力固化到一个可复用镜像中,避免依赖宿主机脚本在容器内 `docker exec` 启动/守护进程。**同一个镜像同时供 head/worker 复用**,通过环境变量区分角色。
约束:**API server 代码与镜像解耦**,短期仍按现状“宿主机代码挂载到 head 容器,在 head 容器内启动 API”不把 API 代码打进本镜像。
---
## 1. 背景(现状与痛点)
当前 `src/mvp/docker-compose.yaml` 里 head/worker 都基于 `verlai/verl:sgl055.latest`,容器启动后 `command: sleep infinity`,再由宿主机脚本完成:
- head`ray start --head ...` + `head_publisher`(写 `head.json`
- worker`worker_watchdog`(读取 `head.json`,自动加入/重连 ray 集群)
现状问题:
- 启动流程依赖宿主脚本 `docker exec`,易受权限/路径/人为操作影响;
- “守护”目前是 bash while-loop出现异常时排障成本高
- 未来生产环境容器可能由算力平台拉起,我们只能 SSH 纳管,更需要把“自启动 + 自愈”放到容器内部。
---
## 2. v2.5 容器固化目标与非目标
### 2.1 目标
- **一个镜像复用**head/worker 统一镜像,通过 `ARGUS_ROLE=head|worker` 区分。
- **supervisor 守护**:无论 head/worker都使用 `supervisord` 守护关键进程:
- watchdog 崩溃 → supervisor 自动重启 watchdog
- ray 节点崩溃 → watchdog/或 supervisor 触发自动恢复(见 3.2 进程模型)
- **与共享存储对齐**:容器内统一挂载根路径 `/private`discovery 文件写到共享存储。
- **最小内置代码**:镜像只内置 stateless pool 相关 python 脚本discovery/publisher/watchdog/entrypoint不把 API 服务代码打进镜像。
- **远端构建**:镜像构建必须在开发/运行机器(例如 `argus@h1`)上完成,本机不要求具备 `verlai/verl:*` 基础镜像。
### 2.2 非目标(本迭代不做)
- 不把 API server 打包进本镜像(后续可做单独 `argus-api` 镜像)。
- 不改变 v2.5 TaskSpec 约束(仍使用 `/private/common/...` 公共资源;用户隔离只隔离 jobs
- 不在本迭代引入 K8s/operator/autoscaler只固化容器自启动/自愈。
---
## 3. 设计方案
### 3.1 单镜像架构概览
新增一个镜像(示例名):
- `argus/argus-ray-node:v2.5`
该镜像:
- `FROM verlai/verl:sgl055.latest`(通过 build-arg 可切换 base
- 内置:
- `argus_raypool`(或复用现有 `argus.ray.*` 子集)脚本:
- `discovery.py`head record 读写head.json
- `head_publisher.py`head 写入 head.json带 TTL/刷新)
- `worker_watchdog.py`worker 读取 head.json自动加入/重连
- (可选)`head_watchdog.py`:把 “ray head + publisher” 组装成一个可恢复的 watchdog
- `/usr/local/bin/argus-entrypoint.sh`:根据 role 生成 supervisor 配置并启动 supervisor
- supervisor 配置模板(或运行时生成)
### 3.2 进程模型确保“ray 崩/ watchdog 崩都能恢复”)
用户新增要求head/worker 均要 supervisor 守护 watchdogray 节点崩溃或 watchdog 崩溃都要自动恢复。
推荐进程组织(避免 “ray start” 后台化导致 supervisor 无法感知):
#### A) Head 容器ARGUS_ROLE=head
由 supervisor 启动 **两个 program**
1) `argus_head_watchdog`(推荐实现为 python 或 bash内部用 `ray start --head --block` 前台运行)
- 关键点:`ray start --head --block` 让 Ray 进程前台阻塞watchdog 作为父进程能感知退出码
- ray 崩 → `ray start --block` 返回 → watchdog 退出非 0 → supervisor 重启 watchdog → ray 自动重启
2) `argus_head_publisher`
- 定期刷新 `head.json`TTL/refresh
- publisher 崩 → supervisor 自动重启
> 备选:把 publisher 逻辑合并进 `argus_head_watchdog`(一个进程同时跑 ray + publisher 线程),减少 supervisor program 数量;但拆分更易观测与定位问题。
#### B) Worker 容器ARGUS_ROLE=worker
由 supervisor 启动 **一个 program**
1) `argus_worker_watchdog`
- 轮询读取 `head.json`,并以 `ray start --address=<head>:6379 --block` 方式加入集群
- 只要 ray 进程退出ray 崩/被 stop`--block` 结束watchdog 进入下一轮重连/重启
- watchdog 自己异常退出 → supervisor 自动重启 watchdog
> 注意:当前仓库里的 `worker_watchdog.py` 是 “`ray start` 非 block + 仅在 head addr 变化时重启”。容器固化建议升级为 “`--block` + 监测 ray 退出” 模式,否则 supervisor 很难准确感知 ray 的生命周期。
### 3.3 配置与环境变量Role 驱动)
镜像入口只依赖环境变量,不依赖宿主脚本参数。
建议环境变量清单(含默认值):
- `ARGUS_ROLE``head` / `worker`(必填)
- `ARGUS_SHARED_ROOT`:默认 `/private`
- `ARGUS_CLUSTER_NAME`:默认 `argus-ray`
- `ARGUS_HEAD_IP_FILE`:默认 `${ARGUS_SHARED_ROOT}/ray/discovery/${ARGUS_CLUSTER_NAME}/head.json`
- `ARGUS_RAY_PORT`:默认 `6379`
- `ARGUS_DASHBOARD_PORT`:默认 `8265`head
- `ARGUS_TTL_S`:默认 `60`head publisher
- `ARGUS_REFRESH_S`:默认 `10`head publisher
- `ARGUS_POLL_S`:默认 `5`worker watchdog
- `ARGUS_NODE_IP`:默认空;若空则 entrypoint 自动探测容器 IP
- `ARGUS_WORKER_RESOURCES_KV`:默认 `worker_node=100`(用于 driver 强制落 worker 的自定义资源)
- `ARGUS_RAY_EXTRA_ARGS`:可选,传递额外 `ray start` 参数
- `ARGUS_LOG_DIR`:默认 `${ARGUS_SHARED_ROOT}/common/logs`(落到共享目录便于排障)
### 3.4 Dockerfile / entrypoint / supervisor 设计
#### Dockerfile建议路径
在仓库新增(后续实现时):
- `src/mvp/images/argus-ray-node/Dockerfile`
- `src/mvp/images/argus-ray-node/entrypoint.sh`
- `src/mvp/images/argus-ray-node/supervisord.conf.tmpl`(可选)
- `src/mvp/images/argus-ray-node/py/argus_raypool/*.py`(仅 stateless pool 子集)
Dockerfile 关键动作:
- `FROM verlai/verl:sgl055.latest`(可 `ARG BASE_IMAGE=...`
- 安装 supervisor
- Debian/Ubuntu 基底:`apt-get update && apt-get install -y supervisor`
- 设定 `CMD ["supervisord","-n","-c","/etc/supervisor/supervisord.conf"]`
- 拷贝 python 脚本到 `/opt/argus/raypool` 并设置 `PYTHONPATH=/opt/argus`
- 拷贝 entrypoint 到 `/usr/local/bin/argus-entrypoint.sh`
- `ENTRYPOINT ["/usr/local/bin/argus-entrypoint.sh"]`
entrypoint.sh 逻辑:
- 探测容器 IP`hostname -i``ip route get 1.1.1.1`
- 根据 `ARGUS_ROLE` 生成 supervisor 配置:
- head启动 `head_watchdog` + `head_publisher`
- worker启动 `worker_watchdog`
- 配置 supervisor
- `autorestart=true`
- `startretries` 合理配置
- stdout/stderr 指向 `${ARGUS_LOG_DIR}/...` 或直接 stdout便于 `docker logs`
### 3.5 与 API server 的关系(保持解耦)
API server 仍按现状(短期方案):
- **代码存放在宿主机**,通过 volume mount 挂载到 head 容器(例如 `/workspace/mvp`)。
- **在 head 容器内启动 API**(例如用脚本 `docker exec argus-ray-head ... python3 /workspace/mvp/py/server.py`)。
- 关键点:即使 API 进程跑在 head 容器里,也仍视作“独立于 ray node 镜像的业务代码”,后续可独立演进为单独的 `argus-api` 镜像。
- 只要 API 能访问 Ray job server通常 `http://127.0.0.1:8265` 在 head 容器视角)即可。
未来(非本迭代)可将 API server 单独做 `argus-api` 镜像,按相同 `/private` 共享目录运行。
---
## 4. docker-compose 调整建议(后续实现)
当前 compose 的变化点(概念上):
- `image: verlai/verl:sgl055.latest``image: argus/argus-ray-node:v2.5`
- `command: sleep infinity` 移除(镜像自带 entrypoint
- head service 增加:
- `ARGUS_ROLE=head`
- 暴露 dashboard 端口保持 `8265:8265`
- worker service 增加:
- `ARGUS_ROLE=worker`
- `ARGUS_WORKER_RESOURCES_KV=worker_node=100`
- volumes 仍需要:
- `../../shared:/private`(共享存储)
- `../../verl:/workspace/verl`verl 代码/依赖按现状)
---
## 5. 验证与回归流程(落地后怎么验收)
### 5.1 构建镜像
1) **在远端 `argus@h1` 构建**(本机不要求具备基础镜像):
- `cd /home2/argus/infra/mvp/src/mvp`
- `docker build -t argus/argus-ray-node:v2.5 -f images/argus-ray-node/Dockerfile .`
2) 也可以使用 compose build推荐和实际运行一致
- `docker compose -f docker-compose.yaml build --no-cache`
### 5.2 基础连通性stateless pool 验证)
1) `docker compose up -d`
2) 验证 head 写入:
- 共享目录存在 `head.json``${ARGUS_SHARED_ROOT}/ray/discovery/${ARGUS_CLUSTER_NAME}/head.json`
3) 验证 worker 自动加入:
- 在 head 容器内 `ray status` 能看到 worker 节点加入
- Dashboard Nodes 页面能看到 head + worker
### 5.3 故障注入supervisor 自愈验证)
1) watchdog 崩溃:
- `pkill -f worker_watchdog`(或 kill 对应 PID
- 期望supervisor 自动拉起 watchdogworker 最终重新加入集群
2) ray 节点崩溃worker
- `ray stop --force` 或 kill raylet
- 期望watchdog 重新执行 `ray start ... --block`worker 恢复
3) ray 节点崩溃head
- kill head ray 前台进程(由 watchdog 启动)
- 期望supervisor 重启 head_watchdoghead 恢复并重写 head.jsonworkers 自动重连
### 5.4 端到端任务回归(与 v2.5 API 协作)
沿用现有 v2.5 E2E
- `src/mvp/scripts/run_all_v25_api.sh`
- `src/mvp/scripts/run_e2e_v25_cases.sh`
验收标准:
- PPO/GRPO/SFT 均能在 worker 上运行head 不跑训练
- API 的 task_id / submission_id 正常携带用户名
- 资源不足可转 `PENDING_RESOURCES` 并按周期重试
---
## 6. 风险点与对策
- **ray start 后台化**如果继续后台启动supervisor 不易感知 ray 崩溃。对策:使用 `ray start --block`(推荐)。
- **IP 探测不稳定**不同环境compose/平台)容器 IP 获取方式不同。对策entrypoint 做多策略探测并允许 `ARGUS_NODE_IP` 显式覆盖。
- **日志可观测性**:建议同时支持写到 `/private/common/logs`(共享)以及 stdout`docker logs`)。