dockerによるkerasとtensorflow, jupyter labの環境構築およびport forwardingでのjupyterへの接続

# 概要
dockerによってkerasのgpuおよびjupyterlab環境構築をする。
dockerがどういうものか、dockerのインストール等は省略する。
またついでにport forwardingについて軽く解説する。

## イメージのpull
[tensorflowのdockerhub](https://hub.docker.com/r/tensorflow/tensorflow/tags)にてタグがlatest gpuのimageをpullする。
タグがlatestのみだとcudaが使えない。
やることは以下の通り

```
$ sudo docker pull tensorflow/tensorflow:latest-gpu
```
## イメージの確認
```
$ sudo docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZE
tensorflow/tensorflow             latest-gpu          20fd065e3887        7 weeks ago         3.15GB
```

## 必要そうなモジュールとjupyterをインストール
### インストールするモジュール
今回importするのは

* matplotlib
* cv2 (opencv-python)
* keras
* jupyter lab

インポートする前にdockerのコンテナをイメージから作成しておく。環境構築の際はrootで入る。

```
$ sudo docker run -it tensorflow/tensorflow:latest-gpu
```

### pipとaptのアップデート
```
# pip install --upgrade pip
# apt-get update -y
```

### opencvのインストールのための準備
以下を参照した
[DockerでpythonのTensorFlowとOpenCVの実行環境を構築する](https://qiita.com/sey323/items/8cb10f90889a6d911cd4)

```
# apt-get install -y libopencv-dev
```

結構時間がかかる。

### 必要モジュールのインストール

```
# pip install matplotlib opencv-python keras
# pip install jupyterlab
```

### モジュールがimportできるかテスト

```
# python
Python 3.6.9 (default, Jul 17 2020, 12:50:27)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import matplotlib
>>> import numpy
>>> import cv2
>>> import keras
2020-09-20 01:02:34.313977: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcudart.so.10.1
>>> import tensorflow
```
大丈夫そうだ。
``Ctrl + d``で対話モードを抜けられる。
jupyterlabもこれにてインストールできているのだが、実行するためにはport forwardingをしないといけない。詳しい実行手順を確かめたかったら最後までこの記事を読んでほしい。

## イメージのコミット

jupyter lab のテストの前に一旦コンテナをイメージにcommitする。つまり色々インストールした現在の状況をセーブする。
`` Ctrl+p,Ctrl+q``でコンテナを稼働させたままコンテナから抜ける。
### 現在稼働中のコンテナの確認

```
$ sudo docker ps -n=-1
CONTAINER ID        IMAGE                              COMMAND                  CREATED             STATUS              PORTS                  NAMES
a24503258ede        tensorflow/tensorflow:latest-gpu   "/bin/bash"              24 minutes ago      Up 24 minutes                              romantic_gauss
```

### コンテナのコミット

```
$ sudo docker commit a24503258ede keras-jupyter
```

``sudo docker commit (コンテナID) (つけたいイメージの名前)`` のようにする。以降今まで作ったものはkeras-jupyterという名前のイメージをビルドすれば先程のコンテナが出来上がる。


### コンテナの停止
先程のコンテナを一旦止める。

```
$ sudo docker stop a245
$ sudo docker ps -n=-1
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
```
コンテナIDは最初4文字くらいで反応してくれる。
## port forwardingについて
これからjupyterlabの動作確認を行うのだが、ついでにport forwardingについて軽く説明する。 
### port forwarding(ポートフォワーディング)とは
現在私は以下の図のように私自身のパソコンからサーバにssh接続し、その上でdockerを走らせて、更にその上でjupyterlabを起動させようとしている。
しかしjupyterlabはブラウザ上で扱うもののためssh接続とは別にもう一つjupyterlabまでの接続経路を作成しないといけない。

![スクリーンショット 2020-09-20 2.12.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548975/12753513-b389-bdbb-21be-b6eae0fd5be5.png)

そのためブラウザ上でもjupyterに接続できるようssh以外の接続経路(トンネル)を作成することを**port forwarding**という。
以下の図のように各コマンドにオプションを加えることでsshのポート22番の他にトンネルを開けることができる。
![スクリーンショット 2020-09-20 2.35.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548975/824742fa-9155-1771-48bd-a5e50ee5bcb3.png)
![スクリーンショット 2020-09-20 2.35.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548975/1b2b4c22-9491-d1dd-b2f3-92890bb60a90.png)






ただ今回は解説のためにすべてポート番号を変えているが、間違えないようすべてのポートフォワードするポートは8888などに統一すると良いだろう。
それでは具体的にjupyterlabのテストを通してポートフォアディングができているか確認していこう。
## jupyterlabへのport forwarding
### sshでのport forward
まずローカルからsshでサーバに接続する。

```
$ ssh user@x.x.x.x -L 8800:x.x.x.x:8880
```
``-L {ローカルのポート番号}:{サーバのip}:{サーバでつなぎたいポート番号}`` のようにオプションを付ける。



### dockerでのport forward

``-p 8880:8888``をオプションに追加する。
これによりサーバからdockerにアクセスした際にサーバのポート8880番がdockerの8888番につながる。

```
$ sudo docker run -it -p 8880:8888 keras-jupyter
```
これによりローカルの8800番はdockerの8888番につながった。
### jupyterlabの起動
次にコンテナ上でjupyterlabを以下のコマンドで起動する。

```
# jupyter lab --port 8888 --ip=* --allow-root
```
以下コマンドの解説

* まずdockerのport8888番がローカルとつながっているためjupyterのportを8888とする。(``--port 8888``)
* jupyterをそのまま起動するとipがわからず接続できないためdocker環境とアドホックにつなぐ。(``--ip=*``)
* 最後に現在dockerはrootで起動してるためルートでの接続を許可する。(``--allow-root``)

以下のような出力の``http://127.0.0.1:8888/?token=614f6e5d410a1c459baa691ee28fdd0e2593c4644189a09e```をコピー。

```bash

[W 02:52:43.023 LabApp] WARNING: The notebook server is listening on all IP addresses and not using encryption. This is not recommended.
[I 02:52:43.028 LabApp] JupyterLab extension loaded from /usr/local/lib/python3.6/dist-packages/jupyterlab
[I 02:52:43.028 LabApp] JupyterLab application directory is /usr/local/share/jupyter/lab
[I 02:52:43.030 LabApp] Serving notebooks from local directory: /
[I 02:52:43.030 LabApp] Jupyter Notebook 6.1.4 is running at:
[I 02:52:43.030 LabApp] http://0829a84d25eb:8888/?token=614f6e5d410a1c459baa691ee28fdd0e2593c4644189a09e
[I 02:52:43.030 LabApp]  or http://127.0.0.1:8888/?token=614f6e5d410a1c459baa691ee28fdd0e2593c4644189a09e
[I 02:52:43.030 LabApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
```

### jupyterへの接続
先程のurlの一部を以下のように変更し、ローカルのブラウザに貼り付ける。

```
http://localhost:8800/?token=614f6e5d410a1c459baa691ee28fdd0e2593c4644189a09e
```
これでサーバで実行中のjupyterにポートフォアディングによって接続できる。


## 余談
実際dockerを実行する際にはユーザーをdockerとserverでバインドするなどここでは詳しく解説しないが以下のようにdocker run コマンドをaliasに登録して使うとよいだろう。

```bash

alias docker-run-keras = 'sudo docker run -v /etc/group:/etc/group:ro -v /etc/passwd:/etc/passwd:ro -v $HOME:$HOME -u $(id -u $USER):$(id -g $USER) -it --gpus 2 -p 8888:8888 --shm-size 30g keras-jupyter
```