使用 `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

知识点

  1. SPANK 插件加载顺序slurm.confrequiredoptional 区别,插件返回值 ESPANK_SUCCESS/ERROR 对作业的影响。
  2. 无根 Docker(Rootless mode)/etc/subuid 映射、fuse-overlayfs 存储驱动、节点级 systemd --user 持久化,国内 CentOS 7 需内核 ≥5.4 并启用 cgroup v2
  3. cgroups 统一层级:Slurm 的 cgroup.conf 必须开启 ConstrainDevices=yesAllowUsers=root,slurm,否则 Docker 创建的子 cgroup 会被 Slurm 视为逃逸而直接 Kill。
  4. 镜像预热与缓存:使用 registry.cn-hangzhou.aliyuncs.com 等国内镜像源,结合 SPANK_LOCAL_INIT 阶段触发 docker pull 并写入节点本地 SSD,避免作业启动时因拉镜像超时 120 s 被 Slurm 中断
  5. 安全加固
    • 强制 --read-only --tmpfs /tmp 防止写层膨胀;
    • 通过 --security-opt=no-new-privileges 关闭提权;
    • 利用 slurm_spank_task_init_privileged 把宿主机 /etc/passwd 只读挂载,实现 UID/GID 与作业用户一致,解决容器内用户权限漂移。
  6. 失败回滚:插件在 slurm_spank_task_exit 中必须 docker rm -f 并清理 cgroup 路径,否则节点会残留 docker-containerd-shim 进程,导致 Slurm 误判节点为 drain 状态。
  7. 日志审计:所有 docker 命令通过 logger -t slurm_spank_docker 写入 rsyslog,并对接 ELK/LTS 平台,满足等保 2.0 对“容器操作可审计”的要求。

答案

  1. 节点准备

    • 升级内核到 5.4+ 并启用 cgroup v2
    • 安装 slurm-spank-docker-1.0-1.el7.x86_64.rpm(自研包,内含 spank_docker.sodocker-rootless.service);
    • 创建 slurm 用户组并写入 /etc/subuidslurm:100000:65536
  2. 插件实现(核心片段,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;
}
  1. 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
  2. 效果验证

    • 作业启动后 squeue -j <JOBID> -o "%N %C" 显示节点 CPU 被 Slurm 与 Docker 同时感知;
    • docker stats 中容器 CPU 限额与 scontrol show jobTRES=cpu=16 完全一致;
    • 作业退出后 docker ps -a 无残留,节点状态 IDLE满足生产级无人值守

拓展思考

  1. 多架构镜像:国内 ARM 超算节点越来越多,需在 spank_docker.so 里调用 crane manifest 判断镜像是否含 linux/arm64 平台,否则自动回退到 qemu-user-static,避免作业跑到 x86 节点才报错。
  2. RDMA 网络:高性能场景要求容器内 ib0 直通,可通过 SPANK_PLUGINslurm_spank_init_post_opt 阶段写入 --device /dev/infiniband 并挂载 /etc/rdma,但需提前在 slurm.conf 给作业分配 GRES=rdma:2,否则插件直接返回 ESPANK_ERROR 拒绝提交。
  3. FUSE Overlay 性能瓶颈:Rootless 模式下 /homefuse-overlayfs 性能下降 15%,可让插件检测节点是否挂载了 nvme 盘,若有则把 DOCKER_ROOT 指向 /nvme/docker/$UID,并定期通过 fstrim 回收,解决国内高校 NVMe 盘小、镜像层爆炸导致节点磁盘满的问题
  4. 与 Kubernetes 共存:部分单位计算节点同时装 k8s kubelet,此时 cgroup driver 必须统一为 systemd,且 spank_docker.so 需把 --cgroup-parent 指向 /kubepods.slice/… 之外,防止 Slurm 与 kubelet 互相迁移进程,这是国内“HPC+K8s 混部”场景面试常问的坑