脚本批量执行平台 — 技术说明(SSOT)
最后更新: 2026-05-22
状态: 已实现,待本地 / 外网 E2E 验证
1. 目标与约束
| 目标 |
实现方式 |
| 2000+ 台批量执行 |
内置分批(默认 50 台/批),批内并发 10 |
| 长任务不阻塞 HTTP |
nohup + 子机结束 curl 主站回调 |
| 短任务执行中可停止 |
Agent wait=false → pid → /exec/kill |
| 减轻 MySQL 压力 |
执行态只写 Redis;MySQL 60s 分批刷盘 |
| 全链路可审计 |
audit_logs 即时写入;results._meta.events 时间线 |
| 不用 WS 换票 / 异步 exec API |
同步 POST /api/scripts/exec + 页顶状态条轮询 |
不可改 / 环境项(.env):
NEXUS_API_BASE_URL — 长任务子机回调主站地址(必填,否则长任务 400)
NEXUS_SCRIPT_EXEC_BATCH_SIZE(默认 50)、NEXUS_SCRIPT_EXEC_CONCURRENCY(默认 10)
代码内常量(server/infrastructure/redis/script_execution_store.py,不写 .env):
| 常量 |
值 |
含义 |
REDIS_TTL_SECONDS |
7200 |
热数据最长 2h |
FLUSH_INTERVAL_SECONDS |
60 |
Redis→MySQL 刷盘周期 |
FLUSH_BATCH_LIMIT |
50 |
每轮最多刷 50 条执行记录 |
STUCK_NO_PROGRESS_SECONDS |
3600 |
卡住检测阈值(1h) |
2. 架构与数据流
2.1 Redis 热数据
- Key:
script_exec:{execution_id} — JSON 全量快照(status、results、events、operator…)
- 队列:
script_exec:flush_queue — 待刷 MySQL 的 id 集合
- 长任务回调:
script_job:{job_id} — 一次性 secret(TTL 7 天,见 script_jobs.py)
2.2 MySQL 冷数据
- 表:
script_executions
results JSON:{ "<server_id>": { ... }, "_meta": { "events": [...] } }
- 刷盘后从 flush 队列移除;读接口优先 Redis,无 Redis 再读 MySQL
2.3 进度字段
- API 字段
progress: "done/total"(pending 计为未完成)
- 实现:
execution_progress_summary() in script_execution_store.py
3. 执行模式
3.1 短任务(默认)
- 主站
POST 子机 Agent /exec + wait: false → 得 pid,立即 patch_live 写入 Redis(phase: pending)
- 主站
POST /exec/wait 等待结束
- 用户点「停止」→ 主站
POST /exec/kill(需新版 Agent)
- 未升级 Agent:回退同步
/exec wait=true,无法中途停止
3.2 长任务(long_task: true)
- 命令存库前缀
[long_task]
script_jobs.build_long_task_command():nohup + 日志 /var/log/nexus-job/{job_id}.log + 结束 curl 主站
- 点火成功 →
phase: pending + job_id + started_at
- 子机
POST /api/agent/script-callback → 更新 results + WebSocket script_job_done
- 停止:kill
${LOG}.pid + 撤销 Redis job secret
4. HTTP API(JWT,除回调)
| 方法 |
路径 |
说明 |
| GET |
/api/scripts/exec-config |
batch_size, concurrency |
| POST |
/api/scripts/exec |
server_ids, command, timeout, long_task, credential_id? |
| GET |
/api/scripts/executions |
分页历史 limit, offset, status |
| GET |
/api/scripts/executions/{id} |
?fetch_logs=true 拉子机 tail 日志 |
| POST |
/api/scripts/executions/{id}/stop |
停止 pending |
| POST |
/api/scripts/executions/{id}/retry |
失败/未完成重试;新建 execution_id,响应含 parent_execution_id |
| POST |
/api/scripts/executions/{id}/mark-stuck |
人工标记卡住 |
| POST |
/api/agent/script-callback |
job_id, secret, server_id, exit_code(无 JWT;Redis 限速) |
4.1 安全与执行面
| 风险 |
现状 |
缓解 |
子机 subprocess_shell / 长任务 bash -c |
运维必需能力 |
仅 API Key 可调 /exec;主站 check_dangerous_command 拒绝危险模式;Agent 每次 exec 打 WARNING 审计日志 |
| 回调 HTTP 明文 |
生产配置错误 |
master_callback_url() 非 localhost 强制 https:// |
| 回调刷接口 |
无 JWT |
script_callback_rate:每 job 10/min、每 IP 60/min |
| 重试语义 |
每次 retry 新建记录 |
API 返回 parent_execution_id 关联原单;历史页按 id 追踪 |
子机应网络隔离:仅中心机 IP 可访问 Agent :8800。
5. 子机 Agent 接口
文件: web/agent/agent.py
| 方法 |
路径 |
说明 |
| POST |
/exec |
wait 默认 true;false 返回 pid |
| POST |
/exec/wait |
等待 pid |
| POST |
/exec/kill |
终止 pid |
部署:各子机更新 Agent 后重启;否则短任务 stop 不可用。
6. 后台任务
- 模块:
server/background/script_execution_flush.py
- 注册:
server/main.py → script_execution_flush_loop()(primary worker)
- 每 60s:
detect_stuck_executions() — 单台 pending 超 1h → server_auto_stuck;整单无更新 1h → auto_stuck
flush_pending_batch() — 最多 50 条写入 MySQL
7. 审计(audit_logs.action)
| action |
触发场景 |
execute_started |
创建执行记录 |
execute_command |
记录实际下发命令摘要 |
stop_execution |
用户停止 |
retry_execution |
用户重试 |
mark_stuck |
用户标记卡住 |
script_job_callback |
长任务子机回调 |
auto_stuck |
整单 1h 无 Redis 更新 |
server_auto_stuck |
单台 pending 超 1h |
前端筛选: web/app/audit.html 已增加上述选项。
8. 前端页面
| 页面 |
路径 |
功能 |
| 脚本库 |
web/app/scripts.html |
执行弹窗、分批、长任务勾选、页顶「批量执行状态」 |
| 执行历史 |
web/app/script-executions.html |
列表、进度、停止/重试/卡住、详情+日志、10s 轮询 |
| 侧栏 |
web/app/layout.js |
菜单项「执行历史」 |
页顶状态条按钮:刷新状态、状态+日志、停止、重试、标记卡住。
9. 涉及文件清单
后端
| 文件 |
职责 |
server/application/services/script_service.py |
分批执行、Agent 调用、stop/retry/list |
server/application/services/script_jobs.py |
长任务 nohup + job Redis |
server/application/services/script_job_callback.py |
回调合并结果 |
server/infrastructure/redis/script_execution_store.py |
Redis 热数据、刷盘队列、卡住检测、progress |
server/background/script_execution_flush.py |
60s 循环 |
server/infrastructure/database/script_repo.py |
list_recent, update_status |
server/api/scripts.py |
REST 路由 |
server/api/agent.py |
script-callback |
server/config.py |
SCRIPT_EXEC_BATCH_SIZE, SCRIPT_EXEC_CONCURRENCY |
server/main.py |
注册 flush 后台任务 |
前端 / Agent
| 文件 |
职责 |
web/app/scripts.html |
执行 UI + 状态条 |
web/app/script-executions.html |
历史页 |
web/app/layout.js |
侧栏 |
web/app/audit.html |
审计筛选 |
web/agent/agent.py |
exec / wait / kill |
10. 已知限制
- MySQL 视图比 Redis 最多晚 60s(直接查库时);页面轮询读 Redis 无影响。
- 短任务 stop 依赖子机 Agent 新版本。
- 自动卡住:单台按
started_at;整单按 updated_at(非单台日志活跃度)。
- 危险命令仍由
check_dangerous_command 拦截。
- 长任务依赖
NEXUS_API_BASE_URL 对子机可达,且生产须为 HTTPS(本地可用 http://localhost)。
- 重试会创建新的
script_executions.id;原单结果保留,通过 parent_execution_id 关联。
11. 测试清单(待执行)
12. 相关 Changelog