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. 目标¶
purge()支持逐层排除;排除项来自 yaml,渲染为find的-not -path。- 默认登记
users/*/virtuoso(相对lustre_root)。 - 三台计算节点(
.18/.19/.20)下发到位,幂等复跑零变更。 - 非法排除模式(绝对路径、含
..、含 shell 元字符)被validate()拦下,不下发。
3. 前置条件¶
- 管控机(本机)对
.18/.19/.20root 免密 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同步,请勿在站点侧修改)