
























在AI计算集群等场景中,系统级容器的稳定部署、GPU资源高效透传及多容器运行时兼容是核心需求,传统容器方案要么无法良好支持systemd服务管理,要么GPU透传配置复杂、需在容器内重复安装驱动,且难以兼顾Rootless Podman与Docker的二级容器运行需求。本文针对这一现状,详细阐述一套集成Nvidia GPU透传、自动驱动注入、systemd-nspawn容器管理及多二级容器运行时的完整方案,该方案已在某高校AI计算集群中稳定运行数月,可有效降低集群容器化部署的复杂度,提升资源利用率。
针对传统容器部署存在GPU透传配置繁琐、需在容器内额外安装驱动导致环境冗余、systemd支持不足、二级容器运行时兼容性差及Rootless模式适配困难等痛点,本方案核心优势在于无需在容器内安装GPU驱动,可动态扫描并挂载NVIDIA库和工具,完美支持GPU透传与CDI驱动注入,兼容Docker与Rootless Podman两种二级容器运行时,同时通过脚本实现systemd-nspawn容器的自动监控与管理,简化集群部署与运维成本。
功能特性:
宿主机需要安装好 NVIDIA GPU 驱动和 NVIDIA Container Toolkit,具体请参考,对于 Ubuntu 22.04,安装步骤如下:
apt update
apt install systemd-container debootstrap tmux
apt install nvidia-container-toolkit nvidia-container-toolkit-base libnvidia-container-tools
systemd-nspawn --version
machinectl --version
由于 Nspawn 的 NAT 存在诸多 Bug,不推荐使用,因此我们还需要宿主机存在一个网络提供者给一级容器使用,这里我直接借用了宿主机的 Docker 网络,为此也需要在宿主机上安装 Docker,后续的脚本会自动创建 Docker network :
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
由于 Nspawn 的端口转发(-p 参数)也存在诸多 Bug,我们使用 rinetd 提供端口转发功能,因此还需要安装 rinetd :
sudo apt install rinetd
本文后续的宿主和容器配置均基于 Ubuntu 22.04 操作系统。其他发行版请参考对应的官方文档进行安装,也欢迎在评论区进行补充。
首先安装一级容器的操作系统,我这里安装到了 /var/lib/machines/inais :
sudo mkdir -p /var/lib/machines/inais
sudo debootstrap \
--include=systemd,dbus \
jammy \
/var/lib/machines/ubuntu22 \
http://archive.ubuntu.com/ubuntu
mkdir -p /var/lib/machines/_Scripts
cd /var/lib/machines/_Scripts
请将我附件提供的 Shell 脚本(点击下载) 上传到 /var/lib/machines/_Scripts 目录下,脚本用于管理基于 systemd-nspawn 的 INAIS 容器环境,支持多节点存储绑定、NVIDIA GPU 透传、自动网络配置和端口转发。
使用方法:
./run.sh daemon
以守护进程模式运行,自动监控和管理容器。
脚本会自动加载同目录下的配置文件 config-{脚本名}.sh,例如 run.sh 会加载 config-run.sh。
| 变量名 | 类型 | 说明 | 示例值 |
|---|---|---|---|
INAIS_HOST_ACCOUNT |
string | 主机账户名,用于构建存储路径 | "Kenvix" |
NODE_ID |
integer | 当前节点ID(1~MAX_NODE_ID) | 15 |
CONTAINER_NAME |
string | 容器名称,用于 machinectl 管理 | "inais" |
CONTAINER_ROOT |
string | 容器根文件系统路径 | "/var/lib/machines/inais" |
CONTAINER_INDEX |
integer | 容器索引,用于计算容器IP地址 | 0 |
INAIS_NETWORK_MODE |
string | 网络模式:"docker" 或 "veth" |
"docker" |
USE_RINETD_PORT_FORWARDING |
boolean | 端口转发方式:true=rinetd, false=systemd-nspawn内置 |
false |
| 变量名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
MAX_NODE_ID |
integer | 21 |
最大节点ID,决定节点范围 1~N |
DISABLE_LOCAL_STORAGE_BINDINGS |
boolean | false |
设为 true 禁用本地存储挂载(当前节点的 /data/inXX) |
DISABLE_NVIDIA_BINDINGS |
boolean | false |
设为 true 禁用所有 NVIDIA 设备和库文件挂载 |
LOCAL_DATA_PREFIX |
string | /data |
本地数据目录前缀 |
NFS_STORAGE_PREFIX |
string | /storagenfs/nfs |
NFS 存储路径前缀(不含节点编号) |
PORT_FORWARD_HOST_START |
integer | 30600 |
主机端起始端口号 |
PORT_FORWARD_CONTAINER_START |
integer | 30000 |
容器端起始端口号 |
PORT_FORWARD_COUNT |
integer | 20 |
端口映射数量 |
INAIS_NETWORK_MODE)docker 模式inais0253.{NODE_ID}.{CONTAINER_INDEX/256+1}.{CONTAINER_INDEX%256}253.{NODE_ID}.0.0/16253.{NODE_ID}.0.1我这里使用了 IPv4 Class-E 类IP这种比较邪门的地址,以避免和现有网络冲突。也可以修改脚本使用其他私有地址段,例如 10.{NODE_ID}.0.0/16 。需注意 Windows 只对 E 类地址提供有限支持,即可以连接但无法监听;Linux 则完整支持 E 类地址作为单播地址。
veth 模式253.{NODE_ID}.{CONTAINER_INDEX}.1/24253.{NODE_ID}.{CONTAINER_INDEX}.2/24端口映射范围由三个变量控制:
主机端口范围: PORT_FORWARD_HOST_START ~ (PORT_FORWARD_HOST_START + PORT_FORWARD_COUNT - 1)
容器端口范围: PORT_FORWARD_CONTAINER_START ~ (PORT_FORWARD_CONTAINER_START + PORT_FORWARD_COUNT - 1)
默认配置:30600-30619 → 30000-30019(共 20 个端口)
端口转发方式:
USE_RINETD_PORT_FORWARDING=false:使用 systemd-nspawn 内置的 --port 参数USE_RINETD_PORT_FORWARDING=true:使用外部 rinetd 工具,支持 TCP 和 UDP脚本支持多个节点组成集群,组成集群时,编号必须是连续的整数,从 1 开始,到 MAX_NODE_ID 结束。
存储绑定是可选的,可以通过上面的选项禁用。
注意:为了提高效率,容器和脚本本身并不直接操作 NFS 存储,而是通过宿主机的 NFS 挂载路径进行绑定。因此请确保宿主机已经正确挂载了 NFS。
可通过以下变量自定义存储路径:
| 变量名 | 默认值 | 说明 |
|---|---|---|
LOCAL_DATA_PREFIX |
/data |
本地数据目录前缀 |
NFS_STORAGE_PREFIX |
/storagenfs/nfs |
NFS 存储路径前缀(不含节点编号) |
脚本会自动为所有节点(1 到 MAX_NODE_ID)创建存储绑定:
| 节点类型 | 主机路径 | 容器路径 |
|---|---|---|
| 当前节点 | {LOCAL_DATA_PREFIX}/{INAIS_HOST_ACCOUNT} |
/data/in{NODE_ID:02d} |
| 远程节点 | {NFS_STORAGE_PREFIX}{XX}/{INAIS_HOST_ACCOUNT} |
/data/in{XX} |
| 主机路径 | 容器路径 | 说明 |
|---|---|---|
{LOCAL_DATA_PREFIX}/{INAIS_HOST_ACCOUNT}/home |
/home |
用户主目录 |
{LOCAL_DATA_PREFIX}/{INAIS_HOST_ACCOUNT} |
/data/this |
当前节点数据(别名) |
/var/lib/machines/_Data/inais |
/ssd |
本地 SSD 存储 |
当 DISABLE_NVIDIA_BINDINGS=false(默认)时,脚本会自动挂载:
/dev/nvidia0 ~ /dev/nvidia7/dev/nvidiactl/dev/nvidia-modeset/dev/nvidia-uvm/dev/nvidia-uvm-tools/dev/nvidia-caps/dev/nvidia-nvswitchctl/dev/dri因为几乎不存在大于8卡的情况,因此这里我写死了最多支持 8 块 GPU(/dev/nvidia0 ~ /dev/nvidia7),如果需要更多,请自行修改脚本。如果少于8卡,则多余的设备文件不会被挂载,不必修改。
/proc/driver/nvidia/sys/devices/pci0000:00/sys/class/drm/sys/module/nvidia*脚本会自动扫描并挂载以下文件:
/usr/bin/nvidia-* - NVIDIA 可执行文件/usr/bin/nvcc* - CUDA 编译器/usr/bin/nsight* - NVIDIA 分析工具/usr/lib/x86_64-linux-gnu/libnvidia* - NVIDIA 库文件/usr/lib/x86_64-linux-gnu/libcuda* - CUDA 库文件虽然会挂载宿主的 CUDA 工具包和库文件,但容器内最好还是另外安装一套 CUDA。驱动是前向兼容 CUDA 的,因此容器内的 CUDA 版本可以低于宿主机的驱动版本,按照个人需求决定CUDA版本即可1。
以下变量由脚本自动计算,无需手动配置:
| 变量名 | 计算方式 | 示例值 |
|---|---|---|
NODE_ID_STR |
printf '%02d' $NODE_ID |
"15" |
INAIS_HOSTNAME |
"IN$NODE_ID_STR" |
"IN15" |
RINETD_CONFIG |
/var/run/rinetd.conf.$CONTAINER_NAME |
/var/run/rinetd.conf.inais |
RINETD_PID |
/var/run/rinetd.pid.$CONTAINER_NAME |
/var/run/rinetd.pid.inais |
#!/bin/bash
# === 必需配置 ===
export INAIS_HOST_ACCOUNT="Kenvix"
export NODE_ID=15
export CONTAINER_NAME="inais"
export CONTAINER_ROOT="/var/lib/machines/$CONTAINER_NAME"
export CONTAINER_INDEX=0
export INAIS_NETWORK_MODE="docker" # docker 或 veth
export USE_RINETD_PORT_FORWARDING=false
# === 可选配置 ===
# 节点范围配置
export MAX_NODE_ID=21
# 禁用本地存储挂载
export DISABLE_LOCAL_STORAGE_BINDINGS=false
# 禁用 NVIDIA 挂载(无 GPU 环境设为 true)
#export DISABLE_NVIDIA_BINDINGS=true
# 存储路径前缀配置
export LOCAL_DATA_PREFIX="/data" # 本地数据目录前缀
export NFS_STORAGE_PREFIX="/storagenfs/nfs" # NFS 存储路径前缀
# 端口映射配置
export PORT_FORWARD_HOST_START=30600
export PORT_FORWARD_CONTAINER_START=30000
export PORT_FORWARD_COUNT=20
| 信号 | 行为 |
|---|---|
SIGHUP |
立即重新执行监控检查。脚本会作为一个 Daemon 监控容器的状态。 |
SIGTERM SIGINT |
优雅退出(停止容器后退出) |
ℹ️ 注意:该脚本是为多服务器集群设计的,因此假设你有很多服务器需要配置相同的环境。如果你只是单机使用,可以忽略脚本中的集群相关配置,或者直接设置数量为1。
ℹ️ 提示:虽然已经稳定运行一段时间,但此脚本的 Vibe coding 成分较高,请自行进行代码审查,如果存在漏洞请告知我。
赋予可执行权限:
chmod +x run.sh
chmod +x config-run.sh
通过下面的命令,测试启动一级容器:
./run.sh daemon
sleep 2s
tmux attach -t inais
nvidia-smi
如果看到 GPU 信息,说明一级容器的 NVIDIA GPU 透传配置成功。此时可以创建一个 systemd 服务,实现在系统启动时自动启动一级容器:
[Unit]
Description=Run Kenvix Dev Containe at boot
Documentation=Kenvix Dev Container monitoring and management daemon
After=network.target docker.service systemd-machined.service systemd-resolved.service modprobe@tun.service modprobe@loop.service modprobe@dm-mod.service
Wants=network.target systemd-machined.service
Requires=docker.service systemd-machined.service
[Service]
Type=simple
ExecStart=/var/lib/machines/_Scripts/run.sh daemon
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -TERM $MAINPID
Restart=on-failure
RestartSec=50
StartLimitInterval=100
[Install]
WantedBy=multi-user.target
保存为 /etc/systemd/system/kenvix-container.service ,然后启用并启动:
sudo systemctl daemon-reload
sudo systemctl enable --now kenvix-container.service
通过下面的命令进入一级容器 Shell:
machinectl shell inais
此时可以在容器内配置 SSH 等常用服务。
二级容器指的是在 systemd-nspawn 容器内运行的 Docker 或 Podman 容器。类似于 DinD(Docker in Docker)或 PinD(Podman in Docker/Podman in Podman),只不过我们这里的一级容器是 systemd-nspawn 容器。
同样的,对于二级容器,切勿使用带有 GPU 驱动的镜像,也不要在容器内安装 GPU 驱动!容器内只应该安装 CUDA 工具包和相关库,GPU 驱动由一级容器通过 CDI 注入。
通过 Docker 官方安装最新版本的 Docker-CE 引擎(具体可参见官方文档)。不要使用从 Ubuntu 官方源安装的 docker,否则缺少 CDI 支持将无法使用 GPU:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
使用 Docker 时,只支持 CDI(Container Device Interface) 方式的GPU驱动注入,因此不能用 Docker 旧版的 --gpus all 参数,需改参数为 --device nvidia.com/gpu=all 即可。
# 例如原命令为
docker run --rm -it --gpus all nvidia/cuda:12.3.1-base-ubuntu22.04 bash
# 则须改为
docker run --rm -it --device nvidia.com/gpu=all nvidia/cuda:12.3.1-base-ubuntu22.04 bash
然后,在二级容器内使用 nvidia-smi 查看 GPU 信息。
对于 Ubuntu 22.04,官方源的 Podman 版本非常陈旧,Podman 3 不支持 CDI 方式的GPU驱动注入,文件系统支持也有问题,需要从 OpenSUSE 源安装较新版本的 Podman 4+(或自行编译最新版本):
ubuntu_version='22.04'
key_url="https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}/Release.key"
sources_url="https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_${ubuntu_version}"
echo "deb $sources_url/ /" | tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list
curl -fsSL $key_url | gpg --dearmor | tee /etc/apt/trusted.gpg.d/devel_kubic_libcontainers_unstable.gpg > /dev/null
apt update
apt install podman
nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
安装后,需配置使用 cgroupfs,编辑 /etc/containers/containers.conf :
[containers]
cgroup_manager = "cgroupfs"
[engine]
cgroup_manager = "cgroupfs"
编辑 /etc/subuid 和 /etc/subgid ,为需要运行 Podman 的用户分配子 UID/GID 范围,例如:
kenvix:100000:65536
然后,切换到此用户,通过以下命令测试:
# 注意:不能使用 Root 用户运行 Podman GPU 容器!请先切换到非 Root 普通用户
podman run --rm -it --device nvidia.com/gpu=all nvidia/cuda:12.3.1-base-ubuntu22.04 bash
然后,在二级容器内使用 nvidia-smi 查看 GPU 信息。
强烈建议也阅读完本章节!在实际使用中几乎必然遇到下面的问题
这是由于 CDI 描述文件未更新所致。请运行下面的脚本,自动在 systemd 的开机早期阶段生成 NVIDIA CDI 描述文件。
sudo tee /etc/systemd/system/nvidia-cdi-generate.service >/dev/null <<'EOF'
[Unit]
Description=Generate NVIDIA CDI specification early
Documentation=https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/
DefaultDependencies=no
Before=docker.service podman.service containerd.service crio.service
After=local-fs.target systemd-udev-settle.service
ConditionPathExists=/proc/driver/nvidia/version
[Service]
Type=oneshot
ExecStartPre=/usr/bin/mkdir -p /etc/cdi
ExecStart=/usr/bin/nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
RemainAfterExit=yes
[Install]
WantedBy=sysinit.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now nvidia-cdi-generate.service
此脚本在一级容器运行。
此问题尚未解决,目前的解决方案是:
这是一个已知问题,Ubuntu 24.04 作为一级容器操作系统时,Cgroup 存在兼容性问题,导致无法正确创建 Docker 所需的命名空间。目前解决方案仍在探索中,建议暂时使用 Ubuntu 22.04 作为一级容器操作系统。
有的,你可以在评论区催一下我发布
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。