Windows7でJupyterのDockerイメージにbash_kernelを導入するまでのメモ

# JupyterのDockerイメージにbashを追加する

## 初めに
+ http://qiita.com/kshigeru/items/2cd504e927869163b4c8 を見て、bashも入れたいなと思い、苦戦したのでまとめました。dockerでjupyterコンテナ上にkernel_bashを導入するまでの備忘録です。

+ docker for windows　は
>>https://docs.docker.com/docker-for-windows/
>
>Docker for Windows requires 64bit Windows 10 Pro, Enterprise and Education (1511 November update, Build 10586 or later) and Microsoft Hyper-V. Please see What to know before you install for a full list of prerequisites.

ということで、使えないんだなぁ。なので、docker-toolboxでやってます。

+ 全面的に、[プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化](https://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AEDocker%E6%95%99%E7%A7%91%E6%9B%B8-%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9%E3%81%AE%E5%9F%BA%E7%A4%8E%E7%9F%A5%E8%AD%98-%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AB%E3%82%88%E3%82%8B%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E3%81%AE%E8%87%AA%E5%8B%95%E5%8C%96-%E9%98%BF%E4%BD%90-%E5%BF%97%E4%BF%9D/dp/479814102X) にはお世話になりました！

+ 多少、dockerネットワークの知識が必要そうなので、準備としてdockerの概要を入れました。間違っていたら、教えてくださると有難いです。

## dockerの概要

docker-machine : VM(今回は"default")を構築するツール。Docker quickStart Terminalを起動すれば、後はよしなにしてくれます。

docker(docker-engine) : VMの上にコンテナを作成、変更、削除したりできるツール 
dockerの簡単なフローチャートは↓を参照してください。
https://denibertovic.com/talks/supercharge-development-env-using-docker/#/11

### インストールと環境設定

http://pppurple.hatenablog.com/entry/2016/07/03/003425 を参照します
> Windowsで仮想化ソフトを利用するには仮想化機能を有効にする

のを忘れずに。実行環境にssh接続するので、cygwinかteratermでsshを取ってきてください。
注)Windows10とかではまた設定方法が異なるみたいです。(自分の場合、biosを起動しなくてもそのままDockerを実行できた。)

---
Docker quickStart Terminalを起動すれば、後はよしなにしてくれますが、一応どのような挙動をしているのかの確認をば。`C:\Program Files\Docker Toolbox\start.sh`で、大まかには、\${DOCKER_MACHINE}を"./docker-machine.exe", \${VM}をdefaultとして、

```bash
# Dockerホストマシンを作成する
${DOCKER_MACHINE} create -d virtualbox  "${VM}"
# Docker machineを起動する。2回目以降はここから
${DOCKER_MACHINE} start "${VM}"
# 環境変数no-proxyの設定
${DOCKER_MACHINE} env --shell=bash --no-proxy ${VM}
bash --login -i 
```
のようなことをおこなっています。
注意）proxy環境の方は、Docker Quickstart Terminalを実行する前に、Windowsの環境変数に、HTTP_PROXY, HTTPS_PROXYを付け加えてください。そうでないと、Docker Hub(Dockerレジストリ)からimageをpullできないです

### dockerのネットワーク構成
丸投げですが、
http://qiita.com/osuo/items/16fd61c7479bd7ef041a
dockerのネットワークの図がとても分かりやすいです。ホストOSとかゲストOS(Dockerホスト)とかいう言葉は上記のurlを参考にしました[^myself]。


[^myself]: （注釈に加えるまででもないですが、）この後、自分でゲストOSで`ifconfig -a`を打ったり、ping打ったりして実際に確かめてました。

## 本題

### Jupyterをwindows7(ホストOS)のブラウザで表示するまで


目標は、ゲストOS(Dockerホスト)で起動しているjupyter notebookをホストOS(Windows)のブラウザ(Firefox)で表示させたいのですが、まずは、ホストOS(Windows7)からゲストOSへポートフォワーディングする必要があります。その後、ゲストOSとコンテナとのポートマッピング(`docker run -p`)を行います。

>> http://bitwave.showcase-tv.com/%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%ABpc%E3%81%A8virtual-box%E3%81%AE%E4%BB%AE%E6%83%B3%E3%83%9E%E3%82%B7%E3%83%B3%E3%81%A8%E3%81%AE%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E6%8E%A5/

>・内部　→　外部への通信：　Virtual Boxにてxxx.xxx.xxx.xxxにNAT[^nat]され、通信が行われる。
>・外部　→　内部への通信： デフォルトの設定だと、通信不可である。

[^nat]: グローバルIPアドレスとプライベートIPアドレスを相互に変換する技術。[マスタリングTCP/IP 入門編 第5版](https://www.amazon.co.jp/%E3%83%9E%E3%82%B9%E3%82%BF%E3%83%AA%E3%83%B3%E3%82%B0TCP-IP-%E5%85%A5%E9%96%80%E7%B7%A8-%E7%AC%AC5%E7%89%88-%E7%AB%B9%E4%B8%8B/dp/4274068765)が定番ですが分かりやすい教科書だと思います。

方法としては、3つあり、
1. Virtual Machineでdefaultを右クリック>> 設定　>> ネットワーク >> アダプターにブリッジ接続を追加する
2. 同じく、Virtual Machine上で、アダプター1のNAT >> 高度 >> ポートフォワーディング >> 次のように追加(anything-OKの部分。名前は何でもよいです):
![anything.PNG](https://qiita-image-store.s3.amazonaws.com/0/133922/af8d870d-be9b-872e-618b-ce86c4ba2d1e.png)


3.sshローカルポートフォワーディング（ポート転送）を利用

とかありますが、sshが一番手っ取り早いので、3でやります。
ローカルポートフォワーディング(-L オプション)のほかには、リモートポートフォワーディング(-R オプション)があるんですが、使い方は http://qiita.com/osuo/items/1fe9d6db2f5534440242 とかがわかりやすかったです。また、Dockerのポートフォワーディング自体は、http://dev.classmethod.jp/tool/docker/getting-started-docker-on-osx/ のポートフォワーディング設定の図が参考になりました。


```bash
# -f: バックグラウンドで動作
# -N : コマンド実行無し
# -L [ローカルマシンで待ち受けるIPアドレス(省略すると,localhost)]:[ローカルPCのポート]:[ターゲットマシンのプライベートIP] (defaultはログインホスト)
# Window7だと、ファイアーウォールの画面（下図参照)が出てくるが、アクセス許可しましょう。
$ docker-machine ssh default -f -N -L 8888:127.0.0.1:8888
$ docker images
REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
jupyter/datascience-notebook   latest              357fc20fb373        2 weeks ago         4.797 GB
```

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/133922/ee93ef33-f17b-d146-7236-6834533b6b9b.png)


---

docker-machineにssh接続した後は、
imageを取得(pull) -> imageからコンテナを生成、プロセス起動(run) -> コンテナ停止(stop) -> コンテナ起動(start) -..-> 稼働コンテナでプロセス実行(exec)
みたいなことをやります[^run]。

[^run]: pullしないでいきなりrunでもimageを取得してくれますが、今回は説明のためあえて冗長な書き方で。

docker machineを終了したい場合は、exitとかshutdownとかあるけど、違いは、
http://www.x-net.nu/technical/linux/command/t_linux_com01.html
とかを見ればいいんじゃないかと思います。

コンテナは必ず何らかのプロセスが起動し続ける必要があります[^container-run]。コンテナはjupyterのDockerFile(https://hub.docker.com/r/jupyter/notebook/~/dockerfile/ )を覗くと、最終行に

```text
CMD ["jupyter", "notebook", "--no-browser", "--allow-root"]
```

[^container-run]: [DevOps導入指南]() p.139 参照のこと。正確には、「コンテナ自体の中のプロセスはフォアグラウンドで動かす必要がある」です。良書でした。

とあるので、docker run [image名]の後にコマンドを明記しない場合は、jupiter notebookが走るようになっています。

```bash
# -p [ホストOS(Linux)のポート番号]:[コンテナ(jupyter)のポート番号]
# -d バックグラウンドで立ち上げる。
# コンテナとホストOSのポートマッピングをしましょう.DockerfileでEXPOSE 8888とあるので、ポート番号8888にしてます
docker@default:~$ docker run -d -p 8888:8888 jupyter/datascience-notebook
ad1cfa7031c04244f1146e5d7f55612131944075e95850091988311933d76d50
Display the running processes of a container
# コンテナが稼働した！
# 稼働コンテナのプロセス確認(top)
docker@default:~$ docker top ad1 # <= ad1はイメージ識別子(一意に判別できさえすれば、途中までで省略可)
PID                 USER                COMMAND
2407                docker              tini -- start-notebook.sh
2424                docker              {jupyter-noteboo} /opt/conda/bin/python /opt/conda/bin/jupyter-notebook
docker@default:~$ docker port ad1
8888/tcp -> 0.0.0.0:8888 # コンテナの8888番ポートがホストの8888番ポートに転送されている

docker@default:~$ docker stop ad1
ad1
# 停止中のコンテナを開始すると、runで起動したコンテナ起動の設定がそのまま反映される。
# 例の場合は、-dオプションと-p 8888:8888オプションが反映されるはず！
docker@default:~$ docker start ad1
ad1
# psで稼働コンテナを一覧表示できる。やっぱり反映されている。
docker@default:~$ docker ps
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                    NAMES
ad1cfa7031c0        jupyter/datascience-notebook   "tini -- start-notebo"   10 minutes ago      Up 5 seconds        0.0.0.0:8888->8888/tcp   infallible_visvesvaraya
```

### bash_kernelの導入


続いて、このコンテナにbash_kernelを導入して、jupyterでもbashを動かせるようにします。
基本的には、https://github.com/takluyver/bash_kernel の通りですが、導入後にimageとして残したい場合は、commitコマンドを用います。

上記から、jupyter/datascience-notebookでは、condaを使っているようです。
使用方法としては、source activate -> python3に入る -> \[インストール] (-> source deactivate)
の手順で仮想環境の出入りを行います。
詳しくは、 http://qiita.com/y__sama/items/5b62d31cb7e6ed50f02c
などが良いと思います。

注) proxy環境の方は、jupyterコンテナ内でpipするので、環境変数の設定が必要です。

```bash
$ docker run -d -p 8888:8888 -e HTTP_PROXY=${HTTP_PROXY} -e HTTPS_PROXY=${HTTPS_PROXY} -e NO_PROXY=${NO_PROXY} jupyter/datascience-notebook
```
これで問題ないと思います。

```bash
# 7d80c442bedbで新規にコンテナを作り直しちゃってます。
docker@default:~$ docker ps
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                    NAMES
7d80c442bedb        jupyter/datascience-notebook   "tini -- start-notebo"   24 minutes ago      Up 24 minutes       0.0.0.0:8888->8888/tcp   zen_fermat
# -itでインタラクティブにbashと対話できます。execコマンドは新規にプロセスを作成します。
docker@default:~$ docker exec -it 7d8 /bin/bash # <=7d8はイメージ識別子(一意に判別できさえすれば、途中までで省略可)
jovyan@7d80c442bedb:~/work$ source activate #anacondaで入れているので、activateして、rootに入ります。(conda install bash_kernelはできないため)
(root) jovyan@7d80c442bedb:~/work$ pip install bash_kernel
Collecting bash_kernel
  Downloading bash_kernel-0.4.1-py2.py3-none-any.whl
Requirement already satisfied (use --upgrade to upgrade): pexpect>=3.3 in /opt/conda/lib/python3.5/site-packages (from bash_kernel)
Installing collected packages: bash-kernel
Successfully installed bash-kernel-0.4.1
You are using pip version 8.1.2, however version 9.0.0 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
# 寄り道になるが、素直に従おう(should)じゃないか！
(root) jovyan@7d80c442bedb:~/work$ pip install --upgrade pip
Collecting pip
  Downloading pip-9.0.0-py2.py3-none-any.whl (1.3MB)
    100% |████████████████████████████████| 1.3MB 948kB/s
Installing collected packages: pip
  Found existing installation: pip 8.1.2
    Uninstalling pip-8.1.2:
      Successfully uninstalled pip-8.1.2
Successfully installed pip-9.0.0
(root) jovyan@7d80c442bedb:~/work$ python -m bash_kernel.install
Installing IPython kernel spec
(root) jovyan@7d80c442bedb:~/work$ exit
exit
# コンテナの稼働確認
docker@default:~$ docker ps
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                    NAMES
7d80c442bedb        jupyter/datascience-notebook   "tini -- start-notebo"   20 minutes ago      Up 20 minutes       0.0.0.0:8888->8888/tcp   zen_fermat
```

で、ホストOS(Windows)で、ブラウザでhttp://localhost:8888 にアクセスして、無事にbash kernelが導入ことが確認できました～
![bash_kernel.PNG](https://qiita-image-store.s3.amazonaws.com/0/133922/d55b3770-68f0-1f46-4382-94c3c9839491.png)

あとは、jupyterにbash_kernelを導入したコンテナをimage化するのみです。

```bash
# commitはコンテナ(7d8:jupyter)からイメージ(jupyterbash)を作成する
docker@default:~$ docker commit 7d8 jupyterbash/datascience-notebook
sha256:ac7319464b50739c010ac7fb56918c28d87a909a2875215533f976bde60226a0
# imageが作成されたかどうか確認
docker@default:~$ docker images
REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
jupyterbash/datascience-notebook   latest              ac7319464b50        6 seconds ago       4.806 GB
jupyter/datascience-notebook       latest              357fc20fb373        2 weeks ago         4.797 GB
docker@default:~$ docker stop 7d8
7d8
# 作成したimageからはいれるかどうかチェック => ブラウザ上でhttp://localhost:8888にアクセスしてOKだった
docker@default:~$ docker run -d -p 8888:8888 jupyterbash/datascience-notebook
aba0ceba873a3466e8306933aad45858226b4ecdbc8cab7561c267b7da9f7b6a
```

## 追記

### DockerFileの作成
すでに、imageをcommitして手順は把握しているので、http://qiita.com/pottava/items/452bf80e334bc1fee69a のスタンスに沿って作成するだけです。

---