JupyterHubのpre_spawn_hookでコンテナ起動前にブートストラップを実行する

2021.10.13

データアナリティクス事業本部の鈴木です。

今回はJupyterHubのDockerSpawnerで、コンテナ起動前にブートストラップを実行する方法を紹介します。

DockerSpawnerとは

DockerSpawnerは、JupyterHub Docker Spawnerの一つで、JupyterHubで認証されたユーザーを受け取り、そのユーザーのためにDockerコンテナ内にノートブックサーバーをspawnします。

JupyterHub Docker Spawner

c.DockerSpawner.pre_spawn_hookを設定することで、コンテナ起動前に、例えば以下のようにブートストラップを設定することが可能です。

# DockerSpawner APIのドキュメントから引用した。(2021/10/10)
# https://jupyterhub-dockerspawner.readthedocs.io/en/latest/api/index.html
from subprocess import check_call
def my_hook(spawner):
    username = spawner.user.name
    check_call(['./examples/bootstrap-script/bootstrap.sh', username])

c.Spawner.pre_spawn_hook = my_hook

今回はブートストラップの例として、ユーザーごとのディレクトリがなければ作成する処理を設定してみます。

この例は、以下のIssueで取り上げられているようなケースに役立ちます。これは、Linux環境でコンテナのマウント先ディレクトリがホスト側にない場合に、Dockerがホスト側にディレクトリをJupyterLab側からアクセスできない権限で作成しているような状況です。

Permission Denied for new user volumes with custom path · Issue #160 · jupyterhub/dockerspawner

c.DockerSpawner.pre_spawn_hookを利用することで、コンテナ起動前にディレクトリ作成を自動化できます。

前提

環境

  • macOS Catalina バージョン10.15.7
  • JupyterHub 1.4.2
  • dockerspawner 12.1.0
  • Docker version 20.10.7

やってみる

pre_spawn_hookを設定する

jupyterhub_config.pyのうち、今回設定するpre_spawn_hookに関係がある箇所を確認します。

必要な作業はとても簡単で、pythonで実行したい関数を作成し、c.Spawner.pre_spawn_hookに設定するだけです。

今回の検証では、ディレクトリを作るだけだと、作ったのがDockerかJupyterHubか判断が付きにくかったので、ディレクトリにtest.txtも作成するようにしました。

jupyterhub_config.pyの抜粋

# 抜粋
from pathlib import Path
import os

# JupyterHubのユーザーがコンテナから参照するディレクトリ
notebook_work = 'JupyterHub用のディレクトリ/jupyterhub-user/'

# コンテナは、参照するディレクトリの下にさらにユーザー名のディレクトリがあるとして、そこを参照する。
c.DockerSpawner.volumes = {notebook_work + "{username}" : {'bind': "/home/jovyan/work", 'mode': 'rw'}}

def create_volume_dir_hook(spawner):
    """ 各ユーザー用のディレクトリがホスト側にないとき、ディレクトリを作成する。
    """
    # JupyterHubからログインしたユーザーのユーザー名を受け取る。
    username = spawner.user.name
    user_volume_dir_path = os.path.join(notebook_work, username)
    if not os.path.exists(user_volume_dir_path):
        os.mkdir(user_volume_dir_path)
        
        # 動作確認のため、作成したディレクトリに`test.txt`を作成する。
        Path(os.path.join(user_volume_dir_path, "test.txt")).touch()

# ブートストラップを設定する。
c.Spawner.pre_spawn_hook = create_volume_dir_hook

動作を確認する

pre_spawn_hookを設定した設定ファイルを読み込んで、JupyterHubを起動します。

jupyterhub -f jupyterhub_config.py

hubadminというユーザーを作って、JupyterHubにログインしてみます。

コンテナ側からは、/home/jovyan/worktest.txtがあることが確認でき、pre_spawn_hookで設定した処理が実行されていることが分かります。 ブートストラップでディレクトリが作成されたことの確認

また、ホスト側でコンテナと共有するディレクトリに移動し、配下のディレクトリ構造を確認すると以下のようになっており、意図通りに動作していることが確認できました。

tree ./jupyterhub-user
# jupyterhub-user
# └── hubadmin
#     └── test.txt

最後に

今回は、JupyterHubのDockerSpawnerで起動するコンテナを起動する前にブートストラップを実行する方法を確認しました。

コンテナ起動前に事前に実行しておきたい処理をpythonで定義できるので、とても便利ですね。