リモートのDockerで実行したGUIアプリをSSHでX11Forwardingする

権限まわりで数時間手間取ったので、それを解決したTips。

実現したいこと

SSHで接続したマシンのDockerコンテナ内でX11クライアントアプリ(e.g. xeyes)を実行して、ローカルマシンのディスプレイに表示する。 その時に、SSHポートフォワーディングを利用してローカルとリモートでX11プロトコル通信をする。

X Window System

ネットワークを通じてGUI環境を構築するためのプロトコルおよびアプリケーション。

UNIXと関わりの深い技術らしいが、専門ではないので詳しくは知らない。 初めてX11プロトコル通信に成功すると感動する(実体験)。

事前準備

ローカルマシンでX11サーバを起動しておく

Google it♡

リモートマシンのsshdでX11Forwardingを許可する

デフォルトで許可されているが、念のため/etc/ssh/sshd_configを確認する。

...
X11Forwarding yes
X11DisplayOffset 10
X11UseLocalhost yes

sshdを再起動して変更の反映

$ sudo systemctl restart sshd

ローカルマシンの環境変数DISPLAYを設定する

これを忘れていて軽く詰まった。

# DISPLAY=[端末のアドレス]:[ディスプレイ番号].[スクリーン番号]
$ export DISPLAY=localhost:0.0

X11 - DISPLAY (environment variable) | X11 | Datacadamia - Data and Co (accessed on 2022/04/30)

リモートマシンでアプリケーションを動かす

SSHでリモートマシンへ接続する

-Xオプションまたは-YオプションでX11Forwardingをssh側でいろいろ処理してくれる。 -X-Yの違いはTrustedかUnTrustedかであるそうだが、詳しくは知らない。

$ ssh -X ...
# または
$ ssh -Y ...

先ほどの設定通りならばリモートマシンの環境変数DISPLAYが次のように設定されているはず。

$ echo $DISPLAY
localhost:10.0

以降はリモートマシンでの作業である。

Dockerfile

今回使用するイメージ。 ユーザーはrootを使用する。

FROM ubuntu:latest

RUN apt-get update \
 && apt-get install -y x11-apps

USER root
CMD ["/bin/bash"]

適当なタグでビルドしておく。

$ docker build -t x11-test .

コンテナを作成する

docker-composeでも良いのだが、今回はshellスクリプトx11-test.shを書くことにする。

#! /bin/bash

docker run \
  -it \ 
  --rm \
  -e DISPLAY=$DISPLAY \
  --net host \
  --mount type=bind,src=$HOME/.Xauthority,dst=/root/.Xauthority.copy  \ # ここがポイント
  x11-test \
  /bin/bash -c " \
  cp /root/.Xauthoriy.copy /root/.Xauthority; \  # ここがポイント2
  chown root:root /root/.Xauthority; \  # ここがポイント3
  xeyes \
  "

やっていることはSSHでポートフォワーディングしているDockerホストのネットワークを利用して、コンテナ内部からX11クライアントアプリをローカルマシンのX11サーバアプリに繋いでいるだけ。

その際の認証情報として$HOME/.Xauthorityが必要であるが、これをコンテナにそのままマウントしてしまうとファイルの所有者が異なるため、x11 authentication failed for x11 forwardingと怒られてしまう。 そこで一旦/root/.Xauthority.copyへとマウントしてから/root/.Xauthorityへコピーして、ファイルの所有者を適切なものに変更している。

手間を惜しんでマウントした.Xauthorityの所有者を変更してしまうとDockerホストのファイルもroot権限になってしまい、面倒なことになる。

$ /bin/bash x11-test.sh

例の目がディスプレイに表示されれば成功。

おわりに

.Xauthorityの所有者が原因でエラーが出ていることを特定できたとき、最初に考えたのがコンテナのユーザ(uid, gid)をDockerホストのユーザと一致させる案だったが移植性を考慮した実装が面倒くさすぎて投げた。 「コピーして所有者変えたらいけるんじゃね?」と実行してみたら上手くいったのでここに記す。

記事内容を実行してみたらxeyesにおいては.Xauthorityの所有者は関係なさそうである。 自分はchromiumを実行した際にエラーにぶち当たったのでアプリケーションに依存するのかもしれない。

X Window Systemに慣れていない場合は、

  • ローカルDockerのコンテナで試してみる
  • リモートでDockerを使わずにxeyesを実行する

と別々に練習すると上手くいくかもしれない。