跳转至

部署工程通用性与安装前预检查加固 — 变更实施手册(SOP)

变更对象:/home/rain/Documents/lustre_ins 部署工程(Python 实现体) 变更类型:交付物变更(代码 / 配置样例 / 文档),不涉及生产服务的安装与停服 实现体为准:本文 shell 代码块仅用于阅读、复核与手工复现,不承担幂等与错误处理 运行期使用手册见 ../docs/RUNBOOK.md

目录

  1. 背景
  2. 目标
  3. 前置条件
  4. 影响范围
  5. 分步操作
  6. 验证
  7. 回滚
  8. 风险说明
  9. 附录

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 后方可真实部署:

  1. 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://)。
  2. /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 同步,请勿在站点侧修改)