跳转至

SOP — LSF 部署器探测批量化(主机内拼一条 shell + 主机间并发)

主题目录:lsf_ins/lsf_probe_batch_20261007/|执行日期:2026-10-07|类型:C 类(改实现体) 参照实现:slurm_ins/sync_status_concurrency_20261007/(同法在该工程已验证:dry-run 墙钟 247.7 → 10.3 s)

1. 背景

轮次效率自查(2026-10-07)实测 LSF 部署器:远端命令 943 条(5 次运行合计)、其中同一主机重复下发可省 545 条 / 110 组, 判据要求「可省重复 = 0」未达标。归因两条:

  1. 逐条串行往返:每次探测都是独立的 ssh_executor.run(host, cmd) —— 同一主机的多条只读检查各建一次往返, 主机之间也是串行。最典型的 utils/precheck.py::check_hosts_entries 是 N² 次(每台节点 × 每个 peer 各一次 grep)。
  2. 重复探测同一事实:同一挂载状态在 pre_check / sync_status / validate 里各查一遍 (nfs_share.probe 每挂载项 3 条命令、每调用一次重查全套),submit_node.probe 每台 6 条命令。

ops-execution-efficiency 的口径是「要降的是会话数(握手/往返数),不是命令条数」, 且「一次调用传多条命令」不算合并(exec_linux 内部逐条 run_cmd,只省建连)—— 真省往返只能把同一主机的多条命令拼成一条 shell。slurm_ins 已完成该改造(_remote_query_many + _kv_script + run_per_host), 本工程(lsf_ins)此前未做。

2. 目标

# 目标 判据
1 同一主机的多条只读探测合并为一次往返(拼成一段 shell,echo '@@键' 分段回填) 命令数下降;判定结果与非合并版逐条一致
2 探测循环主机间并发、主机内串行 墙钟下降
3 三态语义、变更清单逐条不变 --precheck 报告结论一致;dry-run 模块级输出无差异
4 沉淀可复用的推广模板(其它工程同法套用) 见附录 C

3. 前置条件

  • 工程根 /home/rain/Documents/LSF/lsf_ins,deploy/lsf/.venv 可用(.pth 挂载共享库 /home/rain/py_public_modules);
  • 到 6 台节点(.23–.28)的 SSH 凭据沿用 deploy/lsf/config/credentials.yaml(600,凭据不入文档);
  • 变更前完成 5.1 备份;验证全程 --dry-run / --precheck(只读)。

4. 影响范围

项 内容
变更对象 utils/lsf_config.py(新增助手)、utils/precheck.py、stages/nfs_share.py、stages/submit_node.py(共 4 文件)
改的是什么 只改只读探测的往返方式(逐条 → 每主机一条 shell + 主机间并发);判定规则、变更动作、execute/rollback 一律不动(变更类命令仍逐条、串行)
是否停服 否。验证全程只读
窗口期 无要求;集群无在跑作业
并发影响 每台主机一次连接、主机间并发 ≤ 节点数;不触发 sshd MaxStartups

5. 分步操作

5.1 备份(高危前置)

cd /home/rain/Documents/LSF/lsf_ins
TOP=lsf_probe_batch_20261007
STAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$TOP/backup/lsf_ins/deploy/lsf/utils" "$TOP/backup/lsf_ins/deploy/lsf/stages"
cd deploy/lsf
for f in utils/lsf_config.py utils/precheck.py stages/nfs_share.py stages/submit_node.py; do
  cp -a "$f" "../../$TOP/backup/lsf_ins/deploy/lsf/$f.bak.$STAMP"
done
ls -l "../../$TOP/backup/lsf_ins/deploy/lsf/utils" "../../$TOP/backup/lsf_ins/deploy/lsf/stages"

预期:4 个 .bak.20261007-091639。坑:必须在任何编辑之前完成,回滚依赖这份快照。 实现体:无需(纯 shell 前置动作)。

5.2 新增助手 probe_hosts_kv(基础设施)

在 utils/lsf_config.py 末尾新增 probe_hosts_kv(ssh, plan, max_workers=8, timeout=30): 入参 plan = {host: {键: shell 命令}},返回 {host: {键: 原始输出}};某主机 SSH 层整体失败(rc=-1)时该主机值为 None。 内部复用共享库 BaseModule._kv_script() / _parse_kv() 生成与解析分段标记,主机间用 ThreadPoolExecutor 并发。

# 概念等价(Python 侧实现,非 shell):每台一条脚本,主机之间并发
# for host in plan:                       ← 并发(线程池,上限 min(8, 主机数))
#   ssh <host> "echo '@@k1'; <cmd1>; echo '@@k2'; <cmd2>"     ← 一次往返

坑(必须留痕): - exec_linux(host, [c1, c2]) 不等于合并:它内部逐条 run_cmd,只省建连——本助手走的是拼一条 shell 的路径; - 助手只用于只读探测:含变更动作的循环不得并发; - 返回语义沿用「rc=-1 = SSH 层不可用」:调用方据此判 unknown(三态不折平)。

5.3 check_hosts_entries:N² → N

utils/precheck.py:原「每台节点 × 每个 peer 各一次 grep -cE ...」改为「每台一次取回 /etc/hosts 全文 + 本地正则判定」, 判定函数 _hosts_has_name() 与原 grep 式逐行等价(配套自测用真实 grep 逐例对照)。

# 改前(N² 次往返):
ssh <node> "grep -cE '(^|[[:space:]])<peer>([[:space:]]|$)' /etc/hosts || true"   # × 每个 peer
# 改后(每台 1 次往返):
ssh <node> "echo '@@hosts'; cat /etc/hosts 2>/dev/null || true"

预期:--precheck 报告的 hosts_entries 条目级别与文案不变(本机为 INFO「N 节点映射齐全」)。 实现体:utils/precheck.py::check_hosts_entries。

5.4 nfs_share.probe:每主机 1 次往返

stages/nfs_share.py:原本「每挂载项 3 条命令(SOURCE / OPTIONS / fstab)× 每台」改为「每台一条 shell 取回全部挂载项」; unknown 分支改为「整机 rc=-1 → 全部挂载项 unknown」(与逐条版语义一致)。

ssh <host> "echo '@@mount_lsf'; findmnt -n -o SOURCE /data/nfs/lsf 2>/dev/null || true
echo '@@opts_lsf';  findmnt -n -o OPTIONS /data/nfs/lsf 2>/dev/null || true
echo '@@fstab_lsf'; grep -E '^[^#]*<server>:<export>' /etc/fstab 2>/dev/null || true
echo '@@mount_home'; findmnt -n -o SOURCE /home 2>/dev/null || true
echo '@@opts_home';  findmnt -n -o OPTIONS /home 2>/dev/null || true
echo '@@fstab_home'; grep -E '^[^#]*<server>:<export>' /etc/fstab 2>/dev/null || true"

预期:dry-run 阶段1 的「状态: skipped」与变更清单不变。 实现体:stages/nfs_share.py::probe。

5.5 submit_node.probe:每台 6 次往返 → 1 次

stages/submit_node.py:原每台 submit 节点 6 条命令(挂载 / hostsetup / profile.d / sudoers / 守护 / 冒烟标记) 拼成一段 shell 一次取回;sudoers 项的 r_ref(集群侧 eauth key)仍单独查一次(主控)。

ssh <submit-host> "echo '@@lsf_mount'; findmnt -n -o SOURCE <mount> 2>/dev/null || true
echo '@@hostsetup'; systemctl cat lsfd.service 2>/dev/null | grep -qF <machdep>/etc/lsf_daemons && echo yes || echo no
echo '@@profile_d'; cat /etc/profile.d/lsf.sh 2>/dev/null || true
echo '@@sudoers';   stat -c '%a' /etc/lsf.sudoers 2>/dev/null; grep '^LSF_EAUTH_KEY=' /etc/lsf.sudoers 2>/dev/null | md5sum | cut -d' ' -f1
echo '@@daemons';   for d in lim res sbatchd; do pgrep -x \$d >/dev/null || echo \$d; done
echo '@@smoke';     test -f <marker> && echo yes || echo no"

预期:dry-run 阶段14 的变更清单与逐条版一致。 实现体:stages/submit_node.py::probe。

5.6 离线自测(上机前)

cd /home/rain/Documents/LSF/lsf_ins/deploy/lsf
.venv/bin/python -m py_compile utils/lsf_config.py utils/precheck.py stages/nfs_share.py stages/submit_node.py && echo COMPILE_OK
.venv/bin/python ../lsf_probe_batch_20261007/selftest.py

预期:COMPILE_OK + SELFTEST_OK(4 组断言:助手的分组/并发/键值映射/rc=-1→None、_hosts_has_name 与真实 grep 逐例对照、 check_hosts_entries 与 nfs_share.probe 端到端三态)。自测用桩 ssh(按 @@键 回填),不连任何主机。

5.7 上机对照验证(改造前 vs 改造后)

cd /home/rain/Documents/LSF/lsf_ins
bash lsf_probe_batch_20261007/verify_batch.sh

脚本把项目文件临时替换为 5.1 的旧版 → 跑 3 个子命令 → 恢复新版 → 再跑一遍 → 对照命令数与墙钟(结束时 trap 兜底恢复为新版)。

6. 验证

验证一:语义等价(核心判据) —— 剔除逐条命令日志后,三次运行的模块级输出逐行一致; --precheck 落盘报告结论一致:

precheck_20261007-092022.txt(旧)  - 结论:BLOCK 0 项 / WARN 2 项 / INFO 54 项
precheck_20261007-092216.txt(新)  - 结论:BLOCK 0 项 / WARN 2 项 / INFO 54 项
(另有 4 份报告,新旧各 3 份,结论与逐条 findings 全部相同)

唯一差异是报告文件名里的时间戳。

验证二:远端命令数与墙钟(同一命令、同一集群,改造前后各一遍)【已确认】

子命令 命令数(旧 → 新) 省 墙钟(旧 → 新) 降幅
--precheck 84 → 54 30(-35.7%) 32.5 s → 19.0 s -41.5%
--stage nfs --dry-run 138 → 66 72(-52.2%) 52.1 s → 22.1 s -57.6%
--stage submit --dry-run 103 → 68 35(-34.0%) 42.9 s → 25.4 s -40.8%

验证三:离线自测 4 组全过(见 5.6)。

验证四:幂等 —— 三次 dry-run 的受影响阶段均报「状态: skipped」(零变更),未引入新的待执行项。

失败判断:任一处模块级输出出现非时间戳差异,或 py_compile / selftest.py 失败 → 停止,按第 7 章回滚。

7. 回滚

  • 备份位置:lsf_probe_batch_20261007/backup/lsf_ins/deploy/lsf/,台账见 lsf_probe_batch_20261007/backup/README.md。
  • 回滚步骤(4 个文件整份还原为改前快照):
cd /home/rain/Documents/LSF/lsf_ins
BAK=lsf_probe_batch_20261007/backup/lsf_ins/deploy/lsf
cd deploy/lsf
for f in utils/lsf_config.py utils/precheck.py stages/nfs_share.py stages/submit_node.py; do
  cp -a "../../$BAK/$f".bak.* "$f"
done
  • 回滚后验证:py_compile 通过 + 跑一次 --precheck,确认结论仍为 BLOCK 0 / WARN 2 / INFO 54。
  • 回滚触发条件(任一达标即回滚,不靠临场判断):① 任一子命令模块级输出与基线不一致;② --precheck 结论变化; ③ 出现连接类异常(Transport 失效、命令超时激增);④ dry-run 出现新增待执行项。

8. 风险说明

风险 缓解
拼接后单条命令的退出码语义变化(整段 shell 只返回最后一个命令的码) 纳入批量的命令全部是 || true 收尾或以 stdout 判定;「SSH 层失败」仍以整段 rc=-1 判定,unknown 语义不变
主机并发触发 sshd MaxStartups 被拒 并发上限 min(8, 主机数),本集群 6 台
判定结果被改动 上机对照逐条比对模块级输出 + --precheck findings,已证实一致
拼接命令的 stdout 变长(cat /etc/hosts 全文)进入日志 ssh_executor 已把单条 stdout 截到 200 字符;每次运行约 +1 KB,可接受(换取的是一次往返)
只读探测被误用于变更循环 助手文档与 SOP 均标注「仅只读」;变更类命令(mount/awk 改 fstab/systemctl)保持逐条串行

附录 A 交付物清单

文件 说明
SOP.md 本文件
selftest.py 离线自测(4 组断言,桩 ssh 不连主机)
verify_batch.sh 改造前/后对照脚本(命令数 + 墙钟 + 模块级输出比对)
backup/lsf_ins/deploy/lsf/** 4 个改造前文件快照
backup/README.md 备份台账
变更的工程文件 deploy/lsf/utils/lsf_config.py、utils/precheck.py、stages/nfs_share.py、stages/submit_node.py

附录 B 未纳入本轮(后续项)

项 说明 预估收益
共享库 precheck_module 逐节点检查批量化 check_ssh/check_os/check_clock/check_root/check_selinux/check_scheduler/check_disk_space 各是一次往返,6 台即 ~48 次/运行;改为「每台一条 shell + 主机间并发」 已完成,见 ../shared_precheck_batch_20261007/SOP.md:本工程 --precheck 54 → 18 次、墙钟 19.0 → 6.9 s;共享库改动,全部挂载工程同步受益
stages/node_env.py / cluster_up.py / lsf_install.py 的探测循环 尚未逐个审查是否仍有逐条探测 待测
变更类命令的合并 不做:变更必须原子、串行、可失败即停 —

附录 C 推广模板(其它工程同法套用)

  1. 先测量:python3 <deployment-tool-engineering skill>/references/scripts/remote_cmd_profile.py --log <日志>, 看「重复探测表」与「按阶段/按主机」分布——重复过半即须回到「一次探测全程复用」重做设计;
  2. 找热点:优先处理「循环里套循环」(N²)、「同一事实被多阶段各查一遍」、「一台主机 6+ 次往返」三类;
  3. 加助手:把「同一主机多条只读命令拼一段 shell + 主机间并发」做成工程内一处助手 (若第二个工程也需要,按 deployment-tool-engineering 的「上移判据」移入共享库);
  4. 改调用点:只改只读探测循环;判定表达式保持原样,三态(done/pending/unknown)不折平;
  5. 三件套验证:py_compile + 离线自测(桩 ssh)+ 上机 dry-run 前后对照(模块级输出逐行一致 + 命令数/墙钟下降);
  6. 留痕:出本主题 SOP.md 与 backup/README.md,回填实测数字。

来源:/home/rain/Documents/LSF/lsf_ins/lsf_probe_batch_20261007/SOP.md | 最后更新:2026-10-07 10:29(由 build/sync.py 同步,请勿在站点侧修改)