部署工程通用性与安装前预检查加固 — 变更实施手册(SOP)¶
变更对象:
/home/rain/Documents/lustre_ins部署工程(Python 实现体) 变更类型:交付物变更(代码 / 配置样例 / 文档),不涉及生产服务的安装与停服 实现体为准:本文 shell 代码块仅用于阅读、复核与手工复现,不承担幂等与错误处理 运行期使用手册见 ../docs/RUNBOOK.md
目录¶
1. 背景¶
对部署工程做通用性与复用性核查,结论为:配置驱动的骨架可用,但安装前预检查实质缺失,并存在会直接阻断或误伤部署的缺陷。核查发现的问题(均已在本变更中修复):
| 编号 | 问题 | 证据位置(变更前) |
|---|---|---|
| P0-1 | 基类要求 pre_check() 做前置环境校验,但 11 个变更模块中仅 HA 模块覆盖,其余全走默认放行;SOP 第 3 章自列的 8 项前置条件在实现体中一项未落地;无 precheck 子命令 |
modules/*.py、main.py、docs/SOP.md |
| P0-2 | 系统盘白名单硬编码含 sdb,与示例配置把 /dev/sdb 用作数据盘直接冲突;nvme0n1/nvme0n2 亦被写死为系统盘,而 NVMe 恰是 MDT 推荐载体 |
modules/target_format.py |
| P1-1 | LNet 网络类型硬编码 tcp、只生成单个 NI、探测失败回退硬编码 eth0,改 @o2ib 不生效 |
modules/lnet_config.py |
| P1-2 | 多 MGS 只取首个 NID;客户端挂载源无法显式指定 | modules/target_format.py、modules/client_mount.py |
| P1-3 | 幽灵配置项:lustre.backend、lustre.os_variant、ha.*.vip 全工程零引用,lustre.version 不参与 URL 拼接,均无提示 |
config/deploy.yaml.example |
| P1-4 | 无配置合法性校验(fsname 长度/字符、index 唯一性、device 格式等) | utils/、config_module.py |
| P2-1 | modprobe/cron 持久化文件每次无条件备份+覆盖,第二次执行仍产生 .bak 并重写,破坏"第二次零变更" |
modules/lctl_tuning.py |
| P2-2 | 幂等检测"检测失败即当作需变更",无法区分"确实未完成"与"检测不可用" | utils/idempotent_utils.py 及各模块 |
| P2-3 | 包列表、离线安装通道、挂载点、striping 执行节点均不可配置 | modules/package_install.py 等 |
| P2-4 | dry-run 非零改动:mkdir -p、systemctl enable 在 apply_change 之外直接执行 |
modules/mount_targets.py、client_mount.py、stripe_tuning.py、ha_pacemaker.py |
| P2-5 | CLI --rollback 与必填位置参数语义冲突;validate() 未覆盖客户端角色 |
main.py、deployer.py |
2. 目标¶
变更完成后应达到以下可验证状态:
- 新增只读子命令
precheck:一次性核查操作机环境、节点 SSH/OS/时钟、仓库可达性、LNet 网卡、调度器作业、target 设备,并对配置做合法性校验;产出reports/precheck-<时间戳>.md,存在 BLOCK 项时退出码非 0 - 变更类子命令(
bootstrap/add-oss/add-ost/add-mdt/add-client/enable-ha/tune)执行前自动过同一套检查:真实执行存在 BLOCK 即终止;--dry-run下降级为告警并继续预演 - 系统盘判定改为由
lsblk挂载树推导(含/、/boot、/boot/efi、[SWAP]所在盘的祖先盘),删除块名白名单;推导失败按"拒绝格式化"处理,可经disk_safety.system_disks人工覆盖 - LNet 网络类型取自
lnet_nid的@后缀,支持多网卡(lnet_interfaces),探测失败不再回退eth0而是拒绝配置 - 建 target 支持多 MGS NID;客户端挂载源可用
clients[].mgs_nid显式指定 - 配置校验覆盖 fsname、index 唯一性、device 重复、backend 支持范围、未实现字段提示、HA 节点对与 VIP、striping 路径与挂载点关系等
- 幂等检测三态化(done / pending / unknown),unknown 一律中止而非变更;
modprobe/cron写入按 SHA256 比对,内容一致即零变更 - 包列表(
packages.server/client)、离线仓库(file://或本地路径)、挂载点(targets[].mount_point)、striping 执行节点(optimization.stripe_host)均可配置 --dry-run真正零系统改动;--rollback可省略位置参数;validate()覆盖客户端角色docs/SOP.md、docs/RUNBOOK.md、两个配置样例与实现体口径一致
3. 前置条件¶
| 项 | 要求 | 检查命令 |
|---|---|---|
| 操作机 | Python 3.9+,工程 .venv 可用 |
cd /home/rain/Documents/lustre_ins && .venv/bin/python --version |
| 共享库 | base_module / paramiko_module / logger_module / config_module 可导入 |
.venv/bin/python -c "import base_module, paramiko_module" |
| SSH 免密 | 操作机可免密登录各目标节点 | ssh root@192.168.31.21 hostname |
| 配置文件 | config/deploy.yaml 已按实机填写 |
cat config/deploy.yaml |
| 变更窗口 | 本次仅改工程文件与文档,不需要停服窗口;验证用的 dry-run/precheck 为只读 |
— |
| 备份可用 | precheck-hardening/backup/ 内备份完整 |
ls -R precheck-hardening/backup |
4. 影响范围¶
| 维度 | 说明 |
|---|---|
| 主机 | 操作机(工程文件);目标节点仅被只读探测(lsblk/findmnt/curl/systemctl is-active/squeue 等),不被修改 |
| 组件 | 工程代码(main.py、deployer.py、modules/*、utils/*)、配置样例、docs/SOP.md、docs/RUNBOOK.md |
| 是否停服 | 否。本次是工具与文档变更,未执行任何真实部署动作 |
| 窗口期 | 无(代码变更即时生效;验证为只读) |
| 在跑作业 | 若目标节点有 Slurm/LSF 作业,precheck 会报 BLOCK 提示"变更窗口内不得动该节点";本次不触发变更 |
| 数据影响 | 无。未执行 mkfs.lustre、未写任何节点配置 |
5. 分步操作¶
所有命令在工程根
/home/rain/Documents/lustre_ins下执行。以下序列与实现等价,以实现体为准。
5.1 备份(变更前,已执行)¶
# 工程非 git 仓库,逐文件带时间戳备份到本主题目录,并登记台账
TS=$(date +%Y%m%d-%H%M%S)
DEST=precheck-hardening/backup
mkdir -p "$DEST"
for f in main.py deployer.py modules/target_format.py modules/lnet_config.py \
modules/client_mount.py modules/firewall.py modules/lctl_tuning.py \
modules/mount_targets.py modules/fstab_persist.py modules/package_install.py \
modules/stripe_tuning.py modules/inspector.py modules/ha_pacemaker.py \
utils/idempotent_utils.py utils/lustre_state.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
# 台账见 precheck-hardening/backup/README.md
5.2 新增预检查与配置校验模块¶
对应实现文件:utils/precheck.py(新增)、utils/config_validate.py(新增)
# 预检查:操作机环境 / 节点 SSH / OS / 时钟 / 仓库 / 网卡 / 调度器 / 设备(只读)
# 实现函数:PreChecker.check_local / check_hosts / check_networks /
# check_repositories / check_devices
# 系统盘推导:derive_system_disks(),基于 lsblk -J 的挂载树,不使用块名白名单
# 配置校验:validate_config(),返回 BLOCK/WARN 清单
.venv/bin/python -m py_compile utils/precheck.py utils/config_validate.py
5.3 编排层接入门禁与新子命令¶
对应实现文件:main.py、deployer.py
# 新增 precheck 子命令;subcommand 改为可选(配合 --rollback)
# 变更类子命令执行前调用 _gate():配置校验 + 预检查
# 真实执行(无 --dry-run):存在 BLOCK 即终止
# dry-run:BLOCK 降级为告警,继续预演
# 每次预检查落盘 reports/precheck-<时间戳>.md(留痕、可复现)
.venv/bin/python main.py --help # 子命令列表应含 precheck
5.4 格式化前设备判定与多 MGS¶
对应实现文件:modules/target_format.py
# 删除 SYSTEM_DISK_BLOCKLIST 块名白名单;设备校验改调
# PreChecker.check_target_device():存在 / 整盘 / 非分区 / 未挂载 / 非 swap / 非系统盘
# 支持多 MGS:mkfs.lustre 可重复 --mgsnode=<nid>
# 新增 pre_check():逐盘只读校验,dry-run 下降级为告警
5.5 网络、挂载与防火墙¶
对应实现文件:modules/lnet_config.py、modules/client_mount.py、modules/firewall.py
# LNet:net_type 取自 nid 的 @ 后缀;新增 lnet_interfaces 多网卡;探测失败拒绝配置(不再回退 eth0)
# 客户端:clients[].mgs_nid 可显式指定挂载源;fsname 缺失时拒绝挂载
# 防火墙:端口放行判定由子串匹配改为按分隔符拆分的精确匹配(避免 988 命中 19880)
5.6 幂等三态化与可配置化¶
对应实现文件:utils/idempotent_utils.py、utils/lustre_state.py、modules/lctl_tuning.py、modules/mount_targets.py、modules/fstab_persist.py、modules/package_install.py、modules/stripe_tuning.py
# 新增三态探测:probe_state() -> done / pending / unknown
# unknown(连接层失败)一律中止,不再"检测失败即变更"
# modprobe/cron 写入前比对 SHA256,内容一致即跳过(第二次零变更)
# lctl 参数比对由子串包含改为按 "=" 后整值精确比较
# 挂载点统一由 utils/lustre_state.mount_point_for_target() 解析,支持 targets[].mount_point
# 包列表支持 packages.server / packages.client 覆盖
# striping 执行节点支持 optimization.stripe_host
5.7 dry-run 零改动修正¶
对应实现文件:modules/mount_targets.py、modules/client_mount.py、modules/stripe_tuning.py、modules/ha_pacemaker.py
# 把 mkdir -p 与 systemctl enable 从 apply_change 之外移入 action 内,
# 确保 --dry-run 不产生任何远程副作用
5.8 配置样例与文档同步¶
对应实现文件:config/deploy.yaml.example、config/deploy-expand.yaml.example、docs/SOP.md、docs/RUNBOOK.md
# 样例新增 precheck / disk_safety / packages / mount_point / lnet_interfaces /
# clients[].mgs_nid / optimization.stripe_host 等配置项与说明
# docs/SOP.md:5.1 增加 precheck 步骤、第 3 章说明预检查覆盖、第 6 章增加预检查复检、
# 第 8 章更新风险表(系统盘判定与门禁)
# docs/RUNBOOK.md:速查表与日常巡检增加 precheck,交付物清单与修订记录同步
5.9 自动门禁行为(变更类子命令)¶
# 只读预检查(独立执行,存在 BLOCK 时退出码非 0)
.venv/bin/python main.py precheck --config config/deploy.yaml
# dry-run:门禁 BLOCK 降级为告警,仍输出完整变更明细
.venv/bin/python main.py bootstrap --dry-run --config config/deploy.yaml
# 真实执行:门禁 BLOCK 即终止,不做任何变更
.venv/bin/python main.py bootstrap --config config/deploy.yaml --force
6. 验证¶
本次已在真机环境(lustre-mgs-01 = 192.168.31.21、lustre-oss-01 = 192.168.31.22,Rocky Linux 9.8)完成验证,实测结果如下。
| 项 | 命令 | 实测结果 |
|---|---|---|
| 语法与导入 | py_compile main.py deployer.py modules/*.py utils/*.py;逐个 importlib.import_module |
全部通过,无导入失败 |
| 配置校验(合法配置) | validate_config(config/deploy.yaml.example) |
输出"配置校验通过,未发现问题" |
| 配置校验(非法配置注入:backend=zfs、fsname 超长、index/设备重复、HA 缺 primary) | 同上 | 逐条给出 BLOCK/WARN,定位到 nodes.oss[0].targets[1].device 等具体路径 |
| 系统盘推导 | derive_system_disks()(桩数据:/ 与 /boot 在 sda、swap 在 nvme0n1p1) |
返回 ['nvme0n1', 'sda'],数据盘 sdb 未被误判 |
| 设备校验 | check_target_device():/dev/sda、/dev/nvme0n1、/dev/sdc、已挂载盘 |
系统盘/不存在/已挂载均报 BLOCK,可用盘报 INFO |
| 预检查子命令(真机) | .venv/bin/python main.py precheck --config config/deploy.yaml |
退出码 1(存在 BLOCK),报告落盘 reports/precheck-20260922-125043.md;INFO 正确检出 OS 为 Rocky 9.8、时间偏移 0.000s、自动探测网卡 ens2 |
| 门禁降级(真机 dry-run) | .venv/bin/python main.py bootstrap --dry-run --config config/deploy.yaml |
打印"[dry-run] 预检查存在 BLOCK 级问题:预演继续",随后输出全部变更明细;退出码 0 |
| dry-run 完整预演 | 同上 | 11 个模块(10 子模块 + 编排器)全部 success,共 18 条变更明细 |
| dry-run 零系统改动(真机核查) | 在 21/22 上核查 | /etc/yum.repos.d/lustre.repo、e2fsprogs.repo、/etc/lnet.conf、/etc/modprobe.d/lustre.conf、/etc/cron.d/lustre-tune 均不存在;/mnt/mdt0、/mnt/ost0 不存在;/etc/fstab 中 lustre 条目数 0;firewall-cmd --list-ports 为空 |
| 幂等复跑 | 再次执行 dry-run | 变更明细仍为 18 条,模块状态全部 success,无异常、无 not defined |
关键结论:--dry-run 的"零系统改动"承诺已在真机取证确认;门禁在真实执行与预演下行为符合设计(前者阻断、后者降级)。
7. 回滚¶
备份位置¶
变更前已对全部 19 个被改文件做带时间戳备份,位置与台账:
- 备份目录:
precheck-hardening/backup/(时间戳20260922-123308) - 台账:backup/README.md(逐条登记备份文件、原路径、产生时间、因何产生、回滚什么、何时可删)
回滚步骤¶
cd /home/rain/Documents/lustre_ins
TS=20260922-123308
# 逐文件恢复(示例:全部)
for f in main.py deployer.py modules/target_format.py modules/lnet_config.py \
modules/client_mount.py modules/firewall.py modules/lctl_tuning.py \
modules/mount_targets.py modules/fstab_persist.py modules/package_install.py \
modules/stripe_tuning.py modules/inspector.py modules/ha_pacemaker.py \
utils/idempotent_utils.py utils/lustre_state.py \
config/deploy.yaml.example config/deploy-expand.yaml.example \
docs/SOP.md docs/RUNBOOK.md; do
cp -p "precheck-hardening/backup/$f.bak.$TS" "$f"
done
# 新增文件不属回滚范围,可直接删除(明确文件名,不用通配)
rm -f utils/precheck.py utils/config_validate.py
# 预检查报告为只读产出物,按需保留
回滚后验证¶
.venv/bin/python -m py_compile main.py deployer.py modules/*.py utils/*.py # 预期:无输出
.venv/bin/python main.py --help | head -5 # 预期:不再含 precheck
回滚不影响任何目标节点:本次变更未在节点上留下任何配置或数据改动。
8. 风险说明¶
| 风险 | 等级 | 缓解措施 |
|---|---|---|
| 预检查误判系统盘导致拒绝合法设备 | 中 | 系统盘判定基于挂载树推导;如判定有误,可在 disk_safety.system_disks 显式覆盖(配置校验会同时给出 WARN 提示"已人工指定") |
| 自动推导失败(lsblk 输出不可解析) | 中 | 按"拒绝格式化"处理(宁可不做也不误做);报告提示用户显式声明系统盘清单 |
| 门禁误阻断合法变更 | 中 | 每个 BLOCK 项都给出节点/检查项/处置建议;--dry-run 可用于先行预演(不阻断) |
| 三态探测把"检测不可用"当作中止,导致变更未完成 | 中 | 属有意设计(不盲目变更);报告与日志明确标出"检测不可用",修复 SSH 后可重跑(幂等) |
每次变更命令都会新增一份 reports/precheck-*.md |
低 | 留痕设计,与 inspect 报告一致;按需清理旧报告 |
| 仓库地址/设备名与实机不符(真机已发现) | 高 | 见附录"遗留事项"——需用户修正 config/deploy.yaml 后才能真实执行 |
| 代码变更引入新的引用错误 | 中 | 已通过 py_compile + 全模块导入 + 真机 dry-run 全流程覆盖;模块内 validate()/pre_check() 路径均实际执行过 |
9. 附录¶
9.1 交付物清单¶
| 路径 | 内容 |
|---|---|
utils/precheck.py |
新增:安装前预检查与系统盘推导 |
utils/config_validate.py |
新增:配置合法性校验(BLOCK/WARN) |
main.py、deployer.py |
新增 precheck 子命令、变更门禁、--rollback 语义修正、validate() 覆盖客户端 |
modules/*.py(12 个) |
预检查落地、系统盘判定、多 MGS、多网卡、三态幂等、dry-run 零改动、配置化 |
utils/idempotent_utils.py、utils/lustre_state.py |
三态探测、挂载点与参数比对统一实现 |
config/deploy.yaml.example、config/deploy-expand.yaml.example |
新增配置项与说明 |
docs/SOP.md、docs/RUNBOOK.md |
与实现体口径同步 |
precheck-hardening/backup/ |
变更前备份与台账 |
precheck-hardening/SOP.md |
本文档 |
9.2 遗留事项(需用户决策,本次未改动)¶
真机预检查已报出以下阻塞项,属环境与配置不符,需修正 config/deploy.yaml 后方可真实部署:
- Lustre 仓库 404:
https://downloads.whamcloud.com/public/lustre/lustre-2.15.8/el9.7/server/返回 HTTP 404(同主机上e2fsprogs/latest/el9/返回 200)。需确认 whamcloud 上该版本对应的正确elX.Y路径段,或改用内网镜像/本地仓库(file://)。 /dev/sdb在 192.168.31.21 与 192.168.31.22 上均不存在:需按实机lsblk结果填写 MDT/OST 设备(并在确认非系统盘后填入)。
建议先执行 precheck 直到无 BLOCK 项,再进入 bootstrap。
9.3 决策记录¶
- 设备唯一性判重按"主机+设备"而非全局设备名——各节点使用同名路径(如都用
/dev/sdb)是常见拓扑,初次实现曾误报,已在验证中发现并修正。 - 多 MGS 在
mkfs.lustre侧传全部--mgsnode;客户端挂载默认取首个 MGS NID,多 MGS 场景建议用clients[].mgs_nid显式指定(避免引入未经现场验证的挂载语法)。 ha.*.vip保留为记录字段并在配置校验中给出 WARN:HA 为 stub,VIP/cib 资源需人工pcs配置,不自动创建。
10. 真实部署记录(2026-09-22 实施)¶
本章为实际执行记录,补充第 1–9 章的方案描述。目标环境:Rocky Linux 9.8 两节点 (192.168.31.21 = MGS+MDT、192.168.31.22 = OSS),虚拟化 virtio 盘,无 GPU/IB。
10.1 最终选型(与原方案的偏差)¶
原方案为 Lustre 2.15.8 LTS + el9.7。实测发现 whamcloud 在 el9 平台不提供 2.15.8 的服务端包
(该目录下只有 client/,服务端仅存在于 el8.10),故调整为:
| 项 | 取值 | 原因 |
|---|---|---|
| Lustre 版本 | 2.17.0 | el9.7 平台段唯一提供 server 包的版本 |
| 平台段 | el9.7 | 与仓库路径一致 |
| 内核 | whamcloud 定制内核 5.14.0-611.13.1_lustre.el9 |
服务端 kmod-lustre 按该内核的 ABI 构建,节点原内核(5.14.0-687.el9_8)符号不匹配 |
| 客户端包集 | [kmod-lustre, lustre](服务端 kmod 含 llite) |
el9 平台不提供与 9.8 内核匹配的 kmod-lustre-client,故客户端统一使用服务端 kmod + 定制内核 |
10.2 执行结果¶
| 阶段 | 结果 |
|---|---|
| 装包 | 5 个服务端包 + 定制内核安装成功(22 节点受网络带宽限制,下载约 20 分钟) |
| 内核切换 | 两节点重启并进入 5.14.0-611.13.1_lustre.el9,modprobe lustre 可用 |
| LNet | 两节点 lnetctl net add --net tcp --if ens2,NID 192.168.31.21/22@tcp status up |
| 格式化 | 21: /dev/vdb → edafs:MDT0000;22: /dev/vdb → edafs:OST0000 |
| 挂载 | /mnt/mdt0、/mnt/ost0 挂载成功,fstab 各一条 nofail 条目 |
| 客户端 | 21 作为客户端挂载 /mnt/lustre(192.168.31.21@tcp:/edafs),fstab 已持久化 |
| 容量 | MDT 11.1G / OST 294.4G,filesystem_summary 294.4G |
| 读写验证 | 50MB 写入,206 MB/s;读取校验通过 |
| striping 模板 | 4 类目录创建并设置(1M/16M/4M/1M,shared_files 跨全部 OST) |
| 巡检 | 11 项中 10 项通过(详见 10.4 遗留项) |
| 幂等复跑 | bootstrap --force 与 tune 复跑均 rc=0,已格式化/已挂载/内容一致项全部跳过 |
10.3 真机验证暴露并修复的缺陷¶
以下缺陷均为静态审查与本地测试无法发现、只有真实环境才暴露的问题,已全部修复并复测:
| # | 缺陷 | 现象 | 修复 |
|---|---|---|---|
| 1 | 系统盘白名单把数据盘 sdb 列为系统盘 |
按示例配置必然拒绝格式化 | 改为 lsblk -J 挂载树推导(utils/precheck.py) |
| 2 | lsblk -ndo TYPE,PKNAME,NAME 解析按固定 3 列 |
整盘无父设备时只有 2 列 → 误判"输出异常" | 改用 --pairs 键值解析 |
| 3 | 内核切换后判定逻辑错误 | 触发重启后 15s 即查内核,节点尚未重启完 → 误判失败 | 改用 boot_id 变化判定是否真的重启过,并用 systemd-run 脱离会话触发 |
| 4 | _module_ready 要求 lustre 已注册到 /proc/filesystems |
模块按需加载,该条件永不成立 → 反复重启 | 只看 modprobe -n 是否可解析 |
| 5 | LNet 持久化文件格式错误 | 自造 net_types 结构被 lnetctl import 拒绝(Malformed input. Expect 'net type') |
改为命令式 lnetctl net add + lnetctl export 落盘,并启用 lnet.service |
| 6 | 重复 net add 的容错按错误文本匹配 |
实际错误为 errno -17 cannot add LNet NI,不含 exist → 误判失败 |
改为回查 NID 是否已在位(以结果为准) |
| 7 | 幂等检测用 grep -i lustre 判断已格式化 |
Lustre 卷标是 edafs:OST0000,不含 "lustre" → 重复 mkfs 失败 |
改按卷标形态 <fsname>:<MDT/OST/MGT> 判断(blkid) |
| 8 | lfs getstripe 解析按"每行一个键值" |
实际一行含多个键值 → 取不到 stripe_size,每次重复 setstripe |
独立 utils/stripe_utils.py:正则逐对提取 + 去 lmm_ 前缀 + 单位换算 |
10.4 遗留项(需人工决策,工具已正确告警)¶
巡检"lctl 调优参数"一项报 3 条,均为配置项与 Lustre 2.17.0 不兼容,非工具缺陷:
| 参数 | 现象 | 建议 |
|---|---|---|
mdc.*.max_mod_rpcs_in_flight = 50 |
lctl set_param 返回 ERANGE(34),实际值保持 7 |
确认 2.17 的合法范围后调整配置,或移除该项 |
osc.*.max_dirty_mb = 64 |
设置后未生效 | 同上,核对 2.17 参数语义 |
llite.*.statahead_xattr |
lctl get_param 无输出(该参数在 2.17 已移除) |
从 client_tuning 中删除 |
10.5 环境与配置修正记录¶
config/deploy.yaml已按 10.1 更新(version/os_variant/baseurl/kernel 段/packages 段/设备改为/dev/vdb/新增 21 为客户端)- 变更前配置备份:
precheck-hardening/backup/config/deploy.yaml.bak.20260922-133650 - 22 节点因网络慢导致首次
dnf install超时,改为节点后台续装(复用缓存)后完成
来源:
/home/rain/Documents/lustre_ins/precheck-hardening/SOP.md| 最后更新:2026-09-22 15:11(由build/sync.py同步,请勿在站点侧修改)