DockerのRootlessモードでNVIDIAのGPUを使用する


3行まとめ

  • RootlessモードでもGPUは使用できる。
  • RootlessモードでGPUを使用するためにはnvidia-container-runtimeの設定を変更する必要がある。
  • Rootlessモードと通常モードを共存する方法はまだ未解決。

1. はじめに

 機械学習分野では、Docker/Kubernetes上でNVIDIAのGPU(以下、単に「GPU」)を使うということがよく行われます。
複数人で共有するGPUマシンのセキュリティをより高めるため、RootlessモードのDockerでGPUが使用できるかどうか調査してみました。
結果から言えば、簡単な設定変更を行うだけで使用できました。(少なくともPyTorchから認識できました)

2. 環境

 今回、調査に使用した環境は以下の通りです。

  • OS: Ubuntu 19.10(Eoan Ermine)
  • GPU: GeForce GTX1070 8GB x4台(ただし1台のみ電源オン状態)
  • NVIDIAドライバ: 440.82
  • 通常モードのDocker: 19.03.8
  • RootlessモードのDocker: 19.03.8

 より詳細な環境の情報は以下の通りです。

yuya$ cat /etc/os-release
NAME="Ubuntu"
VERSION="19.10 (Eoan Ermine)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 19.10"
VERSION_ID="19.10"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=eoan
UBUNTU_CODENAME=eoan

yuya$ uname -a
Linux ml-1 5.3.0-51-generic #44-Ubuntu SMP Wed Apr 22 21:09:44 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

yuya$ dpkg -l | grep nvidia
ii  libnvidia-cfg1-440:amd64             440.82-0ubuntu0~0.19.10.1              amd64        NVIDIA binary OpenGL/GLX configuration library
ii  libnvidia-compute-440:amd64          440.82-0ubuntu0~0.19.10.1              amd64        NVIDIA libcompute package
ii  libnvidia-container-tools            1.0.7-1                                amd64        NVIDIA container runtime library (command-line tools)
ii  libnvidia-container1:amd64           1.0.7-1                                amd64        NVIDIA container runtime library
ii  nvidia-compute-utils-440             440.82-0ubuntu0~0.19.10.1              amd64        NVIDIA compute utilities
ii  nvidia-container-runtime             3.1.4-1                                amd64        NVIDIA container runtime
ii  nvidia-container-toolkit             1.0.5-1                                amd64        NVIDIA container runtime hook
ii  nvidia-dkms-440                      440.82-0ubuntu0~0.19.10.1              amd64        NVIDIA DKMS package
ii  nvidia-docker2                       2.2.2-1                                all          nvidia-docker CLI wrapper
ii  nvidia-headless-440                  440.82-0ubuntu0~0.19.10.1              amd64        NVIDIA headless metapackage
ii  nvidia-headless-no-dkms-440          440.82-0ubuntu0~0.19.10.1              amd64        NVIDIA headless metapackage - no DKMS
ii  nvidia-kernel-common-440             440.82-0ubuntu0~0.19.10.1              amd64        Shared files used with the kernel module
ii  nvidia-kernel-source-440             440.82-0ubuntu0~0.19.10.1              amd64        NVIDIA kernel source package
ii  nvidia-modprobe                      418.56-1                               amd64        utility to load NVIDIA kernel modules and create device nodes
ii  nvidia-utils-440                     440.82-0ubuntu0~0.19.10.1              amd64        NVIDIA driver support binaries

yuya$ nvidia-smi
Fri May  8 15:31:23 2020
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.82       Driver Version: 440.82       CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 1070    Off  | 00000000:01:00.0 Off |                  N/A |
|  0%   36C    P8     7W / 195W |      0MiB /  8119MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
...

yuya$ docker version
Client: Docker Engine - Community
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        afacb8b7f0
 Built:             Wed Mar 11 01:25:55 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.8
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.17
  Git commit:       afacb8b7f0
  Built:            Wed Mar 11 01:24:26 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 nvidia:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

yuya$ docker info
...
 Security Options:
  apparmor
  seccomp
   Profile: default
...

yuya$ cat /etc/docker/daemon.json
{
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "path": "nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}

 nvidia-docker2パッケージをインストールし、default-runtimenvidiaに設定しているのは、Kubernetes上でGPUを使用するためです。
主題のRootlessモードには関係ありません。

 なお、記事中で使用しているyuyaユーザはdockerグループに属しており、通常モードのDockerにアクセスできます。

yuya$ id yuya
uid=1000(yuya) gid=1000(yuya) groups=1000(yuya),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),115(lxd),998(docker)

3. 通常モードでのGPUの使用

 通常モード(root権限でDockerデーモンが動作している一般的な状態)では、nvidia-container-toolkitパッケージをインストールし、--gpus allオプションを付加することで、DockerコンテナでGPUを使用することができます。
nvidia-smiの実行結果は以下の通りです。Dockerコンテナ内でGPUを認識していることが確認できます。

yuya$ docker container run --tty --rm --gpus all nvidia/cuda:10.2-base-ubuntu18.04 nvidia-smi
Fri May  8 06:54:54 2020
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.82       Driver Version: 440.82       CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 1070    Off  | 00000000:01:00.0 Off |                  N/A |
|  0%   36C    P8     7W / 195W |      0MiB /  8119MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
...

4. RootlessモードでのGPUの使用

4.1. RootlessモードのDockerをセットアップする

 まずは、Rootlessモードの実行に必要なuidmapパッケージ、slirp4netnsパッケージをインストールします。

yuya$ sudo apt update && sudo apt install --yes uidmap slirp4netns

 続いて、Rootlessモードの動作確認を行うため、dockerグループに属していない新しいユーザrootlessを作成します。

yuya$ sudo adduser rootless
yuya$ id rootless
uid=1001(rootless) gid=1001(rootless) groups=1001(rootless)

 準備が整ったら、RootlessモードのDockerをインストールしたいユーザ(今回はrootlessユーザ)でログインし、RootlessモードのDockerをインストールします。
この際、su - rootlessなどユーザを切り替えるのではなく、ログインシェルが起動する状態でログインする必要があります。そうしないとユーザ毎のsystemdデーモンが起動しないので注意が必要です。念のため、systemctl --user daemon-reloadが実行できることを確認しておきましょう。

 インストール自体はcurl -fsSL https://get.docker.com/rootless | shで一発です。sudoコマンドを使用していないことに気を付けましょう。

rootless$ systemctl --user daemon-reload
rootless$ echo $?
0

rootless$ curl -fsSL https://get.docker.com/rootless | sh
# Installing stable version 19.03.8
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 60.7M  100 60.7M    0     0  2995k      0  0:00:20  0:00:20 --:--:-- 2904k
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 17.8M  100 17.8M    0     0  2511k      0  0:00:07  0:00:07 --:--:-- 2559k
# starting systemd service
● docker.service - Docker Application Container Engine (Rootless)
   Loaded: loaded (/home/rootless/.config/systemd/user/docker.service; disabled; vendor preset: enabled)
   Active: active (running) since Fri 2020-05-08 15:39:07 JST; 3ms ago
     Docs: https://docs.docker.com
 Main PID: 14632 (dockerd-rootles)
   CGroup: /user.slice/user-1001.slice/[email protected]/docker.service
           ├─14632 /bin/sh /home/rootless/bin/dockerd-rootless.sh --experimental --storage-driver=overlay2
           └─14639 [grep]

May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + : auto
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + net=
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + mtu=
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + [ -z ]
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + which slirp4netns
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + slirp4netns --help
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + grep -- --disable-host-loopback
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: --disable-host-loopback  prohibit connecting to 127.0.0.1:* on the host namespace
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + net=slirp4netns
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + [ -z ]
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + mtu=65520
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + [ -z slirp4netns ]
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + [ -z 65520 ]
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + [ -z ]
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + _DOCKERD_ROOTLESS_CHILD=1
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + export _DOCKERD_ROOTLESS_CHILD
May 08 15:39:07 ml-1 dockerd-rootless.sh[14632]: + exec rootlesskit --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/rootless/bin/dockerd-rootless.sh --experimental --storage-driver=overlay2
Client: Docker Engine - Community
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        afacb8b7f0
 Built:             Wed Mar 11 01:22:56 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.8
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.17
  Git commit:       afacb8b7f0
  Built:            Wed Mar 11 01:30:32 2020
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          v1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
# Docker binaries are installed in /home/rootless/bin
# WARN: dockerd is not in your current PATH or pointing to /home/rootless/bin/dockerd
# Make sure the following environment variables are set (or add them to ~/.bashrc):

export PATH=/home/rootless/bin:$PATH
export DOCKER_HOST=unix:///run/user/1001/docker.sock

#
# To control docker service run:
# systemctl --user (start|stop|restart) docker
#

4.2. RootlessモードのDockerの動作を確認する

 インストールが完了したら、関連する環境変数を設定し、docker infoを実行します。Security Options:rootlessが含まれていれば、Rootlessモードで動作しています。
また、適宜.bashrcなどを編集し、環境変数の設定を追加します。

rootless$ export PATH=/home/rootless/bin:$PATH
rootless$ export DOCKER_HOST=unix:///run/user/1001/docker.sock
rootless$ docker version
Client: Docker Engine - Community
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        afacb8b7f0
 Built:             Wed Mar 11 01:22:56 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.8
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.17
  Git commit:       afacb8b7f0
  Built:            Wed Mar 11 01:30:32 2020
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          v1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

rootless$ docker info
...
 Security Options:
  seccomp
   Profile: default
  rootless
...

4.3. nvidia-container-runtimeの設定を変更する

 RootlessモードのDockerでnvidia-smiを実行すると、以下の通りエラーとなります。

rootless$ docker container run --tty --rm --gpus all nvidia/cuda:10.2-base-ubuntu18.04 nvidia-smi
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "process_linux.go:449: container init caused \"process_linux.go:432: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: nvidia-container-cli: mount error: open failed: /sys/fs/cgroup/devices/user.slice/devices.allow: permission denied\\\\n\\\"\"": unknown.

 mobyリポジトリのIssue#38729を参考に、nvidia-container-runtimeの設定を変更します。具体的にはno-cgroupsをデフォルト値のfalseからtrueに変更します。

参考: nvidia-container-runtime doesn't work with rootless mode · Issue #38729 · moby/moby

yuya$ sudo cp /etc/nvidia-container-runtime/config.toml /etc/nvidia-container-runtime/config.toml.20200508
yuya$ sudo vim /etc/nvidia-container-runtime/config.toml
yuya$ diff -U 3 /etc/nvidia-container-runtime/config.toml.20200508 /etc/nvidia-container-runtime/config.toml
--- /etc/nvidia-container-runtime/config.toml.20200508  2020-05-08 15:50:44.605107694 +0900
+++ /etc/nvidia-container-runtime/config.toml   2020-05-08 15:50:57.125141605 +0900
@@ -8,7 +8,7 @@
 #debug = "/var/log/nvidia-container-toolkit.log"
 #ldcache = "/etc/ld.so.cache"
 load-kmods = true
-#no-cgroups = false
+no-cgroups = true
 #user = "root:video"
 ldconfig = "@/sbin/ldconfig.real"

4.4. RootlessモードのDockerでGPUを使用する

 nvidia-container-runtimeの設定を変更すると、nvidia-smiが実行できるようになります。なお、Dockerデーモンの再起動は不要です。

rootless$ docker container run --tty --rm --gpus all nvidia/cuda:10.2-base-ubuntu18.04 nvidia-smi
Fri May  8 06:56:51 2020
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.82       Driver Version: 440.82       CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 1070    Off  | 00000000:01:00.0 Off |                  N/A |
|  0%   36C    P8     7W / 195W |      0MiB /  8119MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
...

rootless$ docker container run --interactive --tty --rm --gpus all nvidia/cuda:10.2-base-ubuntu18.04 /bin/bash

root@docker# apt update && apt install --yes python3-pip
root@docker# pip3 install torch
root@docker# python3
Python 3.6.9 (default, Apr 18 2020, 01:56:04)
...
>>> import torch
>>> print(torch.cuda.is_available())
True
>>> torch.cuda.get_device_name(0)
'GeForce GTX 1070'

 上記の通り、Dockerコンテナ内のPyTorchからもGPUが認識できています。

5. 【未解決】通常モードとRootlessモードの共存

 /etc/nvidia-container-runtime/config.tomlno-cgroups = trueを追加し、RootlessモードのDockerでGPUを使用できるようにすると、逆に通常モードのDockerではGPUを使用できなくなってしまいます。
こちらの問題についてはまだ調査しておらず、未解決です。何か情報があれば、お知らせ頂けると嬉しいです。

yuya$ grep no-cgroups /etc/nvidia-container-runtime/config.toml
no-cgroups = true

yuya$ docker container run --tty --rm --gpus all nvidia/cuda:10.2-base-ubuntu18.04 nvidia-smi
Failed to initialize NVML: Unknown Error