跳转至

SOP — scratch 生命周期清理(2026-09-24)

变更档案:本地层 + Lustre 两层 scratch 的生命周期与清理机制。八章结构。 日常运维与故障处置见 ../docs/RUNBOOK.md 第 4.11 节与第八章「场景 I」。 相关:本集群存储演进差距清单.md 4.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 已可被用户写入,缺口从"潜在"变为"现实风险"。

二、目标

变更完成后应达到的可验证状态:

  1. 作业启动时环境内可见 TMPDIR=/local_scratch/users/<user>/<jobid> 与 LOCAL_SCRATCH;
  2. 作业成功结束后,该作业的本地 scratch 目录被自动删除;
  3. 作业失败结束后,该目录改名 <jobid>.failed.<ts> 保留现场;
  4. 三台计算节点各有一个 systemd timer,每日清理本地 >7 天、Lustre >30 天的超期目录;
  5. 上述四项在节点上以文件内容(SHA256)+ timer 状态可核验;
  6. --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 同步,请勿在站点侧修改)