跳转至

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):

  1. --precheck 共 18 条远端命令:其中 6 条来自 LsfPreCheck.check_lsf_ports(域名检查, 每台一次 pgrep,条件成立才再查 ss)、6 条来自 check_hosts_entries(自建批量)、 6 条来自共享库逐节点静态事实批量——后两项本可与第一项共用同一次往返。
  2. 阶段0 prepare.probe() 命令数最多:--drift 一轮 327 条命令里阶段0 占比最高,根因两处:
  3. 公钥采集是 N²:对每台主机、每个 peer 各下发一次 cat /root/.ssh/id_ed25519.pub || ssh-keygen || cat(6 台即 36 次), 而公钥是主机静态事实,每台一次即可;
  4. 逐包探测: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 同步,请勿在站点侧修改)