部署工程通用能力上移共享库与预检查扩充 — 变更实施手册(SOP)¶
变更对象:
/home/rain/Documents/lustre_ins(Python 部署工程)与/home/rain/py_public_modules(共享库) 变更类型:交付物变更(代码 / 配置样例 / 文档),不涉及生产服务的安装与停服 实现体为准:本文 shell 代码块仅用于阅读、复核与手工复现,不承担幂等与错误处理 运行期使用手册见 ../docs/RUNBOOK.md
目录¶
1. 背景¶
上一轮复核(对象:developing-python-deploy-scripts 技能的通用性与重复可用性)结论是:
规范侧已生效,工程落地侧只部分闭环。两个未闭环项即本次变更的触发条件:
| 编号 | 问题 | 证据(变更前) |
|---|---|---|
| G1 | 预检查覆盖面不足:SELinux、执行身份、系统盘可用空间、运行内核版本、LNet NID 可达性五项均无检测;EnvCheckModule 方案提出后未落地 |
utils/precheck.py(无对应函数)、.trae/documents/lustre-env-check-plan.md |
| G2 | 通用能力未上移共享库:预检查、三态探测、文件备份/台账、SSH 执行封装仍留在工程 utils/ 内,共享库只有 4 个基础模块;第二个工程复用时必然重复实现 |
utils/precheck.py、utils/idempotent_utils.py、utils/file_utils.py、utils/ssh_executor.py;/home/rain/py_public_modules/ 仅 4 文件 |
| G3 | EnvCheckModule 方案与既有 precheck/_gate() 功能重叠,若按原样实施会形成两套环境检测 |
.trae/documents/lustre-env-check-plan.md 第 9、52–71 行 |
本次采取的方案(与复核建议一致):不新建 modules/env_check.py,改为
① 在既有 PreChecker 内扩充检测项;② 把通用能力上移共享库,工程侧继承/调用。
原方案文档保留为决策背景,已加状态标注。
2. 目标¶
变更完成后应达到以下可验证状态:
- 共享库
/home/rain/py_public_modules/新增 4 个模块:ssh_executor.py、idempotent_module.py、file_module.py、precheck_module.py(共 8 个模块) - 工程内不再有通用能力的副本:
utils/ssh_executor.py删除;utils/idempotent_utils.py只留 Lustre 专有探测;utils/file_utils.py只留 fstab 追加;utils/precheck.py只留继承PreCheckBase的领域专有检查项 PreChecker新增 5 项检测,默认分级:执行身份非 root → BLOCK;系统盘可用空间低于min_disk_free_gb→ BLOCK;SELinux Enforcing → WARN;LNet NID 不可达 → WARN;运行内核版本 → INFO- 新增检查项的阈值与跳过列表全部下沉到配置(
precheck段),并同步写入配置样例、加入配置合法性校验、被实现体真正引用 - 全部变更类子命令的门禁行为不变:真实执行存在 BLOCK 即终止;
--dry-run降级为告警 - 文档口径一致:
SKILL.md(共享库模块表 4→8)、docs/SOP.md、docs/RUNBOOK.md与本实现体一致 - 验证通过:
py_compile+ 全模块导入 + 真机precheck实跑 +--dry-run连续两次零变更
3. 前置条件¶
| 项 | 要求 | 检查命令 |
|---|---|---|
| 操作机 | Python 3.9+,工程 .venv 可用 |
cd /home/rain/Documents/lustre_ins && .venv/bin/python --version |
| 共享库挂载 | .pth 已挂载 /home/rain/py_public_modules |
.venv/bin/python -c "import precheck_module; print(precheck_module.__file__)" |
| SSH 免密 | 操作机可免密登录 192.168.31.21、192.168.31.22 |
ssh root@192.168.31.21 hostname |
| 备份可用 | py-skill-hardening/backup/ 内备份完整(29 份) |
find py-skill-hardening/backup -type f \| wc -l |
| 变更窗口 | 本次仅改工程与共享库代码、文档;节点仅被只读探测 | — |
| 在跑作业 | 验证用的 precheck / --dry-run 均为只读,不影响作业 |
— |
4. 影响范围¶
| 维度 | 说明 |
|---|---|
| 主机 | 操作机(工程代码、共享库代码、文档);目标节点仅被只读探测(lsblk/df/getenforce/id/uname/lctl ping/curl 等) |
| 组件 | 共享库 4 个新模块;工程 utils/*、modules/*、deployer.py、config/deploy.yaml.example、docs/SOP.md、docs/RUNBOOK.md;全局技能 SKILL.md |
| 是否停服 | 否。未执行任何真实部署动作,未写任何节点配置 |
| 窗口期 | 无(代码变更即时生效;验证为只读) |
| 在跑作业 | 变更窗口内不影响;若节点有 Slurm/LSF 作业,precheck 会报 BLOCK(既有行为) |
| 数据影响 | 无。未执行 mkfs.lustre、未改任何节点文件 |
| 其它工程影响 | 共享库为多工程共用:本次仅新增 4 个文件,未修改既有 4 个模块,对其它工程无影响 |
5. 分步操作¶
所有命令在工程根
/home/rain/Documents/lustre_ins下执行。以下序列与实现等价,以实现体为准。
5.1 备份(变更前,已执行)¶
# 工程非 git 仓库,逐文件带时间戳备份到本主题目录,并登记台账
TS=20260923-141431
DEST=py-skill-hardening/backup
for f in main.py deployer.py utils/*.py modules/*.py \
config/deploy.yaml.example config/deploy-expand.yaml.example \
docs/SOP.md docs/RUNBOOK.md; do
mkdir -p "$DEST/$(dirname "$f")"
cp -p "$f" "$DEST/$f.bak.$TS" # 逐文件原位备份
done
# 全局技能单独留档
mkdir -p "$DEST/skills/developing-python-deploy-scripts"
cp -p /home/rain/.trae-cn/skills/developing-python-deploy-scripts/SKILL.md \
"$DEST/skills/developing-python-deploy-scripts/SKILL.md.bak.$TS"
# 台账见 py-skill-hardening/backup/README.md
5.2 共享库新增 4 个通用模块¶
对应实现文件:/home/rain/py_public_modules/{ssh_executor,idempotent_module,file_module,precheck_module}.py
# 1) ssh_executor.py:工程 utils/ssh_executor.py 整体上移(SSH 执行便捷层)
# 唯一差异:临时文件前缀 lustre_deploy_ → deploy_(去工程耦合)
# 2) idempotent_module.py:三态探测通用项
# probe_state / ssh_available / compute_sha256 / compare_file_content /
# rpm_installed_state / is_rpm_installed,ssh_exec 注解脱耦为 Any
# 3) file_module.py:备份与台账通用项
# timestamp / ensure_dir / backup_with_timestamp / write_file_if_changed /
# register_remote_backup
# 4) precheck_module.py:预检查骨架
# LEVEL_* / finding / summarize / has_block / render_report /
# parse_lsblk_pairs / derive_system_disks / PreCheckBase(通用检查项)
.venv/bin/python -c "import ssh_executor, idempotent_module, file_module, precheck_module; print('ok')"
5.3 工程侧预检查重构与检测项扩充¶
对应实现文件:utils/precheck.py、utils/config_validate.py、config/deploy.yaml.example
# PreChecker 改为继承共享库 PreCheckBase:
# 通用项(操作机/SSH/OS/时钟/防火墙/调度器/执行身份/SELinux/磁盘空间/内核)由基类提供
# 工程侧只保留 Lustre 专有项:LNet 网卡、LNet ping、target 设备、仓库、版本矩阵
# 新增 5 项检测的默认分级(保守):
# check_root 非 root → BLOCK(precheck.require_root 可关)
# check_disk_space 可用空间不足 → BLOCK(precheck.min_disk_free_gb,默认 10G)
# check_selinux Enforcing → WARN (precheck.required_selinux,默认 permissive)
# check_lnet_ping NID 不可达 → WARN (LNet 未配置时记 INFO 跳过,不误报)
# check_kernel 记录 uname -r → INFO
# 配置样例与校验同步:precheck 段新增 5 个字段 + skip_checks,并纳入 config_validate
.venv/bin/python -m py_compile utils/precheck.py utils/config_validate.py
5.4 工程侧裁剪与引用调整¶
对应实现文件:utils/{idempotent_utils,file_utils}.py(裁剪)、utils/ssh_executor.py(删除)、deployer.py 与 modules/*.py(import 调整)
# 工程内删除已上移的副本(明确文件名,不用通配删除)
rm -f utils/ssh_executor.py
# 引用改为直连共享库(不再经工程内转发):
# from utils.ssh_executor import SSHExecutor → from ssh_executor import SSHExecutor
# from utils.idempotent_utils import is_rpm_installed → from idempotent_module import ...
# from utils.file_utils import register_remote_backup → from file_module import ...
# from utils.precheck import LEVEL_BLOCK/… → from precheck_module import ...
# 注意:Lustre 专有项(is_lustre_target_formatted / mounted_state /
# lnet_configured_state / append_to_fstab / config_validate / lustre_state /
# stripe_utils)仍留在工程侧,不动
5.5 文档同步¶
对应实现文件:SKILL.md、docs/SOP.md、docs/RUNBOOK.md、.trae/documents/lustre-env-check-plan.md
# SKILL.md:共享库模块表 4 → 8;标准目录结构中 utils/ 与共享库清单同步;
# 落地要求第三条改为「通用能力一律从共享库导入」
# docs/SOP.md:第 3 章与 5.1 补新增检测项说明
# docs/RUNBOOK.md:4.1 节补检查项清单、处置建议与修订记录
# lustre-env-check-plan.md:顶部标注「已并入 precheck 体系,不再新建 env_check 模块」
6. 验证¶
6.1 静态验证(本地,已执行)¶
| 项 | 命令 | 实测结果 |
|---|---|---|
| 语法 | .venv/bin/python -m py_compile main.py deployer.py modules/*.py utils/*.py |
通过,无输出 |
| 共享库导入 | .venv/bin/python -c "import ssh_executor, idempotent_module, file_module, precheck_module" |
4 个模块均由 /home/rain/py_public_modules/ 解析 |
| 入口可用 | .venv/bin/python main.py --help |
正常,子命令列表含 precheck,无导入错误 |
| 继承关系 | PreChecker.__mro__ |
PreChecker → PreCheckBase → object |
| 检测项齐备 | dir(PreChecker) 中以 check_ 开头的方法 |
18 项,含新增 check_root / check_selinux / check_disk_space / check_kernel / check_lnet_ping |
| 残留引用 | grep -rn "utils.ssh_executor" --include=*.py . |
无输出(无残留、无副本) |
| 配置校验 | validate_config 跑 config/deploy.yaml 与样例 |
均为 0 findings(新字段默认值合法) |
| 副本比对 | AST 比对共享库与工程侧同名函数体 | 已上移函数与原文一致;工程侧无残留副本 |
6.2 真机验证(只读,已执行)¶
预检查实跑(只读)
- 命令:
.venv/bin/python main.py precheck --config config/deploy.yaml - 退出码非 0(存在 BLOCK 项);报告:
reports/precheck-20260923-143259.md - 结论计数:BLOCK 1 / WARN 4 / INFO 35
| 检查项 | 节点 | 实测值 | 级别 |
|---|---|---|---|
root(新增) |
21 / 22 | 执行身份 root | INFO |
disk_space(新增) |
21 / 22 | / 可用空间 17G(阈值 10G) |
INFO |
kernel(新增) |
21 / 22 | 运行内核 5.14.0-611.13.1_lustre.el9.x86_64 |
INFO |
selinux(新增) |
21 / 22 | SELinux 状态 Enforcing,期望 permissive | WARN |
lnet_ping(新增) |
21 → 22 | NID 可达: 192.168.31.22@tcp | INFO |
lnet_ping(新增) |
22 → 21 | 修复去重后:NID 可达: 192.168.31.21@tcp | INFO |
repo(既有) |
22 | 仓库不可达(HTTP 000): .../lustre-2.17.0/el9.7/server/ |
BLOCK |
两点说明:
- SELinux WARN 是真实环境事实(两台均 Enforcing),按保守分级不阻断后续变更;处置方式见 RUNBOOK 4.1 节。
repoBLOCK 属节点到 whamcloud 的网络波动(同一仓库在 21 上返回 200,9/22 记录中 22 亦为 200),非本次引入;--dry-run下门禁按设计降级为告警,故预演继续执行。
首轮真机暴露并修复的缺陷
| 缺陷 | 现象 | 修复 |
|---|---|---|
check_lnet_ping 的对端 NID 未去重 |
21 身兼 mgs_mds 与 clients 两个角色,NID 在候选列表出现两次 → lctl ping <nid> <nid> 超时,误报「NID 不可达」(截断输出 Timeout ... is to large) |
all_nids 改用 dict.fromkeys 去重;复测 21↔22 双向均报可达 |
门禁与幂等(bootstrap --dry-run,真机)
| 项 | 运行 1 | 运行 2(复跑) |
|---|---|---|
| 退出码 | 0 | 0 |
模块状态 success |
12 | 12 |
| 「将执行变更」明细 | 10 条 | 10 条 |
| 「已存在,跳过」 | 7 条 | 7 条 |
ERROR 日志 |
2 条 | 2 条 |
| 日志行数 | 1132 | 1132 |
- 去时间戳后两次日志逐行完全一致(仅命令耗时数值波动),满足「连续执行两次,第二次零变更、全跳过」的幂等验收标准。
- 两处
ERROR均为lctl get_param llite.*.statahead_xattr返回码 2(该参数在 Lustre 2.17 已移除),属既有遗留项,与本次变更无关。 - 两次 dry-run 的门禁日志中,新增 5 项检测均在位且分级与设计一致(
rootINFO、disk_spaceINFO、kernelINFO、selinuxWARN)。
7. 回滚¶
备份位置¶
变更前已对 29 份文件做带时间戳备份,位置与台账:
- 备份目录:
py-skill-hardening/backup/(时间戳20260923-141431) - 台账:backup/README.md(逐条登记备份文件、原路径、产生时间、因何产生、回滚什么、何时可删)
回滚步骤¶
cd /home/rain/Documents/lustre_ins
TS=20260923-141431
DEST=py-skill-hardening/backup
# 1) 逐文件恢复工程内被改文件(示例:全部)
for f in main.py deployer.py utils/*.py modules/*.py \
config/deploy.yaml.example config/deploy-expand.yaml.example \
docs/SOP.md docs/RUNBOOK.md; do
cp -p "$DEST/$f.bak.$TS" "$f" # 覆盖回变更前内容
done
# 2) 恢复被删除的工程内模块
cp -p "$DEST/utils/ssh_executor.py.bak.$TS" utils/ssh_executor.py
# 3) 恢复全局技能
cp -p "$DEST/skills/developing-python-deploy-scripts/SKILL.md.bak.$TS" \
/home/rain/.trae-cn/skills/developing-python-deploy-scripts/SKILL.md
# 4) 删除本次新增的共享库模块(明确文件名,不用通配)
rm -f /home/rain/py_public_modules/ssh_executor.py \
/home/rain/py_public_modules/idempotent_module.py \
/home/rain/py_public_modules/file_module.py \
/home/rain/py_public_modules/precheck_module.py
# 5) 本次新增的报告为只读产出物,按需保留
回滚后验证¶
.venv/bin/python -m py_compile main.py deployer.py modules/*.py utils/*.py # 预期:无输出
.venv/bin/python main.py precheck --config config/deploy.yaml # 预期:回到单体 PreChecker,可正常运行
grep -c "PreCheckBase" utils/precheck.py # 预期:0
回滚不影响任何目标节点:本次变更未在节点上留下配置或数据改动。
8. 风险说明¶
| 风险 | 等级 | 缓解措施 |
|---|---|---|
| import 路径批量调整导致导入失败 | 高 | 全量 py_compile + 逐模块导入 + main.py --help + 残留引用 grep 四道验证;备份可一键还原 |
| 共享库被工程侧专有语义污染 | 中 | 上移时逐函数判定:Lustre 专有项(卷标判定、挂载状态、LNet 配置、fstab 文案、target 解析)一律不上移;共享库内不出现 Lustre 字样 |
同名模块导入歧义(utils/ssh_executor.py 与共享库同名) |
中 | 工程内该文件已删除,from ssh_executor import ... 唯一解析到共享库;已实测确认 |
| 新增门禁项误阻断合法变更 | 中 | 保守分级:仅 root 权限与磁盘空间为 BLOCK;SELinux/LNet ping 为 WARN;各项均可经 precheck.skip_checks 关闭 |
lctl ping 在 LNet 未配置时误报 |
中 | 先用 lnetctl net show/lctl list_nids 做能力探测,未配置时记 INFO 跳过,不报故障 |
check_local 依赖的共享库模块清单随之扩容 |
低 | 清单改为共享库内常量 DEFAULT_SHARED_LIB_MODULES(8 项),工程侧可传参覆盖 |
| 共享库变更影响其它工程 | 低 | 只新增文件,未改既有 4 个模块;其它工程不受影响 |
每次 precheck 都新增一份报告 |
低 | 既有留痕设计,与 inspect 一致;按需清理旧报告 |
9. 附录¶
9.1 交付物清单¶
| 路径 | 内容 |
|---|---|
/home/rain/py_public_modules/ssh_executor.py |
新增:SSH 执行便捷层 |
/home/rain/py_public_modules/idempotent_module.py |
新增:幂等三态探测 |
/home/rain/py_public_modules/file_module.py |
新增:文件备份与台账 |
/home/rain/py_public_modules/precheck_module.py |
新增:预检查骨架 PreCheckBase |
utils/precheck.py |
重写:继承骨架 + 新增 5 项检测 |
utils/config_validate.py |
新增 precheck 段合法性校验 |
utils/{idempotent_utils,file_utils}.py |
裁剪:只留 Lustre 专有项 |
utils/ssh_executor.py |
删除(已上移) |
deployer.py、modules/*.py、utils/{lustre_state,stripe_utils}.py |
import 改直连共享库 |
config/deploy.yaml.example |
precheck 段新增 5 个字段与说明 |
docs/SOP.md、docs/RUNBOOK.md |
口径同步 |
/home/rain/.trae-cn/skills/developing-python-deploy-scripts/SKILL.md |
共享库模块表 4→8、目录结构同步 |
.trae/documents/lustre-env-check-plan.md |
加状态标注(并入 precheck 体系) |
py-skill-hardening/backup/、py-skill-hardening/SOP.md |
备份台账与本文档 |
9.2 决策记录¶
- 不新建
EnvCheckModule:既有precheck子命令 +_gate()已是环境的唯一入口,再建模块级门禁会形成两套检测、两处维护。改为扩充PreChecker,与本技能「预检查独立成只读模块,不得由各业务模块零散实现」一致。 - 上移粒度按函数判定而非整文件:
precheck.py/idempotent_utils.py/file_utils.py均属「通用骨架 + 领域特化」混合体,整文件上移会让共享库带 Lustre 语义。故只上移通用函数,领域项留工程侧。 config_validate.py不上移:其校验域整体绑定 Lustre schema(fsname、NID、target 类型、striping),属工程专有。- 新增检测项默认分级取保守方案:仅「执行身份非 root」与「系统盘空间不足」阻断变更——这两者必然导致变更失败;其余为可继续的告警,避免换环境时被频繁拦截。
9.3 遗留事项(需人工决策,本次未改动)¶
docs/HANDOFF.md不存在,本次结论未落档为交接快照;如需建立请另行说明。modules/下 13 个业务模块中仍有 7 个未覆盖pre_check()(repo_setup、package_install、fstab_persist、mount_targets、stripe_tuning、lctl_tuning、inspector;已覆盖的为target_format、lnet_config、kernel_strategy、firewall、ha_pacemaker、client_mount)——环境门禁由编排层_gate()统一承担,模块级pre_check()仅做本模块相关的补充校验;是否逐模块补齐属既有设计的未决项。- 巡检遗留的 3 项 lctl 调优参数与 Lustre 2.17 不兼容(见
precheck-hardening/SOP.md10.4 节),本次未处理。
来源:
/home/rain/Documents/lustre_ins/py-skill-hardening/SOP.md| 最后更新:2026-09-23 14:40(由build/sync.py同步,请勿在站点侧修改)