リモートにあるサーバで動かしている Docker コンテナ上の X アプリケーションの GUI をローカルのマシンから確認したいと思った。 そこで、Docker コンテナとローカルマシンの間で X11 Forwarding してみることにした。 やってみると意外と手間取ったので記録として残しておく。
まず、リモートの Docker ホスト (Docker コンテナを動かすマシン) の環境は次の通り。
$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=18.04 DISTRIB_CODENAME=bionic DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS" $ uname -r 4.15.0-62-generic
そして、ローカルのマシン環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.6 BuildVersion: 18G95
ローカルの環境には、あらかじめ XQuartz (macOS 向けの X Window System 実装) をインストールしておく。
$ brew cask install xquartz
今回進める作業の手順としては、次の通り。
- リモートのマシン上で SSH でログインできる Docker コンテナを動かす
- ローカルのマシンから Docker コンテナに X11 Forwarding を有効にして SSH でログインする
- Docker コンテナ上で X アプリケーションを実行する
- 実行結果をローカルマシン上で確認する
リモートのマシンに Docker をインストールする
まずは最初の手順としてリモートのマシンに Docker をインストールする。
APT のリポジトリを更新した上で、もし既にインストールされている Docker コンポーネントがあればアンインストールしておく。
$ sudo apt update $ sudo apt -y remove docker docker-engine docker.io containerd runc
続いて、Docker 関連のコンポーネントをインストールするのに必要なパッケージをインストールする。
$ sudo apt -y install \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common \ python3-pip
APT に Docker のリポジトリを登録する。
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - $ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
Docker 本体をインストールする。
$ sudo apt update $ sudo apt -y install docker-ce docker-ce-cli containerd.io
次のように docker version
コマンドでクライアントとサーバの両方が正しく出力されれば上手くいっている。
$ sudo docker version Client: Docker Engine - Community Version: 19.03.2 API version: 1.40 Go version: go1.12.8 Git commit: 6a30dfc Built: Thu Aug 29 05:29:11 2019 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 19.03.2 API version: 1.40 (minimum version 1.12) Go version: go1.12.8 Git commit: 6a30dfc Built: Thu Aug 29 05:27:45 2019 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.2.6 GitCommit: 894b81a4b802e4eb2a91d1ce216b8817763c29fb runc: Version: 1.0.0-rc8 GitCommit: 425e105d5a03fabd737a126ad93d62a9eeede87f docker-init: Version: 0.18.0 GitCommit: fec3683
続いて Docker Compose をインストールする。
$ sudo pip3 install docker-compose
こちらも、次のようにバージョン情報が出れば大丈夫。
$ docker-compose version docker-compose version 1.24.1, build 4667896 docker-py version: 3.7.3 CPython version: 3.6.8 OpenSSL version: OpenSSL 1.1.1 11 Sep 2018
X11 Forwarding が有効な SSH ログインできる Docker イメージをビルドする
Docker がインストールできたら、次はコンテナのイメージを作る。
汎用性をもたせるためにプロセスは Supervisord 経由で起動するため以下の通り設定ファイルを用意する。
$ cat << 'EOF' > supervisord.conf [supervisord] nodaemon=true [program:sshd] command=/usr/sbin/sshd -D -ddd user=root stdout_logfile=/var/log/supervisor/sshd.log redirect_stderr=true EOF
コンテナが起動するプロセスとなるシェルスクリプトを用意する。 このスクリプトの中では SSH でログインするときのユーザのパスワード設定と Supervisord の起動を行っている。
$ cat << 'EOF' > docker-entrypoint.sh #!/usr/bin/env bash set -Ceux : "Change user password" && { # check variable existence if [ ! -v PASSWORD_HASH ]; then echo "please define 'PASSWORD_HASH' environment variable" exit 1 fi usermod -p "${PASSWORD_HASH}" ${USERNAME} } : "Start supervisord" && { supervisord -c /etc/supervisord.conf } EOF
続いて肝心の Docker イメージをビルドするための Dockerfile
を用意する。
このイメージでは必要な設定を sshd に施すと共に、必要な関連パッケージをインストールしている。
ベースイメージは Docker ホストと同じ Ubuntu 18.04 LTS にした。
$ cat << 'EOF' > Dockerfile FROM ubuntu:18.04 # Non administrative username (SSH login user) ENV USERNAME=example # Use mirror repository RUN sed -i.bak -e "s%http://[^ ]\+%mirror://mirrors.ubuntu.com/mirrors.txt%g" /etc/apt/sources.list # Install prerequisite packages RUN apt update \ && apt -yq dist-upgrade \ && apt install -yq --no-install-recommends \ sudo \ xserver-xorg \ openssh-server \ supervisor \ xauth \ x11-apps # Prepare OpenSSH server RUN mkdir /var/run/sshd \ && sed -i -e "s/^#AddressFamily.*$/AddressFamily inet/" /etc/ssh/sshd_config # Setup non administrative user RUN useradd -m ${USERNAME} \ && usermod -aG sudo ${USERNAME} # Expose container ports EXPOSE 22 # Copy settings COPY supervisord.conf /etc COPY docker-entrypoint.sh /var/tmp # Boot process CMD bash /var/tmp/docker-entrypoint.sh EOF
イメージとコンテナを管理するために Docker Compose 用の設定ファイルも用意しておこう。
この設定ではコンテナに SSH でログインする用の TCP/22
ポートを、Docker ホストの 127.0.0.1:2222
にマッピングすることになる。
意図的にループバックアドレスに bind するのは、下手にインターネットから到達できないようにするため。
$ cat << 'EOF' > docker-compose.yml version: "3" services: server: build: context: . environment: - PASSWORD_HASH image: example/sshd-container ports: - "127.0.0.1:2222:22" EOF
Docker Compose 経由でイメージをビルドする。
$ sudo docker-compose build
上手くいけば次のようにイメージが登録される。
$ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE example/sshd-container latest 0c9b2af9011d 19 minutes ago 318MB <none> <none> df1f957680d7 20 minutes ago 318MB ubuntu 18.04 a2a15febcdf3 4 weeks ago 64.2MB
コンテナを起動する
先ほど docker-entrypoint.sh
の中でチェックしていた通り、コンテナの起動には SSH ログイン用のパスワードハッシュが必要になる。
例えば次のように Python などを使って生成しておく。
$ python3 -c "import crypt; print(crypt.crypt('mypasswd', crypt.METHOD_SHA512))" $6$ADfMjPqRhaGQ4r80$Pc4n29qZmdRFMJBGK1FghsmWWQZOqgjom8ia2SA5r9HeCnhASKUoToq30Q5eCh3272/UvvaVfGa0IIpGyOwbK1
パスワードをターミナルの履歴に残したくないときは次のように getpass.getpass()
を使うと良いかも。
あるいは、もっとちゃんとするならパスワード認証ではなく公開鍵を仕込むようにした方が良いと思う。
$ python3 -c "import crypt; from getpass import getpass; print(crypt.crypt(getpass(), crypt.METHOD_SHA512))"
生成したパスワードのハッシュを環境変数ごしに渡しつつコンテナを起動する。
$ export PASSWORD_HASH=$(python3 -c "import crypt; print(crypt.crypt('mypasswd', crypt.METHOD_SHA512))") $ sudo docker-compose run \ --name sshd-container \ --rm \ -e PASSWORD_HASH=${PASSWORD_HASH} \ --service-ports \ server
これでコンテナに SSH でログインするためのポートがリモートマシンの 127.0.0.1:2222
にマッピングされる。
$ ss -tlnp | grep 2222 LISTEN 0 128 127.0.0.1:2222 0.0.0.0:*
X11 Forwarding が有効な状態で SSH ログインする
ここまでの工程でコンテナにアクセスするためのポートは開いた。 ただし、bind されているアドレスが Docker ホストのループバックアドレスになっている。 セキュリティを考えてのことだけど、このままだとローカルのマシンからも疎通がない。 この点は SSH の Port Forwarding で解決する。
リモートの 2222
ポートを、ローカルの 22222
ポートに Port Forwarding で引き出してくる。
例えば今回は Vagrant を使ってリモートサーバを構築してあるので、次のようにする。
新しいターミナルを開いて Port Forwarding しよう。
$ vagrant ssh-config > ssh.config $ ssh -F ssh.config \ -L 22222:localhost:2222 \ default
これでリモートの 2222
ポートが、ローカルの 22222
越しにアクセスできるようになった。
$ lsof -i -P | grep -i listen | grep 22222 ssh 35299 amedama 5u IPv6 0x9d2c7f5e707aebd5 0t0 TCP localhost:22222 (LISTEN) ssh 35299 amedama 6u IPv4 0x9d2c7f5e74def915 0t0 TCP localhost:22222 (LISTEN)
あとはローカルの TCP/22222
に向けて X11 Forwarding を有効にしつつ SSH でログインする。
OpenSSH の実装であれば -X
をつければ X11 Forwarding が有効になる。
$ ssh -XC \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-p 22222 \
example@localhost
ログインすると、ちゃんと DISPLAY
環境変数の内容もセットされている。
$ echo $DISPLAY localhost:10.0
例えばサンプルとして xeyes
を起動してみよう。
$ xeyes
見慣れた目玉が見えるようになるはず。
もし上手くいかないときは、次のようにして Supervisord が起動した sshd のログを確認すると良い。
$ sudo docker exec -it sshd-container tail -f /var/log/supervisor/sshd.log
いじょう。