容器指南

目录

概述

容器正在被 HPC 工作负载采用。 容器依赖于现有的内核特性,以允许用户更好地控制 在任何给定时间应用程序可以看到和交互的内容。对于 HPC 工作负载,这通常限制在 挂载命名空间。 Slurm 原生支持请求无特权的 OCI 容器用于作业 和步骤。

设置容器需要几个步骤:

  1. 设置 内核 和一个 容器运行时
  2. 部署一个适合的 oci.conf 文件,确保计算节点可以访问(下面的示例)。
  3. 在计算节点上重启或重新配置 slurmd。
  4. 为所需的容器生成 OCI 捆绑包,并将其放置在计算节点上。
  5. 验证您可以通过所选的 OCI 运行时 直接运行容器
  6. 验证您可以通过 Slurm 请求容器

已知限制

以下是 Slurm OCI 容器实现的已知限制列表。

  • 所有容器必须在无特权(即无根)调用下运行。 所有命令由 Slurm 作为没有特殊 权限的用户调用。
  • 不支持自定义容器网络。所有容器应与 "host" 网络一起使用。
  • Slurm 不会将 OCI 容器捆绑包传输到执行 节点。捆绑包必须已经存在于请求的路径上。 执行节点。
  • 容器受所使用的 OCI 运行时的限制。如果运行时不支持某个特性,则该特性将无法用于任何使用容器的作业。
  • 执行节点上必须为作业配置 oci.conf,否则请求的容器将被 Slurm 忽略(但可以被作业或任何给定插件使用)。

先决条件

主机内核必须配置为允许用户空间容器:

sudo sysctl -w kernel.unprivileged_userns_clone=1
sudo sysctl -w kernel.apparmor_restrict_unprivileged_unconfined=0
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

Docker 还提供了一个工具来验证内核配置:

$ dockerd-rootless-setuptool.sh check --force
[INFO] 要求已满足

所需软件:

  • 完全功能的 OCI 运行时。它需要能够在 Slurm 外部运行。
  • 完全功能的 OCI 捆绑包生成工具。Slurm 需要符合 OCI 容器的捆绑包用于作业。

各种 OCI 运行时的示例配置

OCI 运行时 规范提供了所有合规运行时的要求,但并没有 明确 提供运行时如何使用 参数的要求。为了支持尽可能多的运行时,Slurm 提供 为每个 OCI 运行时操作发出的命令的模式替换。 这将允许站点根据需要编辑 OCI 运行时的调用方式,以确保兼容性。

对于 runccrun,提供了两组示例。 OCI 运行时规范仅提供 startcreate 操作序列,但这些运行时提供了更高效的 run 操作。强烈建议站点使用 run 操作 (如果提供)因为 startcreate 操作要求 Slurm 轮询 OCI 运行时以了解容器何时完成执行。 虽然 Slurm 尽量高效地进行轮询,但这将导致一个线程在作业内部使用 CPU 时间,并且 Slurm 响应容器执行完成的速度较慢。

提供的示例经过测试可以正常工作,但仅供参考。站点 应确保所使用的根目录是安全的,以防止跨用户查看和修改。提供的示例指向 "/run/user/%U",其中 %U 将被替换为数字用户 ID。Systemd 管理 "/run/user/"(独立于 Slurm),并可能需要额外的配置以确保目录在用户不会直接登录到节点时存在于计算节点上。此配置通常通过调用 loginctl 启用持续会话 来实现。请注意,此示例中的目录将在用户会话结束时由 systemd 清理。

使用 create/start 的 runc 的 oci.conf 示例:

EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeQuery="runc --rootless=true --root=/run/user/%U/ state %n.%u.%j.%s.%t"
RunTimeCreate="runc --rootless=true --root=/run/user/%U/ create %n.%u.%j.%s.%t -b %b"
RunTimeStart="runc --rootless=true --root=/run/user/%U/ start %n.%u.%j.%s.%t"
RunTimeKill="runc --rootless=true --root=/run/user/%U/ kill -a %n.%u.%j.%s.%t"
RunTimeDelete="runc --rootless=true --root=/run/user/%U/ delete --force %n.%u.%j.%s.%t"

使用 run(推荐而非使用 create/start)的 runc 的 oci.conf 示例:

EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeQuery="runc --rootless=true --root=/run/user/%U/ state %n.%u.%j.%s.%t"
RunTimeKill="runc --rootless=true --root=/run/user/%U/ kill -a %n.%u.%j.%s.%t"
RunTimeDelete="runc --rootless=true --root=/run/user/%U/ delete --force %n.%u.%j.%s.%t"
RunTimeRun="runc --rootless=true --root=/run/user/%U/ run %n.%u.%j.%s.%t -b %b"

使用 create/start 的 crun 的 oci.conf 示例:

EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeQuery="crun --rootless=true --root=/run/user/%U/ state %n.%u.%j.%s.%t"
RunTimeKill="crun --rootless=true --root=/run/user/%U/ kill -a %n.%u.%j.%s.%t"
RunTimeDelete="crun --rootless=true --root=/run/user/%U/ delete --force %n.%u.%j.%s.%t"
RunTimeCreate="crun --rootless=true --root=/run/user/%U/ create --bundle %b %n.%u.%j.%s.%t"
RunTimeStart="crun --rootless=true --root=/run/user/%U/ start %n.%u.%j.%s.%t"

使用 run(推荐而非使用 create/start)的 crun 的 oci.conf 示例:

EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeQuery="crun --rootless=true --root=/run/user/%U/ state %n.%u.%j.%s.%t"
RunTimeKill="crun --rootless=true --root=/run/user/%U/ kill -a %n.%u.%j.%s.%t"
RunTimeDelete="crun --rootless=true --root=/run/user/%U/ delete --force %n.%u.%j.%s.%t"
RunTimeRun="crun --rootless=true --root=/run/user/%U/ run --bundle %b %n.%u.%j.%s.%t"

使用 create/start 的 nvidia-container-runtime 的 oci.conf 示例:

EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeQuery="nvidia-container-runtime --rootless=true --root=/run/user/%U/ state %n.%u.%j.%s.%t"
RunTimeCreate="nvidia-container-runtime --rootless=true --root=/run/user/%U/ create %n.%u.%j.%s.%t -b %b"
RunTimeStart="nvidia-container-runtime --rootless=true --root=/run/user/%U/ start %n.%u.%j.%s.%t"
RunTimeKill="nvidia-container-runtime --rootless=true --root=/run/user/%U/ kill -a %n.%u.%j.%s.%t"
RunTimeDelete="nvidia-container-runtime --rootless=true --root=/run/user/%U/ delete --force %n.%u.%j.%s.%t"

使用 run(推荐而非使用 create/start)的 nvidia-container-runtime 的 oci.conf 示例:

EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeQuery="nvidia-container-runtime --rootless=true --root=/run/user/%U/ state %n.%u.%j.%s.%t"
RunTimeKill="nvidia-container-runtime --rootless=true --root=/run/user/%U/ kill -a %n.%u.%j.%s.%t"
RunTimeDelete="nvidia-container-runtime --rootless=true --root=/run/user/%U/ delete --force %n.%u.%j.%s.%t"
RunTimeRun="nvidia-container-runtime --rootless=true --root=/run/user/%U/ run %n.%u.%j.%s.%t -b %b"

使用原生运行时的 Singularity v4.1.3 的 oci.conf 示例:

IgnoreFileConfigJson=true
EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeRun="singularity exec --userns %r %@"
RunTimeKill="kill -s SIGTERM %p"
RunTimeDelete="kill -s SIGKILL %p"

在 OCI 模式下的 Singularity v4.0.2 的 oci.conf 示例:

Singularity v4.x 需要设置 setuid 模式以支持 OCI。

EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeQuery="sudo singularity oci state %n.%u.%j.%s.%t"
RunTimeRun="sudo singularity oci run --bundle %b %n.%u.%j.%s.%t"
RunTimeKill="sudo singularity oci kill %n.%u.%j.%s.%t"
RunTimeDelete="sudo singularity oci delete %n.%u.%j.%s.%t"

警告:Singularity (v4.0.2) 需要 sudo 或 setuid 二进制文件 以支持 OCI,这存在安全风险,因为用户可以修改 这些调用。此示例仅用于测试目的。

警告上游的 singularity 开发 似乎已停止,站点应使用 用户 命名空间支持

hpcng Singularity v3.8.0 的 oci.conf 示例:

EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeQuery="sudo singularity oci state %n.%u.%j.%s.%t"
RunTimeCreate="sudo singularity oci create --bundle %b %n.%u.%j.%s.%t"
RunTimeStart="sudo singularity oci start %n.%u.%j.%s.%t"
RunTimeKill="sudo singularity oci kill %n.%u.%j.%s.%t"
RunTimeDelete="sudo singularity oci delete %n.%u.%j.%s.%t

警告:Singularity (v3.8.0) 需要 sudo 或 setuid 二进制文件 以支持 OCI,这存在安全风险,因为用户可以修改 这些调用。此示例仅用于测试目的。

警告"https://groups.google.com/a/lbl.gov/g/singularity/c/vUMUkMlrpQc/m/gIsEiiP7AwAJ"> 上游的 singularity 开发 似乎已停止,站点应使用 用户 命名空间支持

使用 Charliecloud (v0.30) 的 oci.conf 示例

IgnoreFileConfigJson=true
CreateEnvFile=newline
EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeRun="env -i PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin/:/sbin/ USER=$(whoami) HOME=/home/$(whoami)/ ch-run -w --bind /etc/group:/etc/group --bind /etc/passwd:/etc/passwd --bind /etc/slurm:/etc/slurm --bind %m:/var/run/slurm/ --bind /var/run/munge/:/var/run/munge/ --set-env=%e --no-passwd %r -- %@"
RunTimeKill="kill -s SIGTERM %p"
RunTimeDelete="kill -s SIGKILL %p"

使用 Enroot (3.3.0) 的 oci.conf 示例

IgnoreFileConfigJson=true
CreateEnvFile=newline
EnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeEnvExclude="^(SLURM_CONF|SLURM_CONF_SERVER)="
RunTimeRun="/usr/local/bin/enroot-start-wrapper %b %m %e -- %@"
RunTimeKill="kill -s SIGINT %p"
RunTimeDelete="kill -s SIGTERM %p"

/usr/local/bin/enroot-start-wrapper:

#!/bin/bash
BUNDLE="$1"
SPOOLDIR="$2"
ENVFILE="$3"
shift 4
IMAGE=

export USER=$(whoami)
export HOME="$BUNDLE/"
export TERM
export ENROOT_SQUASH_OPTIONS='-comp gzip -noD'
export ENROOT_ALLOW_SUPERUSER=n
export ENROOT_MOUNT_HOME=y
export ENROOT_REMAP_ROOT=y
export ENROOT_ROOTFS_WRITABLE=y
export ENROOT_LOGIN_SHELL=n
export ENROOT_TRANSFER_RETRIES=2
export ENROOT_CACHE_PATH="$SPOOLDIR/"
export ENROOT_DATA_PATH="$SPOOLDIR/"
export ENROOT_TEMP_PATH="$SPOOLDIR/"
export ENROOT_ENVIRON="$ENVFILE"

if [ ! -f "$BUNDLE" ]
then
        IMAGE="$SPOOLDIR/container.sqsh"
        enroot import -o "$IMAGE" -- "$BUNDLE" && \
        enroot create "$IMAGE"
        CONTAINER="container"
else
        CONTAINER="$BUNDLE"
fi

enroot start -- "$CONTAINER" "$@"
rc=$?

[ $IMAGE ] && unlink $IMAGE

exit $rc

处理多个运行时

如果您希望在环境中支持多个运行时, 可以通过一些额外的设置来实现。此部分概述了一种 可能的方法:

  1. 创建一个通用的 oci.conf,调用一个包装脚本
    IgnoreFileConfigJson=true
    RunTimeRun="/opt/slurm-oci/run %b %m %u %U %n %j %s %t %@"
    RunTimeKill="kill -s SIGTERM %p"
    RunTimeDelete="kill -s SIGKILL %p"
    
  2. 创建包装脚本以检查用户特定的运行配置 (例如,/opt/slurm-oci/run)
    #!/bin/bash
    if [[ -e ~/.slurm-oci-run ]]; then
    	~/.slurm-oci-run "$@"
    else
    	/opt/slurm-oci/slurm-oci-run-default "$@"
    fi
    
  3. 创建一个通用运行配置作为默认配置 (例如,/opt/slurm-oci/slurm-oci-run-default)
    #!/bin/bash --login
    # 解析
    CONTAINER="$1"
    SPOOL_DIR="$2"
    USER_NAME="$3"
    USER_ID="$4"
    NODE_NAME="$5"
    JOB_ID="$6"
    STEP_ID="$7"
    TASK_ID="$8"
    shift 8 # 随后的参数是要在容器中运行的命令
    # 运行
    apptainer run --bind /var/spool --containall "$CONTAINER" "$@"
    
  4. 为两个脚本添加可执行权限
    chmod +x /opt/slurm-oci/run /opt/slurm-oci/slurm-oci-run-default

完成后,用户可以在 '~/.slurm-oci-run' 创建一个脚本,如果 他们希望自定义容器运行过程,例如使用不同的 容器运行时。用户应根据默认的 '/opt/slurm-oci/slurm-oci-run-default' 来建模此文件。

在 Slurm 外部测试 OCI 运行时

Slurm 在作业步骤中直接调用 OCI 运行时。如果失败, 则作业也会失败。

  • 转到包含 OCI 容器捆绑包的目录:
    cd $ABS_PATH_TO_BUNDLE
  • 执行 OCI 容器运行时(您可以在下面找到一些关于如何构建 捆绑包的示例):
    $OCIRunTime $ARGS create test --bundle $PATH_TO_BUNDLE
    $OCIRunTime $ARGS start test
    $OCIRunTime $ARGS kill test
    $OCIRunTime $ARGS delete test
    如果这些命令成功,则 OCI 运行时已正确配置 并可以在 Slurm 中测试。

请求容器作业或步骤

sallocsrunsbatch(在 Slurm 21.08+ 中)具有 '--container' 参数,可用于请求容器运行时 执行。请求的作业容器不会被步骤继承, 排除批处理和交互步骤。

  • 容器内部的批处理步骤:
    sbatch --container $ABS_PATH_TO_BUNDLE --wrap 'bash -c "cat /etc/*rel*"'
    
  • 容器内部的批处理作业与步骤 0:
    sbatch --wrap 'srun bash -c "--container $ABS_PATH_TO_BUNDLE cat /etc/*rel*"'
    
  • 容器内部的交互步骤:
    salloc --container $ABS_PATH_TO_BUNDLE bash -c "cat /etc/*rel*"
  • 容器内部的交互作业步骤 0:
    salloc srun --container $ABS_PATH_TO_BUNDLE bash -c "cat /etc/*rel*"
    
  • 容器内部的作业与步骤 0:
    srun --container $ABS_PATH_TO_BUNDLE bash -c "cat /etc/*rel*"
  • 容器内部的作业与步骤 1:
    srun srun --container $ABS_PATH_TO_BUNDLE bash -c "cat /etc/*rel*"
    

注意:使用 --container 标志运行的命令在发送到容器之前会通过 PATH 解析。如果容器具有 独特的文件结构,可能需要提供命令的完整路径或指定 --export=NONE 以让容器定义 要使用的 PATH:

srun --container $ABS_PATH_TO_BUNDLE --export=NONE bash -c "cat /etc/*rel*"

与无根 Docker 的集成(Docker Engine v20.10+ & Slurm-23.02+)

Slurm 的 scrun 可以直接与 无根 Docker 集成 以将容器作为作业运行。无需特殊用户权限,且 不应 授予使用此功能。

先决条件

  1. slurm.conf 必须配置为使用 Munge 身份验证。
    AuthType=auth/munge
  2. scrun.lua 必须为站点存储配置进行配置。
  3. 配置内核以允许 ping
  4. 配置无根 dockerd 以允许在特权端口上监听
  5. scrun.lua 必须在任何可能运行 scrun 的节点上存在。示例应适用于大多数环境,但路径应修改以匹配可用的本地存储。
  6. oci.conf 必须在任何可能运行容器作业的节点上存在。已提供 已知 OCI 运行时 的示例配置。示例可能需要 路径正确指向安装位置。

限制

  1. 不支持 JWT 身份验证。
  2. Docker 容器构建当前不可用,等待合并 Docker 拉取请求
  3. Docker 不 暴露 配置选项以禁用运行作业所需的安全 选项。这要求所有对 docker 的调用提供以下命令行参数。这可以通过 shell 变量、别名、包装函数或包装脚本完成:
    --security-opt label:disable --security-opt seccomp=unconfined --security-opt apparmor=unconfined --net=none
    Docker 的内置安全功能不是运行 Slurm 运行的容器所需(或想要)的。Docker 仅充当容器映像 生命周期管理器。容器将通过 Slurm 远程执行,遵循 Slurm 中的现有安全配置,而不受无特权用户控制。
  4. 所有容器必须使用 "none" 网络驱动程序 。尝试使用 bridge、overlay、host、ipvlan 或 macvlan 可能会导致 scrun 与网络隔离,无法与 Slurm 控制器通信。容器由 Slurm 在计算节点上运行,这使得 Docker 设置网络隔离层对容器无效。
  5. docker exec 命令不受支持。
  6. docker swarm 命令不受支持。
  7. docker compose/docker-compose 命令不受支持。
  8. docker pause 命令不受支持。
  9. docker unpause 命令不受支持。
  10. docker swarm 命令不受支持。
  11. 所有 docker 命令在容器内部不受支持。
  12. Docker API 在容器内部不受支持。

设置程序

  1. 安装并 配置无根 Docker
    无根 Docker 必须完全正常运行并 能够运行容器,然后才能继续。
  2. 为所有 docker 调用设置环境:
    export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
    所有后续命令将期望设置此环境变量。
  3. 停止无根 docker:
    systemctl --user stop docker
  4. 配置 Docker 调用 scrun 而不是默认的 OCI 运行时。
    • 为所有用户配置:
      /etc/docker/daemon.json
    • 为每个用户配置:
      ~/.config/docker/daemon.json
    设置以下字段以配置 Docker:
    {
        "experimental": true,
        "iptables": false,
        "bridge": "none",
        "no-new-privileges": true,
        "rootless": true,
        "selinux-enabled": false,
        "default-runtime": "slurm",
        "runtimes": {
            "slurm": {
                "path": "/usr/local/bin/scrun"
            }
        },
        "data-root": "/run/user/${USER_ID}/docker/",
        "exec-root": "/run/user/${USER_ID}/docker-exec/"
    }
    正确的 scrun 路径应配置为安装前缀。将 ${USER_ID} 替换为数字用户 ID,或将目标目录指向具有全局 写权限和粘滞位的不同目录。无根 Docker 需要不同的根目录,而不是系统的默认值,以避免权限错误。
  5. 强烈建议站点考虑使用节点间共享 文件系统来存储 Docker 的容器。虽然可以使用 scrun.lua 脚本为每个部署推送和拉取映像,但可能会产生 巨大的性能损失。使用共享文件系统将避免移动这些 文件。
    可能的配置附加到 daemon.json 以使用 vfs 存储 驱动程序
    {
      "storage-driver": "vfs",
      "data-root": "/path/to/shared/filesystem/user_name/data/",
      "exec-root": "/path/to/shared/filesystem/user_name/exec/",
    }
    任何预计能够从 Docker 运行容器的节点必须至少能够读取所使用的文件系统。建议具有完全写权限,如果希望对容器文件系统进行更改,则需要。
  6. 配置 dockerd 以不设置网络命名空间,这将破坏 scrun 与 Slurm 控制器的通信能力。
    • 为所有用户配置:
      /etc/systemd/user/docker.service.d/override.conf
    • 为每个用户配置:
      ~/.config/systemd/user/docker.service.d/override.conf
    [Service]
    Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=none"
    Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=host"
    
  7. 在 systemd 中重新加载 docker 的服务单元:
    systemctl --user daemon-reload
  8. 启动无根 docker:
    systemctl --user start docker
  9. 验证 Docker 是否使用 scrun:
    export DOCKER_SECURITY="--security-opt label=disable --security-opt seccomp=unconfined  --security-opt apparmor=unconfined --net=none"
    docker run $DOCKER_SECURITY hello-world
    docker run $DOCKER_SECURITY alpine /bin/printenv SLURM_JOB_ID
    docker run $DOCKER_SECURITY alpine /bin/hostname
    docker run $DOCKER_SECURITY -e SCRUN_JOB_NUM_NODES=10 alpine /bin/hostname

与 Podman 的集成(Slurm-23.02+)

Slurm 的 scrun 可以直接与 Podman 集成以将容器作为作业运行。无需特殊用户权限,且 不应 授予使用此功能。

先决条件

  1. Slurm 必须在运行 podman 的主机上完全配置并运行。
  2. slurm.conf 必须配置为使用 Munge 身份验证。
    AuthType=auth/munge
  3. scrun.lua 必须为站点存储 配置进行配置。
  4. scrun.lua 必须在任何可能运行 scrun 的节点上存在。示例应适用于大多数环境,但路径应修改以匹配可用的本地存储。
  5. oci.conf 必须在任何可能运行容器作业的节点上存在。 已提供已知 OCI 运行时的示例配置。示例可能需要 路径正确指向安装位置。

限制

  1. 不支持 JWT 身份验证。
  2. 所有容器必须使用 主机网络
  3. podman exec 命令不受支持。
  4. podman-compose 命令不受支持,因为仅部分实现。一些组合可能有效,但每个容器 可能在不同节点上运行。所有容器的网络必须是 network_mode: host 设备。
  5. podman kube 命令不受支持。
  6. podman pod 命令不受支持。
  7. podman farm 命令不受支持。
  8. 所有 podman 命令在容器内部不受支持。
  9. Podman REST API 在容器内部不受支持。

设置程序

  1. 安装 Podman
  2. 配置无根 Podman
  3. 验证无根 podman 是否已配置
    $ podman info --format '{{.Host.Security.Rootless}}'
    true
  4. 在添加 Slurm 支持之前验证无根 Podman 是否完全正常:
    • 以下命令打印的值应相同:
      $ id
      $ podman run --userns keep-id alpine id
      $ sudo id
      $ podman run --userns nomap alpine id
  5. 配置 Podman 调用 scrun,而不是 默认 OCI 运行时。 有关配置位置和加载顺序的详细信息,请参见上游文档
    • 为所有用户配置: /etc/containers/containers.conf
    • 为每个用户配置: $XDG_CONFIG_HOME/containers/containers.conf~/.config/containers/containers.conf (如果 $XDG_CONFIG_HOME 未定义)。
    设置以下配置参数以配置 Podman 的 containers.conf:
    [containers]
    apparmor_profile = "unconfined"
    cgroupns = "host"
    cgroups = "enabled"
    default_sysctls = []
    label = false
    netns = "host"
    no_hosts = true
    pidns = "host"
    utsns = "host"
    userns = "host"
    log_driver = "journald"
    
    [engine]
    cgroup_manager = "systemd"
    runtime = "slurm"
    remote = false
    
    [engine.runtimes]
    slurm = [
    	"/usr/local/bin/scrun",
    	"/usr/bin/scrun"
    ]
    正确的 scrun 路径应配置为安装前缀。
  6. 在未运行 systemd 的系统上,“cgroup_manager”字段需要更改为“cgroupfs”。
  7. 强烈建议站点考虑使用节点间共享 文件系统来存储 Podman 的容器。虽然可以使用 scrun.lua 脚本为每个部署推送和拉取映像,但可能会产生 巨大的性能损失。使用共享文件系统将避免移动这些 文件。
    • 为所有用户配置:
      /etc/containers/storage.conf
    • 为每个用户配置:
      $XDG_CONFIG_HOME/containers/storage.conf
    可能的配置附加到 storage.conf 以使用共享文件系统与 vfs 存储驱动程序
    [storage]
    driver = "vfs"
    runroot = "$HOME/containers"
    graphroot = "$HOME/containers"
    
    [storage.options]
    pull_options = {use_hard_links = "true", enable_partial_images = "true"}
    
    
    [storage.options.vfs]
    ignore_chown_errors = "true"
    任何预计能够从 Podman 运行容器的节点必须至少能够读取所使用的文件系统。建议具有完全写权限,如果希望对容器文件系统进行更改,则需要。
  8. 验证 Podman 是否使用 scrun:
    podman run hello-world
    podman run alpine printenv SLURM_JOB_ID
    podman run alpine hostname
    podman run alpine -e SCRUN_JOB_NUM_NODES=10 hostname
    salloc podman run --env-host=true alpine hostname
    salloc sh -c 'podman run -e SLURM_JOB_ID=$SLURM_JOB_ID alpine hostname'
  9. 可选:为 Docker 创建别名:
    alias docker=podman
    alias docker='podman --config=/some/path "$@"'

故障排除

  • Podman 锁定耗尽:
    $ podman run alpine uptime
    错误:为新容器分配锁定:分配失败;超出锁定数量(2048)
    
    1. 尝试重新编号:
      podman system renumber
    2. 尝试重置所有存储:
      podman system reset

OCI 容器捆绑包

生成 OCI 容器捆绑包有多种方法。以下说明是我们发现的最简单的方法。OCI 标准提供了任何给定捆绑包的要求: 文件系统捆绑包

以下是使用一些替代容器解决方案生成容器的说明:

  • 创建一个镜像并准备与 runc 一起使用:
    1. 使用现有工具在 /image/rootfs 中创建文件系统镜像:
      • debootstrap:
        sudo debootstrap stable /image/rootfs http://deb.debian.org/debian/
      • yum:
        sudo yum --config /etc/yum.conf --installroot=/image/rootfs/ --nogpgcheck --releasever=${CENTOS_RELEASE} -y
      • docker:
        mkdir -p ~/oci_images/alpine/rootfs
        cd ~/oci_images/
        docker pull alpine
        docker create --name alpine alpine
        docker export alpine | tar -C ~/oci_images/alpine/rootfs -xf -
        docker rm alpine
    2. 配置一个捆绑包以供运行时执行:
      • 使用 runc 生成 config.json:
        cd ~/oci_images/alpine
        runc --rootless=true spec --rootless
      • 测试运行镜像:
      • srun --container ~/oci_images/alpine/ uptime
  • 使用 umoci 和 skopeo 生成完整镜像:
    mkdir -p ~/oci_images/
    cd ~/oci_images/
    skopeo copy docker://alpine:latest oci:alpine:latest
    umoci unpack --rootless --image alpine ~/oci_images/alpine
    srun --container ~/oci_images/alpine uptime
  • 使用 singularity 生成完整镜像:
    mkdir -p ~/oci_images/alpine/
    cd ~/oci_images/alpine/
    singularity pull alpine
    sudo singularity oci mount ~/oci_images/alpine/alpine_latest.sif ~/oci_images/alpine
    mv config.json singularity_config.json
    runc spec --rootless
    srun --container ~/oci_images/alpine/ uptime
  • 示例 OpenMPI v5 + PMIx v4 容器

    生成包含 OpenMPI 和 PMIx 的镜像的简约 Dockerfile,以测试基本的 MPI 作业。

    Dockerfile

    FROM almalinux:latest
    RUN dnf -y update && dnf -y upgrade && dnf install -y epel-release && dnf -y update
    RUN dnf -y install make automake gcc gcc-c++ kernel-devel bzip2 python3 wget libevent-devel hwloc-devel
    
    WORKDIR /usr/local/src/
    RUN wget --quiet 'https://github.com/openpmix/openpmix/releases/download/v5.0.7/pmix-5.0.7.tar.bz2' -O - | tar --no-same-owner -xvjf -
    WORKDIR /usr/local/src/pmix-5.0.7/
    RUN ./configure && make -j && make install
    
    WORKDIR /usr/local/src/
    RUN wget --quiet --inet4-only 'https://download.open-mpi.org/release/open-mpi/v5.0/openmpi-5.0.7.tar.bz2' -O - | tar --no-same-owner -xvjf -
    WORKDIR /usr/local/src/openmpi-5.0.7/
    RUN ./configure --disable-pty-support --enable-ipv6 --without-slurm --with-pmix --enable-debug && make -j && make install
    
    WORKDIR /usr/local/src/openmpi-5.0.7/examples
    RUN make && cp -v hello_c ring_c connectivity_c spc_example /usr/local/bin
    

    通过插件支持容器

    Slurm 允许容器开发者创建 SPANK 插件,可以在作业执行的各个点调用以支持容器。任何使用这些插件启动容器的站点 不应 有 "oci.conf" 配置文件。"oci.conf" 文件激活内置的容器功能,可能与基于 SPANK 的插件功能冲突。

    以下项目是与 Slurm 兼容的第三方容器解决方案,但尚未经过 SchedMD 测试或验证。

    Shifter

    Shifter 是一个来自 NERSC 的容器项目,旨在提供与调度程序完全集成的 HPC 容器。

    ENROOT 和 Pyxis

    Enroot 是一个用户命名空间 容器系统,由 NVIDIA 赞助,支持:

    • 通过 pyxis 的 Slurm 集成
    • 对 Nvidia GPU 的原生支持
    • 更快的 Docker 镜像导入

    Sarus

    Sarus 是一个特权 容器系统,由 ETH 苏黎世 CSCS 赞助,支持:

    Sarus 的概述幻灯片 在这里


    最后修改于 2025 年 6 月 26 日