nvidia-docker in nvidia-dockerを実現する

## 2行まとめ

* 「nvidia-docker in nvidia-docker」は可能。
* nvidia-dockerはバージョン1.0系とバージョン2.0系があり、2.0系の方がいろいろと楽。

## 概要

例えばCI（Continuous Integration、継続的インテグレーション）環境として[Jenkins](https://jenkins.io/)のコンテナを動作させており、その中からDockerコンテナを起動したい場合、いわゆる「Docker in Docker」を実現することになります。
また、NVIDIA製GPUをDockerコンテナ内から使いたい場合、[nvidia-docker](https://github.com/NVIDIA/nvidia-docker)を使うのが一般的かと思います。
これらを組み合わせて「nvidia-docker in nvidia-docker」を実現するのがこの記事の目的です。

なお、CI環境などの開発環境を想定しているため、セキュリティについては十分に考慮していません。`/var/run/docker.sock`のマウントなどは十分に注意して行ってください。

## まずは「Docker in Docker」

まずは`nvidia-docker`のことは忘れて、普通のDockerコンテナ内から`docker`コマンドを起動してみましょう。
なお、環境は以下の通りです。

```
yuya@host$ uname -a
Linux xxx 4.4.0-119-generic #143-Ubuntu SMP Mon Apr 2 16:08:24 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

yuya@host$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.4 LTS"

yuya@host$ docker version
Client:
 Version:       17.12.1-ce
 API version:   1.35
 Go version:    go1.9.4
 Git commit:    7390fc6
 Built: Tue Feb 27 22:17:40 2018
 OS/Arch:       linux/amd64

Server:
 Engine:
  Version:      17.12.1-ce
  API version:  1.35 (minimum version 1.12)
  Go version:   go1.9.4
  Git commit:   7390fc6
  Built:        Tue Feb 27 22:16:13 2018
  OS/Arch:      linux/amd64
  Experimental: false
```

Dockerコンテナ内で`docker version`を実行する例は以下の通りです。Dockerデーモンに正しく接続できていることが確認できます。

```
yuya@host$ docker pull ubuntu:16.04
16.04: Pulling from library/ubuntu
Digest: sha256:9ee3b83bcaa383e5e3b657f042f4034c92cdd50c03f73166c145c9ceaea9ba7c
Status: Image is up to date for ubuntu:16.04

yuya@host$ docker run --interactive --tty --rm \
  --volume /usr/bin/docker:/usr/bin/docker:ro \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --group-add $(stat --format "%g" /var/run/docker.sock) \
  ubuntu:16.04 \
  /bin/bash

root@container# id
uid=0(root) gid=0(root) groups=0(root),130

root@container# apt-get update && apt-get install --yes --no-install-recommends libltdl7

root@container# docker version
Client:
 Version:       17.12.1-ce
 API version:   1.35
 Go version:    go1.9.4
 Git commit:    7390fc6
 Built: Tue Feb 27 22:17:40 2018
 OS/Arch:       linux/amd64

Server:
 Engine:
  Version:      17.12.1-ce
  API version:  1.35 (minimum version 1.12)
  Go version:   go1.9.4
  Git commit:   7390fc6
  Built:        Tue Feb 27 22:16:13 2018
  OS/Arch:      linux/amd64
  Experimental: false
```

それぞれのオプションの意味は以下の通りです。

* `--volume /usr/bin/docker:/usr/bin/docker:ro`
    * Dockerホスト側の`/usr/bin/docker`（`docker`コマンド自体）をDockerコンテナ側に読み取り専用でマウントします。
    * `docker`コマンドはGo言語で開発されており、依存関係が少ないため、このようにコマンドをコピーするという乱暴な方法でも動作します。
* `--volume /var/run/docker.sock:/var/run/docker.sock`
    * Dockerデーモンに接続するためのUNIXソケットをマウントします。
* `--group-add $(stat --format "%g" /var/run/docker.sock)`:
    * Dockerコンテナ内の実行ユーザに`/var/run/docker.sock`への権限を与えます。
    * Dockerコンテナ内で`root`ユーザを使う場合は不要ですが、それ以外のユーザで実行する場合は必要です。

`libltdl7`パッケージをインストールしているのは、`ubuntu:16.04`イメージでは`docker`コマンドの実行に必要なライブラリが足りないためです。インストールせずに`docker version`コマンドを実行すると、以下のエラーメッセージが表示されます。

```
root@container# docker version
docker: error while loading shared libraries: libltdl.so.7: cannot open shared object file: No such file or directory
```

## nvidia-docker in nvidia-docker（バージョン1.0系）

バージョン1.0系のnvidia-dockerでは、`nvidia-docker`コマンドを使います。今回は以下の環境で検証しました。

```
yuya@host$ dpkg -l | grep nvidia-docker
ii  nvidia-docker    1.0.1-1    amd64    NVIDIA Docker container tools
```

バージョン1.0系における「nvidia-docker in nvidia-docker」の例は以下の通りです。`nvidia-smi`が正しく実行できていることが確認できます。

```
yuya@host$ docker pull nvidia/cuda:latest
latest: Pulling from nvidia/cuda
Digest: sha256:74570aef804e30486eb74b284fd3091d14ca7144736c44fb48c13e5e6afb963f
Status: Image is up to date for nvidia/cuda:latest

yuya@host$ nvidia-docker run --interactive --tty --rm \
  --volume /usr/bin/docker:/usr/bin/docker:ro \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --volume /usr/bin/nvidia-docker:/usr/bin/nvidia-docker:ro \
  --group-add $(stat --format "%g" /var/run/docker.sock) \
  nvidia/cuda:latest \
  /bin/bash

root@container# id
uid=0(root) gid=0(root) groups=0(root),130

root@container# apt-get update && apt-get install --yes --no-install-recommends libltdl7 nvidia-modprobe

root@container# nvidia-docker version
NVIDIA Docker: 1.0.1

Client:
 Version:       17.12.1-ce
 API version:   1.35
 Go version:    go1.9.4
 Git commit:    7390fc6
 Built: Tue Feb 27 22:17:40 2018
 OS/Arch:       linux/amd64

Server:
 Engine:
  Version:      17.12.1-ce
  API version:  1.35 (minimum version 1.12)
  Go version:   go1.9.4
  Git commit:   7390fc6
  Built:        Tue Feb 27 22:16:13 2018
  OS/Arch:      linux/amd64
  Experimental: false

root@container# nvidia-smi
Mon Apr 16 09:06:38 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 387.34                 Driver Version: 387.34                    |
|-------------------------------+----------------------+----------------------+
| 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 1080    Off  | 00000000:02:00.0  On |                  N/A |
| 33%   49C    P2    37W / 180W |   3166MiB /  8111MiB |     10%      Default |
+-------------------------------+----------------------+----------------------+
|   1  GeForce GTX 1080    Off  | 00000000:03:00.0 Off |                  N/A |
| 27%   39C    P8     5W / 180W |     27MiB /  8114MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

root@container# nvidia-docker run --interactive --tty --rm nvidia/cuda:latest nvidia-smi
Mon Apr 16 09:06:58 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 387.34                 Driver Version: 387.34                    |
|-------------------------------+----------------------+----------------------+
| 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 1080    Off  | 00000000:02:00.0  On |                  N/A |
| 33%   49C    P2    37W / 180W |   3166MiB /  8111MiB |     10%      Default |
+-------------------------------+----------------------+----------------------+
|   1  GeForce GTX 1080    Off  | 00000000:03:00.0 Off |                  N/A |
| 27%   39C    P8     5W / 180W |     27MiB /  8114MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+
```

なお、`nvidia-modprobe`パッケージをインストールしていない場合、`nvidia-docker`時に以下のエラーが発生します。

```
root@container# nvidia-docker run --interactive --tty --rm nvidia/cuda:latest nvidia-smi
nvidia-docker | 2018/04/16 09:31:10 Error: Could not load UVM kernel module. Is nvidia-modprobe installed?
```

## nvidia-docker in nvidia-docker（バージョン2.0系）

バージョン2.0系のnvidia-dockerでは、普段通り`docker`コマンドを使います。今回は以下の環境で検証しました。

```
yuya@host$ dpkg -l | grep nvidia-docker
ii  nvidia-docker2    2.0.2+docker17.12.0-1    all    nvidia-docker CLI wrapper
```

バージョン2.0系における「nvidia-docker in nvidia-docker」の例は以下の通りです。`nvidia-smi`が正しく実行できていることが確認できます。

```
yuya@host$ docker pull nvidia/cuda:latest
latest: Pulling from nvidia/cuda
Digest: sha256:74570aef804e30486eb74b284fd3091d14ca7144736c44fb48c13e5e6afb963f
Status: Image is up to date for nvidia/cuda:latest

yuya@host$ docker run --interactive --tty --rm \
  --runtime nvidia \
  --volume /usr/bin/docker:/usr/bin/docker:ro \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --group-add $(stat --format "%g" /var/run/docker.sock) \
  nvidia/cuda:latest \
  /bin/bash

root@container# id
uid=0(root) gid=0(root) groups=0(root),999

root@container# apt-get update && apt-get install --yes --no-install-recommends libltdl7

root@container# docker version
Client:
 Version:       17.12.0-ce
 API version:   1.35
 Go version:    go1.9.2
 Git commit:    c97c6d6
 Built: Wed Dec 27 20:11:19 2017
 OS/Arch:       linux/amd64

Server:
 Engine:
  Version:      17.12.0-ce
  API version:  1.35 (minimum version 1.12)
  Go version:   go1.9.2
  Git commit:   c97c6d6
  Built:        Wed Dec 27 20:09:53 2017
  OS/Arch:      linux/amd64
  Experimental: false

root@container# nvidia-smi
Mon Apr 16 09:08:41 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 387.34                 Driver Version: 387.34                    |
|-------------------------------+----------------------+----------------------+
| 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 108...  Off  | 00000000:03:00.0  On |                  N/A |
|  0%   44C    P2    93W / 280W |   1786MiB / 11169MiB |     45%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

root@container# docker run --interactive --tty --rm --runtime nvidia nvidia/cuda:latest nvidia-smi
Mon Apr 16 09:08:58 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 387.34                 Driver Version: 387.34                    |
|-------------------------------+----------------------+----------------------+
| 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 108...  Off  | 00000000:03:00.0  On |                  N/A |
|  0%   46C    P2    83W / 280W |   1786MiB / 11169MiB |     30%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+
```

## 参考

* [NVIDIA/nvidia-docker: Build and run Docker containers leveraging NVIDIA GPUs](https://github.com/NVIDIA/nvidia-docker)
    * nvidia-dockerの本家サイト。
* [nvidia-docker でポータブルな機械学習の作業環境を作る - Qiita](https://qiita.com/DUxCA/items/7bf3862569cfca122d73)
    * 内容はnvidia-dockerバージョン1.0系ですが、いろいろと参考になります。
* [nvidia-docker2のインストール（On Ubuntu 16.04 LTS） - Qiita](https://qiita.com/spiderx_jp/items/32c421fd00c6ade19720)
    * Docker、nvidia-dockerバージョン2.0系のインストール方法について。