やりたいこと
- Jupyterなどを使ったハンズオンセミナーを開催したいとします。
- 実行環境は、dockerで用意することにします。
- Tmpnbのように、受講者ごとに動的にdockerコンテナを立ち上げて利用できるようにします。
実現イメージ
- pythonのflaskを使って、webサーバを立ち上げます。
- セミナーの受講者がアクセスすると、自動的にセミナーイメージからコンテナを起動して、そのアドレスをリダイレクトします。
- 下記の説明では、Ubuntu(15.10)上の docker(1.10.2)で確認しています。
webサーバでは、pythonのflaskとdockerの両方を動かします。
これをdockerで提供するのですが、どうせ、ホストでdockerを動かすので、ホストの環境を利用しましょう。pythonもexeとdllを別イメージからvolumeにコピーして、使うことにします。
Step 1. Making volume for python
pythonとflaskの実行環境を持ったtsutomu7/py3flask(Dokcerfile)からファイルをコピーして、pythonという名前のvolumeを作成します。コンテナはコピー後、廃棄します。
bash
docker volume rm python
docker run -it --rm -v python:/usr/local tsutomu7/py3flask sh -c "\
cp /usr/bin/python3.5 /usr/local/bin/python && \
cp /lib/ld-musl-x86_64.so.1 /usr/local/lib && \
ln -s /usr/local/lib/ld-musl-x86_64.so.1 /usr/local/lib/libc.musl-x86_64.so.1 && \
cp /usr/lib/libpython3.5m.so.1.0 /usr/local/lib/ && \
cp -r /usr/lib/python3.5/ /usr/local/lib/"
Step 2. Start server
tsutomu7/seminar(Dockerfile)を使ってwebサーバを起動します。
- IMAGE環境変数で受講者に起動させたいdockerイメージを指定します。
- PORT環境変数でそのイメージの利用するポート番号を指定します。
- CoreOSなどのように"/lib/x86_64-linux-gnu"がない場合は、その部分をとってください。
bash
docker run -it --rm \
-v /lib/x86_64-linux-gnu/:/lib/x86_64-linux-gnu/:ro \
-v /lib64:/lib64:ro \
-v /usr/bin:/usr/bin:ro \
-v /usr/lib:/usr/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v python:/usr/local \
-p 5000:5000 \
-e IMAGE=tsutomu7/jupyter \
-e PORT=8888 \
tsutomu7/seminar
Step 3. Access
ネットワークにつながったPCから「webサーバのホストのアドレス:5000」にブラウザでアクセスしてください。自動的に新しい dockerコンテナが起動し、そのアドレスがリダイレクトされます。
同じPCからのアクセスは、1つのコンテナで対応します。
サーバプログラムの説明。
pythonもdockerも借り物なので、このサーバイメージは、5MBしかないです。中のプログラム(seminar.py)を説明します。
seminar.py
import os
from flask import Flask, request, redirect
from subprocess import run
app = Flask(__name__)
dct = {}
@app.route('/')
def hello_world():
addr = request.environ['REMOTE_ADDR']
if addr not in dct:
img = os.environ.get('IMAGE', 'tsutomu7/jupyter')
prt = os.environ.get('PORT', '8888')
cid = 8001 + len(dct)
run(['docker', 'run', '-d', '--name', str(cid), '-p', '%d:%s' % (cid, prt), img])
srvr = request.environ['HTTP_HOST']
if ':' in srvr:
srvr = srvr[:srvr.index(':')]
dct[addr] = 'http://%s:%d' % (srvr, cid)
return redirect(dct[addr], 302)
if __name__ == '__main__':
app.run('0.0.0.0', 5000)
- 同じPCからのアクセスでは、同じコンテナを使うようにします。その対応関係(アクセス元アドレス→コンテナのアドレス)をdctという辞書で持ちます。
- addrに、アクセス元アドレスを入れます。
- addrが最初のアクセスの時、以下を実行します。
- imgに、受講者用のイメージ名を環境変数IMAGEから取得します。
- prtに、そのイメージで用いるポート番号を環境変数PORTから取得します。
- cidに、受講者用コンテナに対し、サーバ側で用いるポート番号を計算します。(8001から通し番号)
- 受講者用コンテナを起動します。
- srvrに、サーバのアドレスを入れて、元のポート番号(5000)を消して、新しいコンテナのポート番号を足して、dct[addr]に設定します。
- dct[addr]にリダイレクトします。
以上