跳转至

SOP — scratch 老化清理加排除能力

主题:scratch_gc_exclude_20261007|类型:C 类变更|状态:已落地并验证(2026-10-07) 对象:slurm_ins 的 scratch 生命周期清理(--scratch-gc) 关联:HANDOFF §3 #15(本项即其结案)

1. 背景

老化清理脚本按 find <root> -mindepth N -maxdepth N -type d -mtime +D ... -exec rm -rf 匹配后删除, 它假定所匹配的目录就是作业目录。而 Lustre scratch 的实际布局是 /mnt/lustre/scratch/{users/<用户>/<jobid>, projects/<项目>/<X>, tmp},深度 3 —— 于是 任何在 users/<用户>/ 下自建的长期目录(EDA 仿真常见的 virtuoso/)超 30 天都会被整棵删除, 且删除不可恢复(rm -rf,无快照)。

触发来源:2026-10-06 下发的待接手任务(HANDOFF #15)。前置 lustre_ins §3.1 #13 (普通用户写 Lustre 被拒)已于 2026-10-06 结案,故本项可开工。

实测现状:2026-10-07 探测 /mnt/lustre/scratch/users/ 下目前只有 root、无二级目录 —— 风险尚未实际发生,本次为预防性修复。

2. 目标

  1. purge() 支持逐层排除;排除项来自 yaml,渲染为 find 的 -not -path。
  2. 默认登记 users/*/virtuoso(相对 lustre_root)。
  3. 三台计算节点(.18/.19/.20)下发到位,幂等复跑零变更。
  4. 非法排除模式(绝对路径、含 ..、含 shell 元字符)被 validate() 拦下,不下发。

3. 前置条件

  • 管控机(本机)对 .18/.19/.20 root 免密 ssh 可用;工程 .venv/ 可用。
  • 具备 config/slurm_deploy.yaml(600,含凭据,不入文档)。
  • 不需停服:本变更不重启任何 daemon,也不触碰在跑作业;脚本仅在每日 03:30 被 timer 调用。
  • 执行前确认三台节点 /mnt/lustre 与 /local_scratch 均已挂载(否则 purge() 会走 SKIP 分支)。

4. 影响范围

项 内容
主机 slurm-work-01/02/03(192.168.31.18/19/20)
变更文件 /usr/local/slurm/slurm-scratch-gc.sh(内容替换)
不变 slurm-scratch-gc.service / .timer、作业钩子(task_prolog_eda.sh / epilog_eda.sh)
停服 不需要
窗口 任意;避开 03:30 触发点即可(非必须)
在跑作业 不受影响

5. 分步操作

步骤 1:实现体加排除能力

purge() 增加 shift 4 与排除数组,三处 find 全部挂上 "${excludes[@]}"; render_gc_script() 把 yaml 的排除项渲染成 "pat" 形式的调用参数;validate() 增加白名单校验。

注意与坑 - 排除模式按相对各层根路径的 glob 处理,渲染时拼成 <root>/<pat> —— 因而配置里不能以 / 开头, 否则会拼成 <root>//abs 而永不匹配(validate() 直接拦下)。 - 脚本带 set -euo pipefail:空数组 "${excludes[@]}" 展开为零个参数,需 bash ≥ 4.4 才不触发 unbound variable。目标节点为 Rocky 9.8(bash 5.1),满足;换更老发行版时须复核。 - 排除模式会进入生成的 shell,故用白名单正则 ^[A-Za-z0-9_][A-Za-z0-9_./*-]*$ 拦住引号、$、反引号等。

cd /home/rain/Documents/slurm/slurm_ins
python3 -m py_compile scratch_gc_assets.py && echo "py_compile OK"
grep -n 'not -path\|excludes+=\|EXCLUDE_PATTERN_RE' scratch_gc_assets.py

预期:编译无输出(rc=0);grep 命中 EXCLUDE_PATTERN_RE 定义、excludes+=( -not -path ... )、purge() 注释。 失败判断:编译报错 → 修正后重来,勿下发。 实现体:scratch_gc_assets.py(render_gc_script / validate / _exclude_patterns)。

步骤 2:配置登记排除项

cd /home/rain/Documents/slurm/slurm_ins
grep -n -A6 '^  exclude_patterns:' config/slurm_deploy.yaml

预期:local: [] 与 lustre: 下 - "users/*/virtuoso"。 新增排除项时只改这里,不改脚本。

步骤 3:离线自测(上机前必做)

坑:本次自测曾因用例写错报一次 FAIL(本地层保留 7 天,用例却把 10 天的目录当"未超期"), —— 失败先怀疑用例,再怀疑实现。

cd /home/rain/Documents/slurm/slurm_ins/scratch_gc_exclude_20261007
python3 selftest_scratch_gc_exclude.py

预期:17 项全 PASS,结论「全部通过」,退出码 0。覆盖:真实 yaml 解析 / validate 通过 / bash -n 语法 / dry-run 不列被排除目录 / 实跑保留被排除目录 / 反向对照(无排除时该目录确实被删) / 非法与注入模式被拦。

步骤 4:dry-run 预演(必做,须实测零改动)

cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc --dry-run

预期:rc=0;「待执行变更」仅 3 条 slurm-scratch-gc.sh - 写入/更新(+ 备份条目), 幂等跳过 覆盖钩子脚本 / service / timer。实测零改动(dry-run 不写真机)。 失败判断:出现钩子脚本或 timer 的待变更项 → 说明有意外漂移,先查清再继续。

步骤 5:真实下发

等价底层操作(实现体:scratch_gc_deployer.py,按批 1 台串行;实际执行以实现体为准):

# 1) 管控机渲染脚本(内容见 backup/render/slurm-scratch-gc.sh.after.20261007-024425)
install -m 0755 /dev/null /tmp/slurm-scratch-gc.sh   # ← 以渲染结果填充,勿手改
# 2) 逐台分发 + 校验
for h in 192.168.31.18 192.168.31.19 192.168.31.20; do
  scp -p /tmp/slurm-scratch-gc.sh root@$h:/usr/local/slurm/slurm-scratch-gc.sh
  ssh root@$h 'systemctl daemon-reload; sha256sum /usr/local/slurm/slurm-scratch-gc.sh'
done

预期:三台 sha256 一致(本次 前 16 位 7a725383960f918a);daemon-reload 无输出。 失败判断:任一 scp 失败或 sha 不一致 → 停在本批,按第 7 章回滚该主机后再试。

步骤 6:幂等复验(必做)

cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc

预期:待执行变更 0 / 幂等跳过 18 / 退出码 0,日志含「状态已一致,零变更」。 失败判断:仍有变更项 → 内容未真正收敛,查渲染是否被其他配置项影响。

步骤 7:节点侧验收

for h in 192.168.31.18 192.168.31.19 192.168.31.20; do
  echo "== $h =="
  ssh root@$h "grep -n 'not -path' /usr/local/slurm/slurm-scratch-gc.sh; systemctl is-active slurm-scratch-gc.timer"
done
ssh root@192.168.31.18 "/usr/local/slurm/slurm-scratch-gc.sh --dry-run"   # 清单不应含被排除目录

预期:每台 2 处 not -path、timer=active;dry-run 输出不含 virtuoso。

6. 验证

项 命令 本次结果
离线自测 python3 selftest_scratch_gc_exclude.py 17/17 PASS,含反向对照
语法 bash -n(自测内) 通过
dry-run 零改动 main.py ... --scratch-gc --dry-run rc=0,仅 gc 脚本待变更
实跑下发 main.py ... --scratch-gc rc=0,待执行 9
幂等 复跑同命令 待执行 0 / 跳过 18,rc=0
节点一致性 sha256sum ×3 一致(7a725383960f918a)
规则已渲染 grep 'not -path' 每台 2 处;含 users/*/virtuoso
timer 存活 systemctl is-active active ×3

7. 回滚

备份存放:本主题 backup/(台账见 backup/README.md);远端由部署器留 /var/backups/slurm/scratch_gc_20261007_024303/(改前的脚本与 service/timer 原件)。

# 方案 A(推荐):用部署器回滚入口恢复到改前状态
cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc --rollback

# 方案 B:从远端备份逐台手工恢复
for h in 192.168.31.18 192.168.31.19 192.168.31.20; do
  ssh root@$h 'cp -p /var/backups/slurm/scratch_gc_20261007_024303/slurm-scratch-gc.sh /usr/local/slurm/slurm-scratch-gc.sh && systemctl daemon-reload && sha256sum /usr/local/slurm/slurm-scratch-gc.sh'
done

回滚后验证:三台 sha 一致且等于改前值;systemctl is-active slurm-scratch-gc.timer 仍 active; --dry-run 可正常执行。 注意:回滚只影响「清理排除」这一能力,不含历史数据;已删除的目录无法由本回滚找回。

8. 风险说明

风险 等级 说明与缓解
排除模式写错导致该删的没删 中 只保护不删除 —— 后果是 scratch 堆积而非数据丢失;靠 du 与日志「排除 N 条规则」观察
排除模式写错导致误删依旧 中 离线自测含反向对照与 dry-run 断言;新增规则后必须走步骤 3+4
空数组 "${excludes[@]}" 在旧 bash 触发 unbound variable 低 目标节点 bash 5.1;换发行版时须复核(已写入步骤 1 的坑)
模式注入到生成的 shell 低 validate() 白名单拦下引号/$/反引号,实测有断言
批量下发中断造成节点间不一致 低 分批 1 台;失败即停该批;用 sha256 逐台核对
流程缺口:改动前未做本地源文件快照 中 本工程非 git 仓库,改动前未按规范留 .bak;远端部署器备份可恢复节点态,但本地源文件的改前版本已不可取得(当前仅存改后基准)。下轮同类改动须先快照源文件再动手(2026-10-05 Lmod 轮曾犯同款,见 lmod_site_package_20261005/backup/README.md)

附录 A:交付物清单

文件 说明
selftest_scratch_gc_exclude.py / selftest_out.txt 离线自测脚本与 17/17 通过的结果
deploy_run1.log / deploy_run2.log 真实下发与幂等复跑日志
backup/slurm_ins/scratch_gc_assets.py.after.* 改后实现体基准(14805 B)
backup/render/slurm-scratch-gc.sh.after.* 改后渲染产物(2824 B)
backup/README.md 备份台账(含远端备份路径与流程缺口登记)
实现体改动 scratch_gc_assets.py、config/slurm_deploy.yaml(scratch_gc.exclude_patterns)
文档联动 docs/RUNBOOK.md(scratch 生命周期段新增「清理排除」)、docs/HANDOFF.md(#15 结案)

附录 B:决策经过

  • 备选方案「只删 basename 形如 jobid 的目录」语义更准,但会改变删除判据、可能漏清真正的超期目录, 且需与 projects/ 层(<项目>/<X> 同样非 jobid)分别定义 —— 本次按 HANDOFF 既定口径先落「排除」, 更彻底的「jobid 白名单」可作为后续独立决策项。

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