Help us understand the problem. What is going on with this article?

DockerコンテナでPythonスクリプトを動かす (主要なDockerオプション添え)

More than 1 year has passed since last update.

先日参加したみんなのPython勉強会#41 - connpassで、Python環境として「Dockerを使うのも便利」という話が出たので、簡単なスクリプト実行とかをどうするのかについてのメモ。

PythonのOfficialイメージはDockerHubに公開されているので、これを使えば基本的に動く

単発のスクリプトを動かす

こういう1回実行して終了するような処理。

sample.py
#!/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からの入力とかをコンテナが受け付けてくれない。

例えばこんなスクリプト

input.py
#!/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制御が必要な操作は、このオプションがないと表示がおかしくなったりする
シェルの起動だと、プロンプトすら表示されない。

image.png

多くの場合、-i-tはセットで使うことが多く、-itとまとめて書ける

--rm

コンテナの実行終了時に自動でコンテナを削除する。
オプションがない場合は削除されないが、本件のように「同じ処理を何度も動かす場合」は、実行後の残骸がどんどんたまってしまうので、残す必要のないコンテナを実行する際は付加するとよい。

--name my-app

コンテナの名前をmy-appに設定する。
設定がない場合はランダムな英数となる。
確認するには、docker psNAMESのカラムを見る。

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:2docker 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

Pythonの話をしよう(PDF)より

データサイエンスだと、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の画面になるはず。

image.png

イメージサイズはこの通り(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へポートフォワーディングする。

zaki-lknr
メール系のインフラからweb系バックエンド(Perl)・組み込み(C)・業務系BREW(C)/Android(Java)アプリとか雑食性でした。最近はAnsibleとKubernetesが好物
https://zaki-hmkc.hatenablog.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away