LXC で NVIDIA GPU が使える非特権コンテナを作る

異なるバージョンのCUDAを一つの計算機で共存したいときにこうすると便利である。ホスト側のLinuxで `nvidia-smi` を実行したときと同じようにコンテナ内の `nvidia-smi` が動作するようにする。LXC でコンテナを作成し実行することはすでに出来ているとする。（非特権コンテナとUbuntu 20.04に合わせて記事を少し更新した）。一般ユーザー（非rootユーザー）でコンテナを作りたい場合 [ここ](https://qiita.com/kakinaguru_zo/items/8c82954a1bb0a1ef9a40#lxc%E3%81%AB%E3%82%88%E3%82%8B%E9%9D%9E%E7%89%B9%E6%A8%A9%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A) を参照

ホスト側でnvidia-smiを実行すると以下のように見える

```shell-session
# nvidia-smi
Tue Feb  4 10:52:19 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.48.02    Driver Version: 440.48.02    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 RTX 208...  Off  | 00000000:08:00.0 Off |                  N/A |
| 30%   31C    P8    25W / 250W |     18MiB / 11016MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  GeForce RTX 208...  Off  | 00000000:09:00.0 Off |                  N/A |
| 29%   32C    P8    20W / 250W |      1MiB / 11019MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      1262      G   /usr/lib/xorg/Xorg                            16MiB |
+-----------------------------------------------------------------------------+
```

# ホスト側での作業

まず `lxc-create  -n コンテナ名 -t download -- -d ubuntu -r focal -a amd64` でUbuntuのコンテナを作る。もし `/var/lib/lxc` がBTRFSにあるなら `-B btrfs` を付けておくと後で `lxc-copy -n 古いコンテナ -N 新しいコンテナ` でコンテナを複製するときに掛かる時間が劇的に短くなる。

次に `lxc-execute -n コンテナ名 -- /bin/passwd` でrootに適切なパスワードを設定する。

以下の設定を追加する

```config:/var/lib/lxc/コンテナ名/configに追加
lxc.mount.entry = /dev/nvidiactl dev/nvidiactl none bind,rw,create=file 0 0
lxc.mount.entry = /dev/nvidia-modeset dev/nvidia-modeset none bind,rw,create=file,optional 0 0
lxc.mount.entry = /dev/nvidia-uvm dev/nvidia-uvm none bind,rw,create=file 0 0
lxc.mount.entry = /dev/nvidia-uvm-tools dev/nvidia-uvm-tools none bind,rw,create=file 0 0
lxc.mount.entry = /dev/nvidia0 dev/nvidia0 none bind,rw,create=file 0 0
lxc.mount.entry = /dev/nvidia1 dev/nvidia1 none bind,rw,create=file,optional 0 0
# 以下は非特権コンテナなら不要、またホストLinuxがFedora 31などCGroup2を用いるディストロの場合
# cgroupをcgroup2に置き換える
lxc.cgroup.devices.allow = c 195:* rwm
lxc.cgroup.devices.allow = c 235:* rwm
```

最後に `lxc-start -F -n コンテナ名` でコンテナを起動する。ログインプロンプトが表示されたらrootでログインする。**非特権コンテナからNVIDIA GPUにアクセスするためには上記の '/dev' 以下のファイルの所有者またはグループをコンテナ内のroot（たいていの場合100000）に`chown`か `chgrp` で予めホスト側で変更しておく必要がある**。

# コンテナ内での作業
```shell
apt-get --no-install-recommends install software-properties-common
add-apt-repository ppa:graphics-drivers/ppa
# 上記２行はUbuntu Focalでは不要
apt-get --no-install-recommends install nvidia-utils-440
```

最後の`nvidia-utils-440` はホスト側LinuxのNVIDIAドライバのバージョンと完全に同一でないとエラーになり`nvidia-smi`が動作しないので注意。ここまで行いコンテナ内で`nvidia-smi`を実行すると

```shell-session
# nvidia-smi
Tue Feb  4 02:01:33 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.48.02    Driver Version: 440.48.02    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 RTX 208...  Off  | 00000000:08:00.0 Off |                  N/A |
| 30%   31C    P8    26W / 250W |     18MiB / 11016MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  GeForce RTX 208...  Off  | 00000000:09:00.0 Off |                  N/A |
| 29%   32C    P8    20W / 250W |      1MiB / 11019MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+
```

となる。

# ホストの/homeをコンテナ内で見えるようにするには

```config:/var/lib/lxc/コンテナ名/configに追加
lxc.mount.entry = /home home none bind,rw 0 0
```

非特権コンテナの場合上記の方法では `/home` 以下のすべてのファイルの所有者が `nobody` になってしまし、まともにアクセスできない。その不都合を解消するためには、読み書きしたいファイルのUIDが1000であるとして、コンテナの設定ファイルの中のUID/GIDの変換方法を指定している `lxc.idmap` の行を例えば以下のように書き換えるとよい。 

```config:$HOME/.local/share/lxc/コンテナ名/configを変更
lxc.idmap = u 0 100000 1000
lxc.idmap = g 0 100000 1000
lxc.idmap = u 1000 1000 1
lxc.idmap = g 1000 1000 1
lxc.idmap = u 1001 101001 64535
lxc.idmap = g 1001 101001 64535
```

上記のように書き換えるとホスト側ほホームディレクトリのUID 1000, GID 1000 がコンテナ内部でもUID 1000, GID 1000に割り当てられるため、UID 1000のユーザーがそのホームディレクトリにあるファイルを操作できるようになる。

# 他のホストからネットワーク的に見えるようにするには
設定ファイルに以下の内容を追加する。以下の設定は`lxc-start`をrootから起動しないとエラーになりできない（コンテナは非特権でも構わない）。

```config:/var/lib/lxc/コンテナ名/configに追加
lxc.net.0.type = macvlan
lxc.net.0.link = enp6s0 # この名前は "ip l" で表示されるEthernetインターフェースの名前
lxc.net.0.flags = up
lxc.net.0.name = eth0
```