X11を用いたLinux用バイナリファイルをDocker for Macを用いてMac上で楽に動かせるようにする

# はじめに
発端は友人から大学院の授業で使う環境の構築で困ったから手伝ってくれって言われた事でした。
見てみると、ubuntu上で教授が用意したバイナリファイルを実行できるようにできれば良さそうでした。
Windowsは環境構築の方法が解説されて、Macは自力で頑張ってと言われたそうです😇
Dockerを使えば実現できそうなのでやってみました。

# リポジトリ
https://github.com/taka011002/docker-ubuntu-x11

今回使用したバイナリファイルは2次配布が禁止であった為リポジトリには含めていません。各自、実行したいバイナリファイルを用意してください。

# 環境
macOS Catalina: 10.15.4
Docker: 19.03.8
docker-compose 1.25.5
XQuartz 2.7.11 (xorg-server 1.18.4)

X11クライアントを各自変えてあげればmacOS以外でも動作すると思います。

# ゴール
下記のコマンドで簡単に実行できる様にする。

```
$ make up
$ make #{実行したいバイナリファイル名}
```

# 手順
## 0. XQuartzを導入する
macにXQuartzを未導入の場合は導入してください。
XQuartzを用いる事でMacでX11アプリケーションを動作させる事ができる様になります。
brewを導入済みの場合は下記コマンドでXQuartzを導入できると思います。

```
$ brew cask install xquartz
```

導入後は再起動かユーザーのログアウトを行わないとXQuartzの設定の反映が行われないので注意してください。

## 1. XQuartzのネットワーク・クライアントからの接続を許可する
XQuartzを起動できる様にできたら、XQuartzを起動し、環境設定から「ネットワーク・クライアントからの接続を許可」にチェックを入れ、XQuartzを再起動してください。

## 2. Dockerfileを記述する
バイナリファイルを実行する環境のDockerfileを記述しています。
実行したいバイナリファイルによって各自変更してください。
x11関連のライブラリを`apt-get install`する際にタイムゾーンの設定等出てきてしまってビルドが進まなくなってしまった為、タイムゾーンを設定し、`ENV DEBIAN_FRONTEND noninteractive`にて他の選択項目が出てきてしまっても進む様にしています。
今回はcommandsディレクトリにバイナリファイル(複数可)を配置する事を想定しています。

```dockerfile:Dockerfile

FROM ubuntu:20.04

WORKDIR usr/workdir

# TZを設定する
ENV TZ Asia/Tokyo
RUN apt-get update \
    && apt-get install -y tzdata \
    && rm -rf /var/lib/apt/lists/* \
    && echo "${TZ}" > /etc/timezone \
    && rm /etc/localtime \
    && ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
    && dpkg-reconfigure -f noninteractive tzdata

# 必要なライブラリのインストール
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update \
    && apt-get install build-essential x11-apps x11-utils x11-xserver-utils dbus-x11 -y \
    && apt-get install gedit -y \
    && apt-get install libgfortran4 -y

COPY commands/ ./commands
RUN chmod +x ./commands
ENV PATH /usr/workdir/commands:$PATH
```

この時点で実行できるといえばできるはずです。
参考:[Docker for Mac で X11 アプリケーションを動かす](https://qiita.com/hoto17296/items/bdb2ab24bc32b6b7f360) 

毎回長いコマンド打つのはめんどくさい、、、という気持ちの方は次の手順へ進んで下さい。

## 3. docker-coomposeを記述する
コンテナ側にホスト側のX11クライアントの情報を渡してあげる必要があり、コマンドを毎回打つのが面倒な為記述しました。
また、ホスト側srcディレクトリをコンテナ側/usr/workdir/srcディレクトリにマウントする様に記述しています。

```yaml:docker-compose.yml
version: '3.7'
services:
  app:
    build: .
    volumes:
      - ./src:/usr/workdir/src
    env_file:
      - .env
    tty: true
    stdin_open: true
```

docker-compose.ymlから参照している.envは下記です。
この環境変数DISPLAYをもとにコンテナ内からXQuartzへアクセスしにいきます。

```:.env
DISPLAY=host.docker.internal:0
```

[こちら](https://gist.github.com/paul-krohn/e45f96181b1cf5e536325d1bdee6c949)の記事を参考に記述致しました。
host.docker.internalの実態としてはHost(Mac)側のhostnameを取得してくるそうです。
ここら辺の名前解決はDockerのバージョンによって推奨が違う為確認してください。

## 4. Makefileを記述する
毎回docker-composeコマンドを記述するのが面倒な為、Makefileを定義してあげてmakeコマンドで実行できる様にしました。
基本的にはdocker-composeをmakeで実行できるようにしています。
ポイントとしては、upで実行する際に自動的にXquartzを起動し、`xhost +$(hostname)`にてホスト(Mac)のhostnameでXquartzへアクセスできる様にしてコンテナ側との整合性を合わせています。
また、xhostでのアクセスはセキュリティリスクもある為気になる方は他の方法で認証してください。

```:Makefile
build:
	docker-compose build --no-cache

up:
	open -a Xquartz
	xhost +$(hostname)
	docker-compose up -d

down:
	docker-compose down

restart:
	make down
	make up

destroy:
	docker-compose down --rmi all --volumes
	
destroy-volumes:
	docker-compose down --volumes

ps:
	docker-compose ps

bash:
	docker-compose exec app bash

xeyes:
	docker-compose exec app bash -c 'xeyes'

#{実行したいバイナリファイル名}:
	docker-compose exec app bash -c '#{実行したいバイナリファイル名}'
```

# 4. 実行する

下記コマンドでdockerイメージをビルドします

```
$ make build
```

buildに成功すれば下記コマンドでdockerコンテナを起動できるはずです。

```
$ make up
```

起動できたら、コンテナ側からホスト(Mac)のXQuartzへアクセスできるかxeyesを用いてテストしてみます

```
$ make xeyes
```
このような目玉が表示されればコンテナ側からホスト(Mac)のXQuartzへアクセスできています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301673/c71d1aad-b506-1635-97f8-4212e51b37a6.png)

後は実行したいバイナリファイルを各自配置し

```
$ make #{実行したいバイナリファイル名}
```

上記コマンドを下記の様に実行してください

```
$ make binary
```

また、下記コマンドを用いてコンテナへ入ることもできます。

```
$ make bash
```


# まとめ
Dockerを用いればX11を用いたLinux用バイナリファイルもMac上のプログラムを動かすかの様に簡単に動作させる事ができた！