使用 `slurm-spank` 插件在作业中自动拉起 Docker
解读
国内超算中心、高校 GPU 集群及企业私有 HPC 环境普遍采用 Slurm 作为资源调度器,而 AI 训练、生信分析等场景又依赖 Docker 镜像交付环境。直接让普通用户在计算节点上执行 docker run 会触碰 root 权限、镜像安全、cgroups 冲突 三大红线,因此面试官想知道你是否能把 Slurm 的安全审计、资源限制与 Docker 的敏捷交付 打通。slurm-spank 是 Slurm 官方提供的 插件钩子框架,可在作业生命周期的 20+ 个阶段插入自定义逻辑,是实现“无 root、自动、安全”拉起 Docker 的 唯一官方通道。题目考察的是你对 Slurm 插件机制、Docker 无根模式、cgroups v2 统一、镜像仓库加速、失败回滚等落地细节的掌握程度,而不是简单写一句 docker run。
知识点
- SPANK 插件加载顺序:
slurm.conf中required与optional区别,插件返回值ESPANK_SUCCESS/ERROR对作业的影响。 - 无根 Docker(Rootless mode):
/etc/subuid映射、fuse-overlayfs存储驱动、节点级systemd --user持久化,国内 CentOS 7 需内核 ≥5.4 并启用 cgroup v2。 - cgroups 统一层级:Slurm 的
cgroup.conf必须开启ConstrainDevices=yes与AllowUsers=root,slurm,否则 Docker 创建的子 cgroup 会被 Slurm 视为逃逸而直接 Kill。 - 镜像预热与缓存:使用
registry.cn-hangzhou.aliyuncs.com等国内镜像源,结合SPANK_LOCAL_INIT阶段触发docker pull并写入节点本地 SSD,避免作业启动时因拉镜像超时 120 s 被 Slurm 中断。 - 安全加固:
- 强制
--read-only --tmpfs /tmp防止写层膨胀; - 通过
--security-opt=no-new-privileges关闭提权; - 利用
slurm_spank_task_init_privileged把宿主机/etc/passwd只读挂载,实现 UID/GID 与作业用户一致,解决容器内用户权限漂移。
- 强制
- 失败回滚:插件在
slurm_spank_task_exit中必须docker rm -f并清理cgroup路径,否则节点会残留docker-containerd-shim进程,导致 Slurm 误判节点为 drain 状态。 - 日志审计:所有
docker命令通过logger -t slurm_spank_docker写入rsyslog,并对接 ELK/LTS 平台,满足等保 2.0 对“容器操作可审计”的要求。
答案
-
节点准备
- 升级内核到 5.4+ 并启用
cgroup v2; - 安装
slurm-spank-docker-1.0-1.el7.x86_64.rpm(自研包,内含spank_docker.so与docker-rootless.service); - 创建
slurm用户组并写入/etc/subuid:slurm:100000:65536。
- 升级内核到 5.4+ 并启用
-
插件实现(核心片段,C 语言)
#include <slurm/spank.h>
SPANK_PLUGIN(docker, 1);
static char *image = NULL;
static char *runtime = "nvidia"; // GPU 场景
int slurm_spank_init(spank_t sp, int ac, char **av){
spank_option_register(sp, "docker-image", "Image to run", 1, 0,
(spank_opt_cb_f) spank_option_getopt, &image);
return ESPANK_SUCCESS;
}
int slurm_spank_task_init(spank_t sp, int ac, char **av){
if (!image) return ESPANK_SUCCESS;
uid_t uid = getuid();
char cmd[1024];
snprintf(cmd, sizeof(cmd),
"docker run -d --rm --name job${SLURM_JOB_ID}_task${SLURM_PROCID} "
"--user %d:%d --read-only --network=none "
"--runtime=%s --cgroup-parent=/slurm/uid_%d/job_%s/step_%s "
"-v /tmp:/tmp:rshared "
"%s", uid, uid, runtime, uid,
spank_job_control_getenv(sp, "SLURM_JOB_ID"),
spank_job_control_getenv(sp, "SLURM_STEP_ID"),
image);
if (system(cmd) != 0)
return slurm_error("docker run failed");
return ESPANK_SUCCESS;
}
int slurm_spank_task_exit(spank_t sp, int ac, char **av){
char cmd[256];
snprintf(cmd, sizeof(cmd), "docker rm -f job${SLURM_JOB_ID}_task${SLURM_PROCID}");
system(cmd);
return ESPANK_SUCCESS;
}
-
Slurm 配置
/etc/slurm/slurm.conf追加:RequiredPlugins=spank_docker.so;cgroup.conf开启TaskPlugin=task/cgroup并配置AllowedDevicesFile=/etc/slurm/cgroup_allowed_devices_file.conf;- 提交作业:
sbatch --docker-image=registry.cn-hangzhou.aliyuncs.com/myai/pytorch:2.1.0 train.sh。
-
效果验证
- 作业启动后
squeue -j <JOBID> -o "%N %C"显示节点 CPU 被 Slurm 与 Docker 同时感知; docker stats中容器 CPU 限额与scontrol show job的TRES=cpu=16完全一致;- 作业退出后
docker ps -a无残留,节点状态IDLE,满足生产级无人值守。
- 作业启动后
拓展思考
- 多架构镜像:国内 ARM 超算节点越来越多,需在
spank_docker.so里调用crane manifest判断镜像是否含linux/arm64平台,否则自动回退到qemu-user-static,避免作业跑到 x86 节点才报错。 - RDMA 网络:高性能场景要求容器内
ib0直通,可通过SPANK_PLUGIN的slurm_spank_init_post_opt阶段写入--device /dev/infiniband并挂载/etc/rdma,但需提前在slurm.conf给作业分配GRES=rdma:2,否则插件直接返回ESPANK_ERROR拒绝提交。 - FUSE Overlay 性能瓶颈:Rootless 模式下
/home用fuse-overlayfs性能下降 15%,可让插件检测节点是否挂载了nvme盘,若有则把DOCKER_ROOT指向/nvme/docker/$UID,并定期通过fstrim回收,解决国内高校 NVMe 盘小、镜像层爆炸导致节点磁盘满的问题。 - 与 Kubernetes 共存:部分单位计算节点同时装
k8s kubelet,此时cgroup driver必须统一为systemd,且spank_docker.so需把--cgroup-parent指向/kubepods.slice/…之外,防止 Slurm 与 kubelet 互相迁移进程,这是国内“HPC+K8s 混部”场景面试常问的坑。