#機械学習名古屋 勉強会用の Dockerイメージ 解説

# はじめに

この記事は、毎月開催（2017/04/17(月) に初回開催）の [機械学習 名古屋 分科会「ゼロから作る Deep Learning 読書会＋ハンズオン」](https://machine-learning.connpass.com/event/54539/) に向けて、参加者の開発環境を統一するために作成した [Dockerイメージ](https://hub.docker.com/r/antimon2/mlnsc-dlscratch/) の説明と、作り方の解説をまとめたものです。

前半は、主に勉強会参加者向けの Dockerイメージ の使い方の説明です。
後半は、この Dockerイメージ を作るにあたって気付いた点・工夫した点等の技術解説です。

【2017/05/19 Docker イメージ更新に伴い内容を加筆・修正しています】

# Dockerイメージの使い方

## 前提

当たり前ですが [Docker](https://www.docker.com/) がインストールされている必要があります。
この記事では、Docker について・Docker のインストールについて等、深く解説しません。以下の各リンク先を参照してください。

+ [Docker Community Edition](https://www.docker.com/community-edition)（Linux / macOS / Windows10+Hyper-V）
+ [Docker Toolbox](https://docs.docker.com/toolbox/overview/)（古い Mac OSX / Windows の場合の選択肢）

## 勉強会参加者向け

まずは [勉強会用Gitリポジトリ](https://github.com/antimon2/MLNSC_2017A) を（まだ clone してなければ）clone してください。

```shell-session
$ git clone https://github.com/antimon2/MLNSC_2017A
```

clone したリポジトリのディレクトリに `cd` すると、そこに `docker run 〜` する各種スクリプトを用意してあるので、Linux/macOS の場合は `〜.sh` を、Windows の場合は `〜.bat` をそれぞれ実行すればOK。カレントディレクトリをマウントして Dockerコンテナ を実行するようになっています[^pwd_win]。
通常は `docker.run.jupyter.sh`/`docker.run.jupyter.bat` の方をご利用ください。

```shell-session
$ cd MLNSC_2017A
$ ./docker.run.jupyter.sh
```

これで Jupyter notebook が起動するので、ブラウザで `http://localhost:8888/tree/mlnsc/` を開いてください[^docker_toolbox_url][^port8888]。

以下のような表示になればOKです[^julia_ruby]。

![jupyter_mlnsc_1024x640.png](https://qiita-image-store.s3.amazonaws.com/0/30400/feaf4fbd-e9c8-abd1-0c7d-e50f06c44dd0.png)


[^docker_toolbox_url]: Docker Toolbox を利用している場合、`localhost` の代わりに Docker Machine に割り当てられたIPアドレスを指定する必要が（たぶん）あります（例：`http://192.168.99.100:8888/tree/mlnsc/`）
[^pwd_win]: Windows 版（PowerShell スクリプト）は、以下の記事を参考にさせていただきました → [Windows PowerShell でカレントディレクトリのパスを Linux 形式で取得する](http://qiita.com/nobuoka/items/50395c23642b4d459946)
[^port8888]: すでに別のアプリケーション等でポート `8888` が使用されている場合、エラーとなります。その際は、スクリプトファイルを修正してポート番号を変更してください。例えば `8080` 番ポートを利用するには `-p 8888:8888` を `-p 8080:8888` とすればOK。
[^julia_ruby]: Python の他に [Julia](https://julialang.org) と [Ruby](https://www.ruby-lang.org/) が入れてあります。

### 補足

最初に `docker.run〜` を実行するとき、DockerHub からビルド済の Dockerイメージ のダウンロードが始まります。ネットワーク回線状況により数十秒〜数分かかることがあります。2回目以降はダウンロード済のイメージが利用されるので、即座にコンテナが起動します。

なお、Dockerイメージは自動更新はされません。「Dockerイメージ修正したので更新してください」とアナウンスがあった際は、（実行中のコンテナを終了させた上で）`docker pull antimon2/mlnsc-dlscratch` を実行して Dockerイメージ を再ダウンロードしてください。

### Q&A

想定される（もしくは実際にあった）問題とそれに対する回答を記しておきます。

+ `docker.run.jupyter.sh`（または `〜.bat`）を実行すると `[C 01:23:45.678 NotebookApp] Unrecognized flag: '--allow-root'` と出て Jupyter notebook が起動しない。
    + → Docker イメージ と 勉強会用リポジトリ のバージョンが合っていません。  
      Docker イメージ が古いです。`docker pull antimon2/mlnsc-dlscratch` を実行してみてください。
+ `docker.run.jupyter.sh`（または `〜.bat`）を実行すると `[C 01:23:45.678 NotebookApp] Running as root is not recommended. Use --allow-root to bypass.` と出て Jupyter notebook が起動しない。
    + → Docker イメージ と 勉強会用リポジトリ のバージョンが合っていません。  
      勉強会用 Git リポジトリ が古いです。`git pull` を実行してみてください。
+ （Windows）`docker.run～.bat` を実行すると `C: drive is not shared.` 等とエラーが出て失敗する。
    + → Docker の設定で、C:ドライブ（もしくは該当するドライブ）の共有をONにしてください[^shares_drives]。↓  
      ![check_shared_drive.png](https://qiita-image-store.s3.amazonaws.com/0/30400/659d3882-92fc-0ee8-b27f-1bdd60ae9293.png)

[^shares_drives]: なんでドライブ単位の共有しかできないんでしょうね…。Mac はパスで指定（つまり特定のディレクトリ以下という指定）ができるのに。

## 一般利用

勉強会以外でも利用できます。普通に `docker run -it antimon2/mlnsc-dlscratch:latest` すればOK。もちろん必要に応じて各種オプション引数を追加してください（`--rm` とか `-p XXXX:XXXX` とか `-v /host/path:/guest/path` とか）。
ただし Jupyter notebook を起動すると、最初にブラウザで開こうとしたときにパスワードまたはトークンの入力を求められることがあります。起動時にコンソールに表示されたトークンをコピペして入力しましょう[^security]。

以上、主に勉強会参加者向けの内容でした。

[^security]: 勉強会用リポジトリ内に用意したスクリプトファイルは、このトークン入力を省略できるようにしたものです。なおそれを Dockerイメージ の方に入れなかったのは、せっかくセキュリティ向上目的で「トークン/パスワードを入力しないと使えない」という挙動になっているのにそれを無効にしたものをデフォルトの動きにするのが忍びなかったためです。そのまま実運用案件に使われて問題が起きた時にそれをこちら側の責任にされても困るので。

# Dockerイメージ の技術情報

これ以降は、勉強会の内容とは（ほぼ）無関係だし、そもそもほぼ個人的な覚書でしかないので、興味のない方は読み飛ばして頂いて構いません。

## Dockerfile

Dockerfile（最新版）は以下のリンクを参照してください。

+ [DockerHub 内](https://hub.docker.com/r/antimon2/mlnsc-dlscratch/~/dockerfile/)
+ [GitHub 内](https://github.com/antimon2/MLNSC-DLScratch/blob/master/Dockerfile)

2017/04/08 0:00:00 時点の最新を以下にも掲載してみます。

```docker:Dockerfile
FROM continuumio/miniconda3:4.3.11

MAINTAINER antimon2 <antimon2.me@gmail.com>

# Install NumPy / Matplotlib.
RUN /opt/conda/bin/conda install numpy matplotlib -y --quiet

# Install Jupyter42.
RUN /opt/conda/bin/conda install jupyter -y --quiet

# Install libzmq
RUN apt-get update \
    && apt-get upgrade -y -o Dpkg::Options::="--force-confdef" -o DPkg::Options::="--force-confold" \
    && apt-get install -y \
    libzmq3-dev \
    libzmq3

# Install Julia0.5.1
RUN mkdir -p /opt/julia-0.5.1 && \
    curl -s -L https://julialang.s3.amazonaws.com/bin/linux/x64/0.5/julia-0.5.1-linux-x86_64.tar.gz | tar -C /opt/julia-0.5.1 -x -z --strip-components=1 -f -
RUN ln -fs /opt/julia-0.5.1 /opt/julia-0.5

# Make v0.5.1 default julia
RUN ln -fs /opt/julia-0.5.1 /opt/julia

# RUN echo "PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/julia/bin\"" > /etc/environment && \
#     echo "export PATH" >> /etc/environment && \
#     echo "source /etc/environment" >> /root/.bashrc

ENV PATH /opt/julia/bin:$PATH

# Install IJulia with using installed miniconda, and then precompile it
RUN CONDA_JL_HOME=/opt/conda /opt/julia/bin/julia -e 'Pkg.add("IJulia")'
RUN /opt/julia/bin/julia -e 'Pkg.build("IJulia");using IJulia'

# Install PyPlot with using installed matplotlib, and then precompile it
RUN PYTHON=/opt/conda/bin/python /opt/julia/bin/julia -e 'Pkg.add("PyPlot")'
# RUN /opt/julia/bin/julia -e 'Pkg.build("PyPlot")'
RUN /opt/julia/bin/julia -e 'using PyPlot'

# Define working directory.
WORKDIR /opt/notebooks

# EXPOSE
EXPOSE 8888

# Define default command.
# CMD ["/opt/conda/bin/jupyter", "notebook", "--notebook-dir=/opt/notebooks", "--ip='*'", "--port=8888", "--no-browser"]
CMD ["/bin/bash"]
```

簡単に自分の言葉で解説してみます。
業務で Dcokerfile 書くことは今までにもあったのですが、よく分かってない部分もあるかもなので、詳しい方「こここうした方が良いよ」等ありましたらコメントください（＝ツッコミ大歓迎！）

+ 1行目。今回ベースにしたのは `continuumio/miniconda3:4.3.11`。**miniconda** なので **Python3** は入っていますが NumPy/Matplotlib 等はプリインストールされていません。
+ 6行目。↑の **NumPy** / **Matplotlib** をインストール。
+ 9行目。さらに **Jupyter** をインストール。これ、↑の「Numpy/Matplotlib のインストール」とまとめた方が良かったかな？
+ 12-16行目。libzmq 関連をインストール。後でインストールする IJulia 用。
+ 19-30行目。**Julia** 0.5.1（0.5系の最新）をインストール＆最低限の初期設定。  
  この辺は [julialang/julia Dockefile](https://github.com/tanmaykm/JuliaDockerImages/blob/master/base/v0.5/Dockerfile) を参考にしました。
+ 33-34行目。**IJulia** のインストールと設定。  
  ここは julialang/julia の Dockefile に二手間加えています：
    + `CONDA_JL_HOME=/opt/conda` として、IJulia（が依存している `Conda.jl` パッケージ）が既に導入済みの miniconda を利用するよう指定
    + `Pkg.build("IJulia")` の後に `using IJulia` とすることで、インストールしたパッケージのプリコンパイルを可能な限り実施
+ 37-39行目。**PyPlot.jl** のインストールと設定。IJulia と同様：
    + `PYTHON=/opt/conda/bin/python` として、PyPlot（が依存している `PyCall.jl` パッケージ）が既に導入済みの miniconda 内の Python3 を利用するよう指定
    + `using PyPlot` とすることで、インストールしたパッケージのプリコンパイルを可能な限り実施
+ 45行目。Jupyter notebook が使用するデフォルトのポート '8888' を expose。
    + これ、なくても `docker run -p 8888` とかすればポート空くし、`docker run -p 8080:8888` とかすればここで空けた `8888` 以外も利用できるし、意味あるんだろうか？と思ってしまうのですが。いかがでしょうか？
+ 48-49行目。デフォルトで Jupyter notebook が起動するようにしようか？と思って書いたけれど気が変わって普通に `/bin/bash` を起動するように変更。ただ「こうすれば Jupyter 起動できるよ」的な意味でコメントアウトで残してあります（明らかに無駄）。

## Dockerイメージの自動ビルド

実は DockerHub への登録は今回初めて（アカウント作るところから）実施しました。
Github と簡単に連携できて便利ですね。
今回の Build Settings はこのようにしました。

![antimon2_mlnsc_dlscratch_build_settings_Docker_Hub.png](https://qiita-image-store.s3.amazonaws.com/0/30400/e4bee9ba-5ad2-aa00-fd2b-074d62b13494.png)

+ Type:Branch, Name:master
    + Github リポジトリの master ブランチが push される度に、Dockerイメージの `latest` タグを自動ビルドする設定（デフォルトまま）。便利[^caution_auto_build]。
+ Type:Tag, Name:`/^([^m]|.[^a]|..[^s]|...[^t]|....[^e]|.....[^r]|.{0,5}$|.{7,})/`
    + "Type:Tag" のデフォルト設定を利用。Github リポジトリに新しいタグが push される度に、Dockerイメージの同名のタグを自動ビルド。あれ、でもなんで Name の設定が「"master" 以外の文字列」になってるんだっけ？
+ Type:Branch, Name:full
    + Github リポジトリの full という名前のブランチ[^full_branch]が push される度に、Dockerイメージの `latest-full` タグを自動ビルドする設定。便利[^caution_auto_build]。

[^caution_auto_build]: 便利ですが、master/full に push する度にビルドが発生する（なんか毎回20分くらいかかる）ので、気をつけなきゃいけないですね。少なくとも開発・修正は別ブランチで実施して、「リリースするぞ」て時に master/full にマージして push、というよくある手順をちゃんと踏まないと無駄にビルドが発生しちゃいます。
[^full_branch]: ちなみに full ブランチは、miniconda ではなく anaconda を利用して、NumPy/Matplotlib 以外にもデフォルトで SciPy とか Pandas とか scikit-learn とか色々入っているバージョン。ただし anaconda のバージョンが少し低くてプリインストールの Numpy/Matplotlib のバージョンが低いし、何より無駄にサイズが大きいので別ブランチにした、と言うわけでして。

# 参考

+ [Docker](https://www.docker.com/)
+ [DockerHub](https://hub.docker.com/)
+ [Windows PowerShell でカレントディレクトリのパスを Linux 形式で取得する](http://qiita.com/nobuoka/items/50395c23642b4d459946)
+ [DockerImageをDockerHubに登録の仕方](http://qiita.com/kon_yu/items/7c40f4dfbd1cce006ce7)
+ [ゼロから作るDeep Learningの学習環境をDockerで作った](http://yuki10.hatenablog.com/entry/2017/01/11/020924)