DockerでポータブルなLinux開発環境(GUI付き)を構築する

# やってみたいこと
+ javaとかpythonとかtexとかの開発環境をLinuxベースにしたい（Office周りとか普段の作業はWindowsで）
  + CUIだけじゃなく，GUIも含む開発環境をコンテナベースで利用したい 
+ それぞれの開発環境は独立に管理したい
  + jdkのバージョンをUPしたいとか，pythonは2と3を別々に作りたいとか，texは日本語用と英語用を作りたいとかの状況に個別に対応したい
+ 編集対象のファイルはホスト（Windows）で一括管理したい
 + バックアップをまとめて取りたい
 + 特定のファイルに複数のコンテナからアクセスできるようにしたい
+ 編集対象のファイルを持ち歩くだけで，開発環境はDockerさえあればどこでも動かせるようにしたい

# やってみること
 + Docker for Windowsを使って個別の開発環境を導入したLinuxコンテナを立ち上げる
 + Xをコンテナから飛ばしてWindows側で表示し，開発等ができる環境を構築する
 + X2Goを利用してubuntuのデスクトップを飛ばし，セッションを継続的に維持できるようにする（一時的に切断して，再接続すると前の作業を継続できる）
 + ホスト側のディレクトリをコンテナでマウントして編集できるようにする
  + とりあえずマウントできるようにはなったが，マウントしたディレクトリ内のファイルがホスト側（Windows側）のファイルシステムの影響を強く受けるので，今後権限等で問題がおきる可能性あり．ほかに良い方法がないか検討中（rsyncやlsyncdでマウントした箇所と作業ディレクトリを同期するとか，sshfsとかでWindowsからコンテナ内のディレクトリをマウントしてそれをバックアップするとか）

# ホスト側環境
+ Windows10
+ Docker for windows導入済み(1.13.0-rc4-beta34(9562))
+ [Mobaxterm(9.4 Portable edition)](http://mobaxterm.mobatek.net/download-home-edition.html)
 + X serverとして利用する

# とりあえずXが動くかテスト
## ubuntuのコンテナ作成

とりあえずXをssh経由で利用する方向で考えるということで，試行開始．Xを使うのでディストリビューションにubuntuを採用．本家サイトの[opensshを導入しているDockerfile](https://docs.docker.com/engine/examples/running_ssh_service/)をちょっとだけ変更してDockerfileを作成してみた．

```docker:Dockerfile
FROM ubuntu:16.10
MAINTAINER IGAKI

RUN apt-get update && apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:hoge' | chpasswd
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd

# For testing X11 forward
RUN apt-get install -y x11-apps

EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

```
hogeはrootのログインパスワードなので適当に変更すること．x11-appsはxeyesとかxclockなどの簡単なXアプリの集合．Xをちゃんとホストで実行できるか確認するために導入．

```docker:docker-compose.yml
version: '2'
services:
  ubuntux:
    build: .
    image: "igaki/ubuntux"
    container_name: "ubuntux"
    ports:
     - "22000:22"
```
[docker-composeのver2の様式](https://docs.docker.com/compose/compose-file/#/upgrading)で記述．
Dockerが動いているVMのIPの22000ポートをコンテナの22番にフォワードしている．なお，コンテナ起動時にIP Helper絡みのエラーが発生した．詳細は本ページ末尾．

上の2つのファイルを用意して`docker-compose up`したあと，ちゃんと起動してれば`docker ps`でコンテナが起動したことが確認できる．

## Xをコンテナから飛ばしてWindows側で表示する（とりあえずxeyes)

コンテナ起動後，MobaxtermからSSH経由で接続し，同時にX11をフォワードしてホスト側で表示する．Mobaxtermの設定はこちらのサイト「[【学習基盤としてのDocker】Dockerでとっかかろう](http://qiita.com/fireowl11/items/b96a4c2308a6a2c72d0d)」そのまま参考にして下記のような感じに設定．
![image](https://qiita-image-store.s3.amazonaws.com/0/31825/03858d04-d296-71cc-dedc-b2afc667d607.png)
重要なのは，Remote hostに「localhost」をusernameにDockerfileで指定したものを記述し，「Advanced SSH settings」で「X11-Forwarding」にチェックが入っていること．設定が終わったらOKをクリックし，パスワードを入力してログインする．

ここまでうまくいっていれば，SSHでログインしてから`xeyes`と入力・実行すると下記のようなアプリが表示される．
![image](https://qiita-image-store.s3.amazonaws.com/0/31825/2d8c7397-67b7-2307-babe-235ab7d9e874.png)

# AtomをインストールしてX転送で動かしてみる

「[atomをUbuntuにPPA経由でインストールする](http://qiita.com/sudix/items/4e4e1bed2f9cf257e692)」によると，PPA経由でインストールできるらしい．ただし，その前にはまず`add-apt-repository`が使えるようにしないといけない．

ということで，「[Ubuntu 14.04 で add-apt-repository が無いと言われた](http://loumo.jp/wp/archive/20150626000042/)」を参考にatomのインストールを実施．インストール後に，libxss1とlibasound2の2つの共有ライブラリが無いとか言われたので追加してみる．ここまでやったDockerfileが以下．

```docker:Dockerfile
FROM ubuntu:16.10
MAINTAINER IGAKI

RUN apt-get update && apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:hoge' | chpasswd
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd

# For testing X11 forward
RUN apt-get install -y x11-apps

# Install Atom editor
RUN apt-get install -y software-properties-common
RUN add-apt-repository -y ppa:webupd8team/atom
RUN apt-get update
RUN apt-get install -y atom
RUN apt-get install -y libxss1 libasound2

EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
```

`docker-compose.yml`は上で示したものと同じ．`docker-compose up`を実行してコンテナを起動してから，mobaxtermでログインし，`atom`と入力・実行するとここまで正常にできていれば下記のようにAtomが起動する．

![image](https://qiita-image-store.s3.amazonaws.com/0/31825/c9437b6b-e241-a7a6-db28-e13760533012.png)

# X2Goを利用してデスクトップにアクセスできるイメージを作成する

[X2Go](http://wiki.x2go.org/doku.php/start)はVNCやRDPと同様，OSのデスクトップ（GUI）にssh経由でアクセスできるようにするいわゆるリモートデスクトップツール．VNCとかでも良かったんだけど，over SSHでできるのと利用がお手軽な割にはパフォーマンスが良いとの評価を見て試してみることにした．

Dockerfileは以下のとおり．やってることを箇条書きにするとこんな感じ．

+ noninteractiveモードの設定(インストール中にユーザに聞いてこなくなる）
+ apt-get等の参照リポジトリをIIJに変更（「[apt-getの利用リポジトリを日本サーバーに変更する](http://qiita.com/fkshom/items/53de3a9b9278cd524099)」参考）
+ SSHのセットアップ
  + hogeがrootのPASSになってるので要変更
+ `dpkg-divert..`の記述はインストール時にubuntuのupstartにアクセスしに行って失敗するケースへの対策（「[Container cannot connect to Upstart #1024](https://github.com/docker/docker/issues/1024)」参考）
+ x2goからアクセスするデスクトップとしてLXDEをインストールし，そのあとX2Goサーバをインストール．
  + インストール方法は「[DockerX2go/lxde/](https://github.com/paimpozhil/DockerX2go/tree/master/lxde)」を参考．ただ，この記述だけでは正常に動作しなかったので，「[Can't connect with VPS running Ubuntu server 16 with x2go](http://askubuntu.com/questions/819793/cant-connect-with-vps-running-ubuntu-server-16-with-x2go)」を参考に`.profile`を修正した．
+ Oracle JDKとAtomについては↑と同じ．同時にAtomでプログラムを実行するパッケージ(script)とメニューを日本語化するパッケージ(japanese-menu)を導入．さらに，このままだとX2Go上で正常に動作しなかったので，「[VS Code not working on Ubuntu when connected using XRDP. #3451](https://github.com/Microsoft/vscode/issues/3451#issuecomment-217716116)」を参考にバグ修正．
+ OSの日本語化及び日本語IMEの導入．日本語化の際は「[Dockerコンテナ上にDesktop環境(IntelliJ IDEA)を構築する](http://qiita.com/hakaicode/items/79de9b24ad28070fbab2)」を参考にした．ただし，`/etc/default/rcS`についてはubuntu 16でなくなったっぽいので，削除した．
+ `docker-compose.yml`の`Volumes:`設定のところはWindowsの`C:\Users\Hoge\Docker\data`ディレクトリをubuntuの`/data`にマウントしている．

```docker:Dockerfile
FROM ubuntu:16.10
MAINTAINER IGAKI

ENV DEBIAN_FRONTEND noninteractive

#set japanese mirror repository for apt
RUN sed -i.bak -e "s%http://archive.ubuntu.com/ubuntu/%http://ftp.iij.ad.jp/pub/linux/ubuntu/archive/%g" /etc/apt/sources.list

#Install and set SSH
RUN apt-get update && apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:hoge' | chpasswd
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd

# Upstart and DBus have issues inside docker. 
RUN dpkg-divert --local --rename --add /sbin/initctl && ln -sf /bin/true /sbin/initctl

# Install lxde and add-apt-repository
RUN apt-get install -y lxde software-properties-common

#Install X2Go
RUN add-apt-repository -y ppa:x2go/stable
RUN apt-get update
RUN apt-get install x2goserver x2goserver-xsession x2golxdebindings pwgen firefox pulseaudio libcurl3 libappindicator1 -y
RUN sed -i 's/mesg n || true/tty -s \&\& mesg n/' /root/.profile

# Install Oracle JDK
RUN add-apt-repository -y ppa:webupd8team/java
RUN apt-get update
RUN echo debconf shared/accepted-oracle-license-v1-1 select true | debconf-set-selections
RUN apt-get install -y oracle-java8-installer oracle-java8-set-default

# Install Atom editor
RUN add-apt-repository -y ppa:webupd8team/atom
RUN apt-get update
RUN apt-get install -y atom
RUN apt-get install -y libxss1 libasound2

# Install atom package and bugfix
RUN apm install script
RUN apm install japanese-menu
RUN sed -i 's/BIG-REQUESTS/_IG-REQUESTS/' /usr/lib/x86_64-linux-gnu/libxcb.so.1

# Setup Japanese environment.
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && echo 'Asia/Tokyo' > /etc/timezone && date
RUN echo 'LC_ALL=ja_JP.UTF-8' > /etc/default/locale && echo 'LANG=ja_JP.UTF-8' >> /etc/default/locale &&  locale-gen ja_JP.UTF-8
RUN apt-get install -y ibus-anthy
ENV LC_CTYPE ja_JP.UTF-8

RUN apt-get clean

EXPOSE 22

CMD ["/usr/sbin/sshd", "-D"]
```

```docker:docker-compose.yml
version: '2'
services:
  ubuntu_x2:
    build: .
    image: "igaki/ubuntu_x2go"
    container_name: "ubuntu_x2"
    privileged: true
    ports:
     - "22001:22"
    volumes:
     - C:/Users/Hoge/Docker/data:/data
```

+ `docker-compose up`でコンテナが起動できたら，クライアントをインストールする．
    + [X2Go](http://wiki.x2go.org/doku.php/start)のサイトからパッケージをDLして普通にインストールすればOK．
    + ちなみに[Chocolatey](https://chocolatey.org/)(Windowsのパッケージマネージャ)をインストールしていれば，`choco install x2go`でクライアントがインストールできる．
+ X2Goクライアントで下記のようにSessionの設定を行う．
    + Hostをlocalhost，LoginとSSHのポートを`docker-compose.yml`で設定したとおりに指定すること．
    + Session typeを今回インストールした`LXDE`にすること．

![image](https://qiita-image-store.s3.amazonaws.com/0/31825/06981f50-81df-f69b-4cbe-03c81dae171c.png)

+ rootのパスワードを入力し，正常にログインできると，下記の画面が表示される．IMEを「日本語 - Anthy」にすれば日本語入力も可能．

![image](https://qiita-image-store.s3.amazonaws.com/0/31825/2648d7f0-0dc3-9f33-aadd-e6e9e12f3e88.png)

+ X2Goがセッション管理もしてくれるらしく，この画面を閉じてもDockerコンテナが動いてさえいれば，再度接続するとそれまでと同じ画面に繋げる．ちなみにコンテナをStop(`docker stop`)して再起動(`docker start`)するとセッションは維持されない（データは残っている）
+ なお，日本語入力およびセッションの維持が不要な場合はX2Goクライアント起動せずに，sshでログイン後`atom`とか入力すれば，X転送でatom editorが起動するのでそのまま利用できる．

#トラブルとその対処
## コンテナが実行できない
コンテナ作成して実行する際に，`Cannot start container.... Error starting userland proxy: listen tcp 0.0.0.0:22000...`みたいな感じのエラーをDockerがなげてきた．
いろいろ調べたらIP Helperサービスなるもの（IPv4とv6関連のサービスらしい）と競合してるという情報が見つかったので，試しに[こちらのサイト](http://d2b6.seesaa.net/article/441261720.html)に従って停止し，コンテナをbuildするところからやり直してみたら上手くいった．一応の手順は以下の通り
1.スタートボタンクリックしてから「msconfig」と入力してEnter
2.システム構成ウィンドウが開くので，サービスタブをクリックし，サービス名でソートして「IP Helper」を探し，下記のように，チェックを外してOKをしてからPCを再起動すればOK．
![image](https://qiita-image-store.s3.amazonaws.com/0/31825/eeeb3ca1-f98f-27dc-c3b1-357566ab290d.png)

## Oracle JDKがインストールできない

当初はubuntu:17.04でコンテナを作成していた．ただ，このバージョンだとppa経由でのOracle JDKインストールができないことが判明(対応するパッケージがないっぽい）．ubuntu:16.10に変更したら正常にインストールできた．

## コンテナがre-buildされない
すでに同じイメージ名がある場合，`docker-compose up`ではイメージがrebuildされなかったので，`docker-compose build`をしてから`docker-compose up`をすればいけた．

## コンテナ内でdateコマンドを実行するとJSTになっているのに，時間が9時間ずれる

コンテナはホスト側の時計を参照して時刻調整を行う．が，Windowsの場合，ホスト側の時計にUTCやJSTというフラグがデフォルトではついていないらしく，コンテナは自動的にUTCとして認識してしまうらしい．`ntpd -gq`をコンテナ実行後に走らせれば調整されるけど，ほかに方法ないかなーと思って調べていたら，「[神ツイート](https://twitter.com/i/web/status/804393902379659264)」を発見．書いてある通りに，Hyper-Vマネージャーを起動し，MobyLinuxVM(Dockerが動いているVM)の設定から「統合サービス」をクリックし，「時刻の同期」にチェックを入れればOK．
![image](https://qiita-image-store.s3.amazonaws.com/0/31825/48fa4ddb-4de3-fec1-1738-71a1b1d18cba.png)

## ホスト側のディレクトリをコンテナ内にマウントできない

Docker for WindowsのSettingで下記のようにDriveの共有設定が必要．

![image](https://qiita-image-store.s3.amazonaws.com/0/31825/fb985a85-b4de-8914-d6c8-06525566123c.png)