跳转至

SOP — Lustre 挂载自愈扩面(纳入 LSF 执行节点 .25–.27)

  • 主题目录:lustre_mount_selfheal_extend_20261007/
  • 执行日期:2026-10-07
  • 类型:C 类变更(自愈对象扩面 + 部署器缺陷修复 + 节点间免密补齐)
  • 关联:lustre_ins HANDOFF §3.1 #17(本变更结案);slurm_ins HANDOFF §3 #16–17 移交项

1. 背景

机制 B(控制节点 .16 上的 slurm-cluster-health.timer,15 min)会巡检 Lustre 客户端挂载并在 failed 时自愈,但巡检对象只含 Slurm 侧节点(.18/.19/.20/.28)。LSF 执行节点 .25–.27 与它们同源配置、同批于 2026-09-29 挂载失败,却不在巡检范围内,潜伏 8 天无人察觉(lustre_ins HANDOFF §3.1 #14/#17)。

执行中另外查出两个根因(均本次修复):

  1. sync_status() 不比对 cluster.json:该文件只在 execute() 阶段下发,配置源改动会被「零变更 → 幂等跳过」整段吞掉 → 改配置静默失效。首次 dry-run 即报「零变更」暴露此问题。
  2. 节点间免密缺失 + 探测失败被静默吞掉:.16/.17 → .25–.27 的 root 免密不存在;而 _ensure_lustre_mounts() 对 SSH 探测失败与「非 failed」的判据混同(都 continue),导致自愈形同虚设且无任何痕迹——注入验证时首次巡检完全不报,才查出此点。

连带纠正一处历史误判:此前把「Warning: Permanently added ... 出现在首行」当作免密成功(head -1 只取到警告行),实际第二行是 Permission denied (publickey)。探测只取首行会误判。

2. 目标

项 可验证状态
巡检对象 .16 上 /etc/slurm-health/cluster.json 的 lustre_guards 含 7 台(原 4 台 + .25/.26/.27)
自愈有效 对 .25–.27 任一制造 failed,巡检能 ⚠️ 触发并 ✅ 恢复
免密 .16/.17 → .25/.26/.27 root 免密可用(严格模式亦通过)
判据三分 探测失败(SSH 不可达)与「非 failed」分别报出,不再静默
幂等 复跑零变更;注入后已还原,无残留
边界 不改自愈动作逻辑、不改调度状态、不重启节点

3. 前置条件

  • root 免密可达 .16/.17(控制节点)与 .25–.27(LSF 执行节点)
  • .16 上 slurm-cluster-health.timer 已 enabled + active(机制 B 已就位)
  • 工程 venv:/home/rain/Documents/slurm/slurm_ins/.venv/
  • 注入验证前须确认目标节点无在跑作业:本次核 .27(bjobs -u all -m lsf-work-03 → No unfinished job found)

4. 影响范围

对象 变更内容 停服 窗口
.16 / .17 下发新的 cluster.json(+3 台)与 cluster_health_check.py(判据三分) 否 秒级
.16/.17 → .25–.27 追加 root 公钥到 authorized_keys(.17 另补 known_hosts) 否 秒级
.27 注入验证:临时改 fstab 挂载源 → failed → 自愈恢复(期间 /mnt/lustre 不可用约 5 分钟) 否 约 5 min
  • 不重启任何节点、不动 Lustre 服务端、不改自愈动作逻辑;.18–.20/.28 行为不变
  • 注入窗口已确认 .27 无在跑作业

5. 分步操作

步骤 1:扩展自愈对象清单(工程侧)

health_assets.py::lustre_guards() 由「compute + submit」扩为「compute + submit + health_ensure.lustre_extra_guards」——后者用于不在本工程 nodes 清单内、但同源配置的主机。

# 配置侧(config/slurm_deploy.yaml 的 slurm.health_ensure 段)
#   lustre_extra_guards:
#     - {hostname: "lsf-work-01", ip: "192.168.31.25"}
#     - {hostname: "lsf-work-02", ip: "192.168.31.26"}
#     - {hostname: "lsf-work-03", ip: "192.168.31.27"}

坑:lustre_guards() 的去重键是 ip,清单里重复 IP 会被静默去重——新增对象时须核对 ip 字段。

步骤 2:补 sync_status() 对 cluster.json 的比对(缺陷修复)

该文件此前只在 execute() 下发,配置改动会被幂等短路吞掉。修法:在 watchdog 分支内按同一口径(sha256 比对 /etc/slurm-health/cluster.json)登记 ChangeRecord。

# 等价核对命令(部署器对每台控制节点执行的判据)
sha256sum '/etc/slurm-health/cluster.json' | awk '{print $1}'

预期:与本地渲染结果不一致时登记 [CONFIG],dry-run 应显示「待执行变更 ≥ 1」。 失败判断:dry-run 仍报「零变更」→ 比对未生效,不得继续。

步骤 3:探测失败三分开报(缺陷修复)

slurm_health_ensure/cluster_health_check.py::_ensure_lustre_mounts() 原判据:

systemctl is-failed mnt-lustre.mount >/dev/null 2>&1; echo RC=$?

原来仅以 "RC=0" not in out 判断,把「SSH 不通(输出为空)」也当作「非 failed」静默跳过。现增加 "RC=" not in out 分支并记 WARNING。

# 手工核对某台是否真的可探测(注意:不要只看首行,known_hosts 警告会盖住错误行)
ssh -o BatchMode=yes root@<控制节点> \
  "ssh -o BatchMode=yes -o ConnectTimeout=5 root@<目标机> \
   'systemctl is-failed mnt-lustre.mount >/dev/null 2>&1; echo RC=\$?'"

预期:输出 RC=0(failed)或 RC=1(非 failed);输出为空或含 Permission denied 即免密缺失。

步骤 4:补齐节点间 SSH 免密

# ① 取控制节点公钥(.16 与 .17)
ssh root@192.168.31.16 'cat /root/.ssh/id_*.pub' | head -1 > /tmp/k16.pub
ssh root@192.168.31.17 'cat /root/.ssh/id_*.pub' | head -1 > /tmp/k17.pub
# ② 备份并追加到三台目标机
for t in 25 26 27; do
  scp /tmp/k16.pub /tmp/k17.pub root@192.168.31.$t:/tmp/
  ssh root@192.168.31.$t "cp -n /root/.ssh/authorized_keys /root/.ssh/authorized_keys.bak.\$(date +%Y%m%d-%H%M%S); \
    mkdir -p /root/.ssh && chmod 700 /root/.ssh && touch /root/.ssh/authorized_keys; \
    cat /tmp/k16.pub /tmp/k17.pub >> /root/.ssh/authorized_keys; \
    sort -u /root/.ssh/authorized_keys -o /root/.ssh/authorized_keys; \
    chmod 600 /root/.ssh/authorized_keys"
done
# ③ `.17` 首次连接需写入 known_hosts(否则严格模式报 Host key verification failed)
ssh root@192.168.31.17 'for ip in 25 26 27; do \
  ssh -o StrictHostKeyChecking=no -o BatchMode=yes root@192.168.31.$ip hostname; done'

坑:authorized_keys 必须 600、目录 700,否则 sshd 拒绝;sort -u 后写回可去重。巡检脚本用 StrictHostKeyChecking=no,故 known_hosts 只影响人工核查。

步骤 5:预演与下发

cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python main.py --config config/slurm_deploy.yaml --health-ensure --dry-run
.venv/bin/python main.py --config config/slurm_deploy.yaml --health-ensure
.venv/bin/python main.py --config config/slurm_deploy.yaml --health-ensure   # 幂等复跑

预期:第 1 次「待执行变更 ≥ 1」(cluster.json / cluster_health_check.py),第 3 次「待执行 0、幂等跳过」。

步骤 6:验证(见第 6 章)

6. 验证

① 清单生效

ssh root@192.168.31.16 'python3 -c "import json;d=json.load(open(\"/etc/slurm-health/cluster.json\"));print([g[\"ip\"] for g in d[\"lustre_guards\"]])"'

实测:['.18', '.19', '.20', '.28', '.25', '.26', '.27'](7 台)✅

② 端到端自愈(注入验证,核心验收)

阶段 操作 实测结果
注入 .27 挂载源改为 192.168.31.199@tcp:/edafs(不存在)→ systemctl start start_rc=1、is_failed=failed ✅
巡检(源仍错) --fix ⚠️ lsf-work-03: mnt-lustre.mount 处于 failed,执行自愈 + ❌ 自愈失败,需人工介入 ✅
还原 cp /etc/fstab.bak.inject.* /etc/fstab(不手动 restart,保持 unit 待自愈) fstab 复原、fstab_lines=1 ✅
巡检(源已对) --fix ⚠️ lsf-work-03: ... 执行自愈 + ✅ lsf-work-03: /mnt/lustre 已恢复 ✅
终态 .27 active、选项含 noatime、rain01 写入 WRITE_OK ✅

③ 判据三分(负向验证):临时把 cluster.json 中 .27 改为不可达 .199 后跑 --fix →

⚠️ lsf-work-03(192.168.31.199): 无法探测 mnt-lustre.mount(SSH 不可达?)——已跳过,请检查控制节点到该机的免密

随后已还原(残留 199 计数 = 0)✅

④ 幂等:部署器复跑「待执行 0、幂等跳过 5~14」;.27 复跑 systemctl start 零变更。

7. 回滚

备份位置(逐条见 backup/README.md;远程原位 + 本目录回传副本):

备份 位置
工程三文件 backup/slurm_ins/(health_assets.py、config/slurm_deploy.yaml、slurm_health_ensure/cluster_health_check.py)
.25–.27 authorized_keys backup/<IP>/root/.ssh/authorized_keys.bak.20261007-092641(远程原位同名)
.27 注入前 fstab backup/192.168.31.27/etc/fstab.bak.inject.20261007-092355(远程原位同名)
.16 cluster.json /root/cluster.json.bak.20261007-093035(远程原位,负向验证用,已还原)
# A. 回滚工程文件
cd /home/rain/Documents/slurm/slurm_ins
ts=20261007-092159   # 见 backup/slurm_ins/ 实际时间戳
cp lustre_mount_selfheal_extend_20261007/backup/slurm_ins/health_assets.py.bak.$ts health_assets.py
cp lustre_mount_selfheal_extend_20261007/backup/slurm_ins/config/slurm_deploy.yaml.bak.$ts config/slurm_deploy.yaml

# B. 回滚管理端(恢复旧脚本与旧 cluster.json)
.venv/bin/python main.py --config config/slurm_deploy.yaml --health-ensure

# C. 回滚目标机免密
for t in 25 26 27; do
  ssh root@192.168.31.$t "cp /root/.ssh/authorized_keys.bak.20261007-092641 /root/.ssh/authorized_keys"
done

回滚后验证:cluster.json 的 lustre_guards 回到 4 台;.16 巡检脚本恢复旧版;.25–.27 的 authorized_keys 无 .16/.17 公钥。

回滚触发条件(量化):① 部署器 dry-run 报出预期外的变更;② 复跑非零变更;③ 巡检脚本报错或对 .18–.20 误报;④ 任一目标机 /mnt/lustre 由 active 变非 active。任一命中即回滚。

8. 风险说明

风险 缓解
改 authorized_keys 出错导致锁死 SSH 先备份;-n 不覆盖;ssh-copy-id 式追加而非替换;本机 .249 侧通道始终可用
注入验证期间 .27 不可用 事前确认无在跑作业;窗口约 5 min;不重启节点
自愈脚本误判导致误 start 判据仍为「仅 failed 才动」,未放宽;reset-failed + start 对 active 单元是 no-op
三态判据改动引入新误报 负向验证已覆盖(不可达 → WARNING);常规巡检无新增告警

9. 遗留

  1. .21 未纳入:lustre_ins 的 .21(MGS+MDT 同机客户端)同样有过挂载 failed,但属 Lustre 服务端节点,是否纳入本巡检需决策(纳入即 lustre_extra_guards 多加一行)。
  2. 免密未进实现体:节点间免密目前是手工步骤(既有 SOP 步骤 4),部署器 pre_check 不校验——建议加「自愈对象 SSH 可达性」预检,把本次这类静默失效挡在部署前。
  3. LUSTRE_GUARDS 注入方式:脚本经 render_watchdog_py() 渲染注入 _CFG.get("lustre_guards"),模板源内无该常量——后续改模板时勿在模板里直接引用未定义全局名。

来源:/home/rain/Documents/slurm/slurm_ins/lustre_mount_selfheal_extend_20261007/SOP.md | 最后更新:2026-10-07 10:20(由 build/sync.py 同步,请勿在站点侧修改)