実験用Jupyter NotebookをDockerで作成する

Chromebook環境でPythonの実験用にJupyter Notebookを使い、その使いやすさに感心して、Windows上にも環境を構築したくなりました。

とはいえほかの環境を汚してしまいたくないので、DockerでJupyter Notebookのコンテナを作り、その中に実験環境を構築することに。

ついでにインストールしたモジュールの履歴や、ファイルをWindows PC上と同期できるようにし、コンテナを落としてもファイルが消えない(永続化)できるようにしました。


# 環境

* Windows 10 Home(10.0.18362)

## 下準備

Dockerのインストールを行います(直前にPCをリフレッシュしていたのでおさらいも兼ねて)。ChocolateyにDocker-Toolboxが入っているのでここからインストールすると非常に楽です。

```powershell
choco install -y docker-toolbox
```

インストールが終了したら、VirtualBoxの設定画面から、Windows上のどこかのフォルダを「/share」という名前で共有フォルダに設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14499/2c181ab8-d224-7897-4faa-88ca67529cec.png)

## docker-compose.ymlを作成する

最近はDockerfileを直接作らず、docker-compose.ymlというファイルを作り、docker-composeコマンドでコンテナを構築するのが流行っている(？)そうです。

これを使うとポートの開放やホストOSとの共有フォルダ設定など、`docker`コマンドのオプションで指定していたことの一部をまとめることができるので、非常に楽です。

```yaml:docker-compose.yml
version: "3.7"
services:
  notebook:
    image: jupyter/base-notebook 
    ports:
      - target: 8888
        published: 8888
        protocol: tcp
        mode: host
    volumes:
      - type: bind
        source: /share/data
        target: /home/jovyan/work
      - type: bind
        source: /share/pipenv
        target: /home/jovyan/.local
```

## 起動スクリプトを書く

`docker-compose`は多くのコマンドライン処理をまとめることができますが、かといって起動用のバッチが不要になるわけではありません。

今回はコンテナで起動したJupyter Notebookをブラウザで開く処理も追加します。

```powershell:run.ps1
docker-compose run -d --service-ports notebook
Sleep 1
if ((docker-machine ssh default docker ps) -join "\n" -match "jupyter_notebook[\w\d_]+") {
  $cid = $Matches[0]
  if ((docker-machine ssh default docker exec $cid jupyter notebook list) -join "\n" -match "\?token=(\w+)") {
    $ip = docker-machine ip
    $token = $Matches[1]
    start ("http://{0}:8888/?token={1}" -f $ip, $token)
  }else {
    echo "Notebook Open Failed"
  }
}else {
  echo "Container Boot Failed"
}
```

これらを同一のフォルダに配置し、DockerのホストOSが起動している状態でps1ファイルを実行すれば、Jupyter Notebookがブラウザで起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14499/2b9c42b8-ef6b-985e-a2a7-35ce4292786d.png)

ホストOSやコンテナを落としても消えてほしくないデータはworkディレクトリ配下に、`pip`コマンドでモジュールをインストールする際は、オプションに`--user`をつけることをお忘れ無く。

## その他解説

### docker-compose.ymlの各種コマンドの解説

#### image

jupyterで公開されているDockerイメージを参照します。

今回はあくまでただの実験用なので、base-notebookを使います。

用途ごとにイメージは変わるので、以下参考サイトより自分の用途に合ったコンテナを探してください

#### ports

dockerコマンドで言う`-p 8888:8888`と同等の処理です。

8888ポートを開放します。

#### volumes: /share/data

`~/work`ディレクトリを、ホストOS上の`/share/data`ディレクトリと結びつけます。

VirtualBox自身の共有設定により、このフォルダはWindowsのフォルダと同期されます。

#### volumes: /share/pipenv

`~/.local/`はpipコマンドに、オプション`--user`を指定したときのモジュールのインストール先です。

このディレクトリをWindowsと共有しておくことで、モジュールのインストール状況が永続化されます。

### PowerShellスクリプト内の処理の解説

#### docker-compose run -d --service-ports notebook

コンテナを実行します。

なお、`docker-compose`コマンドを使った場合、オプションに`--service-ports`を入れないと、ポートが外部に開放されないようですので注意。

#### docker-machine ssh default...

ホストOSのコマンドより、起動したDockerコンテナのIDを探します(戻り値は改行区切りのリストですので、かならず`-join`コマンドでテキストに変換してください)。

コンテナの名前は、[`docker-compose.yml`があるフォルダの名前]\_[`yml内に書いたサービスの名前`]\_通番になるとのことです。

#### docker-machine ssh default docker exec...

起動しているJupyter Notebookより、トークンIDを取得します。

`docker-machine ip`でIPアドレスを取得すれば、コンテナ内のJupyter NotebookにアクセスするためのURLが生成できますね。

## 参考ページ

* [pip でインストール先を指定する方法 – ためすう](https://blog.tamesuu.com/2018/09/24/81/)
* [Docker Compose \- docker\-compose\.yml リファレンス \- Qiita](https://qiita.com/zembutsu/items/9e9d80e05e36e882caaa)
* [JupyterのDockerイメージまとめ \- Qiita](https://qiita.com/kshigeru/items/ea174d6bcacc474f2a51)
* [docker\-composeを使うと複数コンテナの管理が便利に \- Qiita](https://qiita.com/y_hokkey/items/d51e69c6ff4015e85fce)
* [docker\-compose run は portをmappingしない \- フリーランチ食べたい](https://blog.ikedaosushi.com/entry/2018/07/23/191952)

## 今回作成したファイル

* [TakamiChie/docker\-jupyter\-env: DockerコンテナのJupyter実験環境](https://github.com/TakamiChie/docker-jupyter-env/blob/master/LICENSE)

## 感想

* ホストOSに接続せずに`docker-compose`コマンドなどだけでDockerがいじれるようになったのは非常に楽だし、今回のようなバッチファイルが作りやすくて助かる
    * 複数コンテナの同時起動などもできるようなので、基本的には`docker-compose`を使えばよさげ(オプションでDockerfileを指定して、コンテナごとに細かなカスタマイズを施すことも可能とのこと)
* `--service-ports`をつけないとサービスが公開できないのは割と罠っぽいし、けっこう抜けている記事も多いので要注意。ここが一番詰まった
* Chocolatey最高。