コンテナで GUI / GPU を使う
最近 Stable Diffusion 等 AI 関連のオープンソースソフトが数多く出現しており、いろいろ試してみたくなっている。 多くは Linux でも動くので魅力ではあるが、残念ながら動作環境が限定されていることが多く、普段遣いの OS 環境をそれに合わせ込むのも好ましくない。 そこで、個々のアプリ環境はコンテナで構築して分離しておくのが楽だ。
しかしながら AI 関連ソフトは GPU を使うものが多く、コンテナ上で GPU を扱ったり GUI (X アプリ) を使えるようにするには工夫が必要なのでここにメモしておく。
なお、ここでは以下のような環境を前提にしている。
- ホスト OS: Debian 11 (bullseye)
- Kernel: 5.18.16 (bullseye-backports)
- コンテナイメージベース OS: Ubuntu 18.04 or 20.04 or 22.04
他の OS 環境でもある程度参考にはなるでしょう。
LXD
LXD はコンテナといっても Kernel 以外の OS 環境を構築する軽量 VM のようなもののため単に GUI を使うだけならあまり困らない。 xrdp と任意のデスクトップパッケージをインストールすればリモートデスクトップクライアントを使って GUI は表示できる。 ここではそのようなやり方ではなく、 GPU を使ったり vulkan のようにホスト OS 上の X server にアクセスすることを想定する。
LXD の環境構築 (lxd
グループに所属する等) については他のサイトでも詳しく書かれているのでここでは触れない。
以下 Ubuntu 20.04 のコンテナを作成して動かすものとし、コンテナ名を ubuntu2004 と仮定する。
バージョンやコンテナ名については適宜読み替えて欲しい。
コンテナの作成
タグに Ubuntu のバージョン 20.04
を指定し ubuntu2004
という名のコンテナを立ち上げる
host$ lxc launch ubuntu:20.04 ubuntu2004
続いてコンテナ内で shell を起動し必要なパッケージをインストールして GUI を使える状態にする
host$ lxc exec ubuntu2004 -- bash
以下はインストールするパッケージの一例 (実際にインストールパッケージはお好きなように…)
root@ubuntu2004# apt update
root@ubuntu2004# apt install x11-apps dbus-x11
root@ubuntu2004# exit
コンテナ内ユーザとホストユーザのマッピング
Ubuntu のコンテナイメージにはデフォルトで ubuntu
というユーザが UID=1000
で定義されているので、これをホスト側のユーザ ID とマップする
host$ lxc config set ubuntu2004 raw.idmap "both ${UID} 1000"
これはホスト側で GID=UID
のユーザの GID/UID 両方をコンテナ内の UID=GID=1000
としてマップすることを示す
複数の ID をマップする必要がある場合 (例えば src
グループをコンテナ内でも継承する場合) には以下のように改行で区切って指定する
host$ echo -en "both ${UID} 1000\ngid 40 40" | lxc config set ubuntu2004 raw.idmap -
こうすることでコンテナ内でもホスト側のファイルを共用してアクセスしても問題なくなる
コンテナ内から GPU をアクセスできるようにする
GPU をコンテナ内からアクセスできるようにする方法は LXD のドキュメント に書かれている通り
host$ lxc config device add ubuntu2004 gpu gpu gid=$(getent group video | cut -d: -f3)
デフォルトでは GPU のコンテナ内 GID が 0 (root) に設定されてしまうので ubuntu ユーザでもアクセス可能な video
グループの GID を割り振る
コンテナ内からホストの X サーバにアクセスできるようにする
ホスト側の /tmp/.X11-unix/
配下のソケットをコンテナにも見えるようにすればいいのだが、実はこれが厄介。
通常であればコンテナの設定でディレクトリをマウントさせればいいが、 LXD コンテナ起動時のマウント後にコンテナ内の X11 の初期化が行われることによりクリアされてしまう。
このため、ひとまずコンテナ内の別の場所にマウントさせ、実際にコンテナ内で使う際に /tmp/.X11-unix/
にシンボリックリンクさせることにする。
ここでは X11 ソケットをコンテナ内の /mnt/x11/
配下にマウントする。
host$ lxc config device add ubuntu2004 x11 disk source=/tmp/.X11-unix path=/mnt/x11
コンテナ内のシンボリックリンクについては後述
コンテナ内の ubuntu ユーザのログインスクリプトを変更
ログインスクリプトを一部変更することで GUI が使えるようにする。 一旦以下のように ubuntu ユーザでログインしてログインスクリプトを書き換える。
host$ lxc exec ubuntu2004 -- sudo --user=ubuntu --login bash
スクリプトの意図は:
- 同じコンテナを GUI を使わないで使用する場合には余計なことをしない
- ホスト側の X11 ソケットの番号は固定とは限らないため config に埋め込まずに shell 起動時に DISPLAY 環境変数で指定する
- dbus を使う GUI アプリも多いので dbus が扱えるようにしておく
~/.profile
...
if [ -r "$HOME/.profile-x11" ]; then
. "$HOME/.profile-x11"
fi
次項の ~/.profile-x11
ファイルがあればそれを読み込むようにする
~/.profile-x11
[ -n "$DISPLAY" -a -z "$XDG_RUNTIME_DIR" ] || return
setup_x11 ()
{
local xsock_dir x_id x
xsock_dir=/mnt/x11
[ -d $xsock_dir ] || return
x_id=
x=${DISPLAY#:}
for f in ${xsock_dir}/X*; do
if [ -O "$f" -a "${f##${xsock_dir}/X}" = "$x" ]; then
x_id=$x
fi
done
[ -n "$x_id" ] || return
if [ ! -h /tmp/.X11-unix/X${x_id} ]; then
ln -s ${xsock_dir}/X${x_id} /tmp/.X11-unix/
fi
export XDG_RUNTIME_DIR=/run/user/${UID}
if [ ! -d ${XDG_RUNTIME_DIR} ]; then
sudo mkdir -p ${XDG_RUNTIME_DIR}
sudo chown ${UID}:$(id -g) ${XDG_RUNTIME_DIR}
sudo chmod 700 ${XDG_RUNTIME_DIR}
fi
exec dbus-launch --exit-with-session bash
}
setup_x11
unset setup_x11
ちょっと複雑なため少し解説
DISPLAY
環境変数が設定されていない場合や既にこの処理が済んでいる場合には何もせず抜ける/mnt/x11/
配下にあるソケットの中からDISPLAY
環境変数の番号と一致するものを探してそれがubuntu
の UID と同じ場合に/tmp/.X11-unix/
配下にシンボリックリンクする- dbus 環境を設定してサブシェルを起動する
- 何らかのエラーでサブシェルを起動できなかった場合は
setup_x11
関数定義自体を消して後始末をする
コンテナへのログイン
LXD コンテナに ubuntu ユーザでログインする
host$ lxc exec ubuntu2004 -- sh -c "DISPLAY=$DISPLAY exec sudo --user=ubuntu --login bash"
このシェルから X アプリを実行すればホスト上のデスクトップに表示されるはず
ubuntu@ubuntu2004$ xeyes &
参考
- LXDのコンテナからGPUを利用する
- LXDコンテナとホストの間でファイルを共有する方法
- LXD で作る仮想化 GUI 環境 - Ubuntu 22.04 LTS 版
- Bindmount for .X11-unix only works when done if container is running
Docker
Docker は LXD と違ってアプリケーションコンテナであり、基本的には単一のアプリを実行するための環境を作成する。 このため systemd や init のような初期化ルーチンが走るわけではないので LXD のようなすれ違い (X11 ソケットの初期化等) が起こらない点は楽になる。 しかしながら逆に systemd や init により自動的に起動される初期化ルーチンや daemon が無いため、必要なものは全て自前で整えなければいけない。
正直なところ GUI を動かすには LXD の方が確実だが LXD には面倒なところがあり あまり多くのバリエーションのコンテナを用意するのは難しい。 Docker でも GPU を扱うのは比較的容易であり、ある程度なら GUI も動かせるのでその方法を示す。
元々 AMD の ROCm を動かす環境を用意しその上で Upscayl のような GUI も動かせたら…という目論見で試行錯誤したもの [1] なので、その詳細は Git Hub の ROCm-docker の方を参照されたい。 ここではエッセンスのみ示す。 基本線は LXD で示したこととほぼ同じ。 ただし LXD は UID/GID のマッピングを行うのに対し Docker では ID マッピングせずにそのままコンテナに見せている点に注意。
【2024/07/01 追記】
このあたりのことがオフィシャルドキュメントの Running ROCm Docker containers にも書かれるようになった。
ただし X サーバへのアクセスについては触れられていないので以下の内容はまだ有意義。
Dockerfile
Docker コンテナイメージを build するための Dockerfile を用意する
FROM ubuntu:20.04
ARG UID
ARG RENDER_GID
RUN UID_ADD=; [ -z "$UID" ] || UID_ADD="-u $UID"; \
if [ -z "$RENDER_GID" ]; then \
useradd --create-home $UID_ADD -G sudo,video --shell /bin/bash docker-user; \
else \
groupadd -g $RENDER_GID render; \
useradd --create-home $UID_ADD -G sudo,video,render --shell /bin/bash docker-user; \
fi
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends apt-utils \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
x11-apps \
dbus-x11
RUN apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY start-dbus.sh /usr/local/bin
RUN chmod +x /usr/local/bin/start-dbus.sh
USER docker-user
WORKDIR /home/docker-user
CMD ["/bin/sh", "/usr/local/bin/start-dbus.sh" ]
- ホスト OS のユーザと同じ UID のユーザを作成する
- ホスト OS で
/dev/dri/render*
デバイスのグループがrender
である場合には同じ GID のrender
グループを作成し、前述で作成したユーザもそのグループに入れる
オリジナルの ROCm-docker はコンテナの OS が Ubuntu 20.04 以降の場合にrender
グループを決め打ちの GID で設定しているが、実際にはホスト OS 側のデバイスのパーミッションがそのまま見えるためホスト側の GID を継承する必要がある - コンテナイメージに含めるパッケージはご随意に…
- スタートアップスクリプトを
/usr/local/bin
配下にコピーし、コンテナ起動時には前述で作成したユーザ権限でそのスクリプトを実行するようにする
スタートアップスクリプト
LXD のログインスクリプトと同じような処理をする。
LXD と違うのは /tmp/.X11-unix/
配下の X11 ソケットは起動時には使える状態になっているため特別な対処が不要という点。
#!/bin/sh
UID=${UID:-$(id -u)}
GID=$(id -g)
export XDG_RUNTIME_DIR=/run/user/${UID}
[ -d ${XDG_RUNTIME_DIR} ] || sudo mkdir -p ${XDG_RUNTIME_DIR} \
&& sudo chown ${UID}:${GID} ${XDG_RUNTIME_DIR} \
&& sudo chmod 700 ${XDG_RUNTIME_DIR}
exec dbus-launch --exit-with-session bash -l
docker-compose.yml
docker コマンドに引数を指定して build / 実行することも可能だが docker-compose を使うことで面倒な引数を指定せずに定型処理ができる
version: '3'
services:
xshell:
image: ubuntu-20.04-xshell
build:
context: .
dockerfile: Dockerfile
args:
UID: ${UID}
RENDER_GID: ${RENDER_GID}
environment:
- DISPLAY
cap_add:
- SYS_ADMIN
security_opt:
- apparmor:unconfined
devices:
- /dev/kfd
- /dev/dri
- /dev/fuse
tmpfs:
- /tmp
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix
- ~:/home/docker-user/host-home
- 前述の Dockerfile を使って build する
UID
やRENDER_GID
はホスト側の値を環境変数で渡す- GUI / GPU を扱う場合に必要なセキュリティ関連の指定 (
cap_add
/security_opt
) をする - ホスト側の X11 や GPU へのアクセスに必要なデバイスやディレクトリをコンテナから見えるようにする
.env
build の際に引き渡す環境変数を以下のようにして .env
ファイルに格納する
$ cat >.env <<EOF
UID=${UID:-$(id -u)}
RENDER_GID=$(getent group render | cut --delimiter ':' --fields 3)
EOF
build
$ docker-compose build xshell
実行
$ docker-compose run --rm xshell
このシェルから X アプリを実行すればホスト上のデスクトップに表示されるはず
docker-user@xxxxxxxxxxxx$ xeyes &
参考
- DockerでGUIを表示するときの仕組みについて
- Dockerコンテナ上でGUIアプリを表示する
- Docker privileged オプションについて
- (docker) XRDP + Ubuntu image with Japanese input
- The mount --bind command fail in Docker container on Ubuntu
[1]: 実際には Upscayl はある程度までは動くが完全には動いていない
« 仮想環境のイメージ格納場所の移動 | トップページ | Debian Linux を Secure Boot 環境で使う »
「パソコン・インターネット」カテゴリの記事
- FreeDOS で SATA 接続 CD/DVD が利用可能に(2023.03.22)
- Debian Linux を Secure Boot 環境で使う(2023.03.06)
- コンテナで GUI / GPU を使う(2022.10.19)
- 仮想環境のイメージ格納場所の移動(2022.09.10)
- PC ケースを変えてみたが…(2021.10.31)
「Linux」カテゴリの記事
- Debian Linux を Secure Boot 環境で使う(2023.03.06)
- コンテナで GUI / GPU を使う(2022.10.19)
- 仮想環境のイメージ格納場所の移動(2022.09.10)
- Kodi の設定(2019.12.02)
- メインマシンを AMD Ryzen 5 2400G に(2019.06.24)
« 仮想環境のイメージ格納場所の移動 | トップページ | Debian Linux を Secure Boot 環境で使う »
コメント