前書き
CUDAのバージョンが違う環境を複数用意して使い分けたい場合などにコンテナは便利である。DockerやLXCなど有名なものが幾つかあるが、最も軽量だと思われる systemd-nspawn
を用いて、ホストOSとゲストOSが両方共Ubuntuである場合(両方DebianでもたぶんOK)にコンテナを作りゲストOSの使用メモリ量を制限する手順を解説する。デフォルトでホストとゲストがネットワークインターフェースを共有することに気づいていなかったり、無線LANインターフェースでMACVLANを使えないことを知らなかったため丸一日調べるのに時間がかかった。MACVLANなどでネットワークインターフェースをホストOSとゲストOSで分ける手順は最後の節で述べる。以下の作業はすべてroot権限で実行してください。コンテナ名はcontainer1
にしてあるので適当に置き換えてください。
ホストとゲスト
コンテナ内で動作しているほうがゲストで、ハードウェアの上で直接動作しているほうがホストです。
コンテナ環境の設定
- (もしbtrfsを使っているなら
btrfs subvolume create /var/lib/machines/container1
) apt-get install debootstrap
-
debootstrap --arch amd64 --variant=minbase --include=ubuntu-minimal,openssh-server,language-pack-en-base,language-pack-ja-base bionic /var/lib/machines/container1 http://jp.archive.ubuntu.com/ubuntu
この作業で/var/lib/machines/container1
に最小限のUbuntu 18.04とopensshサーバーsshdがインストールされる。bionic
の部分はホストOSと揃えたほうが無難でしょう。 cp /etc/apt/sources.list /var/lib/machines/container1/etc/apt
-
vi /var/lib/machines/container1/etc/ssh/sshd_config
でPort 26000
,PermitRootLogin yes
にする。ネットワークインターフェースがホストとゲストで共有されているから、ホストで動作するsshd
とゲストで動作するsshd
のポートを変えないと同時に実行できない。 -
systemd-nspawn -M container1
をホストで実行するとゲストOSのシェルプロンプトが出るから、passwd
コマンドで適当なルートパスワードを設定する。 -
systemd-nspawn -b -M container1 --bind=/dev/nvidia0 --bind=/dev/nvidiactl --bind=/dev/nvidia-uvm --bind=/dev/nvidia-uvm-tools --bind=/dev/nvidia-modeset
でゲストOSを起動する。--bind
は指定したホストOSのファイルをゲストにもアクセスさせるオプションである。また/etc/systemd/system/systemd-nspawn@.service
の中のDeviceAllow
の行に上記のデバイスを追加しておく。 - ゲストOSの中で
apt-get --no-install-recommends install software-properties-common; add-apt-repository ppa:graphics-drivers/ppa; apt-get update; apt-get dist-upgrade; apt-get --no-install-recommends install nvidia-utils-???
を実行する。???
の部分にはホストOSに入っているNVIDIAドライバのバージョン(例えば410)を入れる。これでゲストOSの中でnvidia-smi
を使用できる。 - 他の計算機からゲストOSに
ssh
でアクセスするにはssh -l root -p 26000 ホストOSのホスト名
でできる。ゲストOSの終了はログインしてpoweroff
またはshutdown -h now
でできる。 - ゲストOSの
/etc/hostname
の設定をやっておいたほうが便利である -
/home
にコンテナ内からアクセスさせたい場合は--bind=/home
をつければよい
ゲストOSのメモリ制限と自動起動
ホストOSの起動時にゲストOSも起動し、ゲストOSの占有実メモリ量を制限するためには以下のようにする。
-
cp /lib/systemd/system/systemd-nspawn@.service /etc/systemd/system/systemd-nspawn@container1.service
コピー先ファイル名の@の後にコンテナ名を入れてください - コピー先ファイルを編集して
ExecStart
のところから--network-veth -U
を削除し、--settings=false
に変更し、--bind=/dev/nvidia0 --bind=/dev/nvidiactl --bind=/dev/nvidia-uvm --bind=/dev/nvidia-uvm-tools
を追加する。 - '[Service]'欄のどこかに以下を追加
DeviceAllow=/dev/nvidia0 rwm DeviceAllow=/dev/nvidiactl rwm DeviceAllow=/dev/nvidia-uvm rwm DeviceAllow=/dev/nvidia-uvm-tools rwm DeviceAllow=/dev/nvidia-modeset rwm
- '[Service]'欄に
Delegate=memory pids cpu io
を追加し、MemoryHigh=3G
を追加。これでホストOSが使用する実メモリはおおよそ3GBに制限される。MemoryHighの他にもいろいろな制限をホストOSに課すことができる。MemoryHigh
の制限機能を有効にするにはcgroup v1をsystemdにマウントさせない下準備を予め実施してホストOSを起動しなおしておく必要がある。 - ここまで行ったあとに
systemctl start systemd-nspawn@container1
でメモリ占有を制限したゲストOSを開始することが可能で、ホストOS起動時にゲストOSも自動で起動するにはsystemctl enable systemd-nspawn@container1
とする。 - ゲストOSのナイス値を低いままにするには
systemd-nspawn
の起動オプションに--drop-capability=CAP_SYS_NICE
を追加した上で、[Service]
欄に
Nice=19 RestrictRealtime=true
を追加する。 -
--drop-capability
の引数にはCAP_MKNOD,CAP_SYS_TIME,CAP_SYS_BOOT
もあってもよいだろう
独立したIPアドレスをゲストOSに与える方法
systemd-nspawn --network-macvlan=インターフェース名
を使う方法
インターフェース名にはenp0s25
などの有線イーサネットを指定する。無線を指定すると動かない。ゲストOSの中に以下のファイルを作ればDHCPで自動的にIPアドレスをゲストOSが取得する。ゲストOSとホストOSの通信はなぜかできない。これは設定ミスではなくてそういうものらしいです。
network:
version: 2
renderer: networkd
ethernets:
mac-vlan1:
match:
name: mv-*
dhcp4: true
dhcp6: true
このときホストとゲストの間の通信ができない問題は、ホスト側もMACVLAN経由でインターネット接続すればよい。この設定はUbuntu標準のnetplan
ではできないようだが、systemd-networkd
を用いた設定は「仮想環境のネットワーク接続をMACVLANとMACVTAPに切り替えてみた」に説明がある
systemd-nspawn -n
を使う方法
- UbuntuではホストOS側で
systemd-networkd
が標準では起動されていないから、ゲストOSの起動前にsystemctl start systemd-networkd
などで起動しておく必要がある -
10.0.0.6
のようなIPアドレスが割り振られるから、組織内の別の計算機からアクセスできない場合も多そう
systemd-nspawn --network-ipvlan=インターフェース名
を使う方法
- インターフェースは無線でも構わない。DHCPクライアントをゲストOS内で起動してIPアドレスを取得する方法がわからないので、例えば以下のファイルをゲストOSに置いて固定IPアドレスを割り当てる。DHCPでIPアドレスを貰えない一因としてMACアドレスをホストOSとゲストOSが共有していることがあるだろう。
network:
version: 2
renderer: networkd
ethernets:
ip-vlan1:
match:
name: iv-*
dhcp4: false
dhcp6: false
addresses: [192.168.1.150/24]
gateway4: 192.168.1.1
nameservers:
addresses: [192.168.1.1]
- 上記の設定でゲストOSから例えば
apt-get update
などをできるが、ホストOSからゲストOSのIPアドレスにssh
してもつながらない