这是一个去年的需求:我自己的操作系统是 Fedora 43,实验室有一个临时的横向需要在 Ubuntu 22.04 环境下进行,我不能使用虚拟机,因为项目需要用到 CUDA,但我也不想再装一个新系统。后来经研究,我发现了一种能够在 Docker 中使用 CUDA 并且可以运行图形化界面的方法,最近又遇到了类似的情况,才发现我当时并没有记录我的配置方法,今天又进行了回顾,将具体方法记录在此,希望帮助到有需要的人。

有没有人觉得用 Conda 管理 Python 依赖很难处理某些 C++ 库的动态链接问题的,我在《简单记录在 Conda 虚拟环境中编译安装 OpenCV 的方法》一文中就遇到过类似的问题。经过一段时间的使用体验我觉得用 Docker 来做环境隔离要比 Conda 清爽很多,未来很长一段时间我在具有 sudo 权限的机器上应该都会用本文提到的方法,不会考虑用 Conda 了。

前提条件

  • 宿主机具有 NVIDIA 显卡,已安装显卡驱动,可以不装 CUDA。
  • 宿主机安装任意发行版的 Linux 操作系统,我没试过 WSL 行不行。
  • 宿主机的图形栈使用 X11,用 XWayland 应该也可以。

第一步:安装和配置 NVIDIA Container Toolkit

这个东西是用来让 Docker 能够使用 GPU 的。

Debian 系:

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \ 
  | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg

curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \ 
  | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \ 
  | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt update
sudo apt install -y nvidia-container-toolkit

红帽系:

sudo dnf config-manager --add-repo https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo
sudo dnf install -y nvidia-container-toolkit

之后还需要配置一下:

sudo nvidia-ctk runtime configure --runtime=docker
cat /etc/docker/daemon.json  # 验证配置是否生效,生效再执行下一行
sudo systemctl restart docker

如果配置生效,cat的结果如下:

{
  "runtimes": {
    "nvidia": {
      "args": [],
      "path": "nvidia-container-runtime"
    }
  }
}

这里多一点东西没事,只是别的配置项,主要关注没看到 nvidia-container-runtime或者这个文件本身不存在的情况。

第二步:构建开发镜像

拉取 NVIDIA 官方的镜像(网不好这一步会很慢):

sudo docker pull nvidia/cuda:12.9.0-cudnn-devel-ubuntu22.04

CUDA 版本,系统版本都可以根据自己的需求更换,这里的 CUDA 版本是容器内 CUDA 的版本,跟宿主机上的 CUDA 版本没有关系(宿主机上没装 CUDA 也不用管),只需要关注宿主机显卡驱动支持的最高 CUDA 版本(在宿主机上执行 nvidia-smi能看到)。不需要 cudnn 可以删掉 cudnn-,除了 devel版镜像还有 runtime版和 base版,不建议使用,里面不内置 nvcc等编译工具链。所有镜像版本可以看这里

验证第一步是否成功:

sudo docker run --rm --gpus all nvidia/cuda:12.9.0-cudnn-devel-ubuntu22.04 nvidia-smi

输出和 nvidia-smi一样的显卡信息就是成功了。

到这里你可以直接使用官方镜像来构建你的开发容器了,但容器环境一般都是 root 用户,某些奇怪的库并不能以 root 身份使用,容器内外交换文件也常常会因为容器内外用户身份不同而出现很多权限问题,因此我建议在官方库的基础上定制自己的开发镜像。如果你使用 Podman 等权限管理做的更好的容器化方案可以跳过这个步骤,当然,如果你知道 Podman,我觉得应该也不用我教你怎么做。

在任意位置创建 Dockerfile 文件,添加如下内容:

FROM nvidia/cuda:12.9.0-cudnn-devel-ubuntu22.04

ARG USERNAME=user
ARG UID=1000
ARG GID=1000

# 下面这行替换为自己想要预装的 apt 包,sudo 必须安装
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl zsh nano sudo wget git x11-apps
RUN rm -rf /var/lib/apt/lists/*

RUN groupadd -g ${GID} ${USERNAME}
# 如果你的 shell 使用 bash,记得把这里的 /bin/zsh 替换为 /bin/bash
RUN useradd -m -u ${UID} -g ${GID} -s /bin/zsh ${USERNAME}
# 把自己添加为容器内的 sudoer
RUN echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/${USERNAME}
RUN chmod 0440 /etc/sudoers.d/${USERNAME}

WORKDIR /home/${USERNAME}
USER ${USERNAME}
ENTRYPOINT ["tail", "-f", "/dev/null"]

在 Dockerfile 所在的目录中执行

sudo docker build \
  --build-arg USERNAME=$(whoami) \
  --build-arg UID=$(id -u) \
  --build-arg GID=$(id -g) \
  -t cuda-env .

然后你就有了一个叫 cuda-env的镜像可以用来创建开发容器。这里有一个要注意的点是:这个镜像只能你自己使用,同设备的其它用户需要重新构建镜像,毕竟 Dockerfile 只添加了你作为镜像里的 sudoer。

第三步:创建和使用开发容器

执行:

mkdir ~/devel
docker run -itd \
  --gpus all \
  --security-opt=label=disable \
  --restart=no \
  --network=host \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix:rw \
  -e QT_X11_NO_MITSHM=1 \
  -v $HOME/devel:/home/$(whoami)
  --name dev-env cuda-env:latest

这里有几个需要关注的点:

  • --gpus all:让容器可以使用到全部 GPU,如果你想限制只能使用特定几张 GPU 可以改这个配置,怎么改可以问 AI。
  • -e DISPLAY=$DISPLAY:这意味着操作这个容器时使用的显示服务器必须总是同一个 ID,如果你直接使用物理显示器操作这台设备,那就不用管。比较特殊的情况是你使用 VNC 等远程连接协议操作服务器上的容器,此时你必须跟这台服务器的其它用户约定好你要独占这个 ID,否则万一下次你跟同事刚好交换了一下显示服务器 ID,你在容器里打开的窗口就会显示在你同事那。
  • -v $HOME/devel:/home/$(whoami):将宿主机上的 ~/devel目录映射到容器内的主目录,方便进行文件交换。我个人非常不推荐 $HOME:/home/$(whoami)这种直接映射主目录的做法,某些 Python 依赖会安装在 ~/.local目录下,直接映射主目录会把宿主机上的 Python 环境搞崩。

如果只输出了一长串十六进制数,说明一个名叫 dev-env的开发容器已经创建成功了。

用以下命令进入这个容器中:

# 这里的命令会执行在宿主机上
sudo docker exec -it dev-env /bin/zsh  # 如果用 bash 记得换
# 这里的命令会执行在容器里
exit
# 这里的命令会执行在宿主机上

可以用以下命令验证容器是否能通过 X11 协议在屏幕上创建窗口:

sudo docker exec -it dev-env /bin/zsh
xclock  # 需要安装 x11-apps

如果弹出钟表窗口,代表通讯正常,我们就可以在容器里运行其它图形化界面程序了。

如果你创建容器的参数跟我一样,每次重启机器后需要先启动容器才能进入容器环境,使用以下命令启动容器:

sudo docker start dev-env

把创建容器的参数 --restart=no替换为 --restart=always--restart=unless-stop可以跳过这个步骤。

开发完毕后,想要删除该容器,可以使用以下命令(不会删除 ~/devel里面的东西):

sudo docker stop dev-env
sudo docker rm dev-env
# 可选,一并删除镜像:
sudo docker rmi cuda-env:latest

封面(原作者 B 站 @漂流瓶瓶瓶瓶子):https://upyun.kircute.top/cover/12.png

文章作者: 卡比三卖萌KirCute
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 卡比三卖萌KirCute的博客
运维
喜欢就支持一下吧