サーバーのDockerで起動したJupyter Notebookを他のパソコンからアクセスできるようにした話

#概要
[こちらの記事](https://qiita.com/yamasakih/items/baac1d9c3026094487fe)でCentOS7.4をインストールしたパソコンにDockerでJupyter Notebookを起動できるようにし、
それを他のパソコンからアクセスできるようにしたときの作業を備忘録としてまとめておきます。

#環境
Z230 Workstation OS: CentOS7.4 (Jupyter Notebookをインストールするパソコン)
Docker version 17.09.0-ce, build afdb6d4 (Jupyter Notebookをインストールするパソコンにインストールされている)
Mac mini (Late 2014) OS: High Sierra (Jupyter Notebookのユーザー側のパソコン)

#仕様
- Jupyter NotebookをインストールするサーバーとユーザーのパソコンはLANで接続されている。
- サーバーは職場からしかネットワークでアクセスできない(グローバルIPがない、と言う説明でいいのかな？)。
- サーバーのJupyter Notebookには最低限のセキュリティとしてパスワードの設定だけする。
- サーバーのJupyter Notebookには複数人がアクセスする(こういう前提でJupyter Notebookは作られてないっぽい？)
- 仮想環境を破棄してもデータが残るようにマウントするフォルダを設定する

という前提条件で話を進めていきます。
記事を参考に同じ作業をしたい場合は**(特にセキュリティなど)**適宜変更してください。
**なお、この記事の作業を行うときは自己責任でお願いします。**

#作業
##1. ポートを解放する
以降の作業は`SSH`接続ができるならユーザー側のパソコンから`SSH`接続をして作業を進めてもらってかまいません。

サーバーのJupyter Notebookにアクセスするためにポートを解放します。
Jupyter Notebookではデフォルトでは8888が使われるので今回同様に8888を採用しました。

```
sudo firewall-cmd --add-port=8888/tcp --zone=public --permanent
```

こちらでポート8888を解放できます。CentOS7からfirewallの設定が楽になったなと思っています。

続けて

```
sudo firewall-cmd --reload
```

でfirewallを再起動しましょう。

```
sudo firewall-cmd --list-all --zone=public
```

で`ports: 8888/tcp`と表示されていたら正常に解放されています。

##2. Docker imageのダウンロードおよびコンテナの作成
Jupyter NotebookのDocker imageをダウンロードします。
今回は例としてjupyter/datascience-notebookを使うことにしますが、
公式のjupyterが公開している[Docker image](https://hub.docker.com/u/jupyter/)やそれらをベースとしているDocker imageなら同じ作業が可能だと思います。
Docker imageを変更したい場合は適宜以降の作業の**jupyter/scipy-notebook**の箇所を変更したDocker imageに変更してください。

実際、私は日頃ケモインフォマティクスと呼ばれる分野の解析のための
Docker imageとして用意されている[chemowakate/tutorial-7th](https://hub.docker.com/r/chemowakate/tutorial-7th/)を用いています。

さて、話を戻して以下のコマンドを実行するとDocker imageがダウンロードされ仮想環境であるコンテナが作成されます。

```
docker create jupyter/scipy-notebook
```

##3. パスワードを設定する
パスワード設定するためには`ipython`が実行できる環境である必要があります。

###3.1 サーバーですでに`ipython`が実行できる
サーバーで`ipython`が実行できるならそのまま`ipython`と入力して以下の作業を実行してください。

###3.2 サーバーで`ipython`が実行できない
`ipython`が実行できなくても2でダウンロードしてきた仮想環境内で`ipython`が実行できますので安心してください。
そのような場合は
```docker run -it --rm jupyter/scipy-notebook /bin/bash```
で`ipython`が実行できる仮想環境を起動し、仮想環境内に入ります。
そして`ipython`と実行しましょう。

いずれかの方法で`ipython`を実行した前提で話を続けていきます。

```
from notebook.auth import passwd
passwd()
```
と実行すると、パスワードの入力待ちになります。

パスワードとして入力したい文字列を入力し`Enter`を押すと例えば以下のような暗号化された文字列が画面に表示されます。
今後その文字列を使うのでメモ帳などに保存しておいてください。

`sha1:4daaadded8f2:6d42d26960204e16891c7aba6725edbd52fe4b12`
(あくまで一例です)

これ以降は`ipython`は使用しないので`exit()`で終了してください。
仮想環境内で`ipython`を実行した場合はさらに`exit`で仮想環境も終了しましょう。

##4. マウントするフォルダおよびjovyanユーザーの作成
Dockerで用意した仮想環境を破棄してしまうと仮想環境内のデータはすべて消えてしまいます。
そうなるとせっかく解析したデータなどもすべて消えてしまうのでサーバー内のフォルダをマウントしておきましょう。
これにより仮想環境内で保存したファイルなどはサーバーのそちらのフォルダにも反映されるので仮想環境を破棄してもデータが消えなくなります。

今回は複数人がJupyter Notebookを扱うことを前提に進めていきます。
その際、Jupyter Notebookを実行中に作成し、保存するファイルの所有者は`jovyan`と言うグループに所属する`jovyan`と言うユーザーとします。
こちらはご自身の用意したい環境ごとに適宜変更してください。

ちなみに`jovyan`は**Jupyter Notebookを使うみんな**的な意味があるそうです。
参考：[Who is jovyan? #358](https://github.com/jupyter/docker-stacks/issues/358)

###4.1 `jovyan`グループの作成
例えばgid=8888のjovyanと言うグループを作成するには以下のように実行します。

```
sudo groupadd -g 8888 jovyan
```

###4.2 `jovyan`ユーザーの作成
さらにグループ`jovyan`に所属しているuid=8888のユーザー`jovyan`を作成するには以下のように実行します。

```
sudo useradd -g jovyan -u 8888 jovyan
```

と属しているグループを確認すると`docker`に属しているのがわかります。

後で必要になるので`id`や`cat /etc/group`でグループのgidを確認しておいてください。

```
id jovyan
uid=8888(jovyan) gid=8888(jovyan) groups=8888(jovyan)
```

私のパソコンではユーザー`jovyan`はuid=8888, `jovyan`グループの`gid`は8888に設定されています。

###4.3 `jupyter`フォルダの作成
今回はマウントするフォルダは`/jupyter`にしてみました。
こちらも場所を変えたい場合は適宜変更してください。

```
sudo mkdir /jupyter
```

###4.4 フォルダの権限の変更
フォルダの所有者:グループを`jovyan:jovyan`に変更し、所有者は読み、書き、実行すべてできるようにしておきましょう。


```
sudo chown jovyan/jovyan /jupyter
sudo chmod 775 /jupyter
```

```
ls -ld /jupyter
drwxr-xr-x.   2 jovyan jovyan    6 11月 28 13:44 jupyter
```

##４+α 自分のみがJupyter Notebookを利用するなら
自分のみJupyter Notebookを利用し、マウントするフォルダも例えば`/home/yamasakih/jupyter`などの自分のホームディレクトリ以下なら、
4.1, 4.3を行う必要はありません。
`id 自分のユーザー名`で表示されるuid, gidを用いて[5](https://qiita.com/yamasakih/items/d23ac0bf773e9b1b4d9d#5-%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%99%E3%82%8B)の作業を行ってください。

```
id $USER
uid=1000(yamasakih) gid=1000(yamasakih) groups=1000(yamasakih),10(wheel),994(docker)
```

##5. 仮想環境を起動する
最後に以下のように実行し、仮想環境を起動します。

```
docker run -e GRANT_SUDO=yes -e NB_UID=8888 -e NB_GID=8888 -d -p 8888:8888 --rm -v /jupyter:/home/jovyan/work --user root --name jupyter-notebook jupyter/scipy-notebook start-notebook.sh --NotebookApp.password='sha1:4daaadded8f2:6d42d26960204e16891c7aba6725edbd52fe4b12'
```

長いので改行してみます。

```
docker run -e GRANT_SUDO=yes -e NB_UID=8888 -e NB_GID=8888 -d -p 8888:8888 \
    --rm -v /jupyter:/home/jovyan/work --user root --name jupyter-notebook \
    jupyter/scipy-notebook start-notebook.sh --NotebookApp.password='sha1:4daaadded8f2:6d42d26960204e16891c7aba6725edbd52fe4b12'
```

各オプションを説明すると

- -e GRANT_SUDO=yes  
仮想環境内で`jovyan`が`sudo`を実行できるようにしています。
- -e NB_UID=8888
uid=8888として実行する。マウントしたフォルダにデータを保存するときも所有者がuid=8888となります。
今回はユーザー`jovyan`のuidが8888なので`-e NB_UID=8888`としています。

- -e NB_GID=8888
gid=8888として実行する。マウントしたフォルダにデータを保存するときもグループがgid=8888となります。
今回はグループ`jovyan`のgidが8888なので`-e NB_GID=8888`としています。
- -d
仮想環境がバックグラウンドで実行されます。
- -p 8888:8888
ホスト上にコンテナのポートを割り当てます。
- --rm
仮想環境を`docker stop`で終了したとき自動で破棄されるようにします。
マウントされているフォルダに保存しているデータは残ります。
- -v /jupyter:/home/jovyan/work
ホスト側の`/jupyter`フォルダを仮想環境内の`/home/jovyan/work`フォルダにマウントします。
これによりJupyter Notebook内の`/home/jovyan/work`フォルダ下にデータを保存すると
`/jupyter`に保存されるようになります。
- --user root
仮想環境内のユーザーをrootとして仮想環境を実行します。
`-e GRANT_SUDO=yes`, `-e NB_UID=8888`, `-e NB_GID=8888`を設定するために必要です。
(ただしデフォルトでrootだった気もするので省略しても良いと思います)
- --name jupyter-notebook
作られる仮想環境のコンテナの名前を`jupyter-notebook`にしています。
`docker stop`で仮想環境を終了するとき少しだけ楽なので設定しています。
- jupyter/datascience-notebook
実行する仮想環境を指定しています。お好みのJupyter Notebookに合わせて変更してください。
- start-notebook.sh
仮想環境起動時に実行するスクリプトを指定しています。
`start-notebook.sh`を指定することで仮想環境内でJupyter Notebookが自動で起動するようになっています。
- --NotebookApp.password='sha1:4daaa〜'
start-notebook.shに与えている引数です。起動したJupyter Notebookにログインするためのパスワードを設定しています。
**[3](https://qiita.com/yamasakih/items/d23ac0bf773e9b1b4d9d#3-%E3%83%91%E3%82%B9%E3%83%AF%E3%83%BC%E3%83%89%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)で決めたパスワードを暗号化したものをsha1:も含めてこちらに入力してください。**

オプションを詳しく確認したい際はこちらをご覧ください。
[jupyter/docker-stacks/tree/master/base-notebook](https://github.com/jupyter/docker-stacks/tree/master/base-notebook)

上記のコマンドを実行し、2〜3分待つとJupyter Notebookが起動します。
サーバーのIPアドレスを`ip address`などで確認し、
`http://xxx.xxx.xxx.xxx:8888`にユーザー側のパソコンからアクセスするとJupyter Notebookへのログイン画面が表示されます。
（xxx部分はサーバーのIPアドレス)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/130169/89e41581-f187-c67f-c46f-ab8fa113f23f.png)

パスワード入力欄に[3](https://qiita.com/yamasakih/items/d23ac0bf773e9b1b4d9d#3-%E3%83%91%E3%82%B9%E3%83%AF%E3%83%BC%E3%83%89%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)で設定したパスワードを入力しエンターを押すとログインできます。
(sha1:4daaa〜の方でなく暗号化前の文字列を入れましょう)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/130169/2dcfba06-242f-3347-63bc-1f9ae306638d.png)

##5+α 正常に起動しない場合
なかなか起動しない場合は`-d`オプションを外して実行し、ターミナルに処理が表示されるようにすると良いと思います。
私のパソコンの場合`Set jovyan UID to: 8888`というところがすごい時間がかかります。

```
[yamasakih@localhost /]$ docker run -e GRANT_SUDO=yes -e NB_GID=8888 -e NB_UID=8888 -p 8888:8888 --rm -v /jupyter:/home/jovyan/work --user root --name jupyter-notebook jupyter/scipy-notebook start-notebook.sh --NotebookApp.password='sha1:4daaadded8f2:6d42d26960204e16891c7aba6725edbd52fe4b12'
Set username to: jovyan
usermod: no changes
Set jovyan UID to: 8888
Set jovyan GID to: 8888
Granting jovyan sudo access
Execute the command: jupyter notebook --NotebookApp.password=sha1:4d295dded8f2:6d42d26960204e16891c7aba6725edbd52fe4b29
[I 06:27:05.150 NotebookApp] Writing notebook server cookie secret to /home/jovyan/.local/share/jupyter/runtime/notebook_cookie_secret
[W 06:27:05.418 NotebookApp] WARNING: The notebook server is listening on all IP addresses and not using encryption. This is not recommended.
[I 06:27:05.441 NotebookApp] JupyterLab alpha preview extension loaded from /opt/conda/lib/python3.6/site-packages/jupyterlab
[I 06:27:05.444 NotebookApp] Serving notebooks from local directory: /home/jovyan
[I 06:27:05.444 NotebookApp] 0 active kernels
[I 06:27:05.444 NotebookApp] The Jupyter Notebook is running at:
[I 06:27:05.444 NotebookApp] http://[all ip addresses on your system]:8888/
[I 06:27:05.444 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
```

このような画面になればJupyter Notebookが正常に準備されている状態です。
このような画面にならない場合はオプションが正しく入力されているか確認しましょう。

##6. 仮想環境の終了
仮想環境の終了は以下のコマンドでできます。

```
docker stop jupyter-notebook
```

`jupyter-notebook`の部分は[5](https://qiita.com/yamasakih/items/d23ac0bf773e9b1b4d9d#5-%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%99%E3%82%8B)でオプション`--name`で指定したものに適宜変更してください。

オプション`-d`をつけなかった場合はターミナル上で`Ctrl + C`を押すと

```
[I 06:27:05.444 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
^C
Session terminated, terminating shell...[I 06:34:13.592 NotebookApp] Interrupted...
[I 06:34:13.592 NotebookApp] Shutting down 0 kernels
 ...terminated.
```

と表示され終了します。

いずれの場合もオプション`--rm`を設定しているので終了すると同時に破棄されます。

```:stop前
[yamasakih@localhost /]$ docker ps -a
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                    NAMES
af262558dfb4        jupyter/scipy-notebook   "tini -- start-not..."   3 minutes ago       Up 3 seconds        0.0.0.0:8888->8888/tcp   jupyter-notebook
```

ちゃんと破棄されているので何も表示されません。

```:stop後
[yamasakih@localhost /]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

#最後に
以上でサーバーのDockerで起動したJupyter Notebookを他のパソコンからアクセスできるようにした話を終わります。
備忘録としてまとめましたが、同じ作業をしようとしている方の手助けになれば幸いです。
DockerやJupyter Notebookを使いこなして解析ライフを楽しみましょう！

# 参考
最後に参考にさせていただいた記事を掲載いたします。ありがとうございました。
[CentOS7にJupyter Notebookを導入](https://qiita.com/hiroseabook/items/1da01ea439b01c1eb48c) (4.3, 4.4の項を参考にしました)
[Linuxにユーザーを追加する](https://qiita.com/Tooooomin/items/c063cee80227a5f1d95b)
[jupyter/docker-stacks/tree/master/base-notebook](https://github.com/jupyter/docker-stacks/tree/master/base-notebook) (オプションの設定を確認しました)