V100 集群上 Slurm control plane

从 15 台机器的实际状态出发,记录自建软件包、MariaDB 选型、InnoDB 与 ZFS 参数的依据,以及控制面与计算节点的通信路径。

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 台
cgroupv2,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 能够完整回退、且重新部署后历史数据仍然存在。执行了三轮:

  1. 运行一个 GPU 作业 → rollback → reapply,作业 2 恢复,新作业 4 正常记录
  2. 再次 rollback,保留上一代 dump → reapply,2 与 4 均在,新作业 6
  3. 验证 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

MUNGE

MariaDB

ZFS