Memo Log

PANTO MAIMU 's Personal Blog

運用で使えるJupyter Notebookコンテナ構築手順まとめ

ここ1年ぐらい、"Literate Computing for Reproducible Infrastructure" という、Jupyter Notebookを使った運用オペレーションなどをやっていたりします。
今回は、このJupyter NotebookサービスのDockerコンテナのビルド手順や、起動方法について書いてみようと思います。

"Literate Computing for Reproducible Infrastructure"というのは、ざっくりいうと、Jupyter Notebookを使って実行可能なドキュメントを実現しよう...というものです。
ここ最近Ansibleを使って、スイッチ制御を含めたシステム運用をすることが流行ってきていますが、playbookの実行手順や実行結果の証跡を残す方法についてはあまり議論されていないと思っています。
この課題についての一つの解として、"Literate Computing for Reproducible Infrastructure"が有効だと考えていたりします。

"Literate Computing for Reproducible Infrastructure"の詳しい内容については、以下のスライドやブログを読んでみてください。

Jupyter notebook を用いた文芸的インフラ運用のススメ

www.slideshare.net

Literate Automation(文芸的自動化)についての考察
enakai00.hatenablog.com

環境構成概要

それでは、これからコンテナ構築手順の説明に入ります。
コンテナを構築するまえに、Jupyter Notebookコンテナを構築するノードなどについて説明します。

f:id:PANTOMAIMU:20180318194757p:plain
検証環境の構成イメージ
上の図は、今回私が検証に使っている環境の構成図です。
運用サーバ(cent-dev)は、Jupyter Notebookコンテナが動作するホストノードで、dev01、dev02、dev03は、Ansibleで制御する運用対象ノードになります。

運用サーバのOSとDockerのバージョンは以下の通りです。

OS CentOS 7.4
Docker CE 17.12
Docker Compose 1.19.0

Jupyter Notebookコンテナのビルド手順

それでは、Jupyter Notebookコンテナのビルド手順の説明を始めます。

まず、Jupyter Notebookコンテナ構築に必要なファイルを、NII(国立情報学研究所)のクラウド運用チームが公開しているGitHubから入手します。
github.com

入手は、上記のGitHubからgit cloneするか、zipファイルをダウンロードして、運用サーバ上のhomeディレクトリに展開してください。
私は、以下のようにgitを使って取得しました。

$ git clone https://github.com/NII-cloud-operation/Jupyter-LC_docker.git

取得が完了したら、以下の手順でJupyter Notebookコンテナをビルドします。
まずは取得したDockerfileをカスタマイズします。

$ cd ~/Jupyter-LC_docker
$ vi Dockerfile

普通は、このDockerfileをこのまま使うこともできるのですが、Jupyter Notebookのユーザアカウントの変更や、コンテナにパッケージを追加する手順を説明します。

最初は、Dockerfileにあるユーザアカウント生成処理の設定を変更します。

...
# Create 'bit_kun' user
ENV NB_USER bit_kun
ENV NB_UID 1000
RUN useradd -m -s /bin/bash -N -u $NB_UID $NB_USER && \
    mkdir /home/$NB_USER/.jupyter && \
    chown -R $NB_USER:users /home/$NB_USER/.jupyter && \
    echo "$NB_USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$NB_USER
...

ユーザ名称は、ENV NB_USER で設定されています。bit_kun というのは、NIIのマスコットキャラクター(?)ビットくんのことです。
ENV NB_UIDは、ENV NB_USERで指定たユーザのIDを設定します。
あとで説明しますが、ホスト側のディレクトリをコンテナにボリュームマウントするので、ここで指定するユーザ名称やIDは、このコンテナのホスト側のユーザと合わせてください。

次に、コンテナにPythonパッケージの追加です。
特に追加するものがなければこの変更は不要ですが、notebookからSQLを実行する検証がしてみたかったので、今回は追加してみます。

...
## Python kernel with matplotlib, etc...
RUN pip --no-cache-dir install jupyter && \
    pip --no-cache-dir install pandas matplotlib numpy \
                seaborn scipy scikit-learn scikit-image \
                sympy cython patsy pymysql psycopg2 \         <---- 51行目
                statsmodels cloudpickle dill bokeh h5py && \
    apt-get update && apt-get install -yq --no-install-recommends \
    git \
...
### environments for Python3
ENV CONDA3_DIR /opt/conda3
RUN cd /tmp && \
    mkdir -p $CONDA3_DIR && \
    wget --quiet https://repo.continuum.io/miniconda/Miniconda3-4.2.12-Linux-x86_64.sh && \
    echo "c59b3dd3cad550ac7596e0d599b91e75d88826db132e4146030ef471bb434e9a *Miniconda3-4.2.12-Linux-x86_64.sh" | sha256sum -c - && \
    /bin/bash Miniconda3-4.2.12-Linux-x86_64.sh -f -b -p $CONDA3_DIR && \
    rm Miniconda3-4.2.12-Linux-x86_64.sh && \
    $CONDA3_DIR/bin/conda config --system --add channels conda-forge && \
    $CONDA3_DIR/bin/conda config --system --set auto_update_conda false && \
    $CONDA3_DIR/bin/conda install --quiet --yes \
    notebook matplotlib pandas pip pymysql psycopg2 && \        <---- 112行目
    $CONDA3_DIR/bin/conda clean -tipsy
...

51行目と112行目にpymysql psycopg2を追加します。

最後に、AWSクライアントの追加です。
これも必要なければ不要です。

...
### aws client
RUN apt-get update && apt-get install -y groff && \
    pip --no-cache-dir install awscli

### Add files
RUN mkdir -p /etc/ansible && cp /tmp/ansible.cfg /etc/ansible/ansible.cfg
...

97行目あたりに、apt-getを呼び出してawscliをインストールする定義を追加します。

以上で、Dockerfileの変更が完了です。
次のコマンドを実行して、コンテナのビルドを行います。

$ sudo docker build -t sys-ope/notebook:1.0 ~/Jupyter-LC_docker/

ビルドが完了したら、以下のコマンドでイメージのビルドに成功したことを確認します。

$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
sys-ope/notebook    1.0                 357f73ee4430        7 days ago          4.64GB
debian              jessie              ce40fb3adcc6        4 weeks ago         123MB

これで、Jupyter Notebookコンテナのビルドは完了です。
次に、このコンテナを起動するための事前準備の手順を説明します。

マウント先ディレクトリ作成

ここでは、Jupyter Notebookコンテナからボリュームマウントする、ホスト側のディレクトリ作成を行います。

f:id:PANTOMAIMU:20180318194759p:plain
コンテナへのボリュームマウント構成
notebookやAnsibleのplaybookといった定義ファイルは、上の図のようにホスト側のディレクトリに配置し、コンテナからマウントして参照する方法にします。
この方が、定義ファイルのバックアップといった運用が簡単になるのと、コンテナの更新作業が簡単になるからです。
※コンテナは使い捨てなので、ステートレスな使い方をするのを前提にした方がよいと考えています。

コンテナ側のディレクトリと、その概要は以下の通り。

コンテナ側 概要
/notebooks Notebookファイルを保存するディレクト
/etc/ansible Ansibleのディレクトリ。ansible.cfgを配置する。
/home/bit_kun/.ssh Ansibleのssh実行に使用する秘密鍵を配置するディレクト

ホスト側のディレクトリはコンテナから参照されるので、Dockerfileで指定したユーザのIDと同じユーザIDになるようにしてください。

Ansible実行用鍵ファイル作成と配置

ここでは、Ansibleのssh実行に使用する秘密鍵の作成と配置を説明します。
まずは以下の手順でsshの鍵を生成します。

$ cd ~/sys-ope/.ssh
$ ssh-keygen -t rsa -f ansible_id_rsa
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again:
...

Ansibleは、パスワード付き鍵ファイルに対応していないので、鍵生成時に入力を要求されるパスワードは未入力(リターンキー入力)にしてください。
次に、生成した秘密鍵と公開鍵のユーザIDとグループIDを設定します。

$ sudo chown 1000:100 ansible_id_rsa
$ sudo chown 1000:100 ansible_id_rsa.pub

ユーザIDは、各自の環境に合わせて下さい。

Jupyter Notebookコンテナの実行

ここまでで、コンテナを起動するための事前準備が終わったので、これからJupyter Notebookコンテナを実行します。
まずはdockerコマンドで実行してみます。

$ sudo docker run -it --rm -p 8888:8888 \
> -v ~/sys-ope/notebooks:/notebooks \
> -v ~/sys-ope/ansible:/etc/ansible \
> -v ~/sys-ope/.ssh:/home/bit_kun/.ssh \
> -e PASSWORD=xxxxxx sys-ope/notebook:1.0

環境変数PASSWORDのxxxxxxは、Jupyter Notebookのログイン時に入力するパスワードを設定します。
コンテナの起動に成功すると、以下のようにJupyter Notebookの起動完了と、アクセス先のURLを出力してきます。

...
[I 07:43:37.156 NotebookApp] 0 active kernels
[I 07:43:37.156 NotebookApp] The Jupyter Notebook is running at:
[I 07:43:37.156 NotebookApp] http://[all ip addresses on your system]:8888/
...

このURLにアクセス(今回の環境では、http://10.0.0.1:8888/)すると、Jupyter Notebookのログイン画面が表示されます。

f:id:PANTOMAIMU:20180318194754p:plain
Jupyter Notebookログイン画面
環境変数PASSWORDに設定したパスワードを入力してログインすると、Jupyter Notebookのホーム画面が開きます。
f:id:PANTOMAIMU:20180318194750p:plain
Jupyter Notebookホーム 画面
これで、Jupyter Notebookコンテナのビルドと動作確認は完了です。
一旦、Ctrl-Cでコンテナを停止させ、このコンテナをdocker-composeで起動するようにします。

docker-composeの定義ファイル作成

まずはviでdocker-compose.ymlを開いて、新規に作成します。

$ vi ~/sys-ope/docker-compose.yml

内容は以下の通りです。

version: "2"

services:
    jupyter:
        image: sys-ope/notebook:1.0
        ports:
            - 8888:8888
        environment:
            TZ: JST-9
            HOST_HOSTNAME: "${HOSTNAME}"
            hostname: "${HOSTNAME}"
            PASSWORD: xxxxxx
        volumes:
            - ./ansible:/etc/ansible/
            - ./.ssh:/home/bit_kun/.ssh/
            - ./notebooks:/notebooks
        logging:
          driver: journald

PASSWORDのxxxxxxは、Jupyter Notebookのログイン時に入力するパスワードになるので、各自で決めて設定してください。

まずはdocker-compose upで起動します。

$ cd ~/sys-ope
$ sudo docker-compose up -d
Recreating sysope_jupyter_1 ... done

これで、docker-composeでJupyter Notebookコンテナが起動するようになりました。
あとは、docker-compose start、stopを使って、起動停止を行ってください。

インベントリファイルの作成

ここでは、NotebookからAnsibleを実行するための環境設定や、インベントリファイルの配置の手順を説明します。

まずは、インベントリファイルを配置するディレクトリを作成します。

$ mkdir ~/sys-ope/notebooks/ansible
$ mkdir ~/sys-ope/notebooks/ansible/inventory
$ mkdir ~/sys-ope/notebooks/ansible/inventory/host_vars
$ mkdir ~/sys-ope/notebooks/ansible/inventory/group_vars

次に、ansible.cfgを設定します。

$ vi ~/sys-ope/ansible/ansible.cfg

ansible.cfgには、以下の設定を行います。

[defaults]
host_key_checking = False
forks = 3
record_host_keys = False
remote_user = ansible

private_key_file = /home/bit_kun/.ssh/ansible_id_rsa
inventory = /notebooks/ansible/inventory/inventory.ini

次は、以下の構成でインベントリファイルを作成します。

~/sys-ope/notebooks/ansible
|--inventory
|  |--group_vars
|  |--host_vars
|  |  |--cent-dev.yml
|  |  |--dev01.yml
|  |  |--dev02.yml
|  |  |--dev03.yml
|  |--inventory.ini

まず先に、host_varsの下に、ノードごとの情報を定義するyaml形式のファイルを作成します。
ファイル名称がAnsible内で使用されるホスト名称となり、定義のansible_hostに、ssh接続するIPアドレスを設定します。
例:dev01.yml

---
ansible_host: 10.0.0.10

host_varsに、今回の対象ノードの定義ファイルを作成し終わったら、inventory.iniを作成します。

$ vi ~/sys-ope/notebooks/ansible/inventory/inventory.ini

このインベントリファイルは、先ほど作成したansible.cfgに定義したinventory で設定したファイルになります。
内容は、こんな感じになります。

[dev]
dev01
dev02
dev03

[ope]
cent-dev

インベントリファイルの作成が終わったら、ファイルの所有者設定を行っておきます。

$ sudo chown 1000:100 -R ~/sys-ope/notebooks/ansible

以上で、Ansibleの設定を含めた、Jupyter Notebookコンテナ構築は完了です。
それでは、Jupyter Notebookから、お試しでAnsibleを実行してみます。

Notebookからansibleを実行してみる

まずは、先ほどのログイン画面からログインしてホーム画面に入ります。

f:id:PANTOMAIMU:20180318194922p:plain
新規Notebook作成
ホーム画面の右側にあるNewのプルダウンから、Python2を選択すると、Untitledという名前の、Python2のコードが実行できるNotebookが生成されます。
次に、Notebook上でAnsibleを実行してみます。
まだ、対象のノードに公開鍵配置やAnsible実行用アカウントの生成を行っていないので、パスワード入力のrootでlsコマンドを実行してみます。
f:id:PANTOMAIMU:20180318194914p:plain
Ansible実行コマンド入力
左側が”In []:”となっているところに、先頭に"!"をつけて実行するコマンドを入力します。
この"!"をつけることで、Jupyter Notebook上でコマンドを実行することができるようになります。
こんな感じです。

!ansible -m shell -a 'ls -la '  -e 'ansible_user=root ansible_ssh_pass=xxxxxxx' all 

ansible_ssh_passのxxxxxxxには、rootアカウントのパスワードを入力してください。
【補足】Notebook上で実行するコマンドは、対話形式のものが使用できないので、-kを使うことができません。

コマンドの入力ができたら、上のRunボタンを押してコマンドを実行します。

f:id:PANTOMAIMU:20180318194909p:plain
実行結果
実行すると、コマンドの出力が下の方にリアルタイムに出力されます。
この出力結果は、Notebookファイルに保存されるので、実行結果のエビデンスとして残すことができます :-)
そんなわけで、ちょっと長くなってしまいましたが、運用で使えるJupyter Notebookコンテナの構築手順と、ちょっとだけですがNotebookでAnsibleを実行する手順の説明をしてみました。

次はこの環境に対して、ansible実行ユーザの追加や、パスワードログインが出来ないようにsshdの設定を変更するplaybookを実行する手順などを書いてみようと思います。

それでは :-)