811eb97fbf
Rename WSL helpers to Linux-native scripts, expose Redis in docker-compose, prefer .venv tools in pre_deploy_check, and record 2026-06-04 audit waves.
8.9 KiB
8.9 KiB
脚本批量执行平台 — 技术说明(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. 架构与数据流
flowchart LR
UI[scripts.html / script-executions.html]
API[POST /api/scripts/exec]
SVC[ScriptService]
Redis[(Redis script_exec:id)]
Flush[script_execution_flush 60s]
MySQL[(script_executions)]
Agent[子机 Agent :8800]
CB[POST /api/agent/script-callback]
UI --> API --> SVC
SVC --> Redis
SVC --> Agent
Agent -->|长任务结束 curl| CB --> Redis
Flush --> MySQL
UI -->|GET executions/id| Redis
UI -.->|历史列表可能滞后| MySQL
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 resultsJSON:{ "<server_id>": { ... }, "_meta": { "events": [...] } }- 刷盘后从 flush 队列移除;读接口优先 Redis,无 Redis 再读 MySQL
2.3 进度字段
- API 字段
progress:"done/total"(pending计为未完成) - 实现:
execution_progress_summary()inscript_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:回退同步
/execwait=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 + WebSocketscript_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_stuckflush_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. 测试清单(待执行)
- T-SE1: 选 3 台短任务,页顶进度
3/3,审计有execute_started - T-SE2: 选 60 台,确认分 2 批、合并一条状态卡
- T-SE3: 长任务 + 配置
NEXUS_API_BASE_URL,回调后状态变完成、WS 通知 - T-SE4: 长任务执行中「停止」,子机进程结束
- T-SE5: 新版 Agent 短任务执行中「停止」
- T-SE6: 执行历史页停止/重试/状态+日志
- T-SE7: 等待或调低
STUCK_NO_PROGRESS_SECONDS验证server_auto_stuck/auto_stuck - T-SE8: Redis 有 key 时 MySQL 在 60s 内出现对应行
12. 相关 Changelog
| 文档 | 内容 |
|---|---|
| 2026-05-22-script-execution-platform.md | 本次交付总览与文件变更索引 |
| 2026-05-22-script-exec-batching.md | 内置分批与 exec-config |
| 2026-05-22-script-job-callback.md | 长任务回调与 Redis 刷盘摘要 |