跳转至

SOP — 共享库预检查批量化(节点静态事实:每主机一次往返 + 进程内缓存)

主题目录:lsf_ins/shared_precheck_batch_20261007/|执行日期:2026-10-07|类型:C 类(改共享库实现体) 上游:lsf_probe_batch_20261007/SOP.md(工程侧三处探测批量化;其附录 B 列出的「待办」即本次)

1. 背景

上一轮把 lsf_ins 工程内的三处探测批量化后,残留的「可省重复」全部来自共享库:

  • precheck_module.PreCheckBase 的逐节点检查(check_ssh / check_os / check_clock / check_firewalld / check_scheduler / check_root / check_selinux / check_disk_space / check_kernel) 各发一条独立 SSH,6 台节点即 ~48 次往返/运行;
  • 实测(lsf_ins):--precheck 57 次命令中 51 次是这些逐条探测,重复组全部落在此处。

ops-execution-efficiency 的口径:省的是会话数(握手数);同一主机的多条只读检查必须 拼成一条 shell(「一次调用传多条命令」不算合并)。

共享库是全部挂载工程的公共底座——改一处,8 个使用 PreCheckBase 的工程同时受益 (lsf_ins、lustre_ins、services/harbor、k8s_ins/env-monitor-deploy、eda_tools_ins 的 ic617_replay / pdk_integration / ic25_install / rocky9_eda_compat)。

2. 目标

# 目标 判据
1 节点静态事实每台一次往返取回(拼一段 shell,@@键 + @@rc_键=N 分段) 命令数下降;每台恰好一次调用
2 同一次运行内进程内缓存复用,跨检查项/跨阶段不重探 首次之后零新增调用
3 不改公开签名、不改判定口径,调用点零改动 公开方法签名逐个比对无丢失/变更;findings 与逐条版逐条一致
4 逐条退出码不失真(check_ssh/check_root 依赖退出码) 每段命令后紧跟 echo "@@rc_键=$?"

3. 前置条件

  • 共享库 /home/rain/py_public_modules/precheck_module.py 可写;任一挂载工程 venv 可用(本例用 deploy/lsf/.venv);
  • 到验证目标的 SSH 凭据(本例 LSF 6 台 .23–.28,沿用 config/lsf_deploy.yaml);
  • 变更前完成 5.1 备份;验证以 --precheck / --dry-run 为主(只读)。

4. 影响范围

项 内容
变更对象 /home/rain/py_public_modules/precheck_module.py(单文件)
改的是什么 check_* 的探测方式:self._ssh.run(...) → self._probe(host, 键)(批量缓存优先);判定逻辑、消息文案、findings 结构一律不动
影响面 8 个使用 PreCheckBase 的工程;纯新增方法,签名与返回值语义不变
是否停服 否;验证全程只读
窗口期 无要求
默认行为变化 首次探测会把配置内全部节点一次性批量取回(auto_prefetch_all = True);子类置 False 可退化为「只探测被检查的那台」

5. 分步操作

5.1 备份(高危前置)

TOP=/home/rain/Documents/LSF/lsf_ins/shared_precheck_batch_20261007
STAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$TOP/backup/py_public_modules"
cp -a /home/rain/py_public_modules/precheck_module.py \
      "$TOP/backup/py_public_modules/precheck_module.py.bak.$STAMP"
ls -l "$TOP/backup/py_public_modules"

预期:1 个 .bak.20261007-102237。坑:必须在任何编辑之前完成;共享库未纳入独立版本控制, 这份快照是唯一回滚依据。

5.2 探测命令与拼装/解析(新增,模块级)

在 MIN_PYTHON 之后新增:静态命令常量(CMD_OS / CMD_CLOCK / … 只此一处定义)、 cmd_disk_space(path)、build_probe_shell(probes)、parse_probe_output(raw, keys)。

# 概念等价(Python 生成、远端执行):
# echo '@@os'
# cat /etc/os-release 2>/dev/null | grep -E '^(ID|VERSION_ID|PRETTY_NAME)=' ; uname -m
# echo "@@rc_os=$?"
# echo '@@clock'
# chronyc tracking 2>/dev/null | head -20 || ntpq -pn 2>/dev/null | head -5
# echo "@@rc_clock=$?"

坑: - $? 必须紧跟在被探测命令之后且放在双引号里(单引号不展开);每段一条, 这样批量执行后逐条退出码仍可还原(整段 shell 的返回码恒为最后一个 echo 的 0,不参与判定); - 键名可含 : / /(如 disk_space:/var),标记名直接拼接即可。

5.3 PreCheckBase:缓存 + 批量取回 + 统一入口

新增(实现体:precheck_module.py 的「节点侧静态事实」段):

  • __init__ 增加 _probe_cache({主机: {键: (退出码, stdout)}})、_prefetched_hosts、_auto_prefetch_done;
  • static_probes():静态键 → 命令;被 skip_checks 跳过或开关关闭的项不纳入(不发无用命令);
  • prefetch(hosts, max_workers=8):主机间并发取回,幂等(已取回的不重复下发);
  • _ensure_prefetched():首次探测时按 self.nodes() 批量取回(受 auto_prefetch_all 控制);
  • _probe(host, key, cmd=None, timeout=15):统一只读入口——命中缓存即返回; 主机不在配置清单内 → 只对该主机做一次批量;动态键(如非默认路径磁盘空间)→ 单次下发。
# 概念等价(Python 侧实现,非 shell):
# for host in <配置内全部节点>  ← 并发(线程池,上限 min(8, 主机数))
#     ssh <host> "<build_probe_shell(static_probes())>"    ← 一次往返取回全部静态事实
# check_*() 一律经 _probe(host, 键) 读缓存

5.4 check_* 改走统一入口

把 9 个检查项里的 self._ssh.run(...) 替换为 self._probe(...)(判定与文案逐字保留):

# 改动清单(- 为旧写法,+ 为新写法)
# check_ssh         : - self._ssh.run(host, "hostname", timeout=15)          + self._probe(host, "ssh")
# check_os          : - self._ssh.run(host, "cat /etc/os-release …", …)      + self._probe(host, "os", timeout=20)
# check_clock       : - self._ssh.run(host, "chronyc tracking …", …)         + self._probe(host, "clock", timeout=20)
# check_firewalld   : - self._ssh.run(host, "systemctl is-active …")         + self._probe(host, "firewalld")
# check_scheduler   : - self._ssh.run(host, "command -v squeue …", …)        + self._probe(host, "scheduler", timeout=20)
# check_root        : - self._ssh.run(host, "id -u", timeout=15)             + self._probe(host, "root")
# check_selinux     : - self._ssh.run(host, "getenforce 2>/dev/null")        + self._probe(host, "selinux")
# check_disk_space  : - self._ssh.run(host, f"df -B1G … {path}")             + self._probe(host, f"disk_space:{path}", cmd=cmd_disk_space(path))
# check_kernel      : - self._ssh.run(host, "uname -r", timeout=15)          + self._probe(host, "kernel")
# check_repo_url    : - self._ssh.run(host, "test -d …" / curl …)            + self._probe(host, f"repo_dir:{path}" / f"repo_url:{url}", cmd=…)

坑:check_ssh 判 not r.success、check_root 判 not r.success——这两处依赖逐条退出码, 是 5.2「每段带 @@rc_键」存在的原因,不可省。derive_system_disks()(lsblk 推导)不在本次范围, 仍走 ssh_exec.run。

5.5 离线自测(上机前)

cd /home/rain/Documents/LSF/lsf_ins/deploy/lsf
.venv/bin/python -m py_compile /home/rain/py_public_modules/precheck_module.py && echo COMPILE_OK
.venv/bin/python ../shared_precheck_batch_20261007/selftest.py

预期:COMPILE_OK + SHARED_PRECHECK_SELFTEST_OK(7 组断言,桩 ssh 不连主机)。 其中第 5 组是核心:同一份模拟数据下,批量版与逐条版 check_hosts() 的 findings 逐条完全一致。

5.6 上机验证(只读)+ 跨工程回归

cd /home/rain/Documents/LSF/lsf_ins/deploy/lsf
LOG=logs/lsf_deploy.log
for mode in "--precheck" "--stage nfs --dry-run" "--stage submit --dry-run"; do
  S0=$(wc -l < "$LOG"); T0=$(date +%s.%N)
  .venv/bin/python main.py --config config/lsf_deploy.yaml $mode > /tmp/out.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
# 跨工程回归 A:8 个占用工程的预检查文件语法
for f in <各工程 utils/precheck.py>; do .venv/bin/python -m py_compile "$f" || echo FAIL; done
# 跨工程回归 B:公开 API 兼容(新 vs 备份旧版逐个方法签名比对)——见本主题 SOP 第 6 章判据
# 跨工程回归 C:至少一个非 lsf 工程实跑 --precheck(有环境时)

6. 验证

验证一:语义等价(核心) - 离线:批量版 vs 逐条版 check_hosts() findings 逐条完全一致(含整机不可达、os-release 不可读、 firewalld inactive、SELinux Enforcing、非 root 等分支); - 上机:--precheck 落盘报告结论未变 —— BLOCK 0 项 / WARN 2 项 / INFO 54 项(改动前后一致); --stage nfs --dry-run 与 --stage submit --dry-run 的模块级输出逐行一致(差异仅报告文件名时间戳), 两个阶段均报「零变更跳过」。

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

子命令 命令数 墙钟
--precheck 54 → 18(-66.7%) 19.0 s → 6.9–7.1 s
--stage nfs --dry-run 66 → 30(-54.5%) 22.1 s → 7.3 s
--stage submit --dry-run 68 → 32(-52.9%) 25.4 s → 12.5 s

相对原始基线(两轮合计,lsf_ins):

子命令 命令数(原始 → 现在) 墙钟(原始 → 现在)
--precheck 84 → 18(-78.6%) 32.5 s → 6.9 s(-78.8%)
--stage nfs --dry-run 138 → 30(-78.3%) 52.1 s → 7.3 s(-86.0%)
--stage submit --dry-run 103 → 32(-68.9%) 42.9 s → 12.5 s(-70.9%)

验证三:API 兼容(跨工程回归 B) —— 旧版 PreCheckBase 公开方法 19 个、模块级函数 6 个, 无丢失、无签名变更;新增 5 项(static_probes / prefetch / _probe / _probe_cache_from / _ensure_prefetched)均为向后兼容的新增。8 个占用工程的预检查文件 py_compile 全通过。

验证四:幂等 —— 复跑 --precheck 仍 18 次命令、结论不变;两个阶段 dry-run 均「零变更跳过」。

失败判断:findings 出现任何差异,或 py_compile / selftest.py 失败,或 API 比对报「丢失/签名变更」 → 停止,按第 7 章回滚。

7. 回滚

  • 备份位置:lsf_probe_batch_20261007 之外的独立主题 shared_precheck_batch_20261007/backup/py_public_modules/, 台账见 shared_precheck_batch_20261007/backup/README.md。
  • 回滚步骤(单文件整份还原):
cp -a /home/rain/Documents/LSF/lsf_ins/shared_precheck_batch_20261007/backup/py_public_modules/precheck_module.py.bak.* \
      /home/rain/py_public_modules/precheck_module.py
  • 回滚后验证:py_compile 通过 + 任一工程跑 --precheck,确认结论与命令数回到旧值(lsf 为 54 次)。
  • 回滚触发条件(任一达标即回滚):① 任一工程 findings 与基线不一致;② py_compile/自测失败; ③ API 比对出现丢失或签名变更;④ 出现连接类异常(超时激增、Transport 失效)。
  • 影响面提示:共享库回滚会同时影响全部 8 个工程,须一并复测。

8. 风险说明

风险 缓解
默认行为变化:首次探测会批量取回配置内全部节点(调用方可能只检查其中一台) 全部为只读探测、且对象是工程自身节点;提供 auto_prefetch_all = False 退化为「只探被检查的那台」(已自测);已在第 4 章显式登记
批量后逐条退出码失真 每段命令后紧跟 echo "@@rc_键=$?",退出码逐条还原(check_ssh/check_root 依赖它);离线第 7 组断言覆盖
SSH 层失败被误判为「已达标」 整机 rc=-1 → 该主机全部键记 -1(unknown),与逐条语义一致;check_ssh 仍判 BLOCK
拼接 shell 里某条命令失败导致后续不执行 命令之间不加 &&/set -e,逐条独立;失败只影响该键的退出码
缓存导致「执行变更后状态不刷新」 缓存只覆盖静态事实(主机名/OS/时钟/防火墙/调度器作业/身份/SELinux/根分区/内核),不含随变更改动的资源状态;变更类探测(挂载、fstab、服务单元)不在此列
共享库改动波及面大 全程纯新增 + 内部实现替换,公开签名比对无变更;8 个占用工程编译回归通过;launch 前完成单文件快照
check_repo_url 的 url 被拼进 shell 沿用原实现(url 由配置给出,属可信输入);本次未改变其拼接方式

附录 A 交付物清单

文件 说明
SOP.md 本文件
selftest.py 离线自测(7 组断言,桩 ssh 不连主机;含批量版 vs 逐条版等价性对照)
backup/py_public_modules/precheck_module.py.bak.20261007-102237 改前快照(唯一回滚依据)
backup/README.md 备份台账
变更的实现体 /home/rain/py_public_modules/precheck_module.py

附录 B 其余工程(自动受益,无需改动)

8 个使用 PreCheckBase 的工程均为纯受益、零改动(无签名变更、无调用点改动):

工程 说明
lsf_ins 本次实测对象(见表)
lustre_ins run_all() 调用 check_hosts() → 自动批量 + 并发
services/harbor、k8s_ins/env-monitor-deploy、eda_tools_ins/{ic617_replay, pdk_integration, ic25_install, rocky9_eda_compat} 各自 run_all() 调用 check_* → 自动批量(首次探测即并发取回全部节点)

若某工程希望严格限制探测范围(只探调用方检查的节点),在其 PreCheck 子类上加一行 auto_prefetch_all = False 即可;默认 True。

附录 C 尚未批量化(后续项)

项 说明
derive_system_disks()(lsblk 推导系统盘) 每主机一条独立命令,未纳入本次批量(它返回结构化 pairs、且有 _system_disks_cache 二级缓存)
工程内非预检查侧的探测 lsf_ins 已完成:三处探测批量化(lsf_probe_batch_20261007)+ 残余收尾(lsf_probe_batch2_20261007,含阶段0 公钥采集去 N² 与每主机一条 shell);其它工程(samba_ins / kvm / harbor / eda_tools_ins / lustre_ins 缓存项 / vnc-server-deploy)仍待按其自身热点逐工程推广,模板见 lsf_probe_batch_20261007 SOP 附录 C

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