Slurm 是什么
Slurm 是 HPC 集群的工作负载管理器,负责多节点上的资源管理和作业管理。
三个常驻进程:
| 进程 | 位置 | 职责 |
|---|---|---|
slurmctld | 控制节点 | 维护队列和节点状态,决定作业调度 |
slurmd | 每个计算节点 | 接收指令、启动作业进程、上报节点状态 |
slurmdbd | 控制节点 | 把作业记录写进数据库,唯一访问数据库的组件 |
为什么用 Slurm
V100 目前靠人工协调分配,有三个问题:同一张卡可能被多个进程同时占用,显存耗尽时一并失败;资源被占满时没有排队机制,后续作业只能轮询等待;单个作业的资源占用没有记录。
Slurm 解决的是第一个问题,它持有 GPU 的分配权。作业以 --gres=gpu:v100:1 申请资源,Slurm 从空闲设备中分配一张,并通过 cgroup 将其余设备对该进程屏蔽,同一张卡不会被重复分配。另外两个问题是这一机制的延伸:有了统一分配才有队列,有了队列才能产生每个作业的资源占用记录。
选 Slurm 而不用其他调度器,因为它在 HPC 领域是事实标准,原生支持 GRES(Generic RESource,用于描述 GPU 这类不可切分的资源),研究人员也熟悉 srun / sbatch 这套命令。
为什么不复用已有的 K3s
15 台里有 13 台在跑 K3s,复用它是个自然的问题。K8s 的 device plugin 同样能把 GPU 分配给 Pod,区别在调度语义上:它面向长期运行的服务,资源不足时 Pod 停在 Pending 等待,没有优先级队列,没有 backfill,也没有跨用户的公平份额。批处理需要的排队、按份额出队、超时终止、失败重排,这套语义 K8s 不提供。
作业结束后的资源消耗历史同样不会保留,除非另外接一套监控采集。
现状
15 台 V100,每台 8 卡,driver 580.159.03,kernel 5.15.0-185。控制节点是 gpu2000-stor01(10.250.200.253),80 CPU / 193 GB。
部署前的扫描结果:
| 项目 | 结果 |
|---|---|
slurmctld / slurmd / munge | 全部 absent |
apt 候选版本 | 21.08.5-2ubuntu1 |
| 运行 K3s 的节点 | 15 台中的 13 台 |
| cgroup | v2,controller 齐全 |
未运行 K3s 的有两台:gpu2011(10.250.200.21)和 gpu2014(10.250.200.24)。
目标
本期只部署 control plane,让作业开始产生 accounting 数据。Web UI、Grafana、Prometheus 不在范围内。
控制面与计算节点如何通信
各组件之间全部走 TCP RPC,端口在 slurm.conf 里指定:
SlurmctldPort=6817
SlurmdPort=6818
AccountingStoragePort=6819
SrunPortRange=60001-63000
一个作业从提交到运行、再到写入数据库的路径:
srun / sbatch
│ 6817 提交作业
▼
slurmctld ──── 6818 ────▶ slurmd (gpu2011)
│ │
│ │ 60001-63000
│ 6819 │ 作业 stdout/stderr 直接回传客户端
▼ │
slurmdbd │
│ 127.0.0.1:3306 │
▼ ▼
MariaDB 作业进程
slurmctld 把作业派给 slurmd,slurmd 以 root 运行(SlurmdUser=root),需要 setuid 到提交者身份并建立 cgroup 限制。作业结束后 slurmd 回报 slurmctld,再由 slurmctld 转给 slurmdbd 写入数据库。
计算节点不直接连数据库。这条路径在架构上就不存在,slurmd 只认识 slurmctld。
MUNGE 认证
上述 RPC 的身份验证由 MUNGE 承担:
AuthType=auth/munge
CredType=cred/munge
MUNGE 的做法是,发起方在每次 RPC 上附一个凭据,内容包含调用者的 UID、GID 和时间戳,用共享密钥签名;接收方用同一份密钥验签并读出身份。所以两端必须持有相同的 key。
凭据里存的是数字 UID,因此两台机器上 slurm 和 munge 的 UID/GID 必须一致,否则同一个凭据在对端会解析成另一个用户。这里固定为 slurm=64030、munge=64031,部署时校验:
check_user slurm 64030 64030
check_user munge 64031 64031
slurmrestd 走另一条认证路径,用 JWT(AuthAltTypes=auth/jwt),签名密钥在 /var/lib/slurm/slurmctld/jwt_hs256.key,权限 400 slurm:slurm。
为什么不用 apt 安装
仓库候选版本 21.08.5 是 2021 年的。这一期需要的 typed GRES accounting、slurmrestd 的 JWT 认证、cgroup v2 支持在该版本上都不完整。
改为从 SchedMD 源码构建,版本固定在 26.05.4(Debian 版本号 26.05.4-1+v1001):
- 源码 SHA256
035f4b193d4de979ba5381beca206a50b6b886b2793b06f68a1ce7e67022b06a - 在隔离的 Ubuntu 22.04 container 内构建,产出 17 个 DEB
- 存放于
/tank/apps/slurm/26.05.4/debs,附 manifest 与本机 package index
部署时对 slurm-smd* 加 hold,避免后续 apt 升级把仓库里的 21.08 混进来。
为什么只部署一个计算节点
13 台正在运行 K3s,上面有活跃 workload。若把这些节点加入 Slurm partition,两个调度器会各自认为自己拥有该节点的 8 张卡,彼此不知道对方已经分配了什么,结果是超卖。要加入必须先 drain。
因此 canary 从两台未运行 K3s 的机器中选,gpu2011 先上,gpu2014 作为下一台:
NodeName=gpu2011 CPUs=80 SocketsPerBoard=2 CoresPerSocket=20 ThreadsPerCore=2 \
RealMemory=184000 Gres=gpu:v100:8 Feature=v100,sm70
PartitionName=canary Nodes=gpu2011 Default=YES MaxTime=01:00:00 \
DefCpuPerGPU=8 DefMemPerGPU=20000
分区命名为 canary,反映当前只有单节点的状态。
为什么 MariaDB
slurmdbd 的 accounting storage 只有 accounting_storage/mysql 一个可用实现,PostgreSQL 的支持早已从上游移除。选型范围限定在 MySQL 系。
在此范围内选择 MariaDB 11.4,依据是其 LTS 维护周期至 2029 年,期间不需要执行大版本升级。镜像按 digest 引用(sha256:8fade423...)而非 tag,避免上游重新推送同名 tag 导致版本漂移。
InnoDB 参数
innodb-buffer-pool-size=4G
innodb-log-file-size=1G
innodb-lock-wait-timeout=900
innodb-default-row-format=dynamic
innodb-file-per-table=ON
innodb-snapshot-isolation=OFF
max-allowed-packet=64M
innodb-lock-wait-timeout=900 是其中影响最大的一项。slurmdbd 的 rollup 作业按小时、天、月三级聚合用量,会开启长事务扫描历史表。默认 50 秒不足以完成,超时后 rollup 失败,用量统计出现断档。SchedMD 的 accounting 文档同样建议调高该值。
innodb-snapshot-isolation=OFF 显式指定,不依赖版本默认值。该开关启用后,事务读取的行若在快照之后被修改,InnoDB 会中止事务并报错,而不是继续使用旧值。slurmdbd 的负载是一边写入作业记录一边执行 rollup,引入额外的事务失败路径没有收益。该默认值在 MariaDB 各版本间调整过,写在配置里可以避免升级后出现行为变化。
buffer-pool-size=4G 相对 193 GB 物理内存偏小,但 accounting 库规模有限,当前 34 张表,refquota 上限 64 GB。4 GB 足以容纳近期作业和 association 表,继续增大不会带来收益。
ZFS 存储
数据位于 tank/slurmdb:
recordsize=16K lz4 primarycache=metadata logbias=throughput refquota=64G
recordsize=16K 与 InnoDB 的 page size 对齐。ZFS 默认 128K,写入一个 16K page 会退化为读取 128K record、修改其中 16K、再整块写回,产生 8 倍写放大。设为 16K 后一个 page 对应一个 record。
primarycache=metadata 与上面的 4G buffer pool 配合。InnoDB 已在自己的 buffer pool 中缓存数据页,ARC 再缓存一份相同内容属于重复占用内存,因此只让 ARC 保留 metadata。
refquota=64G 用于防止数据库写满整个池。池当前使用率 78%,accounting 库若失控增长会同时影响同池上的 NFS 与模型存储。
rollback 验证
部署本身不复杂,需要验证的是 rollback 能够完整回退、且重新部署后历史数据仍然存在。执行了三轮:
- 运行一个 GPU 作业 → rollback → reapply,作业
2恢复,新作业4正常记录 - 再次 rollback,保留上一代 dump → reapply,
2与4均在,新作业6 - 验证 watchdog 清理与标记摘除顺序 → reapply,最新 dump 恢复
三轮结束后 2 / 4 / 6 状态均为 COMPLETED,MariaDB 中三行记录都在。最新作业 9:
9|ubuntu|v100|COMPLETED|0:0|billing=2,cpu=2,gres/gpu:v100=1,gres/gpu=1,mem=2G,node=1
gres/gpu:v100=1 与 gres/gpu=1 两项都已记录,对应配置:
AccountingStorageTRES=gres/gpu,gres/gpu:v100
只声明 gres/gpu 的情况下,后续混入 A100 将无法区分卡型,账面上都是一张 GPU,而两者的单位卡时价值不同。
删除 ZFS dataset 之前,会先把 dump 导入 scratch 库执行一次验证,确认 34 张表和作业行存在后才继续。zfs destroy 不可逆,而未经导入验证的 dump 不能算作可用备份。相同 change ID 的 reapply 也会先核对 SHA 再导入。
dump 保留两代,存放于 root-only 备份路径与 /tank/archive/slurm-controlplane/rollback/。
网络隔离
slurmdbd 的 6819 由 systemd per-service 网络策略限制,只放行 loopback 与 10.250.200.0/24。MariaDB 仅发布在 127.0.0.1:3306:
LISTEN 0 4096 0.0.0.0:6819 users:(("slurmdbd",...))
LISTEN 0 4096 0.0.0.0:6817 users:(("slurmctld",...))
LISTEN 0 4096 127.0.0.1:3306 users:(("docker-proxy",...))
从计算节点实测:
$ cat < /dev/tcp/10.250.200.253/3306
bash: connect: Connection refused
与前面的通信路径一致,计算节点本来就不需要访问数据库。Tailscale 侧连接 6819 同样被拒绝。
slurmrestd 只建立 Unix socket,不监听 TCP 6820。JWT 验证了三种情况:无认证 401、Slurm ping 200、SlurmDB ping 200。
同机上 12 个 K3s server/agent 与 guacd 全程 active。
MariaDB watchdog
容器使用无上限的 on-failure 重启策略,另有一个 systemd timer 每分钟检查健康状态,异常时重启托管服务。
启动单元设置了 ZFS mount 的前置条件。池未挂载时 /tank/slurmdb 是空目录,MariaDB 会将空 datadir 视为首次启动并初始化一个新库;此时 watchdog 检查会判定为健康,因为进程处于运行状态。等到发现异常,accounting 历史已经被空库覆盖。
还没做的
- 存储节点是单点。 NFS、controller、accounting、备份副本都在同一台。该节点故障不仅导致调度停止,历史数据同样丢失。扩展 canary 之前需要先把 dump 转移到机外
- 没有备用
slurmctld与slurmdbd - association enforcement 未启用。 限额一旦开启,配置错误会导致所有人无法提交作业,需要在用户、账户、QoS 定义完整并测试后再开
- ZFS 池使用率 78%。 数据库侧有 refquota 限制,池整体容量与 scrub、trim 排程是另一项工作
参考
Slurm
- Quick Start User Guide — 三个 daemon 的分工与整体架构
- Accounting and Resource Limits — slurmdbd 与数据库配置,InnoDB 参数建议出自此处
- Generic Resource (GRES) Scheduling — GPU 作为 GRES 的声明与分配
- Trackable Resources (TRES) —
AccountingStorageTRES中泛型与 typed 的区别 - Control Group v2 Plugin — 设备隔离的实现方式
- slurm.conf / slurmdbd.conf — 参数手册
- slurmrestd 与 REST API — Unix socket 模式与 JWT 认证
MUNGE
- dun/munge — 凭据格式与共享密钥机制
- Installation Guide — UID/GID 一致性要求
MariaDB
- InnoDB System Variables —
innodb_lock_wait_timeout、innodb_snapshot_isolation的定义与各版本默认值 - Maintenance Policy 与 Release Dates — LTS 周期
ZFS
- OpenZFS Workload Tuning — 数据库场景下
recordsize与primarycache的取值依据