Files
wecom_it_smart_desk/docs/03-技术架构/03-技术分析/技术分析-jp-webcli自动化部署能力分析.md
T

13 KiB
Raw Blame History

JP-webcli 自动化部署工具 — 能力、限制与优化空间分析

分析时间:2026-06-25 | 基于 v7/v8/v8b/v9_cdp/v10_persistent 全版本代码审查


一、现有能力全景

1.1 核心自动化能力(已验证可用)

能力 实现方式 状态 版本
自动登录 Playwright 填写用户名/密码 稳定 v6+
OTP 双因素认证 pyotp 本地生成 TOTP 自动填充 v4+
资产导航 DOM 选择器定位目标行 支持模糊匹配 v6+
Web CLI 连接 点击连接按钮 → 处理 Luna dialog 含轮询重试 v6+
命令执行 keyboard.type() 逐字输入 含 delay 防丢字 v6+
SFTP 文件上传 set_input_files() 文件管理器 分块(8 chunk v7+
base64 大文件传输 分段 echo + base64 -d 还原 250 字符/段 v8b+
截图留存 page.screenshot() PNG 格式 v3+
会话复用 persistent_context / CDP connect v10 v10
轮询等待(替代死等) 检测 prompt 字符/dialog/terminal 40s → 待命 v10.15
分阶段部署 Part A(传输) + B(执行) + C(验证) 脚本编排 v5+

1.2 已验证的部署场景

场景 日期 成果
Hotfix #116 扫码获取 06-22 auth_qrcode.py 部署成功
Hotfix #120 扫码自动确认 06-23 29KB qrcode_service.py + 8 chunk SFTP
Hotfix #48 Nginx Upstream 06-24 配置修复 + HTTPS 验证
Hotfix SSO 单点登录 06-25 config.py + 环境变量注入

二、当前限制(按严重程度排序)

🔴 P0 — 阻塞性问题

2.1 xterm.js 输出无法结构化捕获(当前最紧急

现状:所有版本(v6v10)均依赖 截图 作为命令执行结果的唯一验证手段。

根因xterm.js 将终端内容渲染到 <canvas> 元素,终端缓冲区存储在 JavaScript 内部对象中:

xterm.js 架构:
  ┌─────────────────────────────┐
  │  DOM: <div class="xterm">   │
  │    ├── .xterm-viewport      │  ← 可滚动视口
  │    ├── .xterm-screen        │  ← 屏幕容器
  │    │   └── <canvas>         │  ← ★ 内容渲染到这里
  │    └── .xterm-accessibility │  ← 无障碍层(可能为空)
  └─────────────────────────────┘
  
  terminal.buffer.active        ← ★ 真正的文本在 JS 内存中
  ├── .baseY / .viewportY       ← 滚动位置
  ├── .length                   ← 总行数
  └── .getLine(y)               ← 按行获取
      └── .translateToString()  ← 转为可见文本

当前所有版本的 "输出检测" 方法:

方法 代码 捕获到的是什么
page.evaluate("document.body.innerText") v10.15 ⚠️ DOM 文本(不含 canvas 内容)
page.screenshot() 所有版本 ⚠️ 二进制图片,不可解析
prompt 正则匹配 v10.15 ⚠️ 只能判定"命令结束了",不能拿到输出

导致的失败模式

  1. Hotfix #120 的 18 个脚本分裂part1 → part2 → diag → verify → sub → tail → view 中,大量 "查看日志" 脚本 (diag/log/tail/view) 的唯一目的是在终端执行 cat/tail/curl,然后人工查看截图确认结果。如果能结构化捕获输出,这些脚本可以合并为带条件判断的单一流程。

  2. 无法自动判断部署成功与否run_v7_v5.pyproc.returncode 只能判断 Playwright 进程是否正常退出,无法判断远程服务器上的 docker restart 是否成功、curl 是否返回预期响应。

  3. 无退出码捕获:执行 docker ps && echo $? 的结果在 canvas 里,脚本只能"相信"它成功了。

  4. 日志无法结构化存储:每次部署的输出是截图(PNG),而非文本。无法 grep、无法 diff、无法自动对比两次部署的差异。

  5. 截图驱动的不稳定性

    • 命令输出超过一屏时,截图只看到最后一段
    • 颜色/样式变化可能被误读为错误
    • 网络延迟导致截图时机窗口不可控

2.2 无 headless 模式(依赖 Chrome 可见窗口)

现状v10 明文注释"默认非 headless 模式(JumpServer 可能检测 headless 并拒绝)"。

影响

  • 无法在无 GUI 的服务器上运行
  • 必须保持 Chrome 窗口可见(占用桌面空间)
  • 无法与用户其他 Chrome 使用并行

2.3 单实例限制

  • 同一时间只能运行一个 Playwright 实例
  • 多个部署任务必须串行
  • persistent_context 和 CDP connect 互斥

🟡 P1 — 重要限制

限制 详情 影响
长命令输入易截断 单次 keyboard.type ~20KB 安全上限,超过需分块 29KB 文件需 8 chunk
依赖 Windows Chrome Playwright channel="chrome" 无法在 Linux 服务器运行
磁盘占用大 Playwright + Chromium ~500MB 多实例部署开销大
无命令队列机制 没有批量执行 + 结果收集 每次部署需人工编排
OTP 需本地密钥文件 依赖 otp_secret.key 换机器需重新配置
错误恢复依赖人工 失败后无自动重试策略 夜间部署需人工值守
硬编码路径 v7 中 V7/V7_DIR 路径硬编码 换环境需修改代码

🟢 P2 — 改进空间

限制 详情
部署包 >500MB 未迁移 deploy-server/ 目录
无企微通知集成 部署结果需手动确认
无 CLI 参数标准 v7/v8/v9/v10 参数不统一
无部署历史数据库 每次部署成功/失败无结构化记录

三、优化空间(按 ROI 排序)

🔴 P0 — 紧急优化

3.1 xterm.js 结构化输出捕获 最高优先级

这是从 "截图验证" → "程序化验证" 的关键跃迁。

实现方案:通过 page.evaluate() 直接访问 xterm.js 的内部 buffer

# 方案 A:读取 xterm.js 内部 buffer(推荐)
def capture_terminal_output(page):
    """从 xterm.js 的 terminal.buffer 中提取全部可见文本"""
    return page.evaluate("""() => {
        // JumpServer Luna 将 xterm 实例挂载在全局或 DOM 属性上
        // 方式1: 通过 xterm 的全局引用
        if (window.term) {
            const term = window.term;
            const buffer = term.buffer.active;
            const lines = [];
            for (let i = 0; i < buffer.length; i++) {
                const line = buffer.getLine(i);
                if (line) {
                    lines.push(line.translateToString());
                }
            }
            return lines.join('\\n');
        }
        // 方式2: 通过 xterm 的 DOM 属性(如 data-xterm 或 __vue__
        // 方式3: 遍历 window 对象找 xterm 实例
        for (const key of Object.keys(window)) {
            try {
                const obj = window[key];
                if (obj && obj.buffer && obj.buffer.active) {
                    const buffer = obj.buffer.active;
                    const lines = [];
                    for (let i = 0; i < buffer.length; i++) {
                        const line = buffer.getLine(i);
                        if (line) lines.push(line.translateToString());
                    }
                    return lines.join('\\n');
                }
            } catch(e) {}
        }
        return null;
    }""")

# 方案 B:利用 xterm.js 的无障碍 DOM 层(如果开启)
def capture_via_a11y(page):
    """xterm.js 可选地把内容同步到 .xterm-accessibility 层"""
    return page.evaluate("""() => {
        const a11y = document.querySelector('.xterm-accessibility');
        if (a11y) {
            const children = a11y.querySelectorAll('[role="presentation"]');
            return Array.from(children).map(el => el.textContent).join('\\n');
        }
        return null;
    }""")

预期收益

改进项 当前状态 优化后
命令输出获取 截图(PNG 二进制) 结构化文本
部署成功率判断 人工看截图 自动 grep exit code / 关键字
日志存储 PNG 图片 文本(可 diff、可搜索)
脚本数量 Hotfix #120 需 18 个 可合并为 3-5 个带条件分支
错误诊断速度 人工翻截图 自动提取错误行
CI/CD 集成 不可行 可行(输出可解析)

实施步骤

  1. 探测阶段:写一个 probe_xterm_buffer.py,在连上终端后执行 page.evaluate(),尝试多种方式找到 xterm 实例引用
  2. 验证阶段:对比 capture_terminal_output() 的输出和截图内容是否一致
  3. 集成阶段:修改 send_terminal_command()send_and_capture(),返回结构化文本
  4. 高级阶段:利用 buffer 实现增量读取(只读新增行),避免重复传输全部内容

风险JumpServer Luna 可能对 xterm 实例做了封装/私有化。需要先探测具体引用路径。

3.2 命令执行结果自动判定

依赖 3.1 的输出,实现:

def execute_and_verify(terminal_page, command, expected_pattern=None, timeout=60):
    """执行命令并自动判定结果"""
    send_command(terminal_page, command)
    output = wait_for_prompt_and_capture(terminal_page, timeout)
    
    # 自动提取退出码
    exit_code = extract_exit_code(output)  # 从 "$?=0" 或显式 echo $?
    
    # 自动匹配预期模式
    if expected_pattern:
        matched = re.search(expected_pattern, output)
    
    return CommandResult(
        output=output,
        exit_code=exit_code,
        matched=matched,
        screenshot=screenshot(terminal_page)
    )

🟡 P1 — 高价值优化

3.3 部署流水线引擎

将当前的手动编排的 Phase 1/2/3 流程抽象为通用引擎:

class DeployPipeline:
    stages = [
        Stage("prepare", commands=[...], verify="docker ps | grep wecom"),
        Stage("upload", sftp_files=[...], verify="ls -la /tmp/"),
        Stage("deploy", commands=[...], verify="curl -s localhost/api/health"),
        Stage("verify", commands=[...], verify="grep 'started' /var/log/app.log"),
    ]
    
    def run(self):
        for stage in self.stages:
            result = self.execute_stage(stage)
            if not result.ok:
                self.rollback()
                self.notify_failure(stage, result)
                return
        self.notify_success()

3.4 headless 模式适配

探索 JumpServer 反 headless 检测的具体方式,针对性绕过:

  • 添加 --disable-blink-features=AutomationControlled
  • 注入 JS 覆盖 navigator.webdriver
  • 使用 stealth 模式的 Playwright 配置

3.5 企微通知集成

部署完成后自动发送企微消息:

✅ Hotfix #XXX 部署成功
   目标: 10.90.5.110
   耗时: 8m 23s
   验证: curl 返回 200 OK
   日志: webcli_output/v10_result.txt

🟢 P2 — 长期优化

优化项 说明 依赖
SSH Key 认证(v17 绕过 OTP 流程,实现无人值守 JumpServer 管理员配置
API Token 方式 直接通过 Koko WebSocket 通信 JumpServer API 权限
部署包 pip 化 pip install jumpserver-webcli 路径去硬编码
Docker 化执行环境 Chrome + Playwright 容器化 headless 模式
部署历史数据库 SQLite 记录每次部署 输出结构化捕获
回滚自动化 失败自动回滚到上一个版本 部署流水线引擎

四、技术路线优先级矩阵

                     紧急度
                    高 ●●● │ 低 ●○○
                ┌──────────┼──────────┐
          高 ●●●│ xterm.js │ 部署流水 │
                │ 输出捕获  │ 线引擎   │
 影响面         ├──────────┼──────────┤
                │ headless │ SSH Key  │
          低 ●○○│ 适配     │ 认证     │
                └──────────┴──────────┘

五、总结

JP-webcli 自动化工具已经通过了 4 次生产部署验证,核心链路(登录 → 导航 → 命令执行 → 文件传输)稳定可用。当前最大的瓶颈是 xterm.js 输出无法结构化捕获,导致:

  1. 所有验证依赖截图(不可解析)
  2. 部署成功/失败无法自动判定
  3. 脚本过多(单个 hotfix 分裂为 18 个)
  4. 无法集成 CI/CD

建议立即启动 xterm.js buffer 探测(估计 1-2 小时),确认 JumpServer Luna 中 xterm 实例的引用路径后,一周内完成 send_and_capture() 改造。这将使自动化覆盖率从当前的 ~40% 提升到 ~90%。