SOP — LSF 残余探测批量化(precheck 并入共享库批量 + 阶段0 主机内拼接/去 N²)¶
主题目录:
lsf_ins/lsf_probe_batch2_20261007/|执行日期:2026-10-07|类型:C 类(改实现体) 前序:lsf_probe_batch_20261007/SOP.md(第一阶段三处)、shared_precheck_batch_20261007/SOP.md(共享库底座)
1. 背景¶
前两轮之后,lsf_ins 仍有两条明显的效率尾巴(实测归因,2026-10-07):
--precheck共 18 条远端命令:其中 6 条来自LsfPreCheck.check_lsf_ports(域名检查, 每台一次pgrep,条件成立才再查ss)、6 条来自check_hosts_entries(自建批量)、 6 条来自共享库逐节点静态事实批量——后两项本可与第一项共用同一次往返。- 阶段0
prepare.probe()命令数最多:--drift一轮 327 条命令里阶段0 占比最高,根因两处: - 公钥采集是 N²:对每台主机、每个 peer 各下发一次
cat /root/.ssh/id_ed25519.pub || ssh-keygen || cat(6 台即 36 次), 而公钥是主机静态事实,每台一次即可; - 逐包探测:
for p in packages: rpm -q <p>每包一条命令(应用rpm -q自带的多包参数)。
ops-execution-efficiency 的口径:优先找「自带批量参数」的工具形式,而不是写循环;
同一事实只应探测一次。
2. 目标¶
| # | 目标 | 判据 |
|---|---|---|
| 1 | check_lsf_ports / check_hosts_entries 并入共享库的每主机批量 shell |
--precheck 远端命令 18 → 6;findings 不变 |
| 2 | 条件探测(LSF 未运行时才查 ss)不退化为无条件都查 |
拼接后仍带 if …; then …; else …; fi |
| 3 | prepare.probe():每主机一条 shell + 公钥每台一次 |
新旧 probe() 的 states 逐条一致;命令数大幅下降 |
| 4 | 判定语义完全不变 | --drift 模块级输出逐行一致、20 阶段无漂移 |
3. 前置条件¶
- 工程根
/home/rain/Documents/LSF/lsf_ins,deploy/lsf/.venv可用; - 到 6 台节点(
.23–.28)的 SSH 凭据沿用deploy/lsf/config/credentials.yaml(600); - 变更前完成 5.1 备份;验证以
--precheck/--drift为主(只读)。
4. 影响范围¶
| 项 | 内容 |
|---|---|
| 变更对象 | utils/precheck.py、stages/prepare.py(2 文件) |
| 改的是什么 | 只改只读探测的往返方式(逐条/逐项 → 每主机一条 shell;N² → N);判定表达式、消息文案、变更动作一律不动 |
| 是否停服 | 否;验证全程只读 |
| 窗口期 | 无要求 |
| 依赖 | 共享库 precheck_module 的 static_probes() 扩展点(shared_precheck_batch_20261007) |
5. 分步操作¶
5.1 备份(高危前置)¶
TOP=/home/rain/Documents/LSF/lsf_ins/lsf_probe_batch2_20261007
STAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$TOP/backup/lsf_ins/deploy/lsf/stages" "$TOP/backup/lsf_ins/deploy/lsf/utils"
cd /home/rain/Documents/LSF/lsf_ins/deploy/lsf
for f in stages/prepare.py utils/precheck.py; do
cp -a "$f" "$TOP/backup/lsf_ins/deploy/lsf/$f.bak.$STAMP"
done
ls -lR "$TOP/backup"
预期:2 个 .bak.20261007-195924。坑:必须在任何编辑之前完成。
5.2 LsfPreCheck.static_probes():把域名检查并入共享库批量¶
utils/precheck.py 覆盖 static_probes(),在共享库静态键之外追加两项;这样它们与
ssh/os/clock/... 共用同一条 shell,一次往返取回。
# 概念等价(每台一次往返;条件分支用哨兵回填,不退化为无条件查)
if pgrep -x lim >/dev/null || pgrep -x res >/dev/null \
|| pgrep -x mbatchd >/dev/null || pgrep -x sbatchd >/dev/null; then
echo __LSF_RUNNING__ # LSF 自身占用端口,属预期
else
ss -lntu | grep -E ':78(5[89]|6[0-9]|7[0-5])\b' || true
fi
cat /etc/hosts 2>/dev/null || true
坑:ss 只应在 LSF 未运行时执行(原逻辑如此);写成无条件会多发一条命令且改变判定口径。
check_lsf_ports 与 check_hosts_entries 改为读 self._probe(host, 键) 的缓存。
5.3 prepare.probe():每主机一条 shell + 公钥每台一次¶
stages/prepare.py 的 probe() 重写为「两阶段」:构造 plan → 一次批量取回 → 本地判定。
# 每台主机一条 shell(主机间并发):
rpm -q --qf '%{NAME}\n' <包1> <包2> … 2>/dev/null || true # ← 多包一次查询(原为逐包)
systemctl is-active firewalld 2>/dev/null || true
getenforce 2>/dev/null || true
grep -E '^SELINUX=' /etc/selinux/config 2>/dev/null || true
cat /root/.ssh/known_hosts 2>/dev/null || true
cat /root/.ssh/authorized_keys 2>/dev/null || true
cat /root/.ssh/id_ed25519.pub 2>/dev/null || ssh-keygen -q -t ed25519 -N '' -f /root/.ssh/id_ed25519 >/dev/null 2>&1; cat /root/.ssh/id_ed25519.pub 2>/dev/null
坑(必须留痕):
- 公钥是主机静态事实:原实现对每台主机遍历全部 peer 各取一次(N²);改为每台取一次、
再本地比对
authorized_keys。ssh-keygen(缺则生成)只随每台那次执行,幂等不变; rpm -q多包一次查询:--qf '%{NAME}\n'只列已安装包名,缺失者对 stderr 报错; 故「缺失 = 配置清单 − stdout 命中」,与原「逐包rpm -q成功即已安装」等价;- 整机不可达(rc=-1)时:包项按原语义记为
pending+ 全部包列入_missing_pkgs(原probe_state对连接层失败返回 unknown,调用方!= "done"即计 missing), 其余项记unknown——_overall与原实现一致; _collect_host_keys()(操作机本地ssh-keyscan)上提为循环外一次(原在每台主机的分支里 各调一次,即 N 次本机 keyscan);execute()里「为每台待改主机重新采集全部 peer 公钥」的 N² 也已上提为循环外一次。
5.4 离线自测(上机前)¶
cd /home/rain/Documents/LSF/lsf_ins/deploy/lsf
.venv/bin/python -m py_compile stages/prepare.py utils/precheck.py && echo COMPILE_OK
.venv/bin/python ../lsf_probe_batch2_20261007/selftest.py
预期:COMPILE_OK + LSF_RESIDUAL_SELFTEST_OK。自测用桩 ssh(同时服务「批量 shell」与
「逐条命令」两种形态),不连任何主机;其中第 3 组是核心:把 backup 版 prepare.py
以模块方式载入,与新版在同一份模拟状态下跑 probe(),states 逐条比对。
5.5 上机验证(只读)¶
cd /home/rain/Documents/LSF/lsf_ins/deploy/lsf
LOG=logs/lsf_deploy.log
for mode in "--precheck" "--drift"; do
S0=$(wc -l < "$LOG"); T0=$(date +%s.%N)
.venv/bin/python main.py --config config/lsf_deploy.yaml $mode > /tmp/out_$mode.txt 2>&1
T1=$(date +%s.%N)
echo "$mode wall=$(awk "BEGIN{printf \"%.1f\", $T1-$T0}")s cmds=$(tail -n +"$((S0+1))" "$LOG" | grep -c '命令执行完成')"
done
6. 验证¶
验证一:语义等价(核心)
- 离线:新版与 backup 版 prepare.probe() 在同一模拟状态下的 states 逐条完全一致
(覆盖 缺包 / firewalld active / SELinux Enforcing / selinux_config 不符 / known_hosts 缺 key /
公钥为空 / 整机不可达);
- 上机:--drift 的模块级输出逐行一致(差异仅报告文件名时间戳),rc=0、20 阶段全无漂移、
0 探测失败;--precheck 报告结论不变 —— BLOCK 0 项 / WARN 2 项 / INFO 54 项,
lsf_ports 仍报 INFO「LSF 守护进程运行中(端口为 LSF 自身占用)」。
(--precheck 输出仅有两处实时值差异:磁盘空闲 1085G→1084G、时钟偏移 0.000s↔0.001s,非语义变化。)
验证二:远端命令数与墙钟【已确认】
| 子命令 | 本轮前 | 本轮后 | 降幅 |
|---|---|---|---|
--precheck |
18 条 / 6.9 s | 6 条 / 1.6 s | 命令 -66.7%、墙钟 -76.8% |
--drift(20 阶段) |
327 条 / 131.8 s | 159 条 / 64.1 s | 命令 -51.4%、墙钟 -51.4% |
相对原始基线(三轮累计,--precheck):84 条 / 32.5 s → 6 条 / 1.6 s(命令 -92.9%、墙钟 -95.1%)。
验证三:离线命令数对照(4 台主机、同一模拟状态):旧 probe() 37 条 → 新 4 条(每台 1 条)。
验证四:幂等 —— --precheck 复跑仍 6 条、结论不变;--drift rc=0 无漂移。
失败判断:states 出现任何差异,或 --drift 出现漂移 / 探测失败,或 py_compile / 自测失败
→ 停止,按第 7 章回滚。
7. 回滚¶
- 备份位置:
lsf_probe_batch2_20261007/backup/lsf_ins/deploy/lsf/,台账见本主题backup/README.md。 - 回滚步骤(2 文件整份还原):
cd /home/rain/Documents/LSF/lsf_ins
BAK=lsf_probe_batch2_20261007/backup/lsf_ins/deploy/lsf
cd deploy/lsf
for f in stages/prepare.py utils/precheck.py; do cp -a "../../$BAK/$f".bak.* "$f"; done
- 回滚后验证:
py_compile+--drift仍 rc=0 无漂移(命令数回到 327 量级)。 - 回滚触发条件:任一 states 差异;
--drift出现漂移或探测失败;自测失败;命令数未下降。
8. 风险说明¶
| 风险 | 缓解 |
|---|---|
| 条件探测被写成无条件,导致多发命令且口径变化 | 用 if …; then echo __LSF_RUNNING__; else ss …; fi 保留分支;离线自测断言「含 if pgrep -x lim 且含哨兵」 |
| 拼接后逐条退出码失真 | prepare.probe() 走的是只看 stdout 的判定(firewalld/selinux/配置文件/known_hosts/authorized_keys),不依赖退出码;--drift 上机比对证实判定未变 |
rpm -q 多包形式与逐包判定不等价 |
--qf '%{NAME}\n' 只列已安装包名,「缺失 = 清单 − 命中」;离线自测含缺包用例并通过 |
公钥「每台一次」改变了 ssh-keygen 触发次数 |
该命令幂等(缺则生成、有则仅打印);每台仍需各自取一次,语义不变 |
| 整机不可达时条目状态与原实现不一致 | 已按原语义逐项对齐(包项 pending + 全 missing、其余 unknown),离线含 down 主机用例通过 |
附录 A 交付物清单¶
| 文件 | 说明 |
|---|---|
SOP.md |
本文件 |
selftest.py |
离线自测(3 组:lsf_ports 四分支、static_probes 覆盖、prepare 新旧逐条等价) |
backup/lsf_ins/deploy/lsf/** |
2 个改造前文件快照 |
backup/README.md |
备份台账 |
| 变更的工程文件 | deploy/lsf/utils/precheck.py、deploy/lsf/stages/prepare.py |
附录 B 仍可继续(未纳入本轮)¶
| 项 | 说明 |
|---|---|
| 其余阶段的探测循环 | node_env.py / cluster_up.py / lsf_install.py / user_sync.py / tune.py 等尚未逐个审查是否仍有逐条探测或 N² 结构 |
derive_system_disks()(lsblk) |
仍逐主机一条独立命令(结构化 pairs + 自带二级缓存),未纳入批量 |
| 其它工程 | samba_ins / kvm / harbor / eda_tools_ins / lustre_ins(缓存项) / vnc-server-deploy,模板见 lsf_probe_batch_20261007/SOP.md 附录 C |
来源:
/home/rain/Documents/LSF/lsf_ins/lsf_probe_batch2_20261007/SOP.md| 最后更新:2026-10-07 20:04(由build/sync.py同步,请勿在站点侧修改)