【小记】在 Google Colab 等平台上运行 GPU 容器
最近想到了可能的创新点,准备开始做实验了。咱想先在 Colab 这种提供免费 GPU 算力的平台上跑一些小实验,后续再转移到实验室机器上。
如果每次都要重复搭建环境多少有些麻烦了。

那咱用容器化技术不就行啦!直接把环境打包成镜像,到时候环境迁移和实验复现都能便捷很多。
比起 Docker,这回咱决定试试更为轻量的 Apptainer(前身为 Singularity):
- Apptainer 默认以普通用户的身份运行容器,没有守护进程,也无需类似于 root 用户的特权,不像 Docker Daemon 那样必须要运行在特权用户下。(因而更安全,也更容易安装部署,不会有什么权限问题)
- 尽管如此,Apptainer 仍然要求系统支持 User Namespace 等特性,如果平台在各方面限制得比较死的话可以看看第 4 节,有惊喜哦 ( ̄▽ ̄) 。
- Apptainer 针对高性能计算(HPC)这种并行场景进行了优化(虽然我还不太用得上)。
- Apptainer 支持 Docker 镜像,体验上近乎无缝(这个是最爽滴)。
这篇笔记主要记录一下咱在 Google Colab 以及 AutoDL 平台上运行 GPU 容器时的踩坑和爬出坑的过程。
1. 安装 Apptainer
这里不多赘述,图快的话咱主要推荐以下两种安装方式:
1.1. 利用官方脚本安装
注:文档中提到在执行脚本前可能需要安装 curl, cpio, rpm2cpio 等必要的工具:
sudo apt-get update
# rpm2cpio 是 rpm 包的工具
sudo apt-get install -y curl cpio rpm
1.2. 利用包管理器安装
Colab 默认是 Ubuntu 系统,copy 官方文档中的命令就能顺利安装了~
2. 尝试跑一下 hello-world 镜像
2.1. 构建 .sif 文件
Apptainer 可以直接拉取 Docker Hub 上的镜像并构建成 .sif 单文件:
apptainer build hello.sif docker://hello-world

2.2. 建立普通用户
Colab 默认在 root 用户下执行命令,这里建立一个普通用户 somebottle :
adduser --home /home/somebottle --gecos "" --shell /bin/bash --disabled-password somebottle
- root 用户当然是可以运行 Apptainer 容器的,但为了安全起见,咱接下来会尝试用普通用户来运行容器。
2.3. 启动容器
Colab 对 root 用户的权限有所限制,如果直接尝试启动容器会出现挂载问题:
apptainer run hello.sif
# >> FATAL: container creation failed: mount hook function failure: mount overlay->/var/lib/apptainer/mnt/session/final error: while mounting overlay: can't mount overlay filesystem to /var/lib/apptainer/mnt/session/final: while setting effective capabilities: CAP_DAC_READ_SEARCH is not in the permitted capability set
利用 setcap 进行 capabilities 权限设定还是麻烦了,这里我根据 issue 中的指引,直接在一个新的命名空间下运行了容器:
# 在 root 用户下执行这条命令,容器内用户为 root
unshare -r apptainer run hello.sif
unshare -r建立一个新的用户命名空间,并把当前用户映射为命名空间内的 root 用户,这个命名空间内的进程将会默认拥有完整的 capabilities 权限集。
- 注:如果在 root 用户下执行上面这条命令,其实仍然相当于在特权用户下启动了容器。
或者以普通用户的身份运行容器:
# 以 somebottle 身份启动容器,容器内用户为 somebottle
sudo -u somebottle apptainer run hello.sif
Apptainer 借助用户命名空间特性来运行容器,系统应支持以非特权方式建立用户命名空间,经测试 Colab 已经满足了这点。具体要求可查看文档。
为了能让普通用户在容器内以 root 用户的身份运行,可以加上 --fakeroot 选项:
# 以 somebottle 身份启动容器,容器内用户为 root
sudo -u somebottle apptainer run --fakeroot hello.sif
具体行为可见官方文档说明。
3. 尝试跑一个 GPU 容器
3.1. 构建 .sif 文件
这里拉取了 Docker Hub 上 PyTorch 官方的一个 GPU 支持镜像:
# 转换得到 pytorch-gpu.sif 文件
apptainer build pytorch-gpu.sif docker://pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime
3.2. 使用 Apptainer 的标准 GPU 支持
Apptainer 默认支持 NVIDIA GPU,前提是宿主机系统中已经安装了 Nvidia 驱动以及 CUDA 相关库。
- 具体要求依旧见官方文档。
在 apptainer 的 run, shell, exec 等命令后加上 --nv 选项即可启用 GPU 支持:
# 以 somebottle 身份启动 GPU 容器,容器内用户为 somebottle
# 启动容器后执行 nvidia-smi 来查看 GPU 状态
sudo -u somebottle apptainer exec --nv pytorch-gpu.sif nvidia-smi
3.3. 問題、襲来

诶?动啊,NVIDIA,为什么不动?

NVIDIA-SMI couldn't find libnvidia-ml.so library in your system. Please make sure that the NVIDIA Display Driver is properly installed and present in your system.
Please also try adding directory that contains libnvidia-ml.so to your system PATH.
定睛一看发现是在容器内找不到动态链接库 libnvidia-ml.so。
3.4. 解决问题
提到动态链接库路径,很快能想到一个环境变量 LD_LIBRARY_PATH,动态链接器会在其列出的目录下搜索库。分别在宿主机和容器内输出这个环境变量看看:
# 宿主机上
unshare -r env | grep LD_
# >> LD_LIBRARY_PATH=/usr/lib64-nvidia
# 容器中
unshare -r apptainer exec --nv pytorch-gpu.sif env | grep LD_
# >> LD_LIBRARY_PATH=/usr/local/nvidia/lib:/usr/local/nvidia/lib64:/.singularity.d/libs
可见宿主机上 NVIDIA 动态链接库位于 /usr/lib64-nvidia。
容器内列出了三个路径,因为其中前两个路径在容器内是不存在的,所以链接器会去 /.singularity.d/libs 这个路径下找共享库。
# 输出看看目录下有什么
unshare -r apptainer exec --nv pytorch-gpu.sif ls -ahl /.singularity.d/libs

推测在使用 --nv 选项时,Apptainer 会自动将宿主机上的 NVIDIA 动态链接库绑定挂载到容器内的 /.singularity.d/libs 目录下。
从上面两个文档可以得知,为了处理绑定挂载,Apptainer 有一个配置文件 nvliblist.conf,其中指定了 NVIDIA 相关的可执行文件和动态链接库的文件名(没错,仅仅是文件名!)。
- 注: 通过
find / -name "nvliblist.conf"可找到配置文件路径。
在默认的 nvliblist.conf 中可以找到上面 NVIDIA-SMI 运行所缺失的库:

当然也可以找到容器内 /.singularity.d/libs 目录下已有的库。
于是现在问题就转换为了:为什么 /.singularity.d/libs 目录下没有 libnvidia-ml.so 和其他一些必要的库呢?
仍然是看文档, [2] 中提到:
When adding new entries to
nvliblist.confuse the bare filename of executables, and thexxxx.soform of libraries. Libraries are resolved vialdconfig -p, and exectuables are found by searching$PATH.
即共享库(.so)路径是通过 ldconfig -p 来解析的,而可执行文件则是通过 $PATH 来搜索的。很有可能 Apptainer 在挂载时没有找到 NVIDIA 的库路径。
# ldconfig 可以管理动态链接库的缓存
# 查看 ldconfig -p 的输出(输出已缓存的库),筛出有 nvidia 字段的
ldconfig -p | grep nvidia
# 没有输出,说明 nvidia 相关的库没有被缓存
哔啵~问题已定位。接下来只需要把宿主机上的 NVIDIA 共享库目录 /usr/lib64-nvidia 加入到缓存中即可。
ldconfig 会从 /etc/ld.so.conf.d 目录中的配置文件读取共享库路径,先看看这个目录下有什么:
ls -hl /etc/ld.so.conf.d
# >> total 36K
# >> -rw-r--r-- 1 root root 41 Sep 9 2023 000_cuda.conf
# >> -rw-r--r-- 1 root root 44 Sep 9 2023 988_cuda-12.conf
# >> -rw-r--r-- 1 root root 15 Jan 17 14:36 colab.conf
# >> -rw-r--r-- 1 root root 38 Mar 5 2022 fakeroot-x86_64-linux-gnu.conf
# >> -rw-r--r-- 1 root root 46 Aug 15 2023 gds-12-2.conf
# >> -rw-r--r-- 1 root root 44 Dec 16 2020 libc.conf
# >> -rw-r--r-- 1 root root 46 Nov 10 2023 nvidia.conf
# >> -rw-r--r-- 1 root root 100 Mar 4 2022 x86_64-linux-gnu.conf
# >> -rw-r--r-- 1 root root 56 May 6 2024 zz_i386-biarch-compat.conf
cat /etc/ld.so.conf.d/nvidia.conf /etc/ld.so.conf.d/*cuda*.conf
# >> /usr/local/nvidia/lib
# >> /usr/local/nvidia/lib64
# >> /usr/local/cuda/targets/x86_64-linux/lib
# >> /usr/local/cuda-12/targets/x86_64-linux/lib
ls /usr/local/nvidia/lib
# >> ls: cannot access '/usr/local/nvidia/lib': No such file or directory
看来大概是 Colab 官方的配置有误。
为了修复这点,咱们直接把库路径 /usr/lib64-nvidia 写入到这里的一个配置文件中:
echo "/usr/lib64-nvidia" >> /etc/ld.so.conf.d/nvidia.conf
然后刷新缓存,让 ldconfig 重新读取配置文件即可:
ldconfig
再次查看 ldconfig -p 的输出,就可以看到 libnvidia-ml.so 等共享库已经被缓存了。
再次尝试运行 GPU 容器,nvidia-smi 就能正常打印出 GPU 信息了,冇问题啦!

3.5. 测试一下 PyTorch 是否能正常使用 GPU
咱先把如下脚本 test.py 放在了 Colab 会话环境的 /content/test 目录下:
import torch
if torch.cuda.is_available():
device = torch.device("cuda")
print(f"GPU 可用,设备: {torch.cuda.get_device_name(0)}")
else:
device = torch.device("cpu")
print("GPU 不可用,设备: CPU")
tensor = torch.tensor([1.0, 2.0, 3.0])
tensor = tensor.to(device)
print(f"张量: {tensor}")
print(f"张量所在设备: {tensor.device}")
# compute
result = tensor * 2
print(result)
把 Python 脚本挂载到容器内并执行:
sudo -u somebottle apptainer exec --bind /content/test:/mnt/data --nv pytorch-gpu.sif python /mnt/data/test.py
# >> GPU 可用,设备: Tesla T4
# >> 张量: tensor([1., 2., 3.], device='cuda:0')
# >> 张量所在设备: cuda:0
# >> tensor([2., 4., 6.], device='cuda:0')
这样一来我应该就能愉快地在 Google Colab 等平台上使用 Apptainer 跑实验啦~
4. 系统不支持用户命名空间
事情并不总是一帆风顺。当我尝试在 AutoDL 平台上复现上面的流程时,发现 AutoDL 禁止了非特权用户创建用户命名空间,且禁用了 unshare,Apptainer 根本就跑不起来:

而且 AutoDL 也禁用了
setuid机制。
难道...就要到此为止了吗!

不,还没完!我还能战斗!
其实还有个能完全在用户空间运行的容器工具实现: udocker。
默认情况下 udocker 利用 PRoot 在用户空间拦截并模拟一些系统调用和特权操作,以在非特权用户下运行容器。
udocker 的环境隔离性没有 apptainer 和 docker 好。但我本来也就只想要迁移运行时环境,隔离性也无所谓了(详见官方文档 1.3. Security)。
udocker 容器中无法执行真正需要特权的操作,比如文件系统挂载、修改受保护的配置等。当然跑个机器学习实验是没啥问题的(详见官方文档 1.2. Limitations)。
以下代码暂且以
udocker 1.3.17版本为例。
4.1. 以 root 用户运行 udocker
直接在 root 用户下运行,命令更为简短,注意需要加上 --allow-root 选项:
点击展开示例代码(IPython)
# 下载 udocker 包并解压
!wget https://github.com/indigo-dc/udocker/releases/download/1.3.17/udocker-1.3.17.tar.gz
!tar -xzf udocker-1.3.17.tar.gz
# 把 udocker 添加到环境变量中
# 方便起见,给 udocker --allow-root 起个别名 mydocker
%alias mydocker export PATH=udocker-1.3.17/udocker:$PATH; udocker --allow-root
# 初始化 udocker(即使不初始化,首次执行时也会自动初始化)
mydocker install
# 拉取镜像
# dockerpull.cn 是一个 DockerHub 镜像站
mydocker pull dockerpull.cn/pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime
# 创建容器,容器名 gputest
mydocker create --name=gputest dockerpull.cn/pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime
# 启动 NVIDIA 支持
mydocker setup --nvidia gputest
# 查看 GPU 信息(如果有问题请见下面的 4.3 节)
mydocker run gputest nvidia-smi
# 挂载测试脚本并运行
mydocker run --volume=/root/test.py:/script/test.py gputest python /script/test.py
4.2. 以普通用户运行 udocker
废话不多说,上代码:
点击展开示例代码(IPython)
# 首先还是新建普通用户
!adduser --home /home/somebottle --gecos "" --shell /bin/bash --disabled-password somebottle
# 下载 udocker 包并解压
!wget -P /home/somebottle https://github.com/indigo-dc/udocker/releases/download/1.3.17/udocker-1.3.17.tar.gz
!cd /home/somebottle && tar zxvf udocker-1.3.17.tar.gz && chown somebottle udocker-1.3.17
# 把 udocker 添加到用户的环境变量中
!echo "export PATH=/home/somebottle/udocker-1.3.17/udocker:$PATH" >> /home/somebottle/.profile
# 给以 somebottle 身份运行的命令起个别名 urun
# 加上 -l 选项后才会执行 Shell 配置文件(包括上面的 .profile)
%alias urun su somebottle -l -c
# AutoDL 平台上 python 路径在 /root 目录下,这里为了方便起见直接把目录所有权给 somebottle
# 在 Colab 上可能不需要这一步
!chown -R somebottle /root 2>/dev/null
# 初始化 udocker(即使不初始化,首次执行时也会自动初始化)
urun 'udocker install'
# 接下来就可以拉取镜像试试了
# dockerpull.cn 是一个 DockerHub 镜像站
urun 'udocker pull dockerpull.cn/pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime'
# 创建容器,容器名 gputest
# 从实现上来讲,这一步实际上是把镜像解包到一个用户可访问的目录中,作为 rootfs
urun 'udocker create --name=gputest dockerpull.cn/pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime'
# 启动 NVIDIA 支持
urun 'udocker setup --nvidia gputest'
# 查看 GPU 信息(如果有问题请见下面的 4.3 节)
urun 'udocker run gputest nvidia-smi'
# 挂载测试脚本并运行
urun 'udocker run --volume=/root/test.py:/script/test.py gputest python /script/test.py'
4.3. udocker 容器中没有 nvidia-smi
udocker 通过将一些 NVIDIA 相关的可执行文件和库文件复制到容器的对应目录中,从而实现了对 GPU 的支持。
但是目前 udocker 复制 NVIDIA 相关文件的逻辑可能有些问题。已知在 Google Colab 平台上可能无法在容器中访问 nvidia-smi 这种可执行文件,但是相关库文件是可以访问的,因此 PyTorch 能正常使用 GPU。
等待修复:
你可以直接用这个 PR 的提交中的 nvidia.py 替换 udocker/engine/nvidia.py 这个文件。
5. 附:关于 sudo -u
在上面的例子中,我使用了 sudo -u somebottle <command> 来以 somebottle 用户的身份执行命令。
然而 sudo -u 实际上可能会重置环境变量,可以打印出来看看:
echo $PATH
# >> /opt/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/tools/node/bin:/tools/google-cloud-sdk/bin
sudo -u somebottle env | grep PATH
# >> PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
很明显至少环境变量 $PATH 被重置了。
3.4 节中文档 [2] 曾提到:
... , and exectuables are found by searching
$PATH.
即 Apptainer 在挂载时, nvidia-smi 这类可执行文件是依赖于 $PATH 环境变量进行搜索的。
幸运的是,可执行文件 nvidia-smi 在 /usr/bin 目录下有软链,而默认的 $PATH 环境变量中包含有这个目录,因此启动容器时 Apptainer 能成功找到。
whereis nvidia-smi
# >> nvidia-smi: /usr/bin/nvidia-smi /opt/bin/nvidia-smi
ls -hl /usr/bin/nvidia-smi
# >> lrwxrwxrwx 1 root root 27 Jan 22 13:34 /usr/bin/nvidia-smi -> /opt/bin/.nvidia/nvidia-smi
假如需要保留 sudo 执行命令时的环境变量,可以使用 sudo -E 选项,但 $PATH 环境变量可能仍然会被重置,因为在配置文件 /etc/sudoers 中可能有 secure_path 配置项进行了限制:
cat /etc/sudoers | grep secure_path
# >> Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
这种情况下要不直接修改 /etc/sudoers 文件,要不就手动设定 $PATH 环境变量:
# env PATH=$PATH <command>,在指定环境变量后执行命令
sudo -u somebottle env PATH=$PATH printenv PATH
# >> /opt/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/tools/node/bin:/tools/google-cloud-sdk/bin
6. 总结
总结一些要点:
在 Google Colab 上以
root用户运行 Apptainer 容器时,需要使用unshare -r命令来在新的命名空间下运行容器,以获得完整的 capabilities 权限集。如果容器中部分 NVIDIA 的库
.so文件找不到:- 先在宿主机上查看
$LD_LIBRARY_PATH环境变量找到 NVIDIA 共享库路径。 - 把这个路径写入到
/etc/ld.so.conf.d/目录下的一个配置文件中。 - 利用
ldconfig刷新共享库缓存。
- 先在宿主机上查看
如果平台(比如 AutoDL)不支持用户命名空间,可以尝试使用 udocker 来运行容器。
希望这篇笔记能多多少少帮助到大家吧,咱也要继续苦逼地做实验去啦!
咱们下次再会~ (∠・ω< )⌒★
【小记】在 Google Colab 等平台上运行 GPU 容器的更多相关文章
- 【Win 10应用开发】如何知道UAP在哪个平台上运行
面向22世纪的现代化应用程序可以同时在多种设备上运行,于是有朋友会有一个疑问:有时候,我们还真的需要判断一下,UAP应用程序在哪个平台上运行.尽管大多情况下我们不必要这样做,但某些特殊情况还得考虑.比 ...
- 在微软平台上运行 SAP 应用程序
本博客介绍了在微软平台上运行 SAP 应用程序的相关信息,作者在基于微软平台使用 SAP 方面有着数十年经验. 发布关于 Azure 的 SAP 说明 几个月前,SAP 针对适用于 SAP 软件 ...
- Android系统移植(一)-让android系统在目标平台上运行起来
编号),文件系统采用ubifs格式,控制台设备为ttyS1,波特率为115200 启动的第一个应用程序是/init (6)确保控制台的设置和硬件保持一致,如:硬件上串口用的是UART1,则内核启动参数 ...
- 如何在国产龙芯架构平台上运行c/c++、java、nodejs等编程语言
高能预警:本文内容过于硬核,涉及编译器原理.cpu指令集.机器码.编程语言原理.跨平台原理等计算机专业基础知识,建议具有c.c++.java.nodejs等多种编程语言开发能力,且实战经验丰富的资深开 ...
- cygwin -- 在windows平台上运行的unix模拟环境
cygwin是一个在windows平台上运行的unix模拟环境,是cygnus solutions公司开发的自由软件(该公司开发了很多好东西,著名的还有eCos,不过现已被Redhat收购).它对于学 ...
- 在.net平台上运行伪JAVA
由于在一个项目局方要求使用JAVA平台, 而当前又都是.net平台的应用. 重新用JAVA开发工作量太大. 时间也来不及. 想到在.net中有url rewrite功能, 何不先"骗&quo ...
- Windows平台上运行Tomcat
从之前的学习中知道,可以调用Bootstrap类将Toomcat作为一个独立的应用程序来运行,在Windows平台上,可以调用startup.bat批处理文件来启动Tomcat,或运行shutdown ...
- 在Windows平台上运行Tomcat
从之前的学习中知道,可以调用Bootstrap类将Toomcat作为一个独立的应用程序来运行,在Windows平台上,可以调用startup.bat批处理文件来启动Tomcat,或运行shutdown ...
- windows平台上运行Flink_转载于CSDN
Flink安装部署-window 本地部署原创冰上浮云 发布于2019-08-17 15:56:06 阅读数 633 收藏分类专栏: flink版权声明:本文为博主原创文章,遵循 CC 4.0 BY- ...
- sublime text 2中Windows下编写的脚本在Linux平台上运行遇到字符问题
今天在windows下的sublime text 2下写了个脚本,上传到ubuntu服务器中执行后提示: -bash: /usr/bin/python: /usr/bin/python^M: bad ...
随机推荐
- 2025年前端面试准备html篇
时光飞逝,一晃已经工作了10年了,2014年一个人背着书包拉着箱子,下火车去做637路公交车的场景历历在目,637路公交车从起点坐到终点,开启了工作的第一站,这趟已经在路上行驶了10年的列车,经历多了 ...
- [昌哥IT课堂]使用MySQL Shell 部署沙盒数据库实例详解
概述:这部分解释了如何使用AdminAPI设置沙盒部署.部署和使用本地MySQL的沙盒实例是开始探索AdminAPI的好方法.在将功能部署到生产服务器之前,您可以在本地测试功能.AdminAPI具有内 ...
- [不得不知道系列]Java面试你不得不知道的基础知识一
当今世界上使用最广泛的编程语言之一是Java.Java是通用的高级编程语言.核心java是java编程技术概念的基础,该术语由sun Microsystems用来描述Java的标准版本(JSE).核心 ...
- Linux下使用谷歌输入法
Linux的中文输入法一直太烂,scim终于出来对googlePinyin的支持了. 安装步骤: 1.安装scim: sudo apt-get install scim 2.从git上checkout ...
- DSPf28335 --工程模版相关文件
创建工程需要的两个文件 DSP2833x_common 1.cmd 下图中的两个文件(由TI公司提供的) 1.28335_RAM_lnk.cmd :程序下载到RAM中进行调试和仿真所使用的启动文件. ...
- Yacc笔记
语义动是一个C语句的序列 $$ 表是和相应产生式头的非终结符号关联的属性值 $i 表示和相应产生式体中第 i 个文法符号(终结符或非终结符号)关联的属性值 按照产生式规约时会执行关联的语义动作 对于 ...
- Winform窗体控件双向绑定数据模拟读写PLC数据
1.用Modbus工具模拟PLC 2.创建一个实体类 点击查看代码 internal class Data : INotifyPropertyChanged { ushort[] ushorts = ...
- C#微信公众号开发
C#微信公众号开发 一> 准备 [开发文档] [微信公众号测试接口] 用自己的微信扫码登陆,然后就可以获取就有了appId 和 appsecret. 二>获取access_token 打开 ...
- manim边做边学--淡入淡出
本篇介绍Manim中的淡入和淡出动画效果. 淡入FadeIn 主要用于让对象以渐变的方式在场景中显现. 它的特点是视觉上柔和过渡,能自然地引导观众注意新出现的元素. 淡出FadeOut 则是使对象逐渐 ...
- node 使用 pm2-logrotate 分割pm2日志 && 停止 pm2-logroatate
使用pm2-logrotate 解决pm2日志体积过大,进行分割 什么是pm2-logrotate? pm2-logrotate 是一个pm2的插件,可以对pm2日志进行管理,所以它的运行需要依靠pm ...