先日参加したみんなのPython勉強会#41 - connpassで、Python環境として「Dockerを使うのも便利」という話が出たので、簡単なスクリプト実行とかをどうするのかについてのメモ。
PythonのOfficialイメージはDockerHubに公開されているので、これを使えば基本的に動く
単発のスクリプトを動かす
こういう1回実行して終了するような処理。
#!/usr/bin/python2
print "hello python"
Descriptionの記述の"Run a single Python script"の項の通り、
$ docker run -it --rm --name my-running-script -v "$PWD":/usr/src/myapp -w /usr/src/myapp python:2 python your-daemon-or-script.py
を実行すれば動く。
zaki@epoisses:~/src/python$ sudo docker run -it --rm --name my-app -v "$PWD":/usr/src/myapp -w /usr/src/myapp python:2 python sample.py
hello python
この通り。
Python3で動かすなら、タグに3
を指定すればOK
zaki@epoisses:~/src/python$ sudo docker run -it --rm --name my-app -v "$PWD":/usr/src/myapp -w /usr/src/myapp python:3 python sample.py
File "sample.py", line 3
print "hello python"
^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print("hello python")?
※ これはスクリプト側でshebangを/usr/bin/python2
でPython2固定にしているためエラー
オプション解説(I)
-i (interactive)
-i, --interactive Keep STDIN open even if not attached
ヘルプを見るとinteractiveと出てくる。その名の通り、これがないとstdinからの入力とかをコンテナが受け付けてくれない。
例えばこんなスクリプト
#!/usr/bin/python3
print ("input value...")
val = input()
print (val)
を-i
無しで動かしてみると
zaki@epoisses:~/src/python$ sudo docker run -t --rm --name my-app -v "$PWD":/usr/src/myapp -w /usr/src/myapp python:3 python input.py
input value...
123
zaki@epoisses:~/src/python$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f60ad0b909c7 python:3 "python input.py" 9 seconds ago Up 8 seconds my-app
zaki@epoisses:~/src/python$
数値を入力・enterしてもスクリプトが反応せず、Ctrl-cで抜けても「コンテナ内のスクリプトはstdinからの入力待ち」のまま起動し続けている
-t (tty)
TTYの割り振り。
パッと見わかりづらく、なくても動くパターンは多い
zaki@epoisses:~/src/python$ sudo docker run -i --rm --name my-app -v "$PWD":/usr/src/myapp -w /usr/src/myapp python:3 python input.py
input value...
123
123
zaki@epoisses:~/src/python$
が、TTY制御が必要な操作は、このオプションがないと表示がおかしくなったりする
シェルの起動だと、プロンプトすら表示されない。
多くの場合、-i
と-t
はセットで使うことが多く、-it
とまとめて書ける
--rm
コンテナの実行終了時に自動でコンテナを削除する。
オプションがない場合は削除されないが、本件のように「同じ処理を何度も動かす場合」は、実行後の残骸がどんどんたまってしまうので、残す必要のないコンテナを実行する際は付加するとよい。
--name my-app
コンテナの名前をmy-app
に設定する。
設定がない場合はランダムな英数となる。
確認するには、docker ps
のNAMES
のカラムを見る。
zaki@epoisses:~$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1d0288ef8f09 python:3 "python input.py" 4 seconds ago Up 3 seconds eloquent_jepsen
コンテナに対する操作(stop/start
とかrm
とか)は、このコンテナ名か、コンテナIDを引数に実行するので、何度も使うようなものは名前を付けたほうがわかりやすい。
なお、同じ名前を複数のコンテナにつけることはできない。
-v "$PWD":/usr/src/myapp
ボリューム設定。
コンテナ内のデータは揮発性でコンテナの削除とともに消えてしまう。データを永続化させるためのもっとも簡単な方法として、ホストOSのディレクトリを参照できるようにする、という機能がある。
Docker の Data Volume まわりを整理する - Qiita
-v "$PWD":/usr/src/myapp
を指定することで、「ホストOSの$PWD
」を「コンテナ内の/usr/src/myapp
」にマウント、という動作になる。
結果的に、カレントディレクトリにあるPythonのソースファイルが、コンテナ内からは/usr/src/myapp/*.py
として参照できる、という構成になる。
-w /usr/src/myapp
コンテナ起動時のカレントディレクトリを/usr/src/myapp
に変更する。
cd
と機能的には同じ。
python:2 python sample.py
python:2
はdocker run
で起動するイメージ名:タグ名
となる。
Pythonイメージに限れば、Python3.xならタグは3
、Python2.xなら2
となる。
細かいサブバージョンなどの指定も必要であれば、タグ一覧から該当バージョンのものを指定すればよい。
(厳密にはDockerイメージのタグ名はただの識別用文字列であり、プロダクトのバージョンとの仕様上の繋がりはない。が、普通の(マナーの良い)イメージであれば、バージョン番号==タグ名と思って問題ない)
タグ名は省略するとlatest
となる。latest
の実体がどのタグに紐づいているかは、イメージの仕様を確認しなければわからないため、できれば指定して実行するのが望ましい。
そのあとのpython sample.py
は、起動するpython:2イメージに対する引数となる。
全てのイメージが引数を解釈するとは限らないが、Pythonイメージはここにインタプリタ名(python
)と実行するスクリプト名(sample.py
)を指定することで任意のスクリプトファイルを起動できる。
この部分は、ホストOS上で$ python sample.py
と実行するのと同等。
(ここにbash
とか指定すると、bashシェルが起動し、シェルログインできる形になる)
daemon動作するスクリプトを動かす
ちょうどいいスクリプトなんて手持ちにないので、Python3のhttp.server
を動かしてみる。
zaki@epoisses:~/src/python$ sudo docker run -it --rm --name my-app -v "$PWD":/usr/src/myapp -w /usr/src/myapp python:3 python -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
起動した。
もう一つターミナルを開き、ホストOSからdocker inspect
でIPアドレスを確認
$ sudo docker inspect my-app | grep -i ipadd
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2",
"IPAddress": "172.17.0.2",
ホストOSからcurlでアクセスしてみると
$ curl http://172.17.0.2:8080
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="input.py">input.py</a></li>
<li><a href="sample.py">sample.py</a></li>
</ul>
<hr>
</body>
</html>
httpdが実行されており、結果(ディレクトリ一覧)が表示される。
docker run
でhttp.serverを実行しているターミナルは以下の通り。
(前述の-t
オプションが付加されてない場合は、アクセスログはリアルタイムに表示されない)
zaki@epoisses:~/src/python$ sudo docker run -it --rm --name my-app -v "$PWD":/usr/src/myapp -w /usr/src/myapp python:3 python -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
172.17.0.1 - - [11/Feb/2019 15:00:29] "GET / HTTP/1.1" 200 -
Python2のSimpleHTTPServer
であれば以下の通り
zaki@epoisses:~/src/python$ sudo docker run -it --rm --name my-app -v "$PWD":/usr/src/myapp -w /usr/src/myapp python:2 python -m SimpleHTTPServer 8080
Serving HTTP on 0.0.0.0 port 8080 ...
172.17.0.1 - - [11/Feb/2019 15:27:53] "GET / HTTP/1.1" 200 -
停止するにはCtrl-cか、別ターミナルでdocker stop my-app
を実行する。
Jupyter Docker Stacks
データサイエンスだと、jupyter/datascience-notebookのイメージがおすすめです
という話もあったのでついでにお試し。
DockerHubには使い方が載ってなかったけど、DescriptionからリンクされているJupyter Docker Stacksに起動方法が載っている
docker run -p 8888:8888 jupyter/scipy-notebook:17aba6048f44
実行
zaki@epoisses:~/src/python$ sudo docker run -p 8888:8888 jupyter/scipy-notebook:17aba6048f44
Unable to find image 'jupyter/scipy-notebook:17aba6048f44' locally
17aba6048f44: Pulling from jupyter/scipy-notebook
a48c500ed24e: Downloading [====================================> ] 22.9MB/30.96MB
1e1de00ff7e1: Download complete
0330ca45a200: Download complete
471db38bcfbf: Download complete
0b4aba487617: Download complete
1bac85b3a63e: Downloading [============================> ] 11.22MB/19.7MB
245be47b44f6: Download complete
ef168d10cf08: Download complete
9a10e240916d: Download complete
f963f5de0a6d: Download complete
84f4b337e3c0: Downloading [==> ] 3.767MB/82.85MB
5db9a0ead114: Waiting
a51db99fbe91: Waiting
初回起動だと大量のイメージのDLが発生するので注意…
0a24f009722c: Pull complete
7620f95f2bda: Pull complete
Digest: sha256:0effde1fa6395184e2846fc7728c66a2afdf2e40618e420907015c3a4d3c8d37
Status: Downloaded newer image for jupyter/scipy-notebook:17aba6048f44
Container must be run with group "root" to update passwd file
Executing the command: jupyter notebook
[I 15:19:37.374 NotebookApp] Writing notebook server cookie secret to /home/jovyan/.local/share/jupyter/runtime/notebook_cookie_secret
[I 15:19:38.176 NotebookApp] JupyterLab extension loaded from /opt/conda/lib/python3.6/site-packages/jupyterlab
[I 15:19:38.177 NotebookApp] JupyterLab application directory is /opt/conda/share/jupyter/lab
[I 15:19:38.180 NotebookApp] Serving notebooks from local directory: /home/jovyan
[I 15:19:38.181 NotebookApp] The Jupyter Notebook is running at:
[I 15:19:38.182 NotebookApp] http://(29bcfc866bd5 or 127.0.0.1):8888/?token=e9df31886f2db43f34f9fe08a7e302e858ecfd333***
[I 15:19:38.184 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 15:19:38.185 NotebookApp]
Copy/paste this URL into your browser when you connect for the first time,
to login with a token:
http://(29bcfc866bd5 or 127.0.0.1):8888/?token=e9df31886f2db43f34f9fe08a7e302e858ecfd333***
みたいな表示になれば、ブラウザから http://:8888/?token=**** にアクセスすればJupyterの画面になるはず。
イメージサイズはこの通り(2019.02.12時点)
zaki@epoisses:~$ sudo docker images jupyter/scipy-notebook
REPOSITORY TAG IMAGE ID CREATED SIZE
jupyter/scipy-notebook 17aba6048f44 82f774a9c85a 6 weeks ago 4.87GB
zaki@epoisses:~$
ちなみにDocker的なtag一覧はここに載っているけど、Dockerfileはどこかで見れるのかな。
オプション解説(II)
-p <host-port>:<container-port>
-p 8888:8888
の部分。
Tomcatでのwebappデプロイでも書いたが、「ホストOS上でDockerがListenするTCPアクセスを、コンテナのポートへbindする」というもの。
同じポート番号を指定すれば、「ホストOS上の8080/TCPをコンテナの8080/TCPへポートフォワーディングする」という動作になり、ホストOS上からだけでなく、ホストOSへアクセスできるリモートのPCからもコンテナにアクセスできるようになる。
-p 8080:80
にすれば、ホストOSでは8080/TCPでlistenし、コンテナの80/TCPへポートフォワーディングする。