JupyterHubをAWS上に構築する
25hoursの宮尾です。JupyterHubの構築案件がありましたので、その構成と構築手順について解説します。
JupyterHubは1つのサーバー上で複数人数のJupyter Notebook環境を提供するためのソフトウェアです。
JupyterHubは主に3つのサブシステムから構成されます。
認証機能や各ユーザー空間でのJupyter Notebookのプロセスを管理するHub、Jupyter Notebookプロセス、そして、HubとNotebookへのアクセスを管理するProxyです。
今回はこのJupyterHubを利用し、LDAPで管理されたユーザーがJupyter Notebook環境を利用できるようにすることが目標です。
構成を検討した結果下図のようになりました。
今回の案件ではデータがPostgreSQL上にあるので図に記載してありますが、本記事の内容には特に関係ないので無視してもらって大丈夫です。
JupyterHubにはロードバランサー経由でアクセスを行い、HTTPS化もロードバランサーで対応しています。
この構成ではJupyterHubはパブリックIPを持つ必要がなくなるので、プライベートサブネットに配置し、外部との通信はNAT Gatewayを経由して行います。
LDAPは複数台構成ですが、JupyterHubのLDAPプラグインは複数のサーバーに対応していないため、DNSを切り替えることによりフェイルオーバーを行います。そのためInternal DNSを用意しています。
内部サーバーの設定などは全て踏み台サーバーを経由して行っています。
このようにごく標準的なAWS上の構成ではありますが、せっかくですのでJupyterHubのインストール手順をまとめておきたいと思います。
1. EC2インスタンスを作成
Amazon Linux 2を利用します。ドキュメントを読む限り必要なサーバースペックは特に記載されていませんでした。JupyterHubそれ自体の負荷が高いということも無いようなので、JupyterNotebookの方の使い方次第でスペックを決定します。
今回は t3.large を選択しました。ディスク容量については、EC2であればオンライン拡張も可能なのである程度多めに用意しておけば良いでしょう。
EFSを利用してディスク容量を意識しなくて良い構成も考えましたが、バックアップを考えるとEBSのスナップショットで環境一式をバックアップしたかったため、不採用としています。
2. LDAPクライアントの設定
通常のサーバーと同じようにldapクライアントの設定を行います。
3. Anacondaのインストール
JupyterHubはAnacondaもしくはpipを用いてインストールできます。
Jupyter Notebookを利用するケースは多くの場合機械学習関連だと思いますので、それらのライブラリがあらかじめインストールされているAnaconda環境を用いるのが早いでしょう。
インストールにpyenvを用いても良いですが、サーバー管理としては直接インストールしたほうが管理しやすいため公式サイトから最新版を確認してインストールします。以下基本的にシェルの操作はec2-userで行っています。
$ wget https://repo.continuum.io/archive/Anaconda3-2018.12-Linux-x86_64.sh$ sudo bash Anaconda3-2018.12-Linux-x86_64.sh
インストール先は /opt/anaconda3
を選択します。 .bashrc
へのPATHの追記はどちらでも良いかと思います。
4. JupyterHubのインストールと設定
conda コマンドを使ってインストールします。
$ sudo /opt/anaconda3/bin/conda install -y -c conda-forge jupyterhub
$ sudo /opt/anaconda3/bin/conda install -y notebook
$ sudo /opt/anaconda3/bin/conda install -y -c conda-forge sudospawner
sudospawnerは非rootユーザーがJupyter Notebookを立ち上げるために必要なソフトウェアです。
また、今回はLDAPを利用するため、jupyterhub-ldapauthenticatorをインストールします。
$ sudo /opt/anaconda3/bin/pip install jupyterhub-ldapauthenticator
次にJupyterHubを起動するためのユーザーを作成します。非ログインユーザーで問題ありません
$ sudo useradd -s /sbin/nologin -M jupyterhub
このユーザーがJupyter Notebookを設定できるようにsudoersを設定します。LDAPで今回JupyterHubのログインを行うユーザーのGroupを jupyterhub-group
としています。
$ sudo visudo
(下記を最後に追記)
Cmnd_Alias JUPYTER_CMD = /opt/anaconda3/bin/sudospawner
%jupyterhub-group ALL=(jupyterhub) /usr/bin/sudo
jupyterhub ALL=(%jupyterhub-group) NOPASSWD:JUPYTER_CMD
ここを間違えるとユーザーがsudoでなんでもできるようになる可能性もあるので、設定後は問題ないか実際にLDAPユーザーでログインしてチェックしてください。
次にjupyterhubの設定を行います。適当なディレクトリで
$ /opt/anaconda3/bin/jupyterhub --generate-config
Writing default config to: jupyterhub_config.py
$ vim jupyterhub_config.py
を行いコメントアウトを外しながら次の設定を行っていきます。
c.Application.log_level = 'DEBUG'
c.Spawner.env_keep = ['PATH', 'PYTHONPATH', 'CONDA_ROOT', 'CONDA_DEFAULT_ENV', 'VIRTUAL_ENV', 'LANG', 'LC_ALL']
c.JupyterHub.spawner_class = 'sudospawner.SudoSpawner'
c.Spawner.notebook_dir = '~/notebooks'
c.JupyterHub.authenticator_class = "ldapauthenticator.LDAPAuthenticator"
また最後にLDAPの設定を追記します。
c.LDAPAuthenticator.server_address = "ldap.25hours.internal"
c.LDAPAuthenticator.server_port = 389
c.LDAPAuthenticator.use_ssl = False
c.LDAPAuthenticator.bind_dn_template = "cn={username},ou=People,o=Users,dc=25hours,dc=jp"
c.Authenticator.admin_users = {'miyao'}
設定が完了したら /etc/jupyterhub/jupyterhub_config.py
にファイルをコピーします。
$ sudo mkdir /etc/jupyterhub/
$ sudo cp jupyterhub_config /etc/jupyterhub/
$ sudo chown -R jupyterhub:jupyterhub /etc/jupyterhub/
systemdで自動起動の設定をします。
$ sudo vim /etc/systemd/system/jupyterhub.service
[Unit]
Description=Jupyterhub[Service]
User=jupyterhub
Environment="PATH=/opt/anaconda3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin"
ExecStart=/opt/anaconda3/bin/jupyterhub
WorkingDirectory=/etc/jupyterhub[Install]
WantedBy=multi-user.target
Environmentは必要に応じて複数行書くことができます。ここで設定した環境変数は jupyterhub_config.py のc.Spawner.env_keepに書いておくことでJupyter Notebook内で利用できます。
さて、これでJupyterHubが起動できますが、設定ファイルで c.Spawner.notebook_dir = '~/notebooks'
を設定したのでユーザーがログインしたときに自動的に ~/notebooks
が作られなくてはいけません。LDAPの設定次第ではありますが、通常の設定であれば/etc/skelを初回読み込むはずなので、
$ sudo mkdir /etc/skel/notebooks
でテンプレート用のディレクトリを作成しておきます。
これであとは起動するだけです。
$ sudo systemctl start jupyterhub
ステータスがactiveになっていることが確認できればOKです。
$ sudo systemctl status jupyterhub
エラーが出ていれば /var/log/messages
などを確認しながらエラーに対応していきます。
5. ELBの設定とログインの確認
上記はSSL設定を無視していますのでインターネット通信にはALBを間に挟みます。TargetGroupのヘルスチェックは /hub/login で問題ないでしょう。DNSの設定、SSL証明書の設定を行い、アクセスができればOKです。
ここまでの設定が全てうまく行っていればLDAPユーザーでログインできるようになっています。
ここでログインがうまくいかない場合は LDAP や sudospawner の設定がうまくいっていない可能性が高いので、 /var/log/messages
を確認して対応していきます。
6. 監視設定
JupyterHubの正常性の確認ですが、ディスク容量やCPUなどの基本的な監視以外で外形監視を念のため取り入れます。
ログインの確認をLambdaで一分に一回実行すれば良いでしょう。適当にPythonで書きます。
import urllib.parse
import urllib.requestclass NoRedirection(urllib.request.HTTPErrorProcessor):
def http_response(self, request, response):
return response
https_response = http_responsedef handler(event, context):
url = "https://jupyterhub.domain.com/hub/login?next="
params = {"username":"monitoring-user", "password" : "some-strong-password"}
params = urllib.parse.urlencode(params).encode('ascii')
cookieprocessor = urllib.request.HTTPCookieProcessor()
opener = urllib.request.build_opener(NoRedirection, cookieprocessor)
urllib.request.install_opener(opener)
req = urllib.request.Request(url)
res = urllib.request.urlopen(req, params)
if res.getcode() != 302:
raise Exception('unhandled status')
return res.getcode()
エラーが発生するとSNSに通知されSlackやメールが飛びます。
以上で基本的な設定が完了です。
JupyterHubは利用しているユーザーがPythonなどを利用できるため、サーバー内は権限の範囲で自由に利用することができてしまいます。
適切な権限管理を行うことは前提として、安全側に寄せるのであれば、ある程度限定されたユーザーが利用する環境で使用する製品と考えたほうが良さそうです。(今回はそのような案件でした。)
不特定多数が利用する場合には、ユーザーごとの環境を完全に分離する仮想化技術を利用してJupyter Notebookを起動したほうが安全でしょう。
いずれにせよ適切な権限設定をし、継続的なセキュリティアップデートを行っていくことが運用上特に重要になってきそうです。
今回はJupyterHubの構築についてご紹介しました。
25hoursはAWSなどクラウド技術を利用したインフラ構築やアプリケーションの作成を得意としています。
引き続きシステム構築に関わる様々な情報を提供していきたいと思います。