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

10 KiB
Raw Blame History

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,再由宿主机脚本完成:

  • headray start --head ... + head_publisher(写 head.json
  • workerworker_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 进程模型)
  • 与共享存储对齐:容器内统一挂载根路径 /privatediscovery 文件写到共享存储。
  • 最小内置代码:镜像只内置 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.pyhead record 读写head.json
      • head_publisher.pyhead 写入 head.json带 TTL/刷新)
      • worker_watchdog.pyworker 读取 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.jsonTTL/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_ROLEhead / 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:默认 8265head
  • ARGUS_TTL_S:默认 60head publisher
  • ARGUS_REFRESH_S:默认 10head publisher
  • ARGUS_POLL_S:默认 5worker 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 逻辑:

  • 探测容器 IPhostname -iip 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.latestimage: 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/verlverl 代码/依赖按现状)

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 ... --blockworker 恢复
  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(共享)以及 stdoutdocker logs)。