SOP — lustre_ins 只读探测批量化改造(probe_batch_20261008)¶
- 日期:2026-10-08
- 工程:
/home/rain/Documents/lustre_ins - 交付物目录:
probe_batch_20261008/ - 变更类型:C 类(工程内代码改造,只改只读探测的打包方式)
- 一句话:把工程里「只读探测」的打包方式改为「同主机拼一段 shell / 主机间并发 /
进程内
(host,命令原文)探测缓存」;判定表达式、变更清单顺序与内容、变更类命令一律不动。
目录:见文末《附录 A 交付物清单》。本文档八章结构,第 5 章可独立阅读。
1. 背景¶
共享库 ~/py_public_modules/base_module.py 的 BaseModule 新提供只读探测三件套:
query_on_host(带 (host, 命令原文) 缓存的只读查询)、probe_per_host(每台一条 shell
取回多个探测并回填缓存)、run_per_host(主机间并发),另有 _kv_script/_parse_kv。
本工程 8 台客户端 + 2 台服务端,此前只有 modules/inspector.py 在上一轮做过批量化;
其余模块仍是裸 self._ssh.run 逐条、且无探测缓存:同一事实被 pre_check/execute/validate
各查一遍(重复往返);预检查的逐节点循环串行(墙钟 = 各节点耗时之和)。
主要浪费(按价值排序):
1. package_install:每个包一次 rpm -q(5 包 = 5 次进程/往返)。
2. kernel_strategy:_resolve_target 的 dnf repoquery 最贵,却在 pre_check 与 execute 各查一遍;
_module_ready 在 pre_check/execute/validate 各查一遍;pre_check 逐台串行。
3. lctl_tuning:nproc 在多处重复探测;lctl get_param 无缓存。
4. is_lustre_target_formatted:单设备最多 3 次串行 run(blkid / dumpe2fs / lctl dl),
被 target_format 在 for-target 循环调用。
5. utils/precheck.py 的 check_networks/check_lnet_ping/check_devices:for-node 逐条串行。
2. 目标¶
- 上述 5 处的只读探测改为三件套打包;判定与输出文案逐字不变;
- 变更类命令(
dnf install、mkfs.lustre、lctl set_param、grubby --set-default、 文件写入/删除等)一律不动; - 交付:
py_compile通过、离线自测(桩远端)通过、上机只读对照(清单一致 + 命令数下降)。 - 可验证状态:
main.py precheck/main.py inspect的问题清单与改造前逐条一致; 改造涉及方法在真机上的远端命令数不增、且缓存命中项下降;墙钟下降。
3. 前置条件¶
- Python:工程
.venv/bin/python(共享库已由.pth挂载)。 - 共享库:
base_module已含query_on_host/probe_per_host/run_per_host(本次不改共享库)。 - 只读对照需目标可达(本环境
.21–.28免密可达);不可达时跳过第 6 章上机部分,仅保留离线自测。 - 执行前检查:
bash cd /home/rain/Documents/lustre_ins .venv/bin/python -c "import base_module, ssh_executor; print('shared-lib ok')" bash probe_batch_20261008/compare_onhost.sh # 只读;含自动还原与 cmp 校验
4. 影响范围¶
- 改动文件(5 个,均在工程内,不涉及共享库):
modules/package_install.py、modules/kernel_strategy.py、modules/lctl_tuning.py、utils/idempotent_utils.py、utils/precheck.py。 - 影响子命令:
precheck、inspect、bootstrap/add-*/tune的只读探测环节。 - 不停服、无窗口期:改造的是「探测怎么发」,不改任何远端状态;
--dry-run语义不变。 - 回滚粒度:按文件回滚(见第 7 章)。
5. 分步操作¶
以下「shell 代码块」是各改造点等价的远端命令形态(旧 → 新),用于复核与手工复现; 编排/重试/缓存/三态判定由实现体保证,不在代码块里展开。
步骤 1:package_install 缺包探测——整组包一次 rpm -q¶
叙述:原实现 for p in packages: rpm -q <p> 逐包一次(5 包 = 5 次进程 + 5 次往返)。
改为 rpm -q --qf '%{NAME}\n' p1 p2 ... 一次查询,已装包名从 stdout 取回,本地求差集。
坑:多包 rpm -q 的返回码「只要有缺包即非 0」,不能拿返回码判定,必须看 stdout 的已装包名;
缺包信息走 stderr(2>/dev/null 丢弃)。判定语义(is_rpm_installed:连接失败按「未安装」处理)不变。
# 旧(逐包)
rpm -q kmod-lustre ; rpm -q lustre ; rpm -q e2fsprogs # 3 次进程
# 新(一次多包;已装包名逐行输出,缺包走 stderr)
rpm -q --qf '%{NAME}\n' kmod-lustre lustre e2fsprogs 2>/dev/null
实现体:modules/package_install.py(_missing_packages,被 _install_on_host 与 validate 复用)。
预期:缺包集合与逐包探测一致;命令数 5→1。
步骤 2:kernel_strategy 只读探测走缓存 + pre_check 主机间并发¶
叙述:把 _running_kernel/_module_ready/_boot_id/_default_kernel/_resolve_target
与 pre_check 的 command -v grubby 从 self._ssh.run 改为 self.query_on_host(只读、只吃
stdout,语义等价)。pre_check 的逐台循环改为 run_per_host(主机间并发)。
坑:_reboot_and_wait 轮询里的 boot_id;uname -r 必须保持逐次实探(判重启),
故留在变更路径上用 self._ssh.run;check_scheduler 仍只在「内核未就绪」的节点上探测
(与串行实现同一条件,避免给就绪节点白发调度器探测)。
# 旧:同一事实在多个阶段各查一遍(pre_check + execute + validate)
uname -r # 每处各一次
modprobe -n lustre >/dev/null 2>&1 && echo READY
dnf ... repoquery --available --qf '%{version}-%{release}' kernel-core | sort -u | tail -1 # 最贵
# 新:命令原文不变,只是同一次运行内只下发一次(进程内缓存)
# —— 无 shell 形态差异;差异在「发几次」。变更(dnf install/重启)后缓存自动失效。
实现体:modules/kernel_strategy.py(_running_kernel L71、_module_ready L76、_boot_id、
_default_kernel、_resolve_target、pre_check L127)。
预期:pre_check 墙钟下降(并发),就绪节点上命令数不增。
步骤 3:lctl_tuning 的 nproc / command -v lctl / lctl get_param 走缓存¶
叙述:_resolve_value(lru_size=auto → 100×nproc)的 nproc、_lctl_path 的
command -v lctl、_apply_params 的 lctl get_param 改为 query_on_host;
_get_lctl_param 的命令原文与 utils.lustre_state.get_lctl_param 逐字一致(命中同一缓存键)。
坑(有意不缓存):_write_remote_file 的 SHA 校验、_remote_file_state、_remove_legacy_cron
读的是「会被裸 SSHExecutor.upload_text/run 改动」的状态——这些写绕过模块缓存、不会自动失效,
故不接缓存,避免读到陈旧值。
# 旧:同一参数/事实在多处各查一遍
nproc
lctl get_param 'timeout' 2>/dev/null
# 新:命令原文不变;同一 (host,命令) 一次运行内只下发一次
# set_param 属变更,走 apply_change → 执行后自动清空探测缓存
实现体:modules/lctl_tuning.py(_get_lctl_param L129、_resolve_value L142、_lctl_path L178)。
步骤 4:is_lustre_target_formatted——两次回退探测拼一段 shell¶
叙述:原为最多 3 次串行 run(blkid → dumpe2fs → lctl dl)。改为:blkid 一次(快路径不变,
命中即返回);未命中时把 dumpe2fs 与 lctl dl 拼成一段 shell 一次往返,用 @@键
分段回填,本地按原优先级判定(blkid → dumpe2fs → lctl dl)。判定表达式不变。
# 旧(未命中时 2 次往返)
dumpe2fs -h /dev/vdb 2>/dev/null | grep -i 'volume name'
lctl dl 2>/dev/null | grep -F /dev/vdb
# 新(1 次往返,@@ 分段)
echo '@@dumpe2fs'
dumpe2fs -h /dev/vdb 2>/dev/null | grep -i 'volume name'
echo '@@lcldl'
lctl dl 2>/dev/null | grep -F /dev/vdb
实现体:utils/idempotent_utils.py(_split_kv L24、is_lustre_target_formatted)。
预期:blkid 命中的快路径仍 1 次;未命中路径 3→2 次。
步骤 5:utils/precheck.py 三个只读循环主机间并发¶
叙述:check_networks(逐节点)、check_lnet_ping(逐节点)、check_devices(逐 target,按主机
分组)改为 _map_concurrent(ThreadPoolExecutor.map,保序)→ 主机间并发、主机内串行;
结果按原顺序聚合,问题清单逐条不变。坑:check_devices 按主机分组后再并发——同主机多 target
串行,复用 system_disks 缓存,避免并发首次推导竞态;只有只读循环并发,变更循环不并发。
# 旧:逐节点串行(墙钟 = Σ 各节点)
# 192.168.31.21: ip link show eth0 (或 ip route get <peer>)
# 192.168.31.22: ...
# 新:同一批命令,主机间并发下发(每台仍是同一条命令)
实现体:utils/precheck.py(_map_concurrent L69、check_networks L98、check_lnet_ping、
check_devices L206)。
步骤 6(可选):上机只读对照脚本¶
cd /home/rain/Documents/lustre_ins
bash probe_batch_20261008/compare_onhost.sh # 新版 vs backup 跑 precheck/inspect,自动还原 + cmp
bash probe_batch_20261008/count_probe.sh # 新版 vs backup 的命令计数 A/B
预期:两脚本末尾打印 RESTORE_OK(工作区被还原为新版并通过 cmp 校验)。
6. 验证¶
6.1 离线自测(桩远端,确定性)¶
cd /home/rain/Documents/lustre_ins
.venv/bin/python probe_batch_20261008/selftest.py # 预期「结果:全部通过」
覆盖:is_lustre_target_formatted 四场景(判定一致 + 命令数:未命中路径 3→2);
package_install(清单一致 + 5→1 + 缓存复用 0 命令);kernel_strategy 缓存(同事实只 1 条 +
变更后失效重发);lctl_tuning(nproc/get_param 各只 1 条);precheck 三方法
(主机间并发后清单逐条一致,含顺序)。
6.2 上机只读对照(2026-10-08,真机 .21–.28)¶
命令计数 A/B(count_probe.sh,单位:远端命令条数):
| 只读探测 | 旧版 | 新版 | 说明 |
|---|---|---|---|
package_install 缺包(5 包 / 1 台) |
5 | 1 | 多包一次 rpm -q |
kernel_strategy(_module_ready×2 + uname -r×1) |
3 | 2 | 缓存去重 |
kernel_strategy.pre_check(9 台) |
9(4.87 s) | 8(0.91 s) | 主机间并发,墙钟 -81% |
lctl_tuning._resolve_value('auto') ×2 |
2 | 1 | nproc 缓存 |
子命令只读对照(compare_onhost.sh):
| 子命令 | 旧版 | 新版 | 清单 |
|---|---|---|---|
precheck(含设备/仓库/版本矩阵) |
50.4 s / 67 条 | 44.2 s / 67 条 | 逐条一致 |
inspect |
8.4 s / 128 条 | 8.1 s / 128 条 | 命令逐条一致 |
口径说明:
precheck首跑(冷)因远端dnf缓存未建而偏慢(206 s),非本次改动所致; 上表取「热缓存」两次可比样本。子命令对照中途用 backup 覆盖为旧版,跑完立即还原并cmp校验(RESTORE_OK)。命令数取自各次捕获日志中「开始执行命令」计数。
6.3 判定「通过」的标准¶
selftest.py全部 PASS;precheck/inspect的问题清单与旧版逐条一致;- 改造涉及方法的真机命令数不增,缓存命中项下降;
kernel_strategy.pre_check墙钟下降; - 工作区在对照脚本结束后 还原为新版并通过
cmp。
7. 回滚¶
- 备份:
probe_batch_20261008/backup/(含台账backup/README.md),逐文件对应原路径。 - 回滚步骤(任选其一):
bash cd /home/rain/Documents/lustre_ins cp -f probe_batch_20261008/backup/package_install.py modules/package_install.py cp -f probe_batch_20261008/backup/kernel_strategy.py modules/kernel_strategy.py cp -f probe_batch_20261008/backup/lctl_tuning.py modules/lctl_tuning.py cp -f probe_batch_20261008/backup/idempotent_utils.py utils/idempotent_utils.py cp -f probe_batch_20261008/backup/precheck.py utils/precheck.py # 或(工程已纳入 git):git checkout -- modules/package_install.py modules/kernel_strategy.py \ # modules/lctl_tuning.py utils/idempotent_utils.py utils/precheck.py .venv/bin/python -m py_compile modules/package_install.py modules/kernel_strategy.py \ modules/lctl_tuning.py utils/idempotent_utils.py utils/precheck.py # 预期无输出 - 回滚后验证:跑
main.py precheck --config config/deploy.yaml,清单应与改造前一致。 - 备份存放位置:
probe_batch_20261008/backup/;删除条件见backup/README.md。
8. 风险说明¶
| 风险 | 缓解 / 现状 |
|---|---|
缓存键是 (host, 命令原文)——命令文本稍变即不命中 |
_get_lctl_param 与 get_lctl_param 命令原文逐字一致;query_on_host 内部只用原文 |
| 「变更后读到陈旧探测值」 | 变更走 apply_change/run_on_host → 执行后自动清空缓存;裸写(upload_text/run)路径的读探测有意不接缓存 |
| 三态(unknown)被缓存折平 | 需要 exit_code 的三态探测(probe_state/lnet_configured_state/含 r.success 的判定)未接缓存,保持逐条下发 |
主机间并发触发 sshd MaxStartups 被拒 |
并发度上限 8,主机数 9;上机实测无连接被拒 |
并发下共享对象竞态(PreChecker._probe_cache/system_disks) |
check_devices 按主机分组后再并发、同主机串行;kernel_strategy.pre_check 仅在未就绪节点探测调度器(就绪节点不触发 prefetch) |
| 日志顺序在主机间可能交错 | 问题清单按原顺序聚合(ThreadPoolExecutor.map 保序 + 按 target 索引回填);文案逐字不变 |
| 判定被无意改动 | 判定表达式未改;离线自测以 backup 旧实现逐一对照 |
遗留 / 有意不做¶
kernel_strategy._reboot_and_wait轮询、lctl_tuning裸写后的读探测:变更路径,不批量化、不缓存。is_lustre_target_formatted的 blkid 命中快路径仍是 1 次(本就最优);未进一步跨 target 合并。target_format的 for-target 循环未做主机间并发(循环内含mkfs.lustre变更,不得并发)。
附录 A 交付物清单¶
| 文件 | 说明 |
|---|---|
backup/(5 个 .py + target_format.py + README.md) |
改造前备份与台账 |
selftest.py |
离线自测(桩远端,判定一致 + 命令数下降) |
compare_onhost.sh |
上机只读子命令 A/B(含自动还原 + cmp) |
count_probe.sh / probe_count.py |
上机命令计数 A/B(只读) |
onhost/ |
上机对照的原始捕获(各次日志、计数 JSON) |
SOP.md |
本文档 |
附录 B RUNBOOK 判定¶
本工程已有 docs/RUNBOOK.md。本次变更只改只读探测的打包方式(拼 shell / 并发 / 缓存),
Lustre 集群的日常操作、交付物形态、配置文件与命令均未变,故无需更新 RUNBOOK;
若日后探测行为对使用者可见,须同步更新它。
来源:
/home/rain/Documents/lustre_ins/probe_batch_20261008/SOP.md| 最后更新:2026-10-08 11:45(由build/sync.py同步,请勿在站点侧修改)