CLOVER🍀

That was when it all began.

DockerコンテナをXクライアントにして、ホスト側のXサーバーで描画を行う

これは、なにをしたくて書いたもの?

Dockerコンテナ内で動かしたアプリケーションの描画内容を、ホスト側に持ってこれないかな?と思いまして。

どうやらできそうです。

Dockerを用いたGUIアプリケーションの実行 | POSTD

試してみましょう。

環境

今回の環境は、こちらです。

$ docker version
Client: Docker Engine - Community
 Version:           20.10.2
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        2291f61
 Built:             Mon Dec 28 16:17:43 2020
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.2
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       8891c58
  Built:            Mon Dec 28 16:15:19 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Dockerfileを用意する

まずは、Dockerfileを用意します。こんな感じで作成。
Dockerfile

FROM ubuntu:20.04

ARG uid=1000
ARG gid=1000

RUN apt-get update -y && \
    apt-get install -y --no-install-recommends \
                    sudo \
                    vim \
                    curl \
                    ca-certificates \
                    && \
    apt-get clean

RUN addgroup --gid ${gid} myuser && \
    adduser --disabled-login --gecos '' --uid ${uid} --gid ${gid} myuser && \
    usermod -G sudo myuser

RUN echo "Defaults:myuser !requiretty" >> /etc/sudoers.d/myuser && \
    echo "myuser ALL=(ALL)      NOPASSWD: ALL" >> /etc/sudoers.d/myuser && \
    chmod 440 /etc/sudoers.d/myuser

USER myuser

CMD ["bash"]

ビルドします。

$ docker image build --build-arg uid=`id -u` --build-arg gid=`id -g` -t kazuhira/ubuntu:20.04 .

Dockerfileはいろいろ書いていますが、ポイントはここだけです。

RUN addgroup --gid ${gid} myuser && \
    adduser --disabled-login --gecos '' --uid ${uid} --gid ${gid} myuser && \

指定のuid、gidでユーザーを作成します。

あとは操作しやすいようにvimやcurlを入れたり、sudoを実行できるようにしました。

コンテナを起動して、ホスト側で描画を行う

では、作成したコンテナを起動します。

$ docker container run -it --rm --name ubuntu \
  -e DISPLAY=${DISPLAY} \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  kazuhira/ubuntu:20.04

この時、ホスト側の/tmp/.X11-unixディレクトリをコンテナ側にマウントします。

 -v /tmp/.X11-unix:/tmp/.X11-unix

${DISPLAY}環境変数も指定します。ホストの値を引き継ぐようにしています。

-e DISPLAY=${DISPLAY} 

コンテナ内で起動するアプリケーションとしては、geditにしましょう。インストールします。

$ sudo apt install -y gedit

インストールが完了したら、起動してみます。

$ gedit &

すると、ホスト側でアプリケーションが描画されます。

f:id:Kazuhira:20210115235507p:plain

成功しました。

どうなっているのか?

Dockerコンテナの起動時の設定を、もう1度見返します。

$ docker container run -it --rm --name ubuntu \
  -e DISPLAY=${DISPLAY} \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  kazuhira/ubuntu:20.04

ホスト側の/tmp/.X11-unixディレクトリをマウントしていました。

  -v /tmp/.X11-unix:/tmp/.X11-unix \

このディレクトリには、Xクライアント、Xサーバー間でのやり取りに使われるUNIXドメインソケットファイルが配置されています。

これをコンテナ側に持ってきたわけです。

$ ll /tmp/.X11-unix 
total 8
drwxrwxrwt 2 root   root   4096 Jan 15 22:56 ./
drwxrwxrwt 1 root   root   4096 Jan 15 23:52 ../
srwxrwxrwx 1 myuser myuser    0 Jan 15 22:56 X0=
srwxrwxr-x 1    121    125    0 Jan 15 22:55 X1024=
srwxrwxr-x 1    121    125    0 Jan 15 22:55 X1025=

そして、$DISPLAY環境変数はホスト側から引き継ぎましたが、:0を指しています。

$ printenv DISPLAY
:0

この設定だと、/tmp/.X11-unix/X0というUNIXドメインソケットファイルでXクライアント、Xサーバー間でやり取りします。

あとは、現在のユーザーでこのファイルを扱う権限が必要です。

このため、ホスト側とコンテナ側のuid、gidを合わせる目的でuid、gidをコンテナイメージ作成時に指定し

$ docker image build --build-arg uid=`id -u` --build-arg gid=`id -g` -t kazuhira/ubuntu:20.04 .

Dockerfile内でのユーザー、グループ作成時のuid、gidに反映させ、コンテナ内からUNIXドメインソケットファイルを
操作できるように調整した、というわけです。

RUN addgroup --gid ${gid} myuser && \
    adduser --disabled-login --gecos '' --uid ${uid} --gid ${gid} myuser && \

なるほどなぁ、と。