10 KiB
MVP v2.5 — Stateless Ray Node Pool 容器固化设计
目标:把 v2.5 的 stateless pool(head 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 守护 watchdog;ray 节点崩溃或 watchdog 崩溃都要自动恢复。
推荐进程组织(避免 “ray start” 后台化导致 supervisor 无法感知):
A) Head 容器(ARGUS_ROLE=head)
由 supervisor 启动 两个 program:
argus_head_watchdog(推荐实现为 python 或 bash,内部用ray start --head --block前台运行)- 关键点:
ray start --head --block让 Ray 进程前台阻塞,watchdog 作为父进程能感知退出码 - ray 崩 →
ray start --block返回 → watchdog 退出非 0 → supervisor 重启 watchdog → ray 自动重启
- 关键点:
argus_head_publisher- 定期刷新
head.json(TTL/refresh) - publisher 崩 → supervisor 自动重启
- 定期刷新
备选:把 publisher 逻辑合并进
argus_head_watchdog(一个进程同时跑 ray + publisher 线程),减少 supervisor program 数量;但拆分更易观测与定位问题。
B) Worker 容器(ARGUS_ROLE=worker)
由 supervisor 启动 一个 program:
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:默认/privateARGUS_CLUSTER_NAME:默认argus-rayARGUS_HEAD_IP_FILE:默认${ARGUS_SHARED_ROOT}/ray/discovery/${ARGUS_CLUSTER_NAME}/head.jsonARGUS_RAY_PORT:默认6379ARGUS_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 自动探测容器 IPARGUS_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/Dockerfilesrc/mvp/images/argus-ray-node/entrypoint.shsrc/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"]
- Debian/Ubuntu 基底:
- 拷贝 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
- head:启动
- 配置 supervisor:
autorestart=truestartretries合理配置- 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.5command: sleep infinity移除(镜像自带 entrypoint)- head service 增加:
ARGUS_ROLE=head- 暴露 dashboard 端口保持
8265:8265
- worker service 增加:
ARGUS_ROLE=workerARGUS_WORKER_RESOURCES_KV=worker_node=100
- volumes 仍需要:
../../shared:/private(共享存储)../../verl:/workspace/verl(verl 代码/依赖按现状)
5. 验证与回归流程(落地后怎么验收)
5.1 构建镜像
- 在远端
argus@h1构建(本机不要求具备基础镜像):cd /home2/argus/infra/mvp/src/mvpdocker build -t argus/argus-ray-node:v2.5 -f images/argus-ray-node/Dockerfile .
- 也可以使用 compose build(推荐,和实际运行一致):
docker compose -f docker-compose.yaml build --no-cache
5.2 基础连通性(stateless pool 验证)
docker compose up -d- 验证 head 写入:
- 共享目录存在
head.json:${ARGUS_SHARED_ROOT}/ray/discovery/${ARGUS_CLUSTER_NAME}/head.json
- 共享目录存在
- 验证 worker 自动加入:
- 在 head 容器内
ray status能看到 worker 节点加入 - Dashboard Nodes 页面能看到 head + worker
- 在 head 容器内
5.3 故障注入(supervisor 自愈验证)
- watchdog 崩溃:
pkill -f worker_watchdog(或 kill 对应 PID)- 期望:supervisor 自动拉起 watchdog;worker 最终重新加入集群
- ray 节点崩溃(worker):
ray stop --force或 kill raylet- 期望:watchdog 重新执行
ray start ... --block,worker 恢复
- ray 节点崩溃(head):
- kill head ray 前台进程(由 watchdog 启动)
- 期望:supervisor 重启 head_watchdog;head 恢复并重写 head.json;workers 自动重连
5.4 端到端任务回归(与 v2.5 API 协作)
沿用现有 v2.5 E2E:
src/mvp/scripts/run_all_v25_api.shsrc/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)。