跳转至

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 同步,请勿在站点侧修改)