SOP — Epilog 覆盖 Cadence X 显示残留(孤儿 X socket / clsbd 清理)¶
主题目录:
slurm_ins/epilog_xdisplay_cleanup_20261005/变更日期:2026-10-05 执行方式:部署器下发(--scratch-gc) 状态:已完成并验证
1. 背景¶
HANDOFF 第 3 节 #14 登记:上层 ADE-XL 经 Slurm 派发时,ICRP child 反复撞「:80 被占」而卡死。
根因:runICRP<N> 会启动 cdsVncserver :80,X server 异常退出后留下三类不带 jobid 的残留:
/tmp/.X11-unix/X80(X socket)/tmp/.X80-lock(X lock)/tmp/clsbd.<user|unknownUser>.<pid>(Cadence 客户端残留)
而 epilog_eda.sh 原有的 /tmp 清理只按 slurm<jobid>* / vcs*<jobid>* 模式匹配,文件名不带 jobid 的残留一律清不到——即「钩子在跑 ≠ 生效」。
实测(变更前):slurm-work-01(.18) 留有 2026-10-04 04:15 的孤儿 socket X80(无 lock、无 X 进程)与 15 个 clsbd 残留;slurm-work-02(.19) 有 2 个 clsbd;slurm-work-03(.20) 干净。
2. 目标¶
epilog_eda.sh增加「孤儿 X 显示 / clsbd」清理段:仅在无对应进程时删;- 清理异常绝不影响 Epilog 退出码(非 0 会 DRAIN 节点);
- 顺手清掉三台节点上的当前残留;
- 全程幂等:复跑第二次零变更、全跳过。
3. 前置条件¶
| 项 | 值 |
|---|---|
| 执行机 | 部署机(本项目根,slurm_ins/.venv) |
| 远端身份 | root(免密,见 config/slurm_deploy.yaml 的 ssh 段) |
| 目标节点 | slurm-work-01/02/03 = 192.168.31.18/19/20(仅计算节点跑 slurmd、执行 Epilog) |
| 判定工具 | 节点需有 fuser(实测 3 台均有 /usr/sbin/fuser);无则退 ss;两者皆无 → 保守保留 |
| 前置约束 | squeue 为空再动手(--scratch-gc 不碰 daemon,但仍建议) |
| 配置源 | config/slurm_deploy.yaml 的 scratch_gc 段(hook_source_dir / hook_scripts / install_dir) |
4. 影响范围¶
- 涉及主机:
192.168.31.18 / .19 / .20(3 台计算节点);不涉及控制节点、提交节点。 - 涉及组件:
/usr/local/slurm/epilog_eda.sh(作业 Epilog 钩子内容)。 - 是否停服:否。钩子脚本内容变更即时生效,无需重启
slurmd、不改slurm.conf。 - 窗口期:无特殊要求;作业结束触发,单次开销为若干
stat+fuser(毫秒级)。 - 在跑作业:条件允许时等
squeue为空;Epilog 只在本作业结束时执行,不影响正在运行的作业。
5. 分步操作¶
实现体:
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc(scratch_gc_deployer.py+scratch_gc_assets.py;本主题只改源模板,实现体未改)
步骤 1:备份源模板¶
高危前置:改任何模板前先备份,失败可回原状。
TS=$(date +%Y%m%d-%H%M%S)
D=/home/rain/Documents/slurm/slurm_ins/epilog_xdisplay_cleanup_20261005
mkdir -p "$D/backup/slurm_ins/slurm_conf_old/pro_epilog"
cp -a /home/rain/Documents/slurm/slurm_ins/slurm_conf_old/pro_epilog/epilog_eda.sh \
"$D/backup/slurm_ins/slurm_conf_old/pro_epilog/epilog_eda.sh.bak.$TS"
sha256sum "$D/backup/slurm_ins/slurm_conf_old/pro_epilog/epilog_eda.sh.bak.$TS"
预期:9af06fee…b6a1f(变更前版本)。台账见 backup/README.md。
步骤 2:修改源模板¶
在 slurm_conf_old/pro_epilog/epilog_eda.sh 第 2 段(scratch)之后、原「同步磁盘」之前插入第 3 段,
原第 3 段顺延为第 4 段。要点:
- 三个函数:
x_socket_in_use(fuser 优先、ss 兜底、都无则视为占用)、x_lock_alive(lock 内 PID 是否存活)、cleanup_stale_x(三段扫描); - 整段以
cleanup_stale_x || true调用 —— 任何异常只记日志,不让 Epilog 返回非 0; - 禁用
set -u(原有约束不变);判定语句一律用if,不用cmd && continue(后者在set -e下有退出风险)。
注意事项与坑:
cmd && continue在set -eo pipefail下,当cmd失败会使整个 AND 列表非 0 → 触发出脚本退出。已统一改为if cmd; then continue; fi(离线自测覆盖)。- lock 编号解析:
/tmp/.X84-lock中/后紧跟.而非X,故 不能用${lock##*/X}(永不命中);须用${lock##*/.X}再去-lock。此坑由离线自测抓出(首版写错,陈旧 lock 清不掉)。 - socket 编号解析用
${sock##*/X}(socket 文件名是X80,无点,正确)。
步骤 3:语法检查¶
bash -n /home/rain/Documents/slurm/slurm_ins/slurm_conf_old/pro_epilog/epilog_eda.sh && echo "syntax OK"
sha256sum /home/rain/Documents/slurm/slurm_ins/slurm_conf_old/pro_epilog/epilog_eda.sh
预期:syntax OK,sha 为 844715fc…fea8af(本次变更后版本)。
步骤 4:离线自测(上机前)¶
用沙箱路径替换 /tmp/ 后构造四类场景跑断言,代价远低于上机后才发现。判定矩阵:
| 场景 | 期望 |
|---|---|
| 孤儿 socket(bind 后关闭,无进程) | 删除 |
| 活跃 socket(进程在 listen) | 保留 |
| 孤儿 socket + lock 内 PID 存活 | 保留(socket 与 lock 都留) |
| 陈旧 lock(PID 不存在、无 socket) | 删除 |
clsbd.<user>.<存活pid> |
保留 |
clsbd.<user>.<死pid> |
删除 |
| 脚本整体退出码 | 0,且打印 REACHED_END |
步骤 5:dry-run 预演¶
cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc --dry-run
预期:只有 3 台 epilog_eda.sh 显示「写入/更新」,task_prolog_eda.sh、slurm-scratch-gc.sh、service、timer 全部 [SKIP] 内容一致;汇总「待执行变更 7 / 幂等跳过 15」。
失败判断:出现 task_prolog_eda.sh 等其它文件的写入 → 说明误改了别的模板,停下排查;出现 unknown 状态 → 立即中止本次变更。
步骤 6:正式下发¶
cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc
预期:3 台各写入一次(批次 1/3、2/3、3/3),节点侧备份到 /var/backups/slurm/scratch_gc_<stamp>/,
health_check 通过,退出码 0。
失败判断:某台写入失败 → 见第 7 章回滚;health_check 报「未达期望内容」→ 停手,按第 7 章恢复。
等价底层命令(仅用于阅读复核与应急复现;正常路径必须走部署器,以保留备份与幂等记录):
for h in 192.168.31.18 192.168.31.19 192.168.31.20; do
scp /home/rain/Documents/slurm/slurm_ins/slurm_conf_old/pro_epilog/epilog_eda.sh \
root@$h:/usr/local/slurm/epilog_eda.sh && ssh root@$h 'chmod 755 /usr/local/slurm/epilog_eda.sh'
done
步骤 7:清掉当前残留(一次性)¶
下发只更新文件,历史残留需执行一次钩子才会被清。以最小环境模拟一次作业结束:
for h in 192.168.31.18 192.168.31.19 192.168.31.20; do
ssh root@$h 'bash /usr/local/slurm/epilog_eda.sh; echo "rc=$?"'
done
预期:rc=0;/var/log/slurm/epilog.log 出现 stale X display purged: / stale clsbd purged: 行。
步骤 8:幂等复跑¶
cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc
预期:待执行变更 0、幂等跳过 18、退出码 0,日志出现「状态已一致,零变更,无需部署」。
6. 验证¶
# ① 三台内容与源模板一致(sha 应为 844715fc…fea8af)
for h in 192.168.31.18 192.168.31.19 192.168.31.20; do
ssh root@$h 'sha256sum /usr/local/slurm/epilog_eda.sh'
done
# ② 残留已清空(X / lock / clsbd 三项均应为空或 0)
for h in 192.168.31.18 192.168.31.19 192.168.31.20; do
ssh root@$h 'ls /tmp/.X11-unix/X[0-9]* /tmp/.X[0-9]*-lock 2>/dev/null; ls /tmp/clsbd.* 2>/dev/null | wc -l'
done
# ③ 日志留痕
ssh root@192.168.31.18 'tail -20 /var/log/slurm/epilog.log'
# ④ 钩子退出码(最小环境复现 slurmd)
ssh root@192.168.31.18 'env -i PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin \
SLURM_JOB_ID=0 SLURM_JOB_USER=root SLURM_JOB_EXIT_CODE=0 \
bash /usr/local/slurm/epilog_eda.sh; echo EXIT=$?'
本次实测结果:
| 节点 | 变更前 | 变更后 |
|---|---|---|
slurm-work-01(.18) |
X80 + 15 个 clsbd |
0 / 0 |
slurm-work-02(.19) |
2 个 clsbd |
0 |
slurm-work-03(.20) |
无残留 | 0(脚本正常退出) |
三台 sha 均为 844715fc…fea8af;脚本退出码均为 0;幂等复跑 0 变更。
7. 回滚¶
备份位置(两处,互为冗余):
- 本地:
epilog_xdisplay_cleanup_20261005/backup/slurm_ins/slurm_conf_old/pro_epilog/epilog_eda.sh.bak.20261005-231956(sha9af06fee…b6a1f); - 节点侧:
/var/backups/slurm/scratch_gc_20261005_232204/(部署器自动创建,含epilog_eda.sh.bak等)。
台账见本目录 backup/README.md。
回滚步骤:
D=/home/rain/Documents/slurm/slurm_ins/epilog_xdisplay_cleanup_20261005/backup/slurm_ins/slurm_conf_old/pro_epilog
cp -a "$D/epilog_eda.sh.bak.20261005-231956" \
/home/rain/Documents/slurm/slurm_ins/slurm_conf_old/pro_epilog/epilog_eda.sh
cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python main.py --config config/slurm_deploy.yaml --scratch-gc # 重新下发回旧内容
回滚触发判据(量化,任一达标即回滚):
health_check未通过(报「未达期望内容」);- 任一节点
sha256sum /usr/local/slurm/epilog_eda.sh≠ 期望值; journalctl -u slurmd出现epilog failed: rc:<非 0>,或节点转为drain;- 出现误删正在使用的 X socket / clsbd(表现为在跑作业显示中断)。
回滚后验证:三台 sha 恢复 9af06fee…b6a1f;sinfo 全部 idle;提交一个测试作业确认 Epilog 正常结束。
8. 风险说明¶
| 风险 | 缓解 |
|---|---|
误删在用 X 显示(并发作业正在用 :80) |
判定为「有进程占用即保留」(fuser 优先、ss 兜底);两个工具都没有则保守保留,宁可漏清 |
| Epilog 返回非 0 → 节点 DRAIN | 整段包成函数并以 || true 调用;set -e 下的 cmd && continue 隐患已消除;判定语句全用 if;保持禁用 set -u |
| 清理范围越界 | 只处理 /tmp/.X11-unix/X[0-9]*、/tmp/.X[0-9]*-lock、/tmp/clsbd.* 三类明确模式,rm -f -- 逐项显式删除,无通配删除 |
| PID 复用导致漏清(非误删) | kill -0 只判存活;方向安全(漏清而非误删),下次作业结束会再尝试 |
| 控制节点持有陈旧副本 | 本变更只下发 3 台计算节点;Epilog 只在执行节点生效,控制节点副本不参与运行 |
附录 A:交付物清单¶
| 文件 | 说明 |
|---|---|
SOP.md |
本文件 |
backup/README.md |
备份台账 |
backup/slurm_ins/slurm_conf_old/pro_epilog/epilog_eda.sh.bak.20261005-231956 |
源模板变更前副本 |
改动清单(1 个文件):
slurm_ins/slurm_conf_old/pro_epilog/epilog_eda.sh(sha9af06fee…b6a1f→844715fc…fea8af)- 同步更新:
docs/RUNBOOK.md(新增场景 J + 修订记录)、docs/BASELINE.md(第 3 节两条机制说明)、docs/HANDOFF.md(#14 结案)
附录 B:决策经过¶
- 为什么不改 slurm.conf:
Epilog=已指向/usr/local/slurm/epilog_eda.sh,改脚本内容即生效;改 conf 会触发scontrol reconfigure,无必要。 - 为什么走
--scratch-gc而非全量部署:该模式只分发钩子脚本 + 老化脚本,不碰 daemon、不重启服务;全量部署会牵动 6 台与服务重启,收益不成比例。 - 为什么判据用
fuser而非 lock:实测残留只有 socket、没有 lock(X server 异常退出时清掉了 lock),仅靠 lock 无法判定,必须以「有无进程占用 socket」为主判据。 - 为什么不限定作业用户:残留文件名不带 jobid、也无法可靠关联到发起作业的用户(实测
clsbd用户名字段是unknownUser);限定用户会漏掉无法关联的孤儿。安全由「无进程占用」判据保证,而非用户名过滤。
来源:
/home/rain/Documents/slurm/slurm_ins/epilog_xdisplay_cleanup_20261005/SOP.md| 最后更新:2026-10-05 23:23(由build/sync.py同步,请勿在站点侧修改)