DockerでGUIを表示するときの仕組みについて


はじめに

DockerでGUIを表示したいとき、何が起きているのか理解したかったのでメモを作りました。参考にしたのはROSのチュートリアルですが、それ以外のコンテナでも同様だと思います。

ちなみにVNCとブラウザでGUIを表示する方法がお手軽なのでおすすめです。が、ここではX window systemを使う場合の挙動について記します。

X window system

UNIXライクなOSでよく使われているウィンドウシステム。サーバーとクライアントから構成され、Xプロトコルを用いて通信する。1987年からプロトコルのバージョンが11なのでシステム指してをX11、あるいは単にXとも呼ぶ。Xの実装がXorgで、多くのLinuxディストリビューションで用いられている。

Xサーバーはディスプレイ、マウス、キーボードなどの入出力装置を制御する。もともとX自体、ネットワーク越しに動くプログラムのウィンドウを手元のディスプレイで表示・操作することを意図して作られており、Xサーバーがローカル、Xクライアントがリモートのイメージ。もちろんXクライアントがローカルにあっても動く。

参考:X Window System の仕組みと設定

DockerでGUIを表示する仕組み

  • ソケット
    DockerでGUIを表示するには、Dockerコンテナ内のXクライアントがホストのXサーバーと通信する必要がある。XサーバとXクライアントが同じコンピュータにある場合、これには通常UNIX-domain socketが使われる。ソケットファイルは/tmp/.X11-unix以下にあり、アクセスすることでXサーバーとの通信が可能になる。Dockerコンテナ起動時に/tmp/.X11-unixをマウントすればコンテナ内からアクセスできる。

  • 環境変数DISPLAY
    DISPLAYでXクライアントが接続するXサーバーを指定する。フォーマットはhostname:displaynumber.screennumber。例えばlocalhost:0.0など。ただしhostnameとscreennumberは省略できるのでこの場合:0でも良い。docker runに--env="DISPLAY"を追加すればホストの環境変数がコンテナ内に反映される。

簡単な方法

ではdocker runで/tmp/.X11-unixDISPLAYを設定すればいいのかというとそれだけではなく、Xサーバーと通信するには認証が必要。通常rootユーザーからXサーバーにアクセスはできないが、以下をホストで実行すると可能になる。ただしこれはセキュリティ的に推奨されない。

$ xhost +local:root

もう少し安全な方法

DockerコンテナのUIDとGIDをホストと同じにすればアクセスできる。例えば次のように--userオプションを設定する。

$ docker run -it --rm \
    --user=$(id -u $USER):$(id -g $USER) \
    --env="DISPLAY" \
    --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
    osrf/ros:melodic-desktop-full

ただし、root以外のユーザーが設定されていないDockerイメージではI have no name!になってしまう。以下のように必要なディレクトリをマウントすると、コンテナ内でホストと同じユーザー名にできる。

$ docker run -it \
    --user=$(id -u $USER):$(id -g $USER) \
    --env="DISPLAY" \
    --volume="/etc/group:/etc/group:ro" \
    --volume="/etc/passwd:/etc/passwd:ro" \
    --volume="/etc/shadow:/etc/shadow:ro" \
    --volume="/etc/sudoers.d:/etc/sudoers.d:ro" \
    --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
    osrf/ros:melodic-desktop-full

隔離する方法

Dockerイメージにroot以外のユーザーがある場合、UIDとGIDがホストと同じに設定してあれば良いらしい。例えば次のようなDockerfileを作る。

FROM osrf/ros:melodic-desktop-full

#Add new sudo user
ENV USERNAME myNewUserName
RUN useradd -m $USERNAME && \
        echo "$USERNAME:$USERNAME" | chpasswd && \
        usermod --shell /bin/bash $USERNAME && \
        usermod -aG sudo $USERNAME && \
        echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/$USERNAME && \
        chmod 0440 /etc/sudoers.d/$USERNAME && \
        # Replace 1000 with your user/group id
        usermod  --uid 1000 $USERNAME && \
        groupmod --gid 1000 $USERNAME

次のコマンドでDockerfileをビルドし、起動すればGUIが表示できるコンテナになる。(参考にしたチュートリアルではX authentication fileを作ってマウントする必要があるとのことだったが、私の環境ではなしで動いた)

$ docker build -t hoge .
$ docker run -it --rm \
    --env="DISPLAY" \
    --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
    -user="myNewUserName" \
    hoge

参考
- Using GUI's with Docker
- Dockerコンテナの中でGUIアプリケーションを起動させる

おわりに

dockerとGUIについての理解が少し深まりました。ただ、X authentication周りは触らずに動いてしまったので少しモヤッとしていますが...