SOP — scratch 生命周期清理(2026-09-24)¶
变更档案:本地层 + Lustre 两层 scratch 的生命周期与清理机制。八章结构。 日常运维与故障处置见
../docs/RUNBOOK.md第 4.11 节与第八章「场景 I」。 相关:本集群存储演进差距清单.md4.5(本变更的立项依据)、SOP-存储架子.md第八章遗留。
一、背景¶
旧集群《是否标准》五条判定中,唯一未达标的是第 5 条「临时数据有明确的生命周期与清理机制」——旧集群 epilog_eda.sh 不清理 scratch、task_prolog_eda.sh 不注入 TMPDIR,清理机制一直留在其 12.4「待补强,未实施」。
本集群已建成两层 scratch:本地 /local_scratch(三节点各 50G 独立虚盘)与 Lustre /mnt/lustre/scratch。两层都没有任何回收机制:
- 作业结束后目录不释放,50G 本地盘会被逐步写满;
TMPDIR未注入,EDA 工具编译产物默认落在 cwd 或系统/tmp,scratch 形同虚设;- 无老化兜底,异常终止(作业被杀、节点宕机)留下的目录永不回收。
触发条件:存储架子落地(2026-09-24)后,scratch 已可被用户写入,缺口从"潜在"变为"现实风险"。
二、目标¶
变更完成后应达到的可验证状态:
- 作业启动时环境内可见
TMPDIR=/local_scratch/users/<user>/<jobid>与LOCAL_SCRATCH; - 作业成功结束后,该作业的本地 scratch 目录被自动删除;
- 作业失败结束后,该目录改名
<jobid>.failed.<ts>保留现场; - 三台计算节点各有一个 systemd timer,每日清理本地 >7 天、Lustre >30 天的超期目录;
- 上述四项在节点上以文件内容(SHA256)+ timer 状态可核验;
--scratch-gc连续执行两次,第二次零变更、全跳过。
三、前置条件¶
| # | 条件 | 检查命令 | 期望 |
|---|---|---|---|
| 1 | 三节点 SSH 可达(root 免密) | ssh root@192.168.31.18 echo OK |
OK |
| 2 | 节点具备 systemd | ssh root@192.168.31.18 'command -v systemctl' |
输出路径 |
| 3 | 本地 scratch 已挂载 | ssh root@192.168.31.18 'findmnt /local_scratch' |
有挂载行 |
| 4 | Lustre 已挂载 | ssh root@192.168.31.18 'findmnt /mnt/lustre' |
有挂载行 |
| 5 | 作业钩子源文件在工程内 | ls slurm_conf_old/pro_epilog/{task,epilog}_eda.sh |
文件存在 |
| 6 | 队列无在跑作业(建议) | ssh root@192.168.31.16 '/usr/local/slurm/bin/squeue' |
空 |
前 5 项已由实现体 pre_check() 落为可执行检查,不通过即终止且不做任何变更。
四、影响范围¶
| 项 | 内容 |
|---|---|
| 主机 | 三台计算节点 slurm-work-01/02/03(192.168.31.18/.19/.20) |
| 写入文件 | /usr/local/slurm/{task_prolog_eda.sh,epilog_eda.sh,slurm-scratch-gc.sh}、/etc/systemd/system/slurm-scratch-gc.{service,timer} |
| 是否停服 | 否。不改 slurm.conf、不重启 slurmctld/slurmd(钩子脚本内容变更在下次调用时生效) |
| 窗口期 | 无。分批逐台推进(batch_size: 1) |
| 在跑作业 | 变更本身不影响在跑作业;但钩子脚本在更新后的作业结束时立即生效,故建议队列为空时执行 |
| 不可逆点 | 无格式化类动作;删除动作由 Epilog(仅本作业目录)与 GC(仅超期目录)承担 |
五、分步操作¶
以下为与实现等价的操作序列,以实现体为准;实现体
scratch_gc_deployer.py自带幂等、备份与错误处理。 高危动作已标注,对应的备份由实现体backup()自动完成(见第七章)。
cd /home/rain/Documents/slurm/slurm_ins # 工程根(配置路径相对该目录)
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc --dry-run # 预演:只输出变更清单,零改动
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc # 正式执行
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc # 复跑:预期待执行 0、全跳过(幂等验收)
实现体内部顺序(每台节点):① 备份既有文件(高危前留档)→ ② 写 3 个脚本 + 2 个 unit(SHA256 一致即跳过)→ ③ systemctl daemon-reload → ④ systemctl enable --now slurm-scratch-gc.timer。
节点侧落地后的结构:
/local_scratch/users/<user>/<jobid> # 作业级目录(TaskProlog 注入 TMPDIR 并创建)
/local_scratch/users/<user>/<jobid>.failed.<ts> # 失败作业保留现场(高危:改名动作)
/usr/local/slurm/task_prolog_eda.sh # 注入 LOCAL_SCRATCH / TMPDIR(高危:覆盖)
/usr/local/slurm/epilog_eda.sh # 本作业目录清理(高危:删除/改名)
/usr/local/slurm/slurm-scratch-gc.sh # 老化兜底(高危:按保留期删除)
/etc/systemd/system/slurm-scratch-gc.{service,timer}
六、验证¶
| # | 验证项 | 命令 | 实测结果(2026-09-24) |
|---|---|---|---|
| 1 | 脚本内容一致 | sha256sum /usr/local/slurm/epilog_eda.sh(对比工程模板) |
一致(9af06fee…) |
| 2 | timer 已启用 | systemctl is-enabled slurm-scratch-gc.timer; systemctl is-active … |
enabled / active(三节点) |
| 3 | 下次触发时间 | systemctl show slurm-scratch-gc.timer -p NextElapseUSecRealtime --value |
03:53–03:59(含 30m 随机延迟) |
| 4 | TMPDIR 注入 | 提交作业并 echo $TMPDIR |
TMPDIR=/local_scratch/users/root/5 |
| 5 | 成功作业清理 | 提交成功作业后 ls /local_scratch/users/<user>/ |
目录消失,日志 scratch purged |
| 6 | 失败作业保留 | 提交 exit 3 作业后同上 |
出现 <jobid>.failed.<ts>,日志 scratch kept (exit=768) |
| 7 | 老化脚本零副作用 | slurm-scratch-gc.sh --dry-run |
只列出待清目录,退出码 0,目录未被删 |
| 8 | 老化脚本实际清理 | 造 9 天前(本地)/31 天前(Lustre)目录后执行 | 两目录均被删,日志 DONE … 清理完成 |
| 9 | 幂等 | --scratch-gc 复跑 |
待执行 0 / 跳过 18 / 退出码 0 |
| 10 | 节点无异常 | sinfo -N -o '%N %T' |
三节点 idle(无 DRAIN) |
| 11 | 回滚可精确回到变更前 | --scratch-gc --rollback 后比对 SHA 与 unit |
三节点 epilog 56161419… / task_prolog 27683f15…(均等于变更前原始脚本);unit、timer、gc 脚本全部移除 |
| 12 | 回滚后重新部署可复原 | 再次执行 --scratch-gc |
39 条变更全部写入,timer 恢复 enabled/active;端到端与幂等复验均通过 |
七、回滚¶
备份位置:各节点 /var/backups/slurm/scratch_gc_<YYYYmmdd_HHMMSS>/,内含被覆盖前的
<原名>.bak(不存在则跳过);台账见 backup/README.md。工程侧改动前的文件备份在
backup/scratch_gc/。
实现体自带回滚:
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc --rollback
回滚动作(逐台):① systemctl disable --now slurm-scratch-gc.timer → ② 删除 service/timer →
③ systemctl daemon-reload → ④ 从最早一次 /var/backups/slurm/scratch_gc_*/(即本能力首次
运行前的原始脚本)恢复 task_prolog_eda.sh 与 epilog_eda.sh → ⑤ 删除 slurm-scratch-gc.sh。
取「最早一次」而非「最近一次」:本能力每次执行都会新建一个备份点,最近的往往对应某次修复前的 中间版本;只有最早的那份才是变更前的原始脚本。
回滚后验证:
ssh root@192.168.31.18 'systemctl is-enabled slurm-scratch-gc.timer' # 预期 not-found / disabled
ssh root@192.168.31.18 'sha256sum /usr/local/slurm/epilog_eda.sh' # 预期等于回滚前模板值
紧急止血(不整体回滚):若 Epilog 在生产期出现异常,可临时移除 Epilog 钩子并
scontrol reconfigure(见 ../docs/RUNBOOK.md 第八章「场景 I」),不必停服。
八、风险说明¶
| # | 风险 | 缓解措施 |
|---|---|---|
| 1 | Epilog 返回非 0 → 节点被 DRAIN(本变更实测踩中,见附录 C) | Epilog 移除 set -u;清理逻辑包成函数并以 \|\| true 调用;全脚本以 exit 0 收尾。上线后必须先做成功/失败双路径实测 |
| 2 | Epilog 误删用户数据 | 路径白名单(必须形如 <root>/<user>/<jobid>)+ 空值拒绝 + 只删本作业目录;失败作业一律改名保留 |
| 3 | 老化脚本误删 | 只删超期目录;根路径绝对路径校验;--dry-run 可先审阅;保留期可配置 |
| 4 | SLURM_JOB_EXIT_CODE 语义差异 |
实测为 wait status(失败作业得 768,非 3)。判据是「等于 0 即成功」,非 0 一律保留,方向安全 |
| 5 | TaskProlog 注入失败影响作业启动 | mkdir 与 source 均带 \|\| true;失败仅不注入变量,不阻塞作业 |
| 6 | 清理动作与作业写盘竞争 | Epilog 在作业进程全部退出后执行;GC 只处理 7/30 天前的目录 |
| 7 | 覆盖 /usr/local/slurm/ 脚本 |
走工程模板 + 部署器分发(禁止手改);分发前自动备份 |
| 8 | Timer 与集群既有 systemd 惯例冲突 | 不使用 Requires=,仅 WantedBy=timers.target;随机延迟 30m 削峰 |
附录 A:交付物清单¶
| 文件 | 说明 |
|---|---|
scratch_gc_deployer.py |
实现体:钩子分发 + 老化脚本/unit 下发 + 三态幂等 + 分批 + 回滚 |
scratch_gc_assets.py |
配置解析与渲染(GC 脚本、service、timer) |
slurm_conf_old/pro_epilog/task_prolog_eda.sh |
追加 LOCAL_SCRATCH / TMPDIR 注入 |
slurm_conf_old/pro_epilog/epilog_eda.sh |
追加本作业目录清理(退出码分级) |
main.py |
新增 --scratch-gc 子命令 |
config/slurm_deploy.yaml / .example |
新增 scratch_gc 段 |
backup/scratch_gc/ |
工程侧改动前备份 + 台账 |
附录 B:执行记录(2026-09-24)¶
| 项 | 结果 |
|---|---|
| dry-run | 3 节点 × 5 项待变更,退出码 0,零改动 |
| 首次正式执行 | 39 条记录 / 全部写入 / 退出码 0 |
| 首次功能验证 | 失败:Epilog rc=1,slurm-work-01 被 DRAIN(见附录 C) |
| 修复后重新分发 | 待执行 15 / 跳过 12 / 退出码 0 |
| 节点级验证 | 成功作业 purged、失败作业 kept(三节点均生效) |
| 老化验证 | 本地 9 天前、Lustre 31 天前目录被清理;dry-run 未删任何东西 |
| 幂等复跑 | 待执行 0 / 跳过 18 / 退出码 0 |
| 节点恢复 | scontrol update NodeName=slurm-work-01 State=RESUME,三节点 idle |
| 回滚演练(23:06) | --scratch-gc --rollback:三节点 epilog/task_prolog SHA 精确回到变更前原始值(56161419… / 27683f15…),unit、timer、gc 脚本全部移除 |
| 重新部署(23:07) | 再次执行 --scratch-gc:39 条变更写入、timer 恢复 enabled/active;端到端复验(成功→purged、失败→kept .failed.<ts>)与幂等复跑(待执行 0)均通过 |
附录 C:执行过程中的问题与修复¶
| # | 现象 | 根因 | 修复 |
|---|---|---|---|
| 1 | Epilog 日志只有 === Epilog start ===、无 done;slurmd 报 error: epilog failed: rc:1,节点 slurm-work-01 被 DRAIN |
Epilog 保留 set -euo pipefail;新段落 . /etc/profile.d/eda_scratch.sh 时,片段内 $USER 在 slurmd 环境下未导出,set -u 触发 unbound variable,shell 直接终止(\|\| true 拦不住 shell 级错误)→ 返回 1 |
① Epilog 改为 set -eo pipefail(去掉 -u,并注释说明原因);② 清理逻辑包成 cleanup_scratch() 函数并以 \|\| true 调用;③ TaskProlog 的 source 段加 set +u / set -u 保护 |
| 2 | 手动模拟(bash -x)一直成功,掩盖了问题 |
手动模拟走 SSH 登录 shell,环境里有 USER;与 slurmd 的真实执行环境不同 |
改为用 env -i PATH=… bash script 复现 slurmd 的最小环境后再验证 |
| 3 | 回滚可能落到「修复前」的中间版本 | 实现体原本取最近一次备份点,而每次执行都会新建备份点 | 改为取最早一次备份点(= 本能力首次运行前);本次演练已验证该语义正确 |
经验:验证 Slurm 钩子脚本必须用"最小环境"复现(
env -i),登录 shell 下测通不等于 slurmd 下能跑;Epilog 类脚本一旦返回非 0 会直接 DRAIN 节点,代价远高于脚本本身的复杂度。
来源:
/home/rain/Documents/slurm/slurm_ins/eda_storage_gap/SOP-scratch生命周期清理.md| 最后更新:2026-09-24 23:24(由build/sync.py同步,请勿在站点侧修改)