Slurm 集群踩坑复盘:openEuler 22.03 + Slurm 23.02.7

背景:300 节点集群,两台控制节点,openEuler 22.03,Slurm 23.02.7(源码编译,系统仓库自带版本太旧)。GPU 分区混有 A100 和 3090,另外有少量昇腾 910B 节点在试运行。下面记录几次真实故障的定位和处理,不是入门教程。

那天下午接到反馈:gpu 分区排队 200+。

先看 squeue,服务正常,200 多个作业全是 PENDING,最早的已经排了快三个小时。第一反应是资源不够,但 sinfo -N -o "%N %t %C %G" 一看,大量节点 IDLE,空闲资源充足。

结合 squeue -o "%.10i %.9P %.20j %.8T %R" 排查,排队的以单卡小作业为主,排队原因集中在 Priority 和 QOS 组资源限制相关字段。再往下核对,某课题组一次性提交了 80 个单卡作业,把 QOS 里 GrpTRES=gres/gpu=16 的配额占满了(这个写法的对错后面说),其他项目组的作业只能等。

更该反思的是:当时集群没启用回填调度,默认调度器更接近 FIFO 行为,队头一大作业堵住,后面不管大小全卡住。

当天做了两件事:启用 sched/backfill;把 QOS 配额从按用户维度改成按项目组维度。下午同类作业排队时间从 3 小时降到 40 分钟左右。具体参数在下面 bf_window 部分。

新节点 down:munge key 不一致

扩容时新增 12 台计算节点,slurmd 安装启动后,sinfo 显示全部 down。

slurmd 日志:

slurmd: error: Credential/Munged could not validate credentials
slurmd: error: Unable to authenticate message from slurmctld

一开始怀疑版本不匹配,比对后一致;又查防火墙和 6817 端口,也正常。折腾到晚上才定位:新节点的 /etc/munge/munge.key 来自系统模板,和集群控制节点上的 key 不一致。

处理:

# 全集群统一 munge key
scp ctld1:/etc/munge/munge.key node-new01:/etc/munge/munge.key
chown munge:munge /etc/munge/munge.key
chmod 400 /etc/munge/munge.key
systemctl restart munge slurmd   # 每台新节点执行

加节点先核对 munge key。Slurm 的认证、授权、记账都依赖 munge,key 不一致时故障表现五花八门,日志也不会直接告诉你“key 不一致”。

升级后 squeue 报 Unable to contact slurm controller

从 23.02.6 升 23.02.7,当时觉得小版本补丁,风险不大,直接上了。

先升了 slurmctld 和 slurmd,slurmdbd 没动。随后用户端开始报:

squeue: error: Unable to contact slurm controller (ctld1):6817, is it running?

slurmctld 起不来,journalctl -u slurmctld 输出:

slurmctld: error: slurmdbd: 6819: Connection refused
slurmctld: fatal: unable to communicate with slurmdbd

根因是:新版 slurmctld 和旧版 slurmdbd 的通信协议不兼容,slurmctld 拒绝启动,squeue 自然连不上 controller。

正确升级顺序固定为:slurmdbd → slurmctld → slurmd,不能跳。

# 服务名按实际 systemd 单元调整
systemctl stop slurmd slurmctld slurmdbd
# 先升级并启动 slurmdbd
systemctl start slurmdbd && sleep 5 && systemctl status slurmdbd
# 再升级并启动 slurmctld
systemctl start slurmctld
# 最后逐批升级 slurmd
systemctl start slurmd

另外两条教训也来自实际:

  • 跨大版本升级,比如 22.05 → 23.02,必须看官方 Upgrade Guide,不能套用小版本习惯。升级完没法回滚到旧大版本,强行降级会丢作业队列和记账数据。
  • 升级要安排维护窗口并提前通知用户。那次放在凌晨,controller 中断约一个半小时,用户次日才发现,通知和窗口管理都得改进。

cgroup v1/v2 混部

新到货节点装了较新的 openEuler 22.03 镜像,启动参数启用了 cgroup v2;存量节点还是 v1。

新节点 slurmd 能正常启动,但作业一提交就 FAILED,日志:

slurmd: error: create cgroup for job 123456: No such file or directory
slurmd: error: unable to create job cgroup: No such file or directory

v1 和 v2 的挂载结构不一样:v1 在 /sys/fs/cgroup 下按子系统分目录,v2 是单一挂载点,子系统以文件形式组织。配置里如果显式指定了 v1 路径,或者依赖 CgroupAutomount 之类的行为,在 v2 节点上就会报 No such file or directory。

当时是 v1/v2 混部,处理方式:按节点内核版本分别验证 cgroup 配置,先调 cgroup.conf 适配 v2,再在单节点实测内存约束生效——作业超内存能被杀,而不是拖垮整机——确认后再批量铺开。

别信“支持 v2”就等于开箱即用,混部环境下配置不匹配就会炸。

MIG 的取舍

有人问过:A100 成本高,要不要上 MIG 切分提高利用率。评估后没上,原因:

  • MIG 只支持 A100 等部分型号,3090 不支持;集群卡型混布,为单一型号引入一套切分逻辑,运维复杂度明显上升。
  • MIG 能隔离显存和算力,但切分粒度、驱动约束、混卡环境下的运维都不轻松;当前业务以整卡训练和单卡推理为主,切分收益不明显。
  • MIG 对驱动版本有要求,驱动升级还得同步调整 MIG 配置,变更风险增加。

最后选择在调度层解决:整卡粒度 + QOS 配额 + 回填。真要卡内切分,昇腾侧有 vNPU/HAMi 类方案,灵活性更高,可以另开专题。

QOS 配额:按项目组划分,注意 TRES 命名

200+ 排队事件后,QOS 从按用户限定额度改成按项目组(account)限定。

按用户限定的问题是:单用户能刷满账户内配额,影响同账户其他成员;按项目组限定后,配额边界和业务团队一致,好治理。

sacctmgr add account project-a
sacctmgr add user zhangsan account=project-a
sacctmgr add qos gpu-4h MaxWall=04:00:00 GrpTRES=gres/gpu=16
sacctmgr modify account project-a set qos=gpu-4h

这里有个关键细节:TRES 的规范命名是 gres/gpu,带类型前缀;GrpTRES=gpu=16 不会生效。当时 200+ 排队事件里,配额写法错误就是重要原因之一——配置不报错,但限制形同虚设。改成 GrpTRES=gres/gpu=16 后配额才真正生效。

配套还要在 slurm.conf 里打开记账跟踪:

AccountingStorageTRES=gres/gpu

如果还有其他 gres,按实际追加。

bf_window:单位是分钟

回填启用后效果不明显,调参时才发现 bf_window 的单位问题。

这个参数单位是分钟,默认 1440,也就是一天;旁边的 bf_resolutionbf_intervalbf_max_time 等单位是秒。当时误以为单位是秒,填了 bf_window=60,以为窗口是 1 分钟;后来查 man 页发现,60 其实是 60 分钟。虽然也能回填,但和分区 7 天的 MaxTime 不匹配,效果没达到预期。

最终配置:

SchedulerType=sched/backfill
SchedulerParameters=bf_window=10080,bf_resolution=120
# bf_window 单位分钟,10080 = 7 天,与分区 MaxTime 对齐
# bf_resolution 单位秒,回填时间分辨率;bf_window 调大时建议同步调大

回填的原理:队头大作业要等资源,调度器就把在它启动前能跑完的小作业插进去执行。所以效果很明显——同一批作业排队时间从 3 小时降到 40 分钟,吞吐上去了,大作业启动时间也没受影响。

gres 命名:名为 gpu 才会注入 CUDA_VISIBLE_DEVICES

昇腾节点接入集群时,gres 名称最初定义成 npu,结果出问题了。

配置大概是这样:

GresTypes=gpu,npub
NodeName=gpu[01-04] Gres=gpu:8
NodeName=npu[01-02] Gres=npub:8

PartitionName=gpu Nodes=gpu[01-04] MaxTime=7-00:00:00 State=UP
PartitionName=npu Nodes=npu[01-02] MaxTime=7-00:00:00 State=UP

作业用 --gres=npub:2 提交后,昇腾程序报错,检查环境变量发现 ASCEND_RT_VISIBLE_DEVICES 是空的。

排查确认:CUDA_VISIBLE_DEVICES 由 Slurm 的 GPU 插件自动注入,而且只对 gres 名为 gpu 的资源生效。自定义名称比如 npub,至少在我们当时的版本和配置下没有触发自动注入,SLURM_JOB_GPUS 也没看到。后来查文档和社区,稳妥做法还是用 gpu + Type 区分型号。

正确做法:

# gres.conf
NodeName=npu[01-02] Name=gpu Type=Ascend910B File=/dev/davinci[0-7]

slurm.conf 里同步:

GresTypes=gpu
NodeName=gpu[01-04] Gres=gpu:8
NodeName=npu[01-02] Gres=gpu:Ascend910B:8

PartitionName=gpu Nodes=gpu[01-04] MaxTime=7-00:00:00 State=UP
PartitionName=npu Nodes=npu[01-02] MaxTime=7-00:00:00 State=UP

提交时:

#SBATCH --gres=gpu:Ascend910B:2
export ASCEND_RT_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES

CUDA_VISIBLE_DEVICES 正常注入后,作业内索引从 0 开始,一行映射就能让昇腾只看到分配给自己的卡。但要注意:昇腾设备在不同驱动、CANN、Slurm 版本下,设备索引和变量注入行为有差异。配置落地前先在单节点验证“作业内仅可见已分配设备”,再扩到多节点。

如果确实要用自定义 gres 名,就得在作业 wrapper 里自己解析分配结果,比如通过 scontrol show jobAllocTRES,或者根据节点上 /dev/davinci* 的 cgroup 可见性,自己构造 ASCEND_RT_VISIBLE_DEVICES。麻烦,不推荐。

高可用配置

高可用用多个 SlurmctldHost,第一个是主,后面是备。BackupControllerBackupAddr 从 18.08 起就弃用了,网上有些旧教程还在推,配的时候以官方文档为准。

SlurmctldHost=ctld1
SlurmctldHost=ctld2
StateSaveLocation=/slurm-state

StateSaveLocation 必须放在主备都能读写的共享存储上,不能丢在任一控制节点的本地盘。放本地盘的话,主节点一挂,备节点接管时没有作业状态,计算节点重新注册过程中可能误杀作业。

写在最后

Slurm 官方文档很全,但更适合查,不适合通读。实际故障大多出在配置交叉点:munge 和 slurmd 的认证关系、slurmdbd 和 slurmctld 的升级顺序、cgroup v1/v2 的挂载差异、gres 命名和变量注入的隐性约定。文档各章节都有,但很少有人会把这几项串在一起排查。

目前集群还固守在 23.02.7,升级节奏另行规划。


本文基于实际环境整理,部分配置已脱敏;关键参数请以 Slurm 官方文档和现场版本为准。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐