DockerでJupyterHub環境を構築する。

# はじめに
開発メンバーが増えたので、jupyterhubを立ち上げてユーザーごとにjupyterlabできるようにしようかな、と。これは、作業の備忘録です。

# 参考
いつものとおり、Qiita先輩方の情報で本件対応させていただきます。参考サイトは次のとおりです。場合によってはこの記事を読むよりも、以下を直接読んだ方がいいかもしれません。しかし、情報が古く最新の環境にマッチするかどうか保証できません。

参考（１）

https://qiita.com/dyamaguc/items/db1da3084e36029f20cc

参考（２）

https://www.hinomaruc.com/install-jupyterhub-on-ubuntu22-10-using-docker/

参考（３）

https://qiita.com/noguhiro2002/items/99758ad66587f24c0ef2


## この記事では、2024年3月の jupyterhub/jupyterhub Doecker Hub の latestイメージをベースにして環境構築します。

# 概要
（１）公式の docker を取得し、コンテナに入って
（２）カスタマイズ設定し、
（３）コミットして専用コンテナを作る。

# 作業開始

## 公式の dockerを取得し、コンテナに入る

取得して、bashでコンテナ内に入っちゃいます。（ポート番号は、利用中のポートと被らないよう適当に設定）
```shell
docker run -it -p 8069:8000 --name jupyterhub-xxx jupyterhub/jupyterhub bash
```

## コンテナに入ってカスタマイズ

### 必要パッケージのインストール

jupyterhub コンテナに入ると、rootユーザーでコマンド操作できます。以下、rootユーザーで実行する前提での記述になっています。

参考（１）とは異なり、apt に libnode64 なんて存在しないそうです。

```shell
apt update
apt upgrade
apt install python3 python3-pip npm nodejs vim git iputils-ping net-tools wget curl python3-dev python3-pip python3-venv python3-pycurl build-essential cmake clang libaio1 libffi-dev libfreetype-dev libpq-dev
apt install -y --no-install-recommends fonts-noto-cjk
apt autoremove -y && apt clean && rm -rf /var/lib/apt/lists/*
```

このコマンドでインストールされるnodejsは12.22です。このままでは、この後のnpmコマンドで「（古くて）サポートされていないnodejsバージョンですので使えません」みたいなメッセージがでていろいろインストールできない場合があります。まず、nvmをインストール、nvmを使ってnodejsのバージョンアップを行いましょう。


https://github.com/nvm-sh/nvm/blob/master/README.md#installing-and-updating

```shell
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
```

ターミナルをいったん閉じて開きなおします。（インストールされた環境変数を適用させます。）

```shell
nvm install Iron
```

そして、デフォルトで使うnodejsをver.20と指定します。

```shell
nvm use 20
```

次に、npm をアップデートします。

```shell
npm install npm -g
```

さて、これで参考（１）にある次の npm install が実行できます。

```shell
npm install -g configurable-http-proxy
```

```shell
python3 -m pip install --upgrade jupyterhub notebook jupyterlab
python3 -m pip install netifaces
```

### 村人第一号と村長を登録（ユーザー第一号と管理者ユーザー、ね）

（ユーザー情報の追加）
村人： testuser01
村長： testadmin

```shell
adduser testuser01
mkdir -p -m 777 /home/testuser01/notebook
chown testuser01: /home/testuser01/notebook

adduser testadmin
mkdir -p -m 777 /home/testadmin/notebook
chown testadmin: /home/testadmin/notebook
```

参考サイト（２）に倣って、sudoコマンドのインストールをします。

```shell
apt install sudo -y
```

管理者ユーザー（村長）のアカウントをsudoグループに追加します。

```shell
gpasswd -a testadmin sudo
```

### jupyterhub_config.py （jupyterhub設定ファイル）の作成

参考サイト（２）の「[jupyterhubの設定](https://www.hinomaruc.com/install-jupyterhub-on-ubuntu22-10-using-docker/#toc10)」を参考に、PAMAuthenticatorを使用したユーザー管理機能を構築します。

まず、設定ファイルの保管場所を確保。
```shell
mkdir -p /etc/jupyterhub && mkdir -p /etc/jupyter
```

作業フォルダをこの場所に移します。

```shell
cd /etc/jupyterhub
```

jupyterhubの画面上でユーザーを追加する時に実行するスクリプトを作ります。ユーザー名を画面から入力するとsudoグループに追加されたユーザーが追加されます。

シェルの変数で、パスワードは固定設定にしてます。各ユーザーはログイン後に独自のパスワードに変更してもらう・・・のかな？

```shell
cat << 'EOF' > /etc/jupyterhub/add_user.sh
#!/bin/bash

USERNAME="${1}"
PASSWORD="password"
# ユーザー追加、パスワード固定
useradd -s /bin/bash -m ${USERNAME} && echo -e "${PASSWORD}\n${PASSWORD}" | passwd ${USERNAME}
# sudoグループに追加
gpasswd -a ${USERNAME} sudo

mkdir /home/${USERNAME}/notebook
chmod 007 /home/${USERNAME}/notebook
EOF
```

このファイルの実行権限変更しておきます。
```
chmod 775 /etc/jupyterhub/add_user.sh
```

次のコマンドでjupyterhub_config.pyを作成します。

```shell
jupyterhub --generate-config
```

作成されたjupyterhub_config.pyは、コマンドを実行したフォルダにあります。
このファイルを開いて、以下の行を編集します。先にaptでインストールしたvim コマンドで作成されたコンフィグファイルを開きます。

```shell
vim jupyterhub_config.py
```

コンフィグファイルに設定対象行が用意され、コメントアウトされています。
先達の以下の情報を参考に、以下を設定編集します。
（１）ログイン後に'/lab'に遷移する設定
（２）各ユーザーのノートブック格納ディレクトリ指定
（３）adminユーザー名
（４）その他

デフォルトだと認証にはPAMAuthenticatorを使用するようになっていて、OSのユーザ＆パスワードでログインするようになっているとのことなので、（３）、（４）は既存のユーザー情報（村人、村長）を利用しましょう。

```jupyterhub_config.py
# ログイン後に http://...:8000/user/<username>/lab? へ遷移する設定（Jupyterlabが起動）
c.Spawner.default_url = '/lab'
# Jupyterlabで作成されたノートブックファイルなどが格納されるディレクトリ
c.Spawner.notebook_dir = '~/notebook'
# adminユーザのユーザ名
c.Authenticator.admin_users = {'testadmin'}

c.LocalAuthenticator.create_system_users=True
c.LocalAuthenticator.add_user_cmd=['/etc/jupyterhub/add_user.sh']
c.PAMAuthenticator.admin_groups = {'sudo'}
```

次のコマンドでnotebookコンフィグのファイルを /etc/jupyter　に保存します。
```shell
cat << 'EOF' > /etc/jupyter/jupyter_notebook_config.py
# 30日 非アクティブのJupyterサーバーのシャットダウン期限
c.NotebookApp.shutdown_no_activity_timeout = 30 * 24 * 60 * 60

# 3日 カーネルのシャットダウン期限
c.MappingKernelManager.cull_idle_timeout = 3 * 24 * 60 * 60

# 1時間 シャットダウンするかどうかの確認インターバル
c.MappingKernelManager.cull_internal = 60 * 60
EOF
```

### ログ動作の設定

jupyterhubのログファイル保存ディレクトリ作成

```shell
mkdir -p /var/log/jupyterhub
```

ログローテーションを仕掛けます。ローテーション頻度は各環境に合わせて調整してください。

```shell
cat << 'EOF' > /etc/logrotate.d/jupyterhub
/var/log/jupyterhub/jupyterhub.log {
  rotate 12
  monthly
  compress
  missingok
  notifempty
}
EOF
```

### 自動起動の設定はできなかった
JupyterHub の起動をコマンドラインで毎回やるのは面倒なので、systemd に登録して service コマンドで起動できるようにしたかったんですが・・・。Dockerでは、systemctlによる管理ができないそうです。

一応、以下のように /etc/systemd/system/jupyterhub.service ファイルを作成することで、他のサーバープロセスなどのように service jupyterhub start で起動できるようにしてみたものの、エラーが出て実行できませんでした。

```shell
cat << 'EOF' > /lib/systemd/system/jupyterhub.service
[Unit]
Description=Jupyterhub
After=syslog.target network.target

[Service]
User=root
ExecStart=/usr/local/bin/jupyterhub -f /etc/jupyterhub/jupyterhub_config.py --port 8069 --ip 0.0.0.0 >> /var/log/jupyterhub/jupyterhub.log 2>&1

[Install]
WantedBy=multi-user.target
EOF
```

```shell
ln -s /lib/systemd/system/jupyterhub.service /etc/systemd/system/jupyterhub.service

systemctl daemon-reload    # config.ファイルを再読み込み
systemctl enable jupyterhub.service    # 起動時にjupyterhub.serviceを自動起動
systemctl start jupyterhub.service    # jupyterhub.serviceをマニュアル起動
systemctl status jupyterhub.service    #jupyterhub.serviceの状態を確認。
```

### 設定終了

ここまでで、いったんDockerコンテナを commit しましょう。

docker コンテナのシェルから　exit して、OSのシェルで次のdockerコマンドを実行。

```shell
docker commit <コンテナID> jupyterhub-xxx:0.0.1
```

docker コンテナを停止します。

```shell
docker stop <コンテナID>
```



## jupyterhub をログファイル出力しながら起動させる

では、作成、commit した新コンテナでjupyterhubを起動しましょう。
まず、コンテナの起動。
```shell
docker run -it -p 8069:8000 --name jupyterhub-xxx 
```
docker attach などでコンテナに入り、コンテナのシェルで次を実行。

```shell
jupyterhub -f /etc/jupyterhub/jupyterhub_config.py >> /var/log/jupyterhub/jupyterhub.log 2>&1
```


# アクセス成功すれば終了
そして、シェル操作を離れて、Ｗｅｂブラウザを用いて Ｈｏｓｔ（ポート番号8069）にアクセス。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699890/83a84235-fac8-63d9-b7f7-7e019bfbbba0.png)

Sigin in してみます。うまくいきました。
（スクリーンショットは載せません）

# 今後
ユーザーの新規登録、利用開始の動作も確認しなくては・・・

Dockerで構築することのうまみがよくわからない。各ユーザーのnotebookがコンテナイメージの中に全部入っているので、バックアップをとるところまで面倒みるのならば簡単というところか。

管理者はときどきcommitしてコンテナイメージをどこかにコピーする、って使い方かな。