SLURM 集群健康检查行业标准改造 SOP¶
项目: SLURM 集群健康检查机制标准化改造 执行日期: 2026-08-14 SLURM 版本: 21.08.8-2 适用系统: CentOS 7 / RHEL 7 (systemd 219) 最终状态: 全量修复完成并验证通过 ✅(含 P0 严重逻辑错误修复 + P1 配置优化) 目标读者: 运维工程师,具备 SLURM/systemd 基础
〇、快速索引¶
| 章节 | 场景 | 关键词 |
|---|---|---|
| 一 | 改造背景与架构对比 | 自研 vs 行业标准、三层保障体系 |
| 二 | 从零部署行业标准 HealthCheck(复现 SOP) | slurm.conf、health_check.sh、configless |
| 二-B | P0 严重逻辑错误修复(全量检查) | 服务依赖链、Restart=always、wait-for-slurmctld |
| 三 | 验证清单 | 部署后如何确认全链路 |
| 四 | 自研方案保留策略 | 降频、定位调整、备份巡检层 |
| 五 | 故障排查 | HealthCheck 不触发、节点不 DRAIN |
| 六 | 回滚方案 | 如何撤销所有变更 |
| 七 | 日常运维速查 | 常用命令清单 |
一、改造背景与目标¶
1.1 改造前状态诊断¶
| 检查项 | 改造前状态 | 是否行业标准 |
|---|---|---|
ReturnToService=1 |
✅ 已配置 | ✅ 是 |
SlurmdTimeout=300 |
✅ 已配置 | ✅ 是 |
systemd Restart=on-failure |
✅ 已配置 | ✅ 是 |
| SLURM 内置 HealthCheck | ❌ 未配置 | ❌ 否(关键差距) |
自研 cluster_health_check.py |
✅ 已部署(5分钟) | ❌ 非官方标准 |
| Prometheus+Grafana 监控 | ✅ 已对接 | ✅ 是 |
核心差距:SLURM 官方推荐的内置 HealthCheck 机制(HealthCheckInterval+HealthCheckProgram)未启用。自研方案通过 SSH 远程检查,依赖网络连通性,且非 SchedMD 官方标准。
1.2 自研方案 vs 行业标准对比¶
| 对比维度 | 自研方案(改造前) | SLURM 内置 HealthCheck(行业标准) |
|---|---|---|
| 检查执行者 | 控制节点通过 SSH 远程检查 | 各计算节点 slurmd 本地执行 |
| 检查方式 | systemctl is-active + scontrol |
执行 HealthCheckProgram 脚本 |
| 网络依赖 | 依赖 SSH 连通性(单点风险) | 无网络依赖,本地执行 |
| 失败处理 | SSH 重启 slurmd + scontrol resume |
节点自动 DRAIN + 记录原因 |
| 服务重启能力 | ✅ 可重启 slurmd 服务 | ❌ 仅 DRAIN,不重启服务 |
| 标准性 | 自研,非官方 | SchedMD 官方推荐 |
| 配置分发 | 需手动部署到控制节点 | configless 模式自动分发 |
1.3 改造后三层保障体系¶
第 1 层:SLURM 原生自动恢复(秒级,行业标准)
├── ReturnToService=2 → 节点 DOWN 后自动恢复 IDLE(比 1 更积极,任何 DOWN 都恢复)
├── SlurmdTimeout=300 → 心跳超时 5 分钟判定 DOWN
├── systemd Restart=on-failure → slurmd 异常退出自动重启
└── HealthCheckInterval=300 → slurmd 本地周期性健康检查(新增)
第 2 层:自研备份巡检(15 分钟,补充层)
├── cluster_health_check.py → SSH 远程检查 + slurmd 服务重启
└── slurm-cluster-health.timer → 定时器(从 5 分钟降频至 15 分钟)
第 3 层:Prometheus 监控告警(告警层)
├── slurm-exporter → 指标采集
├── Grafana 大盘 → 可视化展示
└── Alertmanager → 邮件告警
职责分工: - 第 1 层(内置 HealthCheck):本地检测节点不健康 → 自动 DRAIN,防止作业分配到故障节点 - 第 2 层(自研备份):SSH 远程重启异常 slurmd 服务(内置机制无法重启服务,只能 DRAIN) - 第 3 层(Prometheus):告警通知运维人员
二、从零部署行业标准 HealthCheck(复现 SOP)¶
步骤 0:前置条件检查¶
# 1. 确认 SLURM 集群正常运行
ssh root@192.168.0.2 'sinfo'
# 期望: 3 节点 idle
# 2. 确认 slurm.conf 位置
ssh root@192.168.0.2 'ls /usr/local/slurm/etc/slurm.conf'
# 3. 确认 configless 模式(计算节点无需本地 slurm.conf)
ssh root@k8s-node-01 'ps aux | grep slurmd | grep -v grep'
# 期望: 包含 --conf-server 参数
步骤 1:编写 HealthCheck 脚本¶
原理:SLURM 内置 HealthCheck 由 slurmd 在各节点本地执行,脚本退出码 0=健康、非 0=不健康(节点自动 DRAIN)。
检查项(按 HPC 节点常见故障设计):
1. 根文件系统可写(防止只读挂载)
2. /tmp 可写(作业临时目录)
3. NFS 挂载点已挂载(用户家目录/EDA 工具/数据共享)
4. 关键目录可写(/home、/mnt/nfs/slurm、/opt/lsf)
5. 可用内存 > 100MB(防止 OOM 僵死)
6. 磁盘使用率 < 90%(防止 100% 占满导致 exitcode 126/127)
7. munge 服务运行(SLURM 认证依赖)
步骤 2:部署脚本到所有节点¶
# ⚠️ 高危操作前置:脚本需在所有运行 slurmd 的节点上存在
# 部署到控制节点 + 所有计算节点
for node in k8s-master-01 k8s-node-01 k8s-node-02 k8s-node-03; do
echo "=== 部署到 ${node} ==="
scp /home/rian/Documents/slurm/slurm_health_check.sh root@${node}:/usr/local/bin/
ssh root@${node} '
chmod +x /usr/local/bin/slurm_health_check.sh
# 手动执行验证(退出码应为 0)
/usr/local/bin/slurm_health_check.sh; echo "退出码: $?"
'
done
# 期望: 所有节点退出码为 0
步骤 3:配置 slurm.conf 启用 HealthCheck¶
⚠️ 高危操作:修改 slurm.conf 需重启 slurmctld 和所有 slurmd,会短暂中断调度。
# === 在控制节点执行 ===
# 1. 备份配置(必须执行)
ssh root@k8s-master-01 'cp /usr/local/slurm/etc/slurm.conf \
/usr/local/slurm/etc/slurm.conf.bak.$(date +%Y%m%d%H%M%S)'
# 2. 在 SlurmdTimeout 行后插入 HealthCheck 三参数
ssh root@k8s-master-01 'sed -i "/^SlurmdTimeout=300/a\\
HealthCheckInterval=300\\
HealthCheckProgram=/usr/local/bin/slurm_health_check.sh\\
HealthCheckNodeState=ANY" /usr/local/slurm/etc/slurm.conf'
# 3. 验证配置
ssh root@k8s-master-01 'grep -A3 "^SlurmdTimeout" /usr/local/slurm/etc/slurm.conf'
# 期望输出:
# SlurmdTimeout=300
# HealthCheckInterval=300
# HealthCheckProgram=/usr/local/bin/slurm_health_check.sh
# HealthCheckNodeState=ANY
参数说明:
| 参数 | 值 | 说明 |
|---|---|---|
HealthCheckInterval |
300 | 每 300 秒(5 分钟)执行一次检查 |
HealthCheckProgram |
/usr/local/bin/slurm_health_check.sh |
检查脚本路径(各节点本地) |
HealthCheckNodeState |
ANY | 所有状态节点都检查(含 IDLE/ALLOC/MIXED/REGISTER/RESUME) |
步骤 4:重启服务使配置生效¶
# 1. 重启 slurmctld(使新 slurm.conf 生效)
ssh root@k8s-master-01 'systemctl restart slurmctld; sleep 3; systemctl is-active slurmctld'
# 期望: active
# 2. 重启所有 slurmd(触发 configless 模式重新拉取配置)
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
ssh root@${node} 'systemctl restart slurmd; sleep 2; systemctl is-active slurmd'
# 期望: active
done
# 3. 等待配置同步
sleep 15
# 4. 检查节点状态
ssh root@k8s-master-01 'sinfo'
# 期望: 3 节点 idle
步骤 5:验证 configless 配置分发¶
# 验证所有计算节点已获取 HealthCheck 配置
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
echo "=== ${node} ==="
ssh root@${node} 'grep "^HealthCheck" /var/spool/slurmd/conf-cache/slurm.conf'
# 期望: 3 行 HealthCheck 配置
done
步骤 6:调整自研方案频率(备份巡检层)¶
# 将自研脚本执行频率从 5 分钟降为 15 分钟(避免与内置 HealthCheck 重复)
# 修改 slurm-cluster-health.timer 中 OnUnitActiveSec=5min → 15min
ssh root@k8s-master-01 '
sed -i "s/OnUnitActiveSec=5min/OnUnitActiveSec=15min/" /etc/systemd/system/slurm-cluster-health.timer
systemctl daemon-reload
systemctl restart slurm-cluster-health.timer
systemctl list-timers slurm-cluster-health.timer
'
# 期望: NEXT 显示 15 分钟后
二-B、P0 严重逻辑错误修复(全量检查)¶
背景:2026-08-14 集群物理重启后,节点 DOWN 状态持续 4 小时 40 分钟未自动恢复。全量检查发现 9 个问题(4 个 P0 + 4 个 P1 + 1 个 P2),以下为完整修复记录。
2B.1 故障链复盘¶
12:20:47 系统物理启动
12:22:52 munge 就绪
12:22:58 k8s-node-01 slurmd 启动 ← 此时 slurmctld 尚未就绪!
12:23:16 slurmctld 开始启动
12:23:28 slurmd 失败: "failed to load configs" ← configless 拉取配置失败
→ Restart=on-failure 未触发自动重启(systemd 219 行为)
12:25:56 slurmctld: "Nodes k8s-node-[01-03] not responding, setting DOWN"
→ 健康检查引导服务超时被杀(120秒 < 引导模式需要180秒)
17:03:27 slurmd 才重新启动(4小时40分钟后)
2B.2 问题汇总与修复¶
| # | 严重度 | 问题 | 根因 | 修复方案 |
|---|---|---|---|---|
| 1 | P0 | slurmd 启动不等待 slurmctld | configless 模式下 slurmd 在 slurmctld 就绪前启动 | 添加 ExecStartPre=wait-for-slurmctld.sh |
| 2 | P0 | Restart=on-failure 未可靠触发 |
systemd 219 下 slurmd 失败后 4 小时+ 未重启 | 改为 Restart=always + RestartSec=10 |
| 3 | P0 | slurmctld 不等待 slurmdbd | After= 缺少 slurmdbd.service |
添加 After=...slurmdbd.service + Wants=slurmdbd.service |
| 4 | P0 | 引导服务超时不足 | TimeoutStartSec=120 < 引导模式需要 180 秒 |
延长至 TimeoutStartSec=300 |
| 5 | P1 | ReturnToService=1 |
节点恢复不够积极 | 改为 ReturnToService=2 |
| 6 | P1 | JobAcctGatherType=none |
不收集作业资源数据 | 改为 jobacct_gather/cgroup |
| 7 | P1 | JobCompType=none |
不记录作业完成信息 | 改为 jobcomp/slurmdbd |
| 8 | P2 | DefMemPerCPU=100 |
默认内存过小(节点有 2000MB/CPU) | 改为 DefMemPerCPU=1024 |
| 9 | P2 | RestartSec=5 |
重启间隔过短 | 改为 RestartSec=10 |
2B.3 修复1:创建 wait-for-slurmctld.sh 脚本¶
原理:configless 模式下 slurmd 通过 --conf-server 从 slurmctld 拉取配置。如果 slurmctld 尚未就绪,slurmd 报 failed to load configs 并退出。本脚本在 slurmd 启动前等待 slurmctld 可达。
# 部署到所有节点(控制节点 + 计算节点)
for node in k8s-master-01 k8s-node-01 k8s-node-02 k8s-node-03; do
scp wait-for-slurmctld.sh root@${node}:/usr/local/bin/
ssh root@${node} 'chmod +x /usr/local/bin/wait-for-slurmctld.sh'
done
关键设计:
- 通过 scontrol ping 检测 slurmctld 就绪(注意:SLURM 21.08 输出 is UP,旧版输出 is alive)
- MAX_WAIT=60 秒(需小于 systemd TimeoutStartPre 默认 90 秒)
- 同时支持 TCP 端口检测作为备选
2B.4 修复2:重写 slurmd.service¶
文件路径:slurmd.service
关键变更:
[Service]
# P0修复1:启动前等待 slurmctld 就绪(configless 模式必需)
ExecStartPre=/usr/local/bin/wait-for-slurmctld.sh k8s-master-01 6817
TimeoutStartSec=180
# P0修复2:Restart=on-failure → always(确保任何退出都重启)
Restart=always
RestartSec=10
部署命令:
# ⚠️ 高危操作:批量修改计算节点配置前必须备份
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
# 备份
ssh root@${node} 'cp /etc/systemd/system/slurmd.service \
/etc/systemd/system/slurmd.service.bak.$(date +%Y%m%d%H%M%S)'
# 推送新配置
scp slurmd.service root@${node}:/etc/systemd/system/
ssh root@${node} 'systemctl daemon-reload'
done
2B.5 修复3:重写 slurmctld.service¶
文件路径:slurmctld.service
关键变更:
[Unit]
# P0修复3:添加 slurmdbd 依赖(slurmctld 依赖 slurmdbd 做记账)
After=network-online.target munge.service slurmdbd.service
Wants=network-online.target slurmdbd.service
[Service]
# P0修复2:增强重启策略
Restart=always
RestartSec=10
2B.6 修复4:健康检查服务超时调整¶
# slurm-cluster-health.service & slurm-cluster-health-bootstrap.service
# P0修复4:120 → 300 秒
TimeoutStartSec=300
2B.7 修复5:slurm.conf 配置优化¶
# 在控制节点执行(修改后 configless 模式自动分发到计算节点)
ssh root@192.168.0.2 '
# P1-5: ReturnToService=1 → 2
sed -i "s/^ReturnToService=1/ReturnToService=2/" /usr/local/slurm/etc/slurm.conf
# P1-6: JobAcctGatherType=none → cgroup
sed -i "s|^JobAcctGatherType=jobacct_gather/none|JobAcctGatherType=jobacct_gather/cgroup|" /usr/local/slurm/etc/slurm.conf
# P1-7: JobCompType=none → slurmdbd
sed -i "s|^JobCompType=jobcomp/none|JobCompType=jobcomp/slurmdbd|" /usr/local/slurm/etc/slurm.conf
# P2-8: DefMemPerCPU=100 → 1024
sed -i "s/^DefMemPerCPU=100/DefMemPerCPU=1024/" /usr/local/slurm/etc/slurm.conf
'
2B.8 服务重启顺序(依赖链)¶
⚠️ 必须按依赖顺序重启:slurmdbd → slurmctld → slurmd → 健康检查服务
# 1. 重启 slurmdbd(最底层依赖)
ssh root@192.168.0.2 'systemctl restart slurmdbd; sleep 5; systemctl is-active slurmdbd'
# 2. 重启 slurmctld(等 slurmdbd 就绪后)
ssh root@192.168.0.2 'systemctl restart slurmctld; sleep 5; systemctl is-active slurmctld'
# 3. 验证 slurmctld 就绪
ssh root@192.168.0.2 'scontrol ping'
# 期望: Slurmctld(primary) at k8s-master-01 is UP
# 4. 重启所有 slurmd(wait-for-slurmctld.sh 会先等待 slurmctld 就绪)
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
ssh root@${node} 'systemctl restart slurmd'
done
sleep 20
# 5. 重启健康检查服务
ssh root@192.168.0.2 'systemctl restart slurm-cluster-health.timer slurm-cluster-health-bootstrap.service'
2B.9 验证 Restart=always 自动恢复¶
# 模拟 slurmd 崩溃,验证自动恢复
ssh root@k8s-node-03 'pkill -9 -f "slurmd --conf-server"'
sleep 15
ssh root@k8s-node-03 'systemctl is-active slurmd'
# 期望: active(Restart=always 自动恢复)
# 查看恢复日志
ssh root@k8s-node-03 'journalctl -u slurmd --since "20 sec ago" --no-pager | tail -10'
# 期望: 看到 "scheduling restart" → "wait-for-slurmctld.sh" → "slurmd started"
三、验证清单¶
部署完成后按以下顺序验证:
| # | 验证项 | 命令 | 期望输出 |
|---|---|---|---|
| 1 | slurm.conf 配置 | ssh root@192.168.0.2 'grep "^HealthCheck" /usr/local/slurm/etc/slurm.conf' |
3 行配置 |
| 2 | 配置分发同步 | ssh root@k8s-node-01 'grep -c "^HealthCheck" /var/spool/slurmd/conf-cache/slurm.conf' |
3 |
| 3 | 脚本部署 | ssh root@k8s-node-01 'test -x /usr/local/bin/slurm_health_check.sh && echo OK' |
OK |
| 4 | 脚本手动执行 | ssh root@k8s-node-01 '/usr/local/bin/slurm_health_check.sh; echo $?' |
退出码 0 |
| 5 | slurmd 启动检查 | ssh root@k8s-node-01 'journalctl -u slurmd --since "5 min ago" \| grep HealthCheck' |
HealthCheck 通过 |
| 6 | 集群节点状态 | ssh root@192.168.0.2 'sinfo' |
3 节点 idle |
| 7 | 自研 timer 频率 | ssh root@192.168.0.2 'systemctl list-timers slurm-cluster-health.timer' |
NEXT 15 分钟后 |
| 8 | Restart=always | ssh root@k8s-node-03 'systemctl show slurmd -p Restart' |
Restart=always |
| 9 | wait-for-slurmctld | ssh root@k8s-node-01 'journalctl -u slurmd --since "5 min ago" \| grep 就绪' |
slurmctld 已就绪 |
| 10 | slurmctld 依赖 | ssh root@192.168.0.2 'systemctl show slurmctld -p After \| grep slurmdbd' |
包含 slurmdbd.service |
| 11 | ReturnToService | ssh root@192.168.0.2 'grep ReturnToService /usr/local/slurm/etc/slurm.conf' |
ReturnToService=2 |
| 12 | JobAcctGather | ssh root@192.168.0.2 'grep JobAcctGatherType /usr/local/slurm/etc/slurm.conf' |
jobacct_gather/cgroup |
| 13 | 崩溃恢复测试 | ssh root@k8s-node-03 'pkill -9 slurmd; sleep 15; systemctl is-active slurmd' |
active |
四、自研方案保留策略¶
4.1 保留原因¶
自研方案 cluster_health_check.py 仍有不可替代的价值:
| 能力 | 内置 HealthCheck | 自研方案 | 结论 |
|---|---|---|---|
| 本地健康检测 | ✅ | ✅(SSH 远程) | 内置更优 |
| 节点自动 DRAIN | ✅ | ❌ | 内置更优 |
| 重启异常 slurmd 服务 | ❌ | ✅ | 自研不可替代 |
| 引导模式(重启后节点恢复) | ❌ | ✅ | 自研不可替代 |
| munge 服务前置检查 | ❌ | ✅ | 自研更优 |
4.2 职责重新划分¶
内置 HealthCheck(主,5 分钟周期):
→ 检测节点不健康 → 自动 DRAIN(防止作业分配到故障节点)
自研 cluster_health_check.py(备,15 分钟周期):
→ SSH 远程检查 slurmd 服务状态
→ 自动重启异常 slurmd 服务(内置机制无法做到)
→ 引导模式:集群重启后等待节点就绪并恢复状态
4.3 配置变更¶
| 配置项 | 改造前 | 改造后 |
|---|---|---|
slurm-cluster-health.timer OnUnitActiveSec |
5min |
15min |
| 定位 | 主健康检查 | 备份巡检层 |
五、故障排查¶
5.0 通用排障流程(故障现场标准 SOP,建议按顺序执行)¶
适用范围:任何节点 DOWN / 巡检不触发 / 服务异常的场景。以下 6 步设计为通用闭环,避免遗漏关键证据。
Step 1:现象定位(1 分钟)¶
# === 快速三件套(在控制节点 k8s-master-01 执行) ===
# 1. 集群节点状态(是否 down/drain/down* Not responding)
export PATH=/usr/local/slurm/bin:$PATH; sinfo -Nl
# 2. 自研备份巡检服务状态(是否 enabled、NEXT 是否在未来、最近 LAST 执行结果)
systemctl status slurm-cluster-health.timer slurm-cluster-health.service slurm-cluster-health-bootstrap.service --no-pager
# 3. 所有节点 slurmd / munge 存活
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
echo -n "$node slurmd="; ssh -o ConnectTimeout=3 root@${node} 'systemctl is-active slurmd' 2>&1
echo -n "$node munge ="; ssh -o ConnectTimeout=3 root@${node} 'systemctl is-active munge' 2>&1
done
Step 2:日志抓取(2 分钟,按层级抓,限制行数避免刷屏)¶
# === 控制节点 ===
journalctl -u slurm-cluster-health.service --no-pager -n 80 | tail -60 # 自研巡检脚本最近日志
journalctl -u slurmctld --no-pager -n 40 | tail -30 # slurmctld 调度层日志
# === 问题节点(例:k8s-node-03,按需替换)===
ssh root@k8s-node-03 "
journalctl -u slurmd --no-pager -n 60 | tail -40 # slurmd 启动 / HealthCheck 日志
systemctl status slurmd 2>&1 | head -20 # 当前服务状态 + Main PID
journalctl -u slurm-cluster-health-bootstrap.service --no-pager -n 20 2>/dev/null # 若启动过引导服务
"
Step 3:根因快速判断(按优先级命中表)¶
| 采集到的现象 | 对应故障条目 | 快速修复入口 |
|---|---|---|
slurmd activating 超过 30 秒 + Main PID 为空 |
5.3.1 Type 与 -D 参数矛盾 | 应用 slurmd.service 标准 Type=simple 配置 |
slurmd inactive 但进程不在,且几小时未重启 |
5.3.1 + 2B.2 P0-2 Restart=always | systemctl restart slurmd + 检查 Restart=always |
| timer NEXT 在过去时间(不继续调度) | 5.3.2 RemainAfterExit=yes | RemainAfterExit=no + daemon-reload |
| 服务日志「start operation timed out. Terminating」SIGTERM | 5.3.3 TimeoutStartSec 不足 | 巡检 300s / slurmd 180s |
systemd 报 Unknown lvalue 'StartLimitIntervalSec' |
5.3.4 CentOS 7 systemd 219 不兼容 | 删除 StartLimit* 两参数 |
3 节点 down* Not responding + wait-for-slurmctld 报超时 |
5.4 configless + scontrol ping 逻辑缺陷 | 替换 wait-for-slurmctld.sh 修复版 |
| HealthCheck 不触发 / 节点未自动 DRAIN | 5.1 / 5.2 | 查 conf-cache + 脚本权限 |
| configless 配置未同步到计算节点 | 5.3 configless | restart slurmd 强制重拉 |
Step 4:修复执行¶
按 Step3 命中的条目执行标准修复。批量修改 3 计算节点前,必须先执行 6.4 的强制备份。
Step 5:修复验证(必须 5 项全部通过)¶
# ✅ 1. 集群所有节点 idle
sinfo | awk 'NR>1 && !/idle|PARTITION/' | wc -l # 应为 0(非 idle 的行)
# ✅ 2. 所有 slurmd 服务 active + Main PID 指向 slurmd 进程
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
echo -n "$node: "; ssh root@${node} 'systemctl is-active slurmd' # 全部 active
done
# ✅ 3. 内置 HealthCheck 已部署(退出码 0)
ssh root@k8s-node-02 '/usr/local/bin/slurm_health_check.sh; echo EXIT=$?' # EXIT=0
# ✅ 4. 自研备份巡检 timer NEXT 在未来
ssh root@k8s-master-01 'systemctl list-timers slurm-cluster-health.timer --no-pager'
# LEFT 列为正分钟数(LAST 与 NEXT 差距 15 分钟)
# ✅ 5. 崩溃恢复抽查(可选:杀掉 slurmd 验证 systemd Restart=always)
ssh root@k8s-node-03 'pkill -9 -f "slurmd --conf-server"; sleep 15; systemctl is-active slurmd'
# 应为 active(Restart=always 自动重启成功)
Step 6:预防措施(避免重复踩坑)¶
- 所有 systemd unit 变更后 必须
systemctl daemon-reload。 - 修改 slurmd.service 后 必须在至少 1 节点验证 Main PID 正确指向 slurmd 进程(Type=simple + -D 组合必须同时成立)。
- 批量改动完成后,必须完整执行 Step 5 的 5 项验证,而不是只看 sinfo 一行。
- 所有计算节点的 StartLimitIntervalSec / StartLimitBurst 两参数绝对不要写(CentOS 7 systemd 219 不支持,会直接导致 unit 加载失败)。
5.1 HealthCheck 不触发¶
# 步骤 1: 确认配置已同步到计算节点
ssh root@k8s-node-01 'grep "^HealthCheck" /var/spool/slurmd/conf-cache/slurm.conf'
# 无输出 → 配置未分发,重启 slurmd: systemctl restart slurmd
# 步骤 2: 确认脚本存在且可执行
ssh root@k8s-node-01 'ls -l /usr/local/bin/slurm_health_check.sh'
# 不存在 → 重新部署脚本
# 步骤 3: 手动执行脚本排查
ssh root@k8s-node-01 '/usr/local/bin/slurm_health_check.sh 2>&1; echo $?'
# 非0退出 → 查看具体报错,修复后重新执行
# 步骤 4: 检查 slurmd 日志
ssh root@k8s-node-01 'journalctl -u slurmd --since "10 min ago" | grep -i health'
5.2 节点未自动 DRAIN¶
# 步骤 1: 确认 HealthCheckNodeState 配置
ssh root@192.168.0.2 'grep HealthCheckNodeState /usr/local/slurm/etc/slurm.conf'
# 应为 ANY(所有状态都检查)
# 步骤 2: 手动模拟故障验证
ssh root@k8s-node-03 'echo "exit 1" > /usr/local/bin/slurm_health_check.sh && chmod +x /usr/local/bin/slurm_health_check.sh'
# 等待 5 分钟(HealthCheckInterval=300)或重启 slurmd 触发
ssh root@k8s-node-03 'systemctl restart slurmd'
sleep 10
ssh root@192.168.0.2 'sinfo'
# 节点应变为 drain
# 步骤 3: 恢复脚本
scp /home/rian/Documents/slurm/slurm_health_check.sh root@k8s-node-03:/usr/local/bin/
ssh root@192.168.0.2 'scontrol update nodename=k8s-node-03 state=resume'
5.3 configless 配置未同步¶
# 检查 slurmd 是否使用 conf-server 模式
ssh root@k8s-node-01 'ps aux | grep slurmd | grep -v grep'
# 期望: 包含 --conf-server k8s-master-01:6817
# 检查配置缓存目录
ssh root@k8s-node-01 'ls /var/spool/slurmd/conf-cache/'
# 期望: slurm.conf cgroup.conf
# 强制重新拉取配置
ssh root@k8s-node-01 'systemctl restart slurmd'
sleep 5
ssh root@k8s-node-01 'grep HealthCheck /var/spool/slurmd/conf-cache/slurm.conf'
5.3.1 slurmd 卡 activating 不重启(Type 与 -D 参数矛盾)¶
历史问题:自研方案 v0.1~v0.2 时代的典型故障(2026-08-14 v0.2 修复),现 cluster_health_check.py 重启 slurmd 时仍可能遇到早期遗留配置。
现象:
sinfo:节点 down* / idle*
systemctl status slurmd:Active: activating (start) 超过 30 秒,Main PID 为空
但 systemd 不触发 Restart=on-failure,服务长期卡死 activating
根因:Type=forking 与 -D 前台运行冲突。
| 配置项 | 实际行为 | systemd 期望 | 矛盾结果 |
|---|---|---|---|
Type=forking |
slurmd 被 fork 后父进程退出、子进程守护 | systemd 等父进程退出才标记 active | ✅ 匹配(但我们用了 -D) |
ExecStart=... -D |
-D 强制 slurmd 前台运行,不 fork |
systemd 一直等父进程退出,永远等不到 | ❌ 矛盾 |
后果链:
slurmd 启动 → systemd 看进程在前台 → 状态卡在 activating (start)
→ 若 slurmd 因 munge 未就绪退出
→ systemd 认为 "启动中 activating" 而非 "failed" → Restart=on-failure 不触发 → 节点永久 down
标准修复(当前规范,已固化到 slurmd.service):
# 关键原则:Type=simple 必须配合 -D(前台运行),二者缺一不可
[Service]
Type=simple # systemd 直接追踪主进程
ExecStart=/usr/local/slurm/sbin/slurmd \
--conf-server k8s-master-01:6817 -D -s $SLURMD_OPTIONS
Restart=always # 任何退出都自动重启(P0-2 强化)
RestartSec=10
5.3.2 巡检 timer 执行一次后不再触发(RemainAfterExit=yes)¶
历史问题:自研 v0.3 之前
slurm-cluster-health.service误配了RemainAfterExit=yes,导致 timer 只跑一次就停摆。
现象:
systemctl list-timers slurm-cluster-health.timer
NEXT 显示为过去时间(如 "19min ago"),说明 timer 不再调度
手动 systemctl start slurm-cluster-health.service 能正常执行完
根因:Type=oneshot + RemainAfterExit=yes 组合会让脚本退出后服务永久保持 active (exited),定时器检测到"服务还在 active"就不触发下一次 OnUnitActiveSec。
标准修复(已固化到 slurm-cluster-health.service):
[Service]
Type=oneshot
ExecStart=/root/.pyenv/versions/3.9.6/bin/python3 /usr/local/bin/cluster_health_check.py --fix
TimeoutStartSec=300
RemainAfterExit=no # ✅ 执行完毕服务状态变 inactive,timer 才能继续触发
# RemainAfterExit=yes # ❌ 错误,仅允许 bootstrap 一次性服务使用
注意:
slurm-cluster-health-bootstrap.service(开机只执行一次的引导服务)必须保留RemainAfterExit=yes,因为它不需要重复触发,保持active (exited)代表"已执行过引导逻辑"的标记位。
部署验证:
systemctl daemon-reload
systemctl reset-failed slurm-cluster-health.service
systemctl stop slurm-cluster-health.service
systemctl start slurm-cluster-health.service
sleep 5
systemctl list-timers slurm-cluster-health.timer --no-pager
# NEXT 应显示:LAST + 15 分钟(未来时间)✅
5.3.3 引导服务 / 巡检服务启动超时被杀(TimeoutStartSec 不足)¶
历史问题:自研方案早期引导模式串行等待每个节点就绪 60 秒 × 3 节点 = 最多 180 秒,但
TimeoutStartSec=60或120,结果服务中途被 SIGTERM。
现象:
journalctl -u slurm-cluster-health.service -n 30
... start operation timed out. Terminating.
... code=killed, status=15/TERM
根因对比:
| 版本 | 引导逻辑 | TimeoutStartSec | 结论 |
|---|---|---|---|
| 旧版 | 串行等待:每节点就绪 60s × 3 节点 = 最多 180s | 60 / 120 | ❌ 超时被杀 |
| 新版 | _ensure_slurmd_running 先确保所有 slurmd 启动,再并行等待 SLURM 内部同步(单节点≤30s)+ wait-for-slurmctld 前置等待 | 300(slurm-cluster-health.service / bootstrap) | ✅ 足够 |
| 配套 | slurmd 启动前 ExecStartPre=wait-for-slurmctld.sh 先等 slurmctld 就绪,避免启动时就拉不到 config | slurmd TimeoutStartSec=180 | ✅ |
标准修复(已固化到各 service):
# slurm-cluster-health.service & slurm-cluster-health-bootstrap.service
TimeoutStartSec=300 # P0 修复,从 120 → 300 秒
# slurmd.service
TimeoutStartSec=180 # 预留 ExecStartPre=wait-for-slurmctld.sh 60s + slurmd 启动
5.3.4 CentOS 7 systemd 报 Unknown lvalue StartLimitIntervalSec/Burst¶
历史问题:早期在 slurmd.service 里误用了 systemd 230+ 才引入的新参数。
现象:
systemctl status slurmd
Unknown lvalue 'StartLimitIntervalSec' in section 'Service'
Unknown lvalue 'StartLimitBurst' in section 'Service'
根因:CentOS 7 / RHEL 7 的 systemd 版本为 219,不支持以下 230+ 引入的参数:
- StartLimitIntervalSec
- StartLimitBurst
标准修复:从 unit 中删除这两行,使用基础重启策略即可:
# 删除以下两行:
# StartLimitIntervalSec=60
# StartLimitBurst=3
# 保留基础重启策略(已足够)
Restart=always
RestartSec=10
如需全局重启频率限制,可通过
/etc/systemd/system.conf全局配置DefaultStartLimitIntervalSec=/DefaultStartLimitBurst=,或交给健康巡检脚本处理。
5.4 冷开机重启后节点仍 DOWN 且巡检不生效(2026-08-27 实际案例)¶
场景:宿主机(KVM)冷开机重启后,
sinfo -Nl显示所有计算节点down* Not responding,slurm-cluster-health.timer虽然已 enabled 且cluster_health_check.py --fix引导模式反复运行,但节点状态始终无法恢复,用户感知为「巡检没生效」。 适用条件:configless 模式(计算节点无本地 slurm.conf,slurmd 以--conf-server k8s-master-01:6817启动)。
5.4.1 故障现象 & 快速判断¶
| 采集项 | 命令(在 k8s-master-01 上执行) | 故障时典型结果 |
|---|---|---|
| 节点状态 | export PATH=/usr/local/slurm/bin:$PATH; sinfo -Nl |
3 节点 STATE=down* REASON=Not responding |
| 计算节点 slurmd 状态 | ssh root@k8s-node-01 'systemctl is-active slurmd; journalctl -u slurmd -n 20 \| grep -E "等待.*超时\|exit.*status=1"' |
activating,且 wait-for-slurmctld.sh 报「等待 slurmctld 就绪超时(60 秒)」 |
| 计算节点 scontrol ping 报错 | ssh root@k8s-node-01 '/usr/local/slurm/bin/scontrol ping 2>&1; echo EXIT=$?' |
fatal: Could not establish a configuration source,EXIT=1(configless 节点天然如此,不是故障) |
| 巡检引导模式日志 | ournalctl -u slurm-cluster-health.service -n 60 \| grep -E "启动失败\|slurmd.*异常\|activating" |
反复报「节点 k8s-node-xx slurmd 服务启动失败」 |
5.4.2 根因(一句话原理)¶
ExecStartPre=wait-for-slurmctld.sh 的逻辑缺陷:configless 计算节点没有本地 slurm.conf,scontrol ping 必然失败(上面 EXIT=1 的报错是 configless 天然特性)。但旧脚本要求「TCP 6817 端口通并且 scontrol ping 通过」才算 slurmctld 就绪,结果永远判定失败 → 60 秒超时 EXIT=1 → slurmd 永远无法启动 → slurmctld 300 秒未收到心跳 → 全节点 down* Not responding → 即便巡检脚本反复重启 slurmd 也无济于事(上游 ExecStartPre 卡死)。
5.4.3 修复 SOP(可直接复制执行)¶
# ============================================================
# 环境变量(按需替换)
SRC_SCRIPT="/home/rian/Documents/slurm/slurm_health_ensure/wait-for-slurmctld.sh"
NODES=("192.168.0.5:k8s-node-01" "192.168.0.6:k8s-node-02" "192.168.0.7:k8s-node-03")
# ---------- 步骤 1:备份旧脚本 + 推送修复版 ----------
# 修复要点:TCP 6817 连通即视为就绪(configless 模式 slurmd 只要 TCP 通就能 --conf-server 拉配置),
# 不再强求 scontrol ping 通过。
for pair in "${NODES[@]}"; do
IP="${pair%%:*}"
NAME="${pair##*:}"
echo "━━━ $NAME ($IP) ━━━━━"
# 1.1 计算节点原脚本备份
ssh -o StrictHostKeyChecking=no "root@${IP}" '
cp /usr/local/bin/wait-for-slurmctld.sh \
/usr/local/bin/wait-for-slurmctld.sh.bak.$(date +%Y%m%d_%H%M%S)
'
# 1.2 推送修复后脚本 + 赋权 + 停止卡住的 ExecStartPre + 重启 slurmd
scp -o StrictHostKeyChecking=no "$SRC_SCRIPT" "root@${IP}:/usr/local/bin/wait-for-slurmctld.sh"
ssh -o StrictHostKeyChecking=no "root@${IP}" '
chmod +x /usr/local/bin/wait-for-slurmctld.sh
systemctl daemon-reload
systemctl stop slurmd
pkill -f wait-for-slurmctld.sh || true
sleep 1
systemctl start slurmd
sleep 3
echo "slurmd 状态: $(systemctl is-active slurmd)"
'
done
# ---------- 步骤 2:验证修复生效 ----------
echo ""
echo "=== 等待 20 秒让 slurmd 注册心跳 + ReturnToService=2 自动置 IDLE ==="
sleep 20
export PATH="/usr/local/slurm/bin:$PATH"
sinfo -Nl
echo ""
# 3 节点 STATE=idle + REASON=none → 修复完成
修复版本的脚本(已同步更新到项目归档 wait-for-slurmctld.sh)的核心逻辑:
bash if scontrol ping 通过; then exit 0; fi # 非 configless 节点(有 slurm.conf)走这条 if TCP 连通 k8s-master-01:6817; then sleep 1; exit 0 # ✅ configless 节点走这条(TCP 通即拉配置) fi
5.4.4 修复验证清单(Pre-fix vs Post-fix)¶
| 验证项 | 修复前 | 修复后(合格判据) |
|---|---|---|
sinfo -Nl 状态 |
3 节点 down* Not responding |
3 节点 idle none |
systemctl is-active slurmd(3 节点) |
activating 或 failed |
active,且 journalctl -u slurmd \| grep "slurmd started" 有启动日志 |
bash /usr/local/bin/wait-for-slurmctld.sh k8s-master-01 6817 |
EXIT=1(60 秒超时) | EXIT=0,1~3 秒返回(TCP 连通即通过) |
| cluster_health_check.py 引导模式 | 反复报 slurmd 启动失败 | 日志显示 3/3 节点就绪(状态: idle),服务变 inactive 正常退出 |
systemctl list-timers slurm-cluster-health.timer |
LAST 与 NEXT 差距正常 | NEXT 时间与 LAST 相差 15 分钟(OnUnitActiveSec=15min,作为备份巡检层补充),下一轮巡检会自动运行 |
六、回滚方案¶
6.1 回滚 HealthCheck 配置¶
# 恢复 slurm.conf(从备份)
ssh root@k8s-master-01 '
BACKUP=$(ls -t /usr/local/slurm/etc/slurm.conf.bak.* | head -1)
cp $BACKUP /usr/local/slurm/etc/slurm.conf
systemctl restart slurmctld
'
# 重启所有 slurmd 使旧配置生效
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
ssh root@${node} 'systemctl restart slurmd'
done
6.2 回滚自研 timer 频率¶
# 恢复为 5 分钟
ssh root@k8s-master-01 '
sed -i "s/OnUnitActiveSec=15min/OnUnitActiveSec=5min/" /etc/systemd/system/slurm-cluster-health.timer
systemctl daemon-reload
systemctl restart slurm-cluster-health.timer
'
6.3 删除 HealthCheck 脚本(可选)¶
for node in k8s-master-01 k8s-node-01 k8s-node-02 k8s-node-03; do
ssh root@${node} 'rm -f /usr/local/bin/slurm_health_check.sh'
done
6.4 批量修改计算节点配置强制备份(必须执行)¶
高危警示:任何批量推送 slurmd.service / wait-for-slurmctld.sh / slurm_health_check.sh 之前,必须先对 3 个计算节点做本地备份,便于回滚。
# ⚠️ 强制备份(循环变量按需扩展)
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
ssh root@${node} '
cp /etc/systemd/system/slurmd.service \
/etc/systemd/system/slurmd.service.bak.$(date +%Y%m%d%H%M%S)
[ -f /usr/local/bin/wait-for-slurmctld.sh ] && \
cp /usr/local/bin/wait-for-slurmctld.sh \
/usr/local/bin/wait-for-slurmctld.sh.bak.$(date +%Y%m%d%H%M%S)
[ -f /usr/local/bin/slurm_health_check.sh ] && \
cp /usr/local/bin/slurm_health_check.sh \
/usr/local/bin/slurm_health_check.sh.bak.$(date +%Y%m%d%H%M%S)
'
echo "✅ $node 备份完成"
done
# ↩️ 一键回滚(如果修复失败,按 ls -t 取最新备份恢复)
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
ssh root@${node} '
BACKUP=$(ls -t /etc/systemd/system/slurmd.service.bak.* 2>/dev/null | head -1)
[ -n "$BACKUP" ] && cp $BACKUP /etc/systemd/system/slurmd.service
BACKUP=$(ls -t /usr/local/bin/wait-for-slurmctld.sh.bak.* 2>/dev/null | head -1)
[ -n "$BACKUP" ] && cp $BACKUP /usr/local/bin/wait-for-slurmctld.sh
systemctl daemon-reload
systemctl restart slurmd
sleep 3
echo "$(hostname): slurmd=$(systemctl is-active slurmd)"
'
done
6.5 systemd 服务卡死紧急清理(reset-failed / 激活态清理)¶
当服务处于 failed / activating 卡住 / 「Main PID exited but state still activating」死锁时:
# ================
# 适用:slurmd / slurm-cluster-health 服务
# ================
UNIT="slurmd.service" # 按需替换
systemctl stop $UNIT # 1. 先尝试正常停
systemctl reset-failed $UNIT # 2. 清失败计数(关键!否则 Restart 计数器可能被锁)
systemctl daemon-reload # 3. 如果改了 unit 文件
systemctl start $UNIT # 4. 重新启动
# 特殊:slurmd 被 ExecStartPre 卡住(wait-for-slurmctld.sh 进程残留在后台)
ssh root@k8s-node-01 "
systemctl stop slurmd
pkill -f wait-for-slurmctld.sh || true # 清理 ExecStartPre 残留子进程
systemctl reset-failed slurmd
systemctl daemon-reload
systemctl start slurmd
sleep 3
systemctl is-active slurmd
"
6.6 节点 DOWN 手动恢复(三层自动恢复全部失效时的兜底)¶
当第 1 层 ReturnToService=2 + 第 2 层自研巡检 15 分钟都没把节点救回来时,手动干预:
# 步骤 1:先修底层(确保 3 节点 slurmd active)
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
ssh root@${node} "
systemctl restart slurmd
sleep 5
systemctl is-active slurmd
"
done
# 步骤 2:在控制节点强制将 DOWN / DRAIN 节点置为 resume
export PATH=/usr/local/slurm/bin:$PATH
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
scontrol update nodename=${node} state=resume reason="manual recovery ops"
done
# 步骤 3:20 秒后验证
sleep 20
sinfo -Nl
# 期望:3 节点 STATE=idle, REASON=none
七、日常运维速查¶
# === 查看 HealthCheck 配置 ===
ssh root@192.168.0.2 'grep HealthCheck /usr/local/slurm/etc/slurm.conf'
# === 查看各节点最近 HealthCheck 执行记录 ===
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
echo "=== ${node} ==="
ssh root@${node} 'journalctl -u slurmd --since "10 min ago" --no-pager | grep HealthCheck | tail -1'
done
# === 手动触发 HealthCheck(重启 slurmd 即触发 REGISTER 检查)===
ssh root@k8s-node-01 'systemctl restart slurmd'
# === 查看节点 DRAIN 原因 ===
ssh root@192.168.0.2 'sinfo -o "%N %T %E"'
# === 手动恢复 DRAIN 节点 ===
ssh root@192.168.0.2 'scontrol update nodename=k8s-node-03 state=resume reason="manual recovery"'
# === 查看自研备份巡检状态 ===
ssh root@192.168.0.2 'systemctl list-timers slurm-cluster-health.timer'
ssh root@192.168.0.2 'journalctl -u slurm-cluster-health.service --since "1 hour ago" --no-pager | tail -20'
# === 查看 SLURM 集群状态 ===
ssh root@192.168.0.2 'sinfo; sinfo -o "%N %T %C %m %e"'
# === 查看所有服务状态 ===
ssh root@192.168.0.2 'systemctl is-active slurmctld'
for node in k8s-node-01 k8s-node-02 k8s-node-03; do
ssh root@${node} 'systemctl is-active slurmd munge'
done
附录:项目文件清单¶
| 文件 | 用途 |
|---|---|
| SLURM_HEALTHCHECK_STANDARD_SOP.md | 本文档(行业标准 HealthCheck SOP) |
| slurm_health_check.sh | SLURM 内置 HealthCheck 脚本(行业标准) |
| wait-for-slurmctld.sh | configless 模式启动前等待 slurmctld 就绪脚本(P0修复) |
| slurmd.service | 计算节点 slurmd 服务配置(含 ExecStartPre + Restart=always) |
| slurmctld.service | 控制节点 slurmctld 服务配置(含 slurmdbd 依赖) |
| slurm-cluster-health.timer | 自研备份巡检定时器(已降频至 15 分钟) |
| slurm-cluster-health.service | 自研备份巡检服务(超时调整为 300 秒) |
| slurm-cluster-health-bootstrap.service | 启动引导服务(超时调整为 300 秒) |
| cluster_health_check.py | 自研健康检查脚本(备份巡检层,保留 15min 频率) |
| SLURM_CLUSTER_HEALTH_SOP.md | ~~自研方案历史 SOP(参考)~~ → 已合并至本文档,源文件及旧 path.bak 已清理 |
| SLURM_PROMETHEUS_GRAFANA_SUMMARY.md | Prometheus+Grafana 监控 SOP(第 3 层告警) |
变更记录¶
| 日期 | 版本 | 变更内容 |
|---|---|---|
| 2026-08-13 | v0.1 | 自研方案首版部署:cluster_health_check.py 首版 + 引导模式标记机制 + slurm-cluster-health.timer(5 分钟频率,主健康检查角色) |
| 2026-08-14 | v0.2 | 修复 slurmd.service:明确 Type=simple 必须配合 -D 前台运行,解决 activating 卡死、Restart 不触发问题(对应 5.3.1 历史问题) |
| 2026-08-14 | v0.3 | 修复 slurm-cluster-health.service:RemainAfterExit=no,解决 oneshot+yes 导致 timer 只跑一次就停摆(对应 5.3.2 历史问题) |
| 2026-08-14 | v0.4 | 优化 引导模式逻辑:_ensure_slurmd_running + 并行 SLURM 状态同步,TimeoutStartSec=120(对应 5.3.3 历史问题旧版,后续 P0-4 升级至 300) |
| 2026-08-14 | v1.0 | 部署 SLURM 内置 HealthCheck(行业标准),自研方案降频至 15 分钟作为备份巡检层 |
| 2026-08-14 | v2.0 | P0 严重逻辑错误修复:4个P0(slurmd等待slurmctld+Restart=always+slurmdbd依赖+超时调整)+ 4个P1(ReturnToService/JobAcct/JobComp/DefMem)+ 1个P2(RestartSec) |
| 2026-08-27 | v2.1 | ① 合并自研方案历史 SOP(新增 5.0 通用排障 Step1~6 + 5.3.1~5.3.4 四条历史问题条目 + 6.4~6.6 高危操作警示);② 修正与实机不符值:1.3 三层体系 ReturnToService=2、5.4.4 备份巡检 timer 15min;③ 清理重复的「〇、快速索引」;④ 追加 8-13~8-14 自研 v0.x 早期演进记录;⑤ 5.4 节记录 2026-08-27 冷开机 configless + wait-for-slurmctld.sh 逻辑缺陷的实际修复案例 |
来源:
/home/rain/Documents/slurm/slurm_ins/slurm_health_ensure/SLURM_HEALTHCHECK_STANDARD_SOP.md| 最后更新:2026-08-27 19:34(由build/sync.py同步,请勿在站点侧修改)