Windows10のPCに分析環境（VirtualBox／Vagrant＋Docker＋JupyterHub／JupyterLab）を作ってみた

# はじめに

過去の記事([※1](https://qiita.com/Blaster36/items/c3abf6650c9cc025a684)）では、組織運用に用いる分析環境を整備した。
その際、Kubernetesを構築したり、ActiveDirectoryを構築したり、NFSを構築したり、と準備に時間を要した（複数の仮想サーバを準備するのも正直めんどくさい）。

というわけで、もっと気軽に**[JupyterHub](https://jupyter.org/hub)**を利用した分析環境を整備できないかと思い、本稿では「コンテナ実行環境（Docker）を有する仮想サーバ1台に集約した方法」を紹介してゆく。

## 本稿で紹介すること

- OpenLDAP／FusionDirectoryの起動、環境設定
- JupyterHub／JupyterLabの環境設定
- Dockerコンテナイメージのビルド
- JupyterHub／JupyterLabの起動

[hrektts/fusiondirectory-openldap](https://hub.docker.com/r/hrektts/fusiondirectory-openldap)
[hrektts/fusiondirectory](https://hub.docker.com/r/hrektts/fusiondirectory)
[jupyterhub | Read the Docs](https://readthedocs.org/projects/jupyterhub/)
[JupyterHub &#8212; JupyterHub 1.0.0 documentation](https://jupyterhub.readthedocs.io/en/1.0.0/)
[GitHub - jupyter/docker-stacks: Ready-to-run Docker images containing Jupyter applications](https://github.com/jupyter/docker-stacks)
[GitHub - jupyterhub/jupyterhub: Multi-user server for Jupyter notebooks](https://github.com/jupyterhub/jupyterhub)
[GitHub - jupyterhub/ldapauthenticator: LDAP Authenticator Plugin for Jupyter](https://github.com/jupyterhub/ldapauthenticator)
[GitHub - jupyterhub/dockerspawner: Spawns JupyterHub single user servers in Docker containers](https://github.com/jupyterhub/dockerspawner)

## 本稿で紹介しないこと

- Windows10のセットアップ（含むWebブラウザ）　※Windows10 Pro Ver.21H1
- VirtualBoxのインストール　※VirtualBox 6.1.18を使用
- Vagrantのインストール　※Vagrant 2.2.9を使用
- Ubuntuのインストール　※Ubuntu 18.04.4 LTSを使用
- Dockerのインストール、仕組み、操作　※Docker Community Edition 20.10.7を使用
- Jupyterの全般
- MeCabの全般

# 環境構築
大きく、4ステップです。
1. OpenLDAP／FusionDirectoryの起動、環境設定
2. JupyterHub／JupyterLabの環境設定
3. Dockerコンテナイメージのビルド
4. JupyterHub／JupyterLabの起動

## 事前準備
以下、仮想OSの環境設定ファイルです。**VirtualBox／Vagrant**を利用します。

```shell:Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/bionic64"
  config.vm.box_download_insecure = true
  config.vm.box_version = "20200525.0.0"
  config.vm.hostname = "ubuntu18"
  config.vm.box_check_update = false
  config.vm.network "forwarded_port", guest: 8888, host: 8888
  config.vm.network "forwarded_port", guest: 389, host: 389
  config.vm.network "forwarded_port", guest: 80, host: 80
  config.vm.network "forwarded_port", guest: 8000, host: 8000
  config.vm.network "forwarded_port", guest: 8001, host: 8001
  config.vm.network "forwarded_port", guest: 8081, host: 8081
  config.disksize.size = '50GB'

  config.vm.provider "virtualbox" do |vb|
    vb.gui = false
    vb.cpus = "8"
    vb.memory = "16384"
  end
end
```

以下、仮想OSに設定したポートフォワーディングルールです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/e4cacb05-6e6e-3e7c-dd2c-cf03b5996dbf.png)

また、Dockerネットワーク「jupyterhub」を作成します。形態は**Bridge**です。

```shell:
$ docker network ls
NETWORK ID     NAME         DRIVER    SCOPE
a39e0fdf712d   bridge       bridge    local
37c027508261   host         host      local
958e6364260c   jupyterhub   bridge    local
70f2a751e6b0   none         null      local
```

## 1. OpenLDAP／FusionDirectoryの起動、環境設定

### 1.1. OpenLDAP／FusionDirectoryの起動

以下のリンクに記載された手順を見本とし、起動します。
[hrektts/fusiondirectory-openldap](https://hub.docker.com/r/hrektts/fusiondirectory-openldap)
[hrektts/fusiondirectory](https://hub.docker.com/r/hrektts/fusiondirectory)

以下、環境設定（環境変数の定義）を含む起動コマンドです。

```shell:
$ docker run --name ldap -p 389:389 \
  -e LDAP_ORGANISATION="OpenLDAP for ldapauthenticator.LDAPAuthenticator" \
  -e LDAP_DOMAIN="hoge.com" \
  -e LDAP_ADMIN_PASSWORD="ldapadminp@ssw0rd" \
  -e FD_ADMIN_PASSWORD="fdadminp@ssw0rd" \
  -d hrektts/fusiondirectory-openldap:1.1.9-1.2-1

$ docker run --name fd -p 80:80 --link ldap:ldap \
  -d hrektts/fusiondirectory:0.2.0
```

Webブラウザで http://127.0.0.1/fd/ にアクセスし、上述の「**FD_ADMIN_PASSWORD**」に指定した文字列でログインできればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/7551d3f2-571f-e5b1-cf67-b0c2171b0cdf.png)

### 1.2. 部署（Department）を作成

「jhub」という名前の部署を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/e7b398de-f885-f0d4-6fd8-2563758dd1dc.png)

### 1.3. ユーザ（User）を作成

部署「jhub」の配下に、ユーザを作成します。今回は、2名を登録（一方は管理者ユーザ、他方は一般ユーザを想定）します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/710e381f-3e2a-083b-d58b-aec0e1fa456b.png)

## 2. JupyterHub／JupyterLabの環境設定

以下のリンクに記載された項目を参考に、JupyterHub／JupyterLabの振る舞いを決めて、**環境設定ファイル（jupyterhub_config.py）**に記述します。
[jupyterhub | Read the Docs](https://readthedocs.org/projects/jupyterhub/)
[JupyterHub &#8212; JupyterHub 1.0.0 documentation](https://jupyterhub.readthedocs.io/en/1.0.0/)
[GitHub - jupyter/docker-stacks: Ready-to-run Docker images containing Jupyter applications](https://github.com/jupyter/docker-stacks)


以下、環境設定ファイル（jupyterhub_config.py）です。Jupyterの公式イメージをBaseとしてカスタマイズしたコンテナイメージを、起動（Spawn）対象のコンテナイメージ選択肢に指定します。
※選択肢を増やしたいときは、準備したコンテナイメージを鑑みて、<option>～</option>の記載を追加する。

```python:jupyterhub_config.py
c.JupyterHub.authenticator_class = 'ldapauthenticator.LDAPAuthenticator'
c.LDAPAuthenticator.valid_username_regex = r'^[a-z0-9][.a-z0-9_-]*$'
c.LDAPAuthenticator.use_ssl = False
c.LDAPAuthenticator.server_address = '10.0.2.15'
c.LDAPAuthenticator.server_port = 389
c.LDAPAuthenticator.bind_dn_template = 'uid={username},ou=people,ou=jhub,dc=hoge,dc=com'
c.LDAPAuthenticator.lookup_dn = False

c.JupyterHub.hub_connect_ip = 'jupyterhub'
c.JupyterHub.hub_ip = '0.0.0.0'
c.Authenticator.admin_users = {'admin', 'jupyter'}

from dockerspawner import DockerSpawner

class DemoFormSpawner(DockerSpawner):
  def _options_form_default(self):
    default_stack = 'jupyter/minimal-notebook'
    return """
    <label for="stack">Select your desired stack</label>
    <select name="stack" size="1">
      <option value="jupyter/minimal-notebook:2343e33dec46">jupyter/minimal-notebook</option>
      <option value="jupyter/minimal-notebook:2343e33dec46-custom">jupyter/minimal-notebook(w/MeCab)</option>
    </select>
    """.format(stack=default_stack)

  def options_from_form(self, formdata):
    options = {}
    options['stack'] = formdata['stack']
    container_image = ''.join(formdata['stack'])
    print('SPAWN: ' + container_image + 'IMAGE')
    self.image = container_image
    return options

c.JupyterHub.spawner_class = DemoFormSpawner

notebook_dir = '/home/jovyan'
c.DemoFormSpawner.notebook_dir = notebook_dir
c.DemoFormSpawner.volumes = {'jupyterhub-user-{username}':notebook_dir}
c.DemoFormSpawner.remove = True
c.DemoFormSpawner.default_url = '/lab'
c.DemoFormSpawner.start_timeout = 300
c.DemoFormSpawner.http_timeout = 120
c.DemoFormSpawner.network_name = 'jupyterhub'
```

## 3. Dockerコンテナイメージのビルド

### 3.1. JupyterHubのカスタマイズ

以下のリンクに記載された手順を参考に、Baseコンテナイメージに対してPythonライブラリをインストールします。
[GitHub - jupyterhub/jupyterhub: Multi-user server for Jupyter notebooks](https://github.com/jupyterhub/jupyterhub)
[GitHub - jupyterhub/ldapauthenticator: LDAP Authenticator Plugin for Jupyter](https://github.com/jupyterhub/ldapauthenticator)
[GitHub - jupyterhub/dockerspawner: Spawns JupyterHub single user servers in Docker containers](https://github.com/jupyterhub/dockerspawner)

以下、コンテナイメージのビルド用環境設定ファイルです。JupyterHub／JupyterLabの振る舞いを決める環境設定ファイル（jupyterhub_config.py）を**コンテナイメージの中に**埋め込んでいます。

```shell:Dockerfile.hub
FROM jupyterhub/jupyterhub:1.0

RUN python3 -m pip install IPython==7.16.1
RUN python3 -m pip install jupyter_client==6.1.12
RUN python3 -m pip install jupyterhub==1.0.0
RUN python3 -m pip install notebook==6.4.0
RUN python3 -m pip install jupyterlab==1.2.14
RUN python3 -m pip install dockerspawner==12.0.0
RUN python3 -m pip install PyOpenSSL==19.1.0 ldap3==2.7 jupyterhub-ldapauthenticator==1.3.0

WORKDIR /srv/jupyterhub
COPY ./jupyterhub_config.py /srv/jupyterhub/

EXPOSE 8000 8001 8081

CMD ["jupyterhub", "-f", "/srv/jupyterhub/jupyterhub_config.py", "--debug"]
```

以下、ビルドコマンドです。

```shell:
$ docker image build -t jupyterhub/jupyterhub:1.0-custom -f Dockerfile.hub .
```

### 3.2. JupyterLabのカスタマイズ

過去の記事([※2](https://qiita.com/Blaster36/items/84b1af5802f150017d84)）に記載された手順を参考に、Baseコンテナイメージに対して**MeCab**の利用に必要なOSパッケージおよびPythonライブラリをインストールします。

以下、コンテナイメージのビルド用環境設定ファイルです。

```shell:Dockerfile.lab
FROM jupyter/minimal-notebook:2343e33dec46

USER jovyan
RUN python3 -m pip install pip==21.2.2
RUN python3 -m pip install notebook==6.0.2
RUN python3 -m pip install nbformat==5.1.3
RUN python3 -m pip install nbconvert==5.6.0
RUN python3 -m pip install nbclient==0.5.3
RUN python3 -m pip install tornado==6.0.3
RUN python3 -m pip install jupyter_client==6.1.12
RUN python3 -m pip install jupyter-core==4.6.1
RUN python3 -m pip install jupyterhub==1.0.0
RUN python3 -m pip install jupyterlab==1.2.3
RUN python3 -m pip install jupyterlab-server==1.0.6
RUN python3 -m pip install ldap3==2.7
RUN python3 -m pip install mecab-python3==1.0.1

USER root
RUN cat /etc/lsb-release
RUN apt-get update
RUN apt-get install -y iputils-ping net-tools
RUN apt-get install -y mecab libmecab-dev
RUN apt-get install -y mecab-ipadic mecab-ipadic-utf8
RUN apt-get install -y libc6-dev build-essential
```

以下、ビルドコマンドです。

```shell:
$ docker image build -t jupyter/minimal-notebook:2343e33dec46-custom -f Dockerfile.lab .
```

## 4. JupyterHub／JupyterLabの起動

以下のリンクに記載された手順を見本とし、JupyterHubを起動しました。
ただし、DockerSpawnerを利用する（≒各ユーザ用のJupyterrLabもコンテナとして起動する）ため、-vオプションを使って仮想OSの**Dockerソケット**をJupyterHubのコンテナに割り当てます。
[JupyterHub &#8212; JupyterHub 1.0.0 documentation](https://jupyterhub.readthedocs.io/en/1.0.0/)

以下、環境設定（環境変数の定義）を含む起動コマンドです。

```shell:
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -p 8000:8000 -p 8001:8001 -p 8081:8081 \
  -d --name jupyterhub --net jupyterhub jupyterhub/jupyterhub:1.0-custom
```

尚、ここまでの手順で起動済みのコンテナ一覧は以下のようになります。

```shell:
$ docker ps -a
CONTAINER ID   IMAGE                                          COMMAND                  CREATED         STATUS             PORTS                                                                                                      NAMES
c2d58d39d794   jupyterhub/jupyterhub:1.0-custom               "jupyterhub -f /srv/…"   31 hours ago    Up About an hour   0.0.0.0:8000-8001->8000-8001/tcp, :::8000-8001->8000-8001/tcp, 0.0.0.0:8081->8081/tcp, :::8081->8081/tcp   jupyterhub
9dcb256e0f0a   hrektts/fusiondirectory:0.2.0                  "/sbin/entrypoint.sh…"   34 hours ago    Up About an hour   0.0.0.0:80->80/tcp, :::80->80/tcp, 443/tcp                                                                 fd
9d85d1f3233f   hrektts/fusiondirectory-openldap:1.1.9-1.2-1   "/container/tool/run"    34 hours ago    Up About an hour   0.0.0.0:389->389/tcp, :::389->389/tcp, 636/tcp                                                             ldap
```

Webブラウザで http://127.0.0.1:8000/hub/login にアクセスし、JupyterHubのログイン画面が表示できればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/e23e707e-abfa-3486-8e4a-0126f3e572a1.png)

# では早速、動作確認

予めOpenLDAPに登録しておいたユーザ「jupyter」でログイン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/4ff83eed-23ad-ecfd-d2d4-d0ff48ccda36.png)

コンテナイメージ選択起動画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/c64ee2ba-e61a-c4f4-abad-bab533bd79b5.png)

JupyterLab初期表示画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/8e5a9ac8-b8e9-42b7-3894-51059f19aa9b.png)

MeCab動作画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/d3596358-cb35-4f28-e7fb-3d5a4dc04ff2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/60c7c9f7-d45e-a2c1-3dd9-9876c8d78d51.png)

## おまけ：永続ボリューム

NFSを準備しなかったが、永続ボリュームはJupyterHubで用意されます。
仮想OS上に、Dockerボリュームがユーザ単位で作成され、各々の配下にJupyterLab上の操作で作成されたファイル群が保管されます。

```shell:
$ docker volume ls
DRIVER    VOLUME NAME
local     jupyterhub-user-admin
local     jupyterhub-user-jupyter

$ docker volume inspect jupyterhub-user-jupyter
[
    {
        "CreatedAt": "2021-08-06T16:25:43Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/jupyterhub-user-jupyter/_data",
        "Name": "jupyterhub-user-jupyter",
        "Options": null,
        "Scope": "local"
    }
]
```

# まとめ
Kubernetes／複数台の仮想OSは使わず、Docker／1台の仮想OSを使い、JupyterHub／JupyterLabの分析環境を仕立てる方法を紹介しました。

（JupyterHub／JupyterLabの振る舞いを決める環境設定ファイル（jupyterhub_config.py）をコンテナイメージの中に埋め込んでいるので、振る舞いを変えたいときにコンテナイメージをビルドする必要があるのは、何とか回避して楽できるといいな。。。）