已合并
[Feature] 引擎重启兜底策略:容器不重启重拉引擎 + 自杀裁决收敛 Daemon #710
[Feature] 引擎重启兜底策略:容器不重启重拉引擎 + 自杀裁决收敛 Daemon #710
已合并
jason lyu创建于 9 天前
jason lyu
jason lyu成员
9 天前

1. 合入背景

引擎进程死亡后,当前唯一兜底是「NodeManager 心跳 abnormal ×5(约 15s)→ 自杀 → k8s 重启 pod」,代价是全容器重建。本 PR 实现「容器不重启重拉引擎」:Controller 协同实例的所有 NodeManager 一起重拉全部引擎(rank 集合通信组一致性),重拉失败再回退到容器重启;同时把自杀裁决权收敛到 Daemon(进程生命周期管理者)。

本 PR 基于最新主线:原生引擎拉起(#698:NodeManager 直接 spawn 原生 vLLM/SGLang,删除 EngineServer 层)与 EngineServer 删除收尾(#717)均已合入,重拉链路已对接原生拉起路径。

关联 ISSUE:#472

2. 修改内容

  1. Controller 引擎重启兜底策略motor/controller/fault_tolerance/strategy/engine_relaunch.py,两阶段):
    • Phase 1 重启引擎:探活实例全部 NodeManager(任一不可达 → 直接 Phase 2,保证 rank 一致性)→ 逐个下发 POST /node-manager/engine-restart → 轮询 /node-manager/status 全部 NORMAL → 成功
    • Phase 2 重启容器:仅对未成功派发重启的 NM 发 abort 解冻自杀(已派发 NM 保持 freeze,避免打断正在恢复的引擎)→ 心跳机制触发容器重启(k8s);abort 发送失败补 error 日志指名 NM;实例 DELETED/消失提前结束
    • stop() 被打断时 fire-and-forget 发 abort(防冻结窗口内 NM 自杀被延迟)
  2. 策略失败升级机制(可复用)StrategyBase.mark_failed() + InstanceMetadata.prev_strategy_failed——token 重推/UCE/弹性扩缩容等「期望引擎不重启快速恢复」的策略失败后,策略中心自动降级到 EngineRelaunchStrategy(兜底链);升级分支同样受 enable_engine_relaunch 门控
  3. level2_strategy 挂钩:ENGINE_DEAD 软件故障直接触发 EngineRelaunchStrategy(开关关闭 → None)
  4. NodeManager 新路由 POST /node-manager/engine-restartnode_manager_api.py):body {"action": "restart"|"abort", "instance_id"}薄路由——restart 整体委托 Daemon.restart_engine(Daemon 内部解析启动参数、冻结自杀、暂停/恢复 FaultReporter、只停引擎服务重拉不动 KV store/监控线程),路由只做 HTTP 语义映射(并发 409、快照恢复中 409、未 start 400、失败 500,零锁零状态)。部分失联协同复用既有 /node-manager/stop(不在 restart 路由新加 shutdown action)
  5. 自杀冻结daemon.py):截止时间制 freeze/unfreeze(abort 丢失自动过期,容器重启兜底永存);freeze 仅在死亡上报成功后才执行(上报失败 → Controller 不可达 → 不冻结,仲裁继续计数,容器重启兜底不被拖死);freeze 受 enable_engine_relaunch 门控(未开开关 → 不上报后不冻结,pod 快速自终止走 k8s 容器重启,不被 180s 冻结窗口拖延)
  6. FaultReporter 重拉编排:重拉期间由 Daemon 暂停轮询(引擎被杀期间 FT 端口不可达,poll 失败会被误报死亡),重拉成功后 Daemon 恢复轮询并清空轮询状态(新引擎自然重新起算启动宽限);另修复首次 poll 失败即冻结自杀的时序竞态(自杀 ~15s vs DEAD 上报 15-30s)
  7. 自杀裁决收敛 Daemon:心跳模块收敛为状态源(has_abnormal_endpoints/endpoints_generation/is_within_grace_period),Daemon 独立 3s 仲裁线程统一裁决(5×3s≈15s,间隔跟随 heartbeat_interval_seconds 配置)+ 统一 freeze/should_suicide;冷启动误判门槛:只有「曾经 NORMAL」的 endpoint 异常才上报死亡(模型加载期不误报)
  8. 引擎就绪等待下沉_engine_ready 握手事件 + Daemon 后台 wait_ready——heartbeat 不再感知引擎就绪、不再 import Daemon(循环依赖解除),状态线程在探测前等待握手
  9. 改名EngineManagerRegisterManagerregister_manager.py,职责=注册/元数据管理,与 Daemon 进程管理区分)
  10. 结构StrategyBase 移至 strategy/base.py(消除循环 import)
  11. 配置收敛(唯一开关)enable_engine_relaunch(默认开)为「容器不重启重拉引擎」唯一开关——Controller 侧 fault_tolerance_config(策略选择门控)+ NodeManager 侧 fault_tolerance_config(同名配置,freeze 门控);删除 MOTOR_RESTART_ENGINE 环境变量及 health_check 的 SIGTERM 自杀快路径,恢复路径统一由 Daemon 按开关决策
  12. 重拉日志分隔标记:旧引擎 stop 后、新引擎 pull 前,print 直达容器 stdout 打印 [ENGINE RELAUNCH #N] banner(容器生命周期内重拉计数 + 时间),多次重拉日志按次分区检索
  13. 原生拉起适配(rebase #698/#717):重拉/就绪握手/死亡上报链路对接 native_engine;心跳经 RuntimeState probe 查询(STARTING/STOPPING 保留原状态);进程组清理(start_new_session + killpg + 退出等待 + 僵尸收割);bootstrap_port 字段;过时 EngineServer 提法清理

3. 资料变更

涉及,同步更新:

  • docs/zh/developer_guide/components/node_manager.md:模块表(RegisterManager)、自杀裁决章节
  • docs/zh/design/fault_tolerance/overview.md / fault_manager.md:策略/模块引用同步
  • examples/features/config_sample.json:两侧 fault_tolerance_config 新字段
  • skill refs(nodeman.md、controller.md、code-style.md)——nodeman.md 已随 #717 文档清理合并

4. 接口变更

涉及(NodeManager 管理面接口 + 配置):

  • 新增 POST /node-manager/engine-restart:body {"action": "restart"|"abort", "instance_id": int?},200/400/409/500
  • /node-manager/stop 语义补全为「自杀」:停引擎后延时 SIGTERM 自身(退出 -1 → k8s 重启 pod)。部分失联时 Controller 对存活 NM 复用该接口下发自杀(不再依赖引擎重拉路由)
  • 新增 Controller 配置 fault_tolerance_config.enable_engine_relaunch(默认 true)/ engine_relaunch_complete_timeout_sec / engine_relaunch_poll_interval_sec / engine_relaunch_dispatch_retries / engine_relaunch_nm_unreachable_threshold
  • 新增 NodeManager 配置 fault_tolerance_config.enable_engine_relaunch(默认 true,与 Controller 对齐)/ engine_restart_wait_timeout_sec(180s)/ engine_restart_freeze_sec(720s)
  • 删除 MOTOR_RESTART_ENGINE 环境变量(配置收敛,统一由 enable_engine_relaunch 决策)

5. 测试结果

单测bash tests/run_tests.sh 全量):2665 passed(rebase #698/#717 后全量基线之上)

新增/重写测试覆盖:

  • EngineRelaunchStrategy:Phase 1 探活全部 NM / 探活失败直接 Phase 2 / 下发全部 NM / 轮询成功 / NM 连续不可达进 Phase 2 / 超时 abort / 实例消失提前结束 / event 打断 / Phase 2 只 abort 未派发 NM
  • 策略失败升级:StrategyBase 失败标记 / 策略中心 prev_strategy_failed 置位与清除 / 升级选择 EngineRelaunchStrategy
  • level2_strategy 挂钩:ENGINE_DEAD → EngineRelaunchStrategy、开关关闭 → None、UNHEALTHY → None
  • engine-restart 路由:restart/abort/400/409(并发、快照恢复中)/500(pull 失败且解冻)
  • /node-manager/stop 自杀语义:停引擎 + 延时 SIGTERM 自身(pod 重启)
  • 自杀冻结与裁决:freeze 清零复位 / 冻结期不计数 / 解冻恢复 / 冻结到期自动恢复 / generation 变化重置 / grace 期跳过 / 阈值触发 / 上报失败不冻结 / 未开 enable_engine_relaunch 不冻结
  • 引擎死亡双信号源:PID 死亡事件(monitor health_check 返回死亡列表 + pid 去重)/ 心跳 ABNORMAL 上报(ep 去重 + 恢复清除 + 失败重试)
  • FaultReporter:回归纯 FT 状态上报
  • 心跳状态源:has_abnormal_endpoints / abnormal_endpoint_ids / endpoints_generation / grace getter
  • daemon:restart_engine 只动引擎服务(KV/监控线程不动)/ 裁决(阈值、重置条件、冻结窗口与过期)/ PID 死亡上报去重与失败重试 / 冷启动不误报、曾 NORMAL 后异常上报 / 重拉分隔标记(计数 + banner 打印)
  • RegisterManager:master_dp_ip/role 持久化、get_restart_params
  • 配置:两侧新字段默认值与校验

静态检查:pylint / ruff 通过

K8s e2e(infer195,A2 跨机 EP16 = 2 pod × 8 卡):已验证通过——

  • 杀 vLLM 进程 → 双信号检测 → EngineRelaunchStrategy 协同两个 pod 一起重拉全部引擎 → 实例回 ACTIVE
  • 杀 NodeManager 进程 → 实例稳定 INACTIVE(无 ACTIVE↔INACTIVE 震荡)→ volcano podgroup 自动重启两个 pod(既有机制)→ 新实例组装恢复
  • 冷启动加载期(30B 模型 > grace 120s)不再被误判死亡:新增「曾 NORMAL 门槛」——e2e 对比:修复前加载期多条误报触发重拉打断加载(恶性循环),修复后误报 0、实例平滑 ACTIVE

e2e 定位并修复的问题:

  • engine-restart 路由快照恢复检查条件错误(非快照部署误拒 409)
  • EngineServer SIGKILL 后子进程孤儿残留(占旧 ZMQ 端口 → 重拉引擎握手 5 分钟超时)——进程组管理修复(start_new_session + killpg + 退出等待 + 僵尸收割)
  • 重拉与 FaultReporter 解耦:PID 级检测 + 心跳 ABNORMAL 双信号源,不依赖 FT 配置
  • 部分节点失联防震荡:转 ACTIVE 加心跳新鲜度门槛 + 存活 NM 经既有 /node-manager/stop(自杀语义)协同退出(volcano 兜底)
  • daemon 职责收敛:engine 重启生命周期(stop/分隔标记/pull)下沉 EngineService.restart,daemon 回归 service 无关的编排者

6. CheckList

likedislike
Pull Request已成功合入, 合并人@tobking
(感谢 jason lyu 的贡献)
jason lyujason lyu成员
9 天前 关联了issue:[Feature-Request|需求反馈]: 引擎重启兜底策略:容器不重启重拉引擎 + 自杀裁决收敛 Daemon
atomgit-bot
atomgit-bot
9 天前 评论:

变更摘要

本 PR 实现了引擎进程死亡后的容器不重启兜底策略:新增 EngineRelaunchStrategy 两阶段恢复流程(先重拉引擎,失败再回退到容器重启),同时将自杀裁决权从 HeartbeatManager 收敛到 Daemon(进程生命周期管理者),并引入自杀冻结/解冻机制保护重拉窗口。EngineManager 更名为 RegisterManager 以明确职责边界,FaultReporter 从 ZMQ PUB/SUB 改为 HTTP 轮询引擎 /fault_tolerance/status 端点。

主要改动

  • 新增 EngineRelaunchStrategy 两阶段引擎兜底策略:Phase 1 探活全部 NodeManager → 下发 POST /node-manager/engine-restart → 轮询 /node-manager/status 直至全部端点 NORMAL;Phase 2 对可达 NM 发 abort 解冻自杀触发容器重启(motor/controller/fault_tolerance/strategy/engine_relaunch.py,333 行新增)
  • 新增策略失败升级链StrategyBase.mark_failed() 标记策略失败 → InstanceMetadata.prev_strategy_failed 置位 → 策略中心自动降级到 EngineRelaunchStrategybase.py 新增 53 行,fault_manager.py 在策略完成时记录失败标记并选择升级策略)
  • NodeManager 新增 POST /node-manager/engine-restart 路由:支持 restart(冻结自杀 → Daemon.restart_engine 重拉引擎 → 重置 FaultReporter 启动宽限)和 abort(解冻自杀)两种 action,并发 409、快照恢复中 409、未 start 400、pull 失败 500 且解冻(node_manager_api.py 新增约 100 行)
  • 自杀裁决收敛到 Daemon:心跳模块退化为状态源(has_abnormal_endpoints/endpoints_generation/is_within_grace_period),Daemon 新增独立 3s 仲裁线程统一裁决自杀(5×3s≈15s),并支持截止时间制 freeze_suicide/unfreeze_suicidedaemon.py 新增 163 行,heartbeat_manager.py 删除约 39 行自杀计数逻辑)
  • FaultReporter 从 ZMQ 改为 HTTP 轮询:通过 query_engine_ft_status 轮询各引擎的 /fault_tolerance/status 端点,首次 poll 失败即冻结自杀(修复自杀 ~15s vs DEAD 上报 15-30s 时序竞态),新增 reset_startup_grace 方法在重拉后重置启动宽限防重拉风暴(fault_reporter.py 重写,+275/-145 行)
likedislike
atomgit-bot
atomgit-bot
9 天前 评论:

🤖 AI Code Review

⚠️ 审查未能完成

本次代码审查未能完成,可能是临时性故障。

你可以在评论区输入 /ai review 重新发起审查。若多次失败,请联系管理员并附上下方追踪 ID。

追踪 ID: task-181818-run-174242

likedislike
Xxiangjie10成员
9 天前 添加了label:pr-audit-failed
xiangjie10成员
9 天前 评论:
🔍 PR 规范审计未通过,以下项目需要修正:
  • PR 新增代码 2792 行超过 1000 行,且标题未标注"反合"

请修正后重新提交,或联系仓库管理员。

likedislike
此处折叠了483条消息 查看更多
liu
liu成员
3 天前 评论:

/lgtm

likedislike
tobking
tobking成员
3 天前 评论:

/approve

likedislike
ascend-robotascend-robot成员
3 天前 添加了label:approvedlgtm
tobkingtobking成员
3 天前 关闭了关联的issue
tobkingtobking成员
3 天前 合入了pull request