JupyterHubのminimalな構築手順

# 1. 概要

## 1.1. はじめに

本記事はPython venvもしくはセルフビルドPythonを利用したJupyterHubの構築です。なぜJupyterHubかって？ 会社では計算資源が比較的潤沢なUbuntuマシン上に複数人で利用するJupyter環境が欲しかったし、自宅では常時動いているN100ミニPC上でJupyterHub動いていればWindowsからでもChromebookからでもいつでも作業ができるから。

IPythonだけでなく、SageMath（これがメインユース）、Wolfram Engine、IJavascriptなんかを動かします。

JupyterHubの基本カーネル（JupyterLab）だけのminimalな環境が欲しいので、Anacondaは使わずにPython pipによるセットアップを実施します。基本は[JupyterHubの公式手順](https://github.com/jupyterhub/jupyterhub#using-pip)に従いますが、ベースとなるPython環境の準備やnpmの準備について少し詳しく記載します。

## 1.2. 25年4月 アップデート

本記事の公開から2年以上の時間が経過しました。  
25年4月に私が使っているUbuntu 20.04(Lubuntu 20.04)がEoLとなるので、24.04の新規導入に合わせてJupyterHubもイチから導入し直しました。作業記録を兼ねて記事を更新します。

この2年の間に、JupyterHub単体の導入手順には大きな変更はないのですが、（私個人としては）ベースとなるPythonやNode.jsの管理方法を見直しています。具体的には

<dl>
<dt>Pythonはvenvも使わず自前ビルドに変更：</dt>
<dd>
Linuxディストリビューション標準のPython自体が時間とともに古くなる（例えばUbuntu 20.04だと脆弱性対応等が行われるのは、標準バージョンであるPython 3.9のみ。古いとpipで問題を起こしやすくなる）ので、Jupyter専用の任意バージョンのPython環境を特定のディレクトリに作ってしまう。
</dd>
<dt>Node.jsもシステム標準から独立させる：</dt>
<dd>
Node.jsもaptなどのシステムコマンドから独立させて、configurable-http-proxy（JupyterHubの依存パッケージ）のためだけに特定ディレクトリにインストールする。
</dd>
</dl>

の2点です。

## 1.3. 準備の流れ

23年の記事公開時と大まかな流れは変わりませんが、Python環境・Node.js環境準備について、それぞれ2コース用意しました。手順は以下の通り。

1. JupyterHub用Pythonのセットアップ
   - venvを使ったセットアップ方法
   - 専用Pythonを自前ビルドする方法（やや玄人向け）
1. JupyterHub用Node.jsのセットアップ
   - Node.js提供バイナリtarballを特定ディレクトリにインストールする方法
   - Node.js提供debパッケージによるグローバル・セットアップ方法
1. JupyterHubのインストール
1. JupyterHubの初期設定
1. SystemdへのJupyterHubサービスの登録（オプション）

以降の手順は **Ubuntu, Debianを対象に書いていますが、aptなどのインストールコマンドが異なるだけで、その他のLinuxに限らずFreeBSDなどでも基本的な流れは同じ** です。

尚、最近ならばDockerでJupyterHubを構築する方が便利そうな気もしますが、

1. 複数ユーザーの管理そのもの
1. 複数ユーザーのデータ置き場所（Samba等のアクセシビリティ）
1. JupyterHubコンテナ外のJupyter Kernelの扱い

をきちんと設計・運用するのが面倒（コンテナの嫌な所。何年たっても状況変わらず）だったりして見送ってます。一方、Jupyter Kernel自体にはDockerコンテナが時として便利だったりするのです。


# 2. JupyterHub用Python環境のセットアップ

次の2つの方法から**どちらかを選んで**行ってください。

- システム標準Pythonにvenvを使ってセットアップする
- 専用Pythonを自前ビルドする（やや玄人向け）

システム標準Python + venvが簡単ですが、[1.2](./8f683bc9a8f4bef1d051#12-25%E5%B9%B44%E6%9C%88-%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88)にも書いた通り、システム標準Pythonが古い場合は自前ビルドも良いです。（ただし、最新Pythonをソースコードからビルドするので、ビルド用の各種ツールをインストールする必要があります）

## 2.1. システム標準Pythonにvenvを使ってセットアップする場合

venv環境について、詳しくは公式の[説明](https://docs.python.org/ja/3/library/venv.html)をご一読ください。要は**システムにインストール済みのPython環境は汚さずに閉じた場所にPythonパッケージをインストールする環境を与えるのがvenv**です。

ここでは、システム全体で利用するローカル環境（root権限ディレクトリ）にvenvを構築します。

```bash
sudo mkdir -p /opt/python                     # /opt/pythonディレクトリを作成
sudo python3 -m venv /opt/python/             # 作成したディレクトリにvenv環境を作成
cd /opt/python/bin                      
sudo ./pip3 install -U pip setuptools wheel   # venv環境で利用するpip等を最新のものにVer. UP
```

以上でvenv環境は完成。

## 2.2. 専用Pythonを自前ビルドする場合

Jupyter専用のPythonをソースコードからビルドする方法です。この方法の場合、システムに標準インストールされているPythonのバージョンを気にすることなく、自分の使いたいバージョンのPythonを自由に選択することができます。一方、やや難易度が高く、ビルド用のツール群などでディスクの容量も多少食います。

Pythonのソースコード・ビルドについては、公式の[手順](https://devguide.python.org/getting-started/setup-building/#build-dependencies)に従います。自分はLubuntu 24.04なので、Ubuntu用のガイドに従います。

1. **apt deb-srcの有効化**
   ```bash
   sudo vi /etc/apt/sources.list.d/ubuntu.sources
   ```
   でファイルを開き、**“Types:”行に“deb-src”を追加し保存**する。以下のような感じ。
   ```txt:/etc/apt/sources.list.d/ubuntu.sources
   Types: deb deb-src
   ```
   （Ubuntuは24.04では新しいapt.confのフォーマットになっており、Python公式のガイドはすでに古い）
   <br>
1. **ビルド依存パッケージのインストール**  
   ```bash
   sudo apt build-dep python3-defaults
   sudo apt install build-essential gdb lcov pkg-config \
      libbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev \
      libncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev \
      lzma lzma-dev tk-dev uuid-dev zlib1g-dev
   ```
   以上で前準備は完了。
   <br>
1. **ソースコードのダウンロードと展開**  
   一般ユーザー権限で行います。
   ```bash
   # ソースのダウンロード
   wget https://www.python.org/ftp/python/3.13.3/Python-3.13.3.tar.xz
   # Python-3.13.3.tar.xzの展開
   tar Jxf Python-3.13.3.tar.xz
   ```
   <br>
1. **configureとmake（ビルド）**  
   引き続き一般ユーザー権限で行います。  
   makeの並列実行数は自身のCPU性能に合わせて与えてください。
   ```bash
   # 展開ディレクトリに移動
   cd Python-3.13.3
   # 専用Pythonのインストール先は/opt/python
   ./configure --prefix=/opt/python --enable-optimizations
   # configureで生成したMakefileに従いビルドを実行（時間短縮のため-j8で8並列make）
   make -j8
   ```
   makeの出力を確認し、以下のように全モジュールがビルドできていることを確認します。
   ```bash
   Checked 112 modules (33 built-in, 78 shared, 1 n/a on linux-x86_64, 0 disabled, 0 missing, 0 failed on import)
   #disabled, missing, failedが全て0であることを確認する
   ```
   <br>
1. **make install（インストール）**  
   makeが完了したら、管理者権限でインストールを実施します。インストール先はconfigure時に指定した /opt/python です。  
   ```bash
   # /opt/pythonにバイナリ他をインストールする
   sudo make install

   # ソースアーカイブ、makeに使ったディレクトリはもう不要なので削除する
   cd ../
   rm -rf Python-3.13.3.tar.xz Python-3.13.3/
   ```
   <br>
1. **pipのアップデート**
   ```bash
   cd /opt/python/bin                      
   sudo ./pip3 install -U pip setuptools wheel   # 専用環境で利用するpip等を最新のものにVer. UP
   ```


# 3. JupyterHub用Node.jsのセットアップ

JupyterHubはユーザーのログインを受け付けるプロキシをNode.js（configurable-http-proxy）に頼っているので、Node.jsをセットアップする必要があります。**ここをきちんとしておかないと運用後に面倒なトラブルになりやすい**。

結論を言えば、**OS標準（Linuxディストリビューションの標準提供パッケージ）のNode.jsは使わない方がいい！ なぜならば、バージョンが古い（最新のJupyterHubが対応していない）ことが多い**から。

というわけで次の2つの方法から**どちらかを選んで**行ってください。

- Node.js提供のバイナリtarballを特定ディレクトリに展開する
- Node.js提供のdebパッケージをグローバル・セットアップする

どちらも手間はそれほど変わりません。JupyterHubの関連バイナリ（といってもconfigurable-http-proxy関連のみ）は全てOS管理パッケージから分離してローカル管理したい場合はバイナリtarballが良いです。OS標準のNode.jsを既にインストールして使っているなどの場合もバイナリtarballが良いです。

## 3.1. Node.js提供のバイナリtarballを特定ディレクトリに展開する場合

**Node.js 22.15(LTS)をインストールしたい場合：**
```bash
# スタンドアロン・バイナリのダウンロード
wget https://nodejs.org/dist/v22.15.0/node-v22.15.0-linux-x64.tar.xz
# インストールディレクトリの作成とバイナリ展開
sudo mkdir -p /opt/node-v22
sudo tar Jxf node-v22.15.0-linux-x64.tar.xz -C /opt/node-v22 --strip-components 1
# 所有者がrootになっていない場合など、必要に応じて実行する
sudo chown -R root:root /opt/node-v22
```
この場合、**nodeやnpmコマンドは /opt/node-v22/bin にインストールされます。当然ながら何もしなければPATHは通っていないことに注意します**。

## 3.2. Node.js提供のdebパッケージをグローバル・セットアップする場合

Node.js提供のdebパッケージを利用する場合（システムの標準ディレクトリにNode.jsをインストール（グローバル・セットアップ）する場合）は[NodeSource Node.js Binary Distributions](https://github.com/nodesource/distributions/blob/master/README.md)にしたがって、Node.js公式が用意してくれているバイナリを利用します。

**Node.js 22系(LTS)をインストールしたい場合：**
```bash
sudo apt install curl    # curlがまだインストールされていない人はまずはこれを実行
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - &&\
sudo apt install -y nodejs
```
この場合は、**nodeやnpmコマンドは /usr/bin にインストールされます。PATHが通っているので、コマンド名だけで実行可能**。

以上で、最低限の準備が完了しました。続いてJupyterHubをインストールします。

# 4. JupyterHubのインストール

## 4.1. configurable-http-proxyのインストール

Node package managerからJupyterHubの依存パッケージであるconfigurable-http-proxyをインストールします。
```bash
# 専用Node.js環境(/opt/node-v22)にPATHを通しつつnpm実行
# debパッケージでNode.jsをインストールした場合は /opt/node-v22/bin は無視される
sudo env PATH=/opt/node-v22/bin:${PATH} npm install -g configurable-http-proxy
```

## 4.2. JupyterHubのインストール

pipコマンドでJupyterHubをインストールします。**ただしpipコマンドは先ほど構築した専用Python環境のものを利用する**ことに注意します。

```bash
# JupyterHub単体 + 基本カーネル(JupyterLab)
sudo /opt/python/bin/pip3 install jupyterlab jupyterhub

# 他に必要なものがある場合はここでインストールする。例えば以下のような感じ
sudo /opt/python/bin/pip3 install pandas matplotlib plotly
```

以上でインストール自体は完了です。続いて初期設定に移ります。

# 5. JupyterHubの初期設定

## 5.1. コンフィグファイルの生成

作成した専用Python環境下にJupyterHubのコンフィグファイルを生成します。

```bash
cd /opt/python                                       # 専用python環境に移動
sudo mkdir -p etc/jupyterhub                         # jupyterhubディレクトリを作成する
cd etc/jupyterhub                                    # jupyterhubディレクトリに移動する
sudo /opt/python/bin/jupyterhub --generate-config    # JupyterHubコンフィグファイル(jupyterhub_config.py)を生成する。
```

以上により、/opt/python/etc/jupyterhub 配下に jupyterhub_config.py が生成されます。

## 5.2. 自己署名証明書の準備（オプション）

**JupyterHubをHTTPSで運用する場合は、SSLサーバー証明書が必要になります**。HTTPSが不要な場合（HTTP接続の運用で良い場合）は、本項はスキップできます。

最近では無料でSSLサーバ証明書を扱う色々な方法がありますが、閉じたLAN環境での運用というのもあって旧来の「自己署名証明書（オレオレ証明書）」で運用します。（ブラウザでアクセスすると警告が出るアレです）

:::note warn
外部に公開するなど、内輪で閉じた世界での利用でない場合はきちんとしたSSLサーバー証明書を準備しましょう。
:::

```bash
# ユーザー権限で証明書は作成できます。OpenSSLで証明書を作成。
# 途中で色々聞かれます。都度入力します。
openssl genrsa 2048 > jupyterhub.key
openssl req -new -key jupyterhub.key > jupyterhub.csr
openssl x509 -req -days 3650 -signkey jupyterhub.key < jupyterhub.csr > jupyterhub.cert
```

作成した証明書 "jupyterhub.key", "jupyterhub.cert" をJupyterHub設定ディレクトリに配置します。

```bash
sudo mkdir -p /opt/python/etc/jupyterhub/keys
sudo cp jupyterhub.key /opt/python/etc/jupyterhub/keys/
sudo cp jupyterhub.cert /opt/python/etc/jupyterhub/keys/
```

## 5.3. コンフィグファイルの調整

生成したコンフィグファイルを自身の環境に合わせて調整します。私の環境では以下のパラメータをコメントから外して設定します。

```python:/opt/python/etc/jupyterhub/jupyterhub_config.py
# ローカルIPアドレスをJupyterHubに使用する
c.JupyterHub.ip = '0.0.0.0'
# 以下の2つはHTTPSアクセス用（設定しない場合は自動的にHTTPアクセスになったはずだが…）
c.JupyterHub.ssl_cert = '/opt/python/etc/jupyterhub/keys/jupyterhub.cert'
c.JupyterHub.ssl_key = '/opt/python/etc/jupyterhub/keys/jupyterhub.key'
# 専用Python環境にPATHが通っていない場合は、Spawnerに実行パスを教える
c.Spawner.cmd = ['/opt/python/bin/jupyterhub-singleuser']
# JupyterLabを使うように指定する
c.Spawner.default_url = '/lab'
# JupyterHubで利用する個人ディレクトリのパスを指定する
c.Spawner.notebook_dir = '~/notebook'
```

さらに初期ユーザーを設定します。（私の場合は利用者が少ないのでGUIは使わずに以下ファイルに全ユーザーを列挙する運用です）

```python:/opt/python/etc/jupyterhub/jupyterhub_config.py
# 管理者アカウントを設定。OSのユーザーアカウントで指定する。
c.Authenticator.admin_users = {'yksantaro'}
# 利用者アカウントを設定。OSのユーザーアカウントで指定する。
c.Authenticator.allowed_users = {'yksantaro'}
```

以上で準備完了です。起動して上手く動くか確認します。

## 5.4. 起動確認

まず最初に**ユーザーのホームディレクトリにc.Spawner.notebook_dirに設定したディレクトリ（上記設定では ~/notebook）が存在するか確認**します。存在しない場合はJupyterHubからのログイン時にエラー停止してしまうので作成しておきます。

```bash
cd              # ユーザーのホームディレクトリに移動
mkdir notebook  # notebookディレクトリを作成しておく
```

JupyterHubを起動します。

```bash
# コンフィグファイルの配置ディレクトリに移動
cd /opt/python/etc/jupyterhub
# 専用Node.js環境にPATHを通しつつJupyterHubを起動
# debパッケージでNode.jsをインストールした場合は /opt/node-v22/bin は無視される
sudo env PATH=/opt/node-v22/bin:${PATH} /opt/python/bin/jupyterhub
```

コンソールにメッセージがずらずらと出力されます。  
Webブラウザから **https://{JupyterHubのIPアドレス}:8000/ （あるいは http://{JupyterHubのIPアドレス}:8000/）** にアクセスしてログインします。正常にログインできれば設定は上手く行きました。

もし、**うまく動いていない場合はコンソールに出力されるメッセージを手寧に確認します**。大抵はPythonのエラーメッセージが出ていると思います。メッセージを手掛かりにトラブルシュートします。

:::note info
configurable-http-proxyやSpawner周辺でエラーとなることが多い。あとはPermission関係とか…
:::

動作確認ができれば、コンソールにてCtrl-Cを押下してJupyterHubを停止します。

# 6. SystemdへのJupyterHubサービスの登録（オプション）

多くの場合JupyterHubは常時動いているサーバー扱いとなるでしょうから、Systemdにサービス登録しておくと便利です。Linuxの起動と同時にJupyterHubも起動するように設定します。

## 6.1. Systemdユニットファイル作成・リンク作成

先ほどの専用Python環境内にSystemdユニットファイルを作成します。

```bash
sudo mkdir -p /opt/python/etc/jupyterhub/systemd  # Systemd用ディレクトリ作成
cd /opt/python/etc/jupyterhub/systemd             # 移動
sudo touch jupyterhub.service                     # Systemdユニットファイル作成
```

エディタで jupyterhub.service を以下のように編集します。手動で起動したときにコンソールに出ていたログはすべて /var/log/jupyterhub.log に保存するようにしています。（各自調整してください。**特に専用Python、専用Node.jsのインストール先に合わせたEnvironmenのPATHの再調整をお忘れなく！**）

```systemd:/opt/python/etc/jupyterhub/systemd/jupyterhub.service
[Unit]
Description=JupyterHub
After=syslog.target network.target

[Service]
User=root
WorkingDirectory=/opt/python/etc/jupyterhub
Environment="PATH=/opt/python/bin:/opt/node-v22/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"
ExecStart=/opt/python/bin/jupyterhub
StandardOutput=file:/var/log/jupyterhub.log
StandardError=file:/var/log/jupyterhub.log

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

ファイルの作成が完了したら、Systemdの標準ディレクトリから見えるようにリンクを作成します。

```bash
cd /etc/systemd/system
sudo ln -s /opt/python/etc/jupyterhub/systemd/jupyterhub.service
```

## 6.2. Systemdサービス登録・開始

```bash
sudo systemctl daemon-reload
sudo systemctl enable jupyterhub.service  # JupyterHubサービスの有効化
sudo systemctl start jupyterhub.service   # JupyterHubサービスの開始
sudo systemctl status jupyterhub.service  # JupyterHubサービスの状態確認
```

以上により、サービス登録・開始ができました。  
OSを再起動し、JupyterHubが正しく起動していることを確認できればすべて完了です。


お疲れ様でした！