dockerコンテナの中で立ち上げたGUIアプリをmacに表示してみる

# この記事で出来ること
dockerコンテナの中でGUIアプリを立ち上げて、mac(※1)に表示する
具体的には、VSCodeを立ち上げれるようになる

(※1 macに限らずとも[X Window System](https://eng-entrance.com/linux-gui-x11)がインストールされている環境ならどこでも使えるはず)


# 背景
Dockerを使って開発するのがかなり一般的になってきました。かく言う私も、仕事や趣味でDockerばかり使っています。
Dockerfileさえ書いてしまえば、いつでも同じ環境を構築できるのでとても便利です。




ただDockerを使うことでやりづらくなる点もあります。それが**デバッグ作業**です。


私はIDEのデバッガーをよく使います。ブレークポイントを自由自在に置くことができ、現時点での変数やコールスタックが一覧で表示されるのでとても捗ります。ターミナルで行うデバッグも良いですが、使い勝手の良さでは圧倒的にIDEのデバッガーが優れていると思います。


私の知る限り、IDEのデバッガーはIDEがインストールされているホストマシンの環境をまず見に行きます。そのため、初期設定のままではDockerコンテナの中で立ち上げたプロセスを知る術がなく、デバッグのしようがありません。


もちろん、きちんと設定をすればDockeコンテナのプロセスのデバッグをすることができます。
が、設定、めんどくさいときないですか？
設定ができない言語やIDEだったらもう諦めるしかありません。


そんな時、**Dockerコンテナの中でエディタを立ち上げちゃいましょう**


# 方針
`ssh`の`X11 Forwarding`を使います。


Dockerコンテナ内で立ち上げたGUIアプリが、コンテナ内の`X Window System`ではなく、ホストコンピューターの`X Window System`に対して描画指示を送るようにします。


# 手順
今回は手始めにVSCodeをDockerコンテナの中で立ち上げたいと思います。

※以下、ローカル開発することを前提としたコードです。セキュリティの保証はできかねますので十分注意してください。

## 準備

macには標準でX Window Systemがインストールされていないみたいです。 
なので、インストールします。


```bash
brew update
brew cask install xquartz
```
UbuntuなどのLinuxにはおそらくデフォルトでインストールされてるんじゃないかなと思います。


dockerのバージョンはこちら

```
$ docker -v
Docker version 19.03.5, build 633a0ea
```



## 1. SSHの公開鍵と秘密鍵を作成
Dockerコンテナとssh通信を行うのでそこで使う鍵を作ります。
今回は `id_rsa_for_docker_ssh`と言う名前で作ります。

```bash
$ ssh-keygen -t rsa -b 4096
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/machisuke/.ssh/id_rsa): /Users/machisuke/.ssh/id_rsa_for_docker_ssh
```


## 2. Dockerfileを作る

sshやX11 Forwarding、VSCodeの起動に必要なライブラリを入れます。
他のエディタやGUIツールを使う場合は、必要に応じて書き換えてください。 
Dockerfileは[こちら](https://qiita.com/FaithnhMaster/items/a7e9aea842b9eb7392e2)を参考にさせていただきました。

`machisuke`と言う名前でユーザーを追加しており、パスワードは`password`としておきました。ここも適宜変えてください。

```Dockerfile:Dockerfile

FROM ubuntu:18.04

RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates\
        sudo \
        openssh-server \
        wget \
        libnotify4 \
        gnupg \
        libxkbfile1 \
        libsecret-1-0 \
        libgtk-3-0 \
        libxss1 \
        libnss3 \
        libx11-xcb-dev \
        libxtst6 \
        libasound2 \
        xauth \
        iputils-ping \
    && \
    apt-get clean  && \
    rm -rf /var/lib/apt/lists/*

### User settings ###
ARG NB_USER=machisuke
ENV NB_USER ${NB_USER}
# HOST_ID should be same as host's user id.
ARG HOST_UID
ENV NB_UID ${HOST_UID}

# Add user and set password. (ここではユーザーのパスワードは"password"とします)
RUN useradd -m -G sudo -s /bin/bash -u $NB_UID $NB_USER && \
    echo "${NB_USER}:password" | chpasswd

RUN echo "Defaults visiblepw"             >> /etc/sudoers
RUN echo "${NB_USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
### End ###

### SSHでDockerにアクセスするための設定
ARG AUTHORIZED_KEYS
RUN ( echo "#!/bin/bash"; \
      echo "echo ${AUTHORIZED_KEYS} > /home/${NB_USER}/.ssh/authorized_keys"; \
      echo "chown -R ${NB_USER}:${NB_USER} /home/${NB_USER}"; \
      echo "chmod 600 /home/${NB_USER}/.ssh/authorized_keys"; \
      echo "service ssh start"; \
      echo "tail -f /dev/null"; ) > /home/${NB_USER}/entrypoint.sh && \
    chmod +x /home/${NB_USER}/entrypoint.sh && \
    sed -i.bak 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
    echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config && \
    echo "X11UseLocalhost no" >> /etc/ssh/sshd_config && \
    mkdir /home/${NB_USER}/.ssh && chmod 700 /home/${NB_USER}/.ssh && \
    ( echo "Host *"; \
      echo "    StrictHostKeyChecking no"; \
      echo "    UserKnownHostsFile=/dev/null"; ) > /home/${NB_USER}/.ssh/config
# Disable annoying warning about sudo
RUN touch ~/.sudo_as_admin_successful

# ユーザーの切り替え
USER $NB_USER
ENV LANG=ja_JP.UTF-8


# VSCodeのダウンロードとインストール
RUN cd /home/${NB_USER} && \
    wget --no-verbose https://go.microsoft.com/fwlink/?LinkID=760868 -O vscode.deb && \
    sudo apt-get install -y --no-install-recommends ./vscode.deb

EXPOSE 22

# SSHサーバーを起動
CMD ["sudo","bash","-c", "bash ~/entrypoint.sh"]

```



## 3. 各種スクリプトの用意
作成したスクリプトは全て同じディレクトリにおいてください。

- Dockerイメージビルド用

公開鍵を変数として渡しています。

```bash:docker_build.sh
#!/usr/bin/env bash
PUB_KEY=$(cat ~/.ssh/id_rsa_for_docker_ssh.pub)
IMAGE_NAME="docker_ssh"
docker build \
    --build-arg  HOST_UID="$(id -u)" \
    --build-arg  AUTHORIZED_KEYS="${PUB_KEY}" \
    -t ${IMAGE_NAME} \
    .
```

- Dockerコンテナ起動用

このコマンドを実行すれば、buildも同時に走るようにします。
sshで使うコンテナの`22`番ポートをホストマシンの`8022`番にマッピングします。


```bash:docker_run.sh
#!/usr/bin/env bash
IMAGE_NAME="docker_ssh"
CONTAINER_NAME=${IMAGE_NAME}
bash ./docker_build.sh
docker run --name=${CONTAINER_NAME} --rm -it -p 8022:22 ${IMAGE_NAME}
```

- X11 forwarding用

ファイルにするまでもないですが、覚えるのも大変なので。
`docker_run.sh`により、Dockerコンテナの`22`番ポートはlocalhostの`8022`番にマッピングされています。

```bash:docker_connect.sh
#!/usr/bin/env bash
ssh -p 8022 -X -o StrictHostKeyChecking=no machisuke@localhost
```


## 4. Dockerコンテナの起動

```bash:
$ bash ./docker_run.sh
Sending build context to Docker daemon  66.56kB
Step 1/17 : FROM ubuntu:18.04
 ---> 775349758637
Step 2/17 : RUN apt-get update && apt-get install -y --no-install-recommends         sudo         openssh-server         wget         libnotify4         gnupg         libxkbfile1         libsecret-1-0         libgtk-3-0         libxss1         libnss3         libx11-xcb-dev         libxtst6         libasound2         ca-certificates        &&     apt-get clean  &&     rm -rf /var/lib/apt/lists/*
 ---> Using cache
 ---> 9d3d8a65d0d7

(省略)

Step 16/17 : EXPOSE 22
 ---> Using cache
 ---> 3d096b9c1108
Step 17/17 : CMD ["sudo","bash","-c", "bash ~/entrypoint.sh"]
 ---> Using cache
 ---> c9e835fc358a
Successfully built c9e835fc358a
Successfully tagged docker_ssh:latest
 * Starting OpenBSD Secure Shell server sshd               [ OK ]
```

## 5. XQuartsのシェルからX11 forwardingとvscodeの起動

- XQuartsを起動して、メニューバーからターミナルを開きます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/faecd89d-8980-512b-5894-8fa884644a42.png" width=50%>


- docker_connect.shを実行
パスワードはDockerfileに設定した `password`です。

```shell:XQuartsのターミナル上からvscodeを起動
$ ./docker_connect.sh 
machisuke@localhost's password: 
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.9.184-linuxkit x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Last login: Wed Dec  4 07:14:55 2019 from 172.17.0.1
machisuke@1ff26b7a58a3:~$ code .
```


- 無事起動できました！お疲れ様です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/5093c25a-7b6e-4529-dabc-379603c4d247.png)


# 終わりに

この記事では、Dockerコンテナの中でGUIアプリを立ち上げるための最低限必要なことを共有させていただきました。みなさんのデバッグ作業の助けになっていれば、幸いです。

もちろん、エディタだけでなく、どんなGUIアプリもこの方法で表示することができると思いますので、興味のある方は挑戦してみてください。


この記事を参考に、Dockerコンテナ内でエディタを起動しようと思っている人は、下記の点に注意していただければと思います。

- コンテナを起動する度にエディタの設定がリセットされてしまう。
    - プラグインなど、エディタの設定を恒久的に維持するためには改修が必要
- デバッガーによっては追加の設定が必要
    - C++のデバッグ環境の構築は大変でした。（私はClionを使ってました）