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):
--precheck57 次命令中 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同步,请勿在站点侧修改)