リモートにあるサーバで動かしている 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
いじょう。
参考
blog.n-z.jp