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」未达标。归因两条:
- 逐条串行往返:每次探测都是独立的
ssh_executor.run(host, cmd)—— 同一主机的多条只读检查各建一次往返, 主机之间也是串行。最典型的utils/precheck.py::check_hosts_entries是 N² 次(每台节点 × 每个 peer 各一次grep)。 - 重复探测同一事实:同一挂载状态在
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 推广模板(其它工程同法套用)¶
- 先测量:
python3 <deployment-tool-engineering skill>/references/scripts/remote_cmd_profile.py --log <日志>, 看「重复探测表」与「按阶段/按主机」分布——重复过半即须回到「一次探测全程复用」重做设计; - 找热点:优先处理「循环里套循环」(N²)、「同一事实被多阶段各查一遍」、「一台主机 6+ 次往返」三类;
- 加助手:把「同一主机多条只读命令拼一段 shell + 主机间并发」做成工程内一处助手
(若第二个工程也需要,按
deployment-tool-engineering的「上移判据」移入共享库); - 改调用点:只改只读探测循环;判定表达式保持原样,三态(done/pending/unknown)不折平;
- 三件套验证:
py_compile+ 离线自测(桩 ssh)+ 上机 dry-run 前后对照(模块级输出逐行一致 + 命令数/墙钟下降); - 留痕:出本主题
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同步,请勿在站点侧修改)