Proxyが厳しい企業内からも利用できるJupyter NotebookをAWS上に用意する

# やりたいこと

* Deep Learning向けのAIMを、Amazon AWSのEC2で動かす
* Proxy内からもアクセスできるように、Jupyter NotebookをPort443で動かす
* 通常は安く動かし、必要なときだけパワフルなGPUで動かす
 * インスタンスタイプを変更したらJupyter Notebookが自動で起動 
 * インスタンスタイプを変更してもIPアドレスが変わない

Machine Learning や Deep Leaning を学習していると、自分のPCではパワーが足りなかったり、複数の環境を整えるために容量が足りなくなってきたりします。また、時間がかかる処理をさせている時には、コーヒーを飲みながらサクサクとWebサーフィンして待ちたいです。

そこでAmazonのAWS上にEC2インスタンスとして環境を作ります。Lazy Girlの記事を見ると、まさにDeep Learning向けのAMIが用意されている。GPUも使えます。

[A Lazy Girl's Guide to Setting Up Jupyter on EC2 for Deep Learning](https://kenophobio.github.io/2017-01-10/deep-learning-jupyter-ec2/)

# Deep Learning AMIを起動

[Lazy Girlの記事](https://kenophobio.github.io/2017-01-10/deep-learning-jupyter-ec2/)に従ってセットアップし、デフォルトのPort 8888 できちんとJupyter Notebookにアクセスできることを確認する。

３点ほど補足：

私はDeep Learning AMI Ubuntu Version を選んだので、デフォルトのユーザ名は ec2-userではなく ubuntu.
なのでSSHするときは

`ssh -i <PATH_TO_PEM> ubuntu@ec2-xx-xxx-xx-xxx.eu-west-1.compute.amazonaws.com`

また、

`source src/anaconda3/bin/activate root`

をするだけで、

`> which jupyter
~/src/anaconda3/bin/jupyter`

となったので、.bash_profile にPATHを追加するくだりは必要なかった。

Kerasはすでにインストールされている。

# ~~Port 443 で Jupyter Notebookを起動~~

~~Portを8888から、Proxyを通過できる443へ変更します。~~

~~`nano ~/.jupyter/jupyter_notebook_config.py`~~

~~> c.NotebookApp.port = 443~~

~~そのままubuntuユーザでjupyter notebookを起動させようとすると、443のような低い番号のポートはダメだと怒られるので、rootで起動します。~~

~~`sudo jupyter notebook --allow-root`~~

# Port 8888 を Proxyが通過できる443へフォワードする

Jupyter Notebookを Port 443で起動するには rootユーザで起動しなければいけないのですが、せっかくubuntuユーザで動くように設定してあるGPUがうまく動かないようです。

なのでubuntuユーザでPort 8888 でJupyter Notebookを起動させて、Port 8888 を 443へフォワードするようにします。

`sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-ports 8888`


# 自動起動の設定

インスタンスタイプを変更するには、一度EC2インスタンスをStopする必要があります。
自動的にJuypter Notebookが立ち上がるようにserviceとして登録しておくと便利です。
（会社のProxyがPort 22を通してくれないので、SSHして手動で起動させられない問題も解決）

## Service を定義

`sudo nano /etc/systemd/system/jupyter.service`

```
[Unit]
After=network.target

[Service]
User=ubuntu
ExecStart=/home/ubuntu/start_jupyter.sh

[Install]
WantedBy=default.target
```

`sudo chmod 664 /etc/systemd/system/jupyter.service`

## 起動スクリプト

`nano /home/ubuntu/start_jupyter.sh`

```
#!/bin/bash

source /home/ubuntu/src/anaconda3/bin/activate root
sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-ports 8888
cd /home/ubuntu/notebook
jupyter notebook
```

`chmod 744 /home/ubuntu/start_jupyter.sh`

## GPU用の環境変数の設定

コマンドプロンプトからstart_jupyter.shを呼び出したときは`import keras`がうまくいくのですが、サービスの自動起動ではなぜかimport errorとなりました。
どうやら[セキュリティを確保するために環境変数LD_LIBRARY_PATHを引き継がない機能](https://github.com/jupyter/notebook/issues/1290)があるようです。しょうがないのでjupyterのコンフィグレーションを読み込む時に設定するようにします。

`nano ~/.jupyter/jupyter_notebook_config.py`

```
# Configuration file for jupyter-notebook.
c = get_config()
c.NotebookApp.certfile = u'/home/ubuntu/certs/mycert.pem'
c.NotebookApp.keyfile = u'/home/ubuntu/certs/mycert.key'
c.NotebookApp.ip = '*'
c.NotebookApp.open_browser = False
c.NotebookApp.password = u'sha1:xxxxxxxxxxxx'
c.NotebookApp.port = 8888
import os
os.environ['LD_LIBRARY_PATH'] = '/home/ubuntu/src/torch/install/lib:/home/ubuntu/src/torch/install/lib:/home/ubuntu/src/cntk/bindings/python/cntk/libs:/usr/local/cuda/lib64:/usr/local/lib:/usr/lib:/usr/local/cuda/extras/CUPTI/lib64:/usr/local/mpi/lib:/home/ubuntu/src/cntk/bindings/python/cntk/libs:/usr/local/cuda/lib64:/usr/local/lib:/usr/lib:/usr/local/cuda/extras/CUPTI/lib64:/usr/local/mpi/lib:’
```

## サービスを登録してスタート

きちんと動いていることを確認。

```
~$ sudo systemctl enable jupyter.service
~$ sudo systemctl start jupyter.service
~$ sudo systemctl status jupyter.service
```

# Elastic IP でIPを固定

EC2インスタンスは再起動すると、IPアドレスが変わってしまいます。
Elastic IPサービスを使用すると、同じIPアドレスでアクセスできるようになります。

EC2 Management Console -> Elastic IPs -> Allocate new address

なお、EC2インスタンスをSTOPしている時にはElastic IPサービスの利用料が掛かります。(EC2インスタンスにattachされている時にはElastic IPサービスの部分は無料) 

N.Virginiaでの料金だと、倹約のためにEC2インスタンスをSTOPしていても月に\$3.6掛かります。t2.nanoをずっと動かし続けた\$4.18とほとんど変わらない。でもセキュリティ上、使わない時にはSTOPしていたほうがいいでしょう。

| Instance Type | Monthly \$ |
|:--|:--|
| stopped |	3.60 |
| t2.nano |	4.18 |
| t2.micro |	8.35 |


# おまけ （System Shell Command）

会社のProxyは非常に厳しく、Port 22を通してくれません。SSHでサーバにアクセスしてGitHubからexampleをダウンロードしたり、必要なモジュールをインストールしたりしたい時に困ります。

でもJupyter NotebookにさえWebブラウザからアクセスできればなんとかなります。

Jupyter NotebookのPython promptでは、`!ping www.bbc.co.uk`のように「!」で行をスタートすれば[システムシェルコマンド](https://ipython.org/ipython-doc/3/interactive/tutorial.html)が打てます。

<img width="843" alt="Screen Shot 2017-10-21 at 10.40.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/208592/c200d635-e802-049e-f05d-9f85bc054cec.png">

# 所感

これでやっと厚いProxyの壁に阻まれた会社内からでもJupyter Notebookへアクセスして学習を続けられるようになりました。
簡単に対処できると思っていましたが、意外とハマリポイントがいくつもあって難儀しました。会社で学ぶよりも家で学ぶほうが手軽にできる世の中って不思議ですね。