IT業務効率化

【入門】PythonのreponderをDockerで起動してみる

docker responder

はじめに

今回はPythonのresponderで管理画面webページを作るための記事第5弾です。

過去の記事はこちらにまとまっておりますので、ぜひご覧ください。

Python responder
初心者がPythonのresponderを学ぶ旅まとめ記事です Pythonのresponderを触ってみた記事を一覧でまとめており、随時更新中(2019/07/12)です。 ...

まず結論:responderをDockerで起動するには

  1. Dockerfileを用意する。
    FROM kennethreitz/pipenv
    ENV PORT '80'
    COPY . /app
    CMD python3 api.py
    EXPOSE 80​
  2. pipenvをローカルにインストールする。
  3. Pipfileを作成する
    [[source]]
    url = "https://pypi.org/simple"
    verify_ssl = true
    name = "pypi"
    
    [packages]
    responder = "*"
    
    [dev-packages]
    pytest = "*"
    
    [requires]
    python_version = "3.7"
    
    [pipenv]
    allow_prereleases = true​
  4. pipenv installを実行する
  5. イメージを作成する
    docker build . -t docker_responder_init
  6. コンテナを起動する
    docker run -p <port番号>:<port番号> docker_responder_init

ここまで紆余曲折あったので、ここから下はつまづいた箇所などをそのまま書いております。

Dockerのおさらい

responderの公式ドキュメントには

公式ドキュメントに、responderを起動させる方法はこのように書かれています。

Docker Deployment
Assuming existing api.py and Pipfile.lock containing responder.

FROM kennethreitz/pipenv
ENV PORT '80'
COPY . /app
CMD python3 api.py
EXPOSE 80

That’s it!

えぇ、、That’s it と言われましても。。

ということでDocekr fileの復習をしながら理解していきます。私はオライリーのDockerで勉強しました。これに倣って思い出しながらDockerで起動してみたいと思います。

Dockerfileを解読する

Dockerfileとは

Dockerのイメージを生成するための一連の手順を含む、単なるテキストファイルです。

とあります。dockerコンテナを起動するには、ベースとなるイメージをダウンロードし、それにカスタマイズする形で起動すると思います。

その一連の流れを1つのファイルに記述しているものです。

それでは、公式ドキュメントにあるDockerfileを上から解読していきます!

FROMでベースイメージを指定

FROM kennethreitz/pipenv

FROMはベースイメージを指定する記述です。このkennethreitz/pipenvを基本として、Dockerイメージを起動させようとしています。kennethreitzはpipenvの制作者であり、Dockerリポジトリ名です。

pipenvはpythonの仮想環境を構築するツールです。奥が深いので、ここを掘り下げることは割愛させていただきます。一言でいうと、python仮想環境の管理や、pipfileとpipfile.lock利用して、requirements.txtに変わるライブラリのバージョン管理が可能です。

とうことで、このpipenvをdockerイメージの基本としています。

環境変数を設定する

ENV PORT '80'

ENVは環境変数を設定します。(詳細

だから上の記述はPORT=’80’を設定したことになります。

ローカルのファイルをコピーする

COPY . /app

これはローカルにあるファイルを、dockerイメージにコピーする記述です。(詳細

COPY source destinationという書き方です。

Dockerfileの書き方ですと、ローカルにあるカレントディレクトリのファイルが全て、Dockerイメージの/appディレクトリに移動されます。

api.pyを実行する

CMD python3 api.py

CMDはそのあとに続く命令文を実行します。ここではpython3 api.pyを実行します。

プロセスの待ち受けるポートを指定する

EXPOSE 80

EXPOSEはプロセスがどのポートで待ち受けるか指定します。ここでのプロセスはapi.pyです。

Dockerイメージを構築してみる

それではdockerイメージをビルドしてみようと思います。えい!

dockerイメージを作成するにはDockerfileのあるディレクトリで

docker build . -t <好きなイメージ名>

yuki-PC:test_responder yuki$ docker build . -t docker_responder_init
Sending build context to Docker daemon  92.16kB
Step 1/5 : FROM kennethreitz/pipenv
# Executing 3 build triggers
COPY failed: stat /var/lib/docker/tmp/docker-builder403292948/Pipfile: no such file or directory

早速Pipfileがないよと怒られてしまいました。pipenvを利用するので、バージョンを管理に使うPipfileを、実行ディレクトリに置いておく必要がありそうです。(dockerあるんだからpipenv使う必要ってあるんだろうか・・・?)

pipfieは公式ドキュメントに記載があったので、そのまま転用します。

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
responder = "*"

[dev-packages]
pytest = "*"

[requires]
python_version = "3.7"

[pipenv]
allow_prereleases = true

Pipfileも完成したので、ビルドしようとしたら、また怒られました。Pipfile.lockがないとのことです。

Pipfile.lockはPipfileを元に各ライブラリをインストールしたあと、その記録して残るファイルです。

Pipfileのあるディレクトリでpipenv installと打てばPipfile.lockは作成されます。

すると以下のようにエラーが起きました。

yuki-PC:test_responder yuki$ pipenv install
Warning: Python 3.7 was not found on your system…
You can specify specific versions of Python with:
  $ pipenv --python path/to/python

python3.7がないと言っています。私のmacはAnacondaを入れてしまっているので、ややこしいことになっているような気がします。まずは標準のPythonのバージョンをあげることにしました。brew install pythonをします。

yuki-PC:test_responder yuki$ brew install python
Warning: python 3.7.4 is already installed, it's just not linked
You can use `brew link python` to link this version.

3.7系は入っているようですが、linkができていないそうなので、brew link pythonをします。そのあとにpipenv installすると、見事Pipfile.lockの生成に成功しました。

pipenv docker

では気を取り直して、docker build . -t docker_responder_initを実行してみます。

yuki-PC:test_responder yuki$docker build . -t docker_responder_init
Sending build context to Docker daemon  115.7kB
Step 1/5 : FROM kennethreitz/pipenv
# Executing 3 build triggers
 ---> Using cache
 ---> Running in da1321e7728b
+ pipenv install --deploy --system
Installing dependencies from Pipfile.lock (5640d4)…
Removing intermediate container da1321e7728b
 ---> 3598d1d9a551
Step 2/5 : ENV PORT '80'
 ---> Running in 30ed97d6a0be
Removing intermediate container 30ed97d6a0be
 ---> 0ec84131b043
Step 3/5 : COPY . /app
 ---> 3fa5a264ceff
Step 4/5 : CMD python3 main.py
 ---> Running in 6e6c710aa320
Removing intermediate container 6e6c710aa320
 ---> 35e5cf0914b3
Step 5/5 : EXPOSE 80
 ---> Running in ed87def87e5a
Removing intermediate container ed87def87e5a
 ---> b982c144ae1e
Successfully built b982c144ae1e
Successfully tagged docker_responder_init:latest

見事成功しました!

イメージも確認できました。

yuki-PC:test_responder yuki$ docker images docker_responder_init
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
docker_responder_init   latest              b982c144ae1e        6 minutes ago       1.41GB

dockerのイメージを確認するには

docker images

Dockerコンテナを建ててアクセスする

それではイメージが出来上がったので、実行します。docker run docker_responder_init

この実行ですが、失敗しました。すでに80番ポートを利用しているようです。

嵌った、port番号を調整する

port番号を80から3001(適当)に変更しました。Dockerfileは以下の用意変更されました。

FROM kennethreitz/pipenv
ENV PORT '3001'
COPY . /app
CMD python3 main.py
EXPOSE 3001

これでbuildし直して、再度イメージを構築します。無事イメージできたら、コンテナを起動して、webブラウザからアクセスしてみます。

これでもダメでした。。

どこから調査しようか悩みましたが、気になっていることが2つあったので、そこから探ることにします。

  • Pythonコードの中にもportを指定している箇所があったような?
  • Dockerファイルの中にENV PORT ’80’と記述したことの意味は?

もしかしたらPythonコードのapi.run(port=3000)と書いてしまっているため、不具合が起きているかもしれません。pythonコードの引数portを無くしてみました。

if __name__ == '__main__':
    api.run()  # port=3000の記述を削除

しかし、解決しませんでした。そもそもこの引数を消す前からログとして、

yuki-PC:test_responder yuki$ docker run docker_responder_init
INFO: Started server process [8]
INFO: Waiting for application startup.
INFO: Uvicorn running on http://0.0.0.0:3001 (Press CTRL+C to quit)

と出ているので、port番号をpythonコードで指定しても、関係ないのかもしれません。

嵌った、コンテナにアクセスする時のIPアドレスを調べる

とくにもかくにもDockerコンテナを調べてみます。

コンテナの詳細を知りたい時は

docker inspect <コンテナID>

するとこのような記述がありました。

“IPAddress”: “172.17.0.2”とありました。

しかしhttp://172.17.0.2:3001/hello.htmlにアクセスしてみましたが、反応はありませんでした。(後から気づくのですが、勘違いしていました。)

なんかもうよくわかんない

課題を切り分けるために、コンテナ内に入ってみます。

docker exec -it container_id bashでコンテナ内に入り、curlを叩いてみたところ、レスポンスがあったので、どうやらローカルからdockerコンテナにうまくアクセスできていないようです。

そもそもport番号の指定をpythonコード側とDockerfile側でやっていたり、色々とぐちゃぐちゃです。確認のためにソースコードを読みにいきます。

responderのport番号とホストのアドレスの指定の仕組み

ソースコードを読みに行くと以下のようになっていました。

        if "PORT" in os.environ:
            if address is None:
                address = "0.0.0.0"
            port = int(os.environ["PORT"])

        if address is None:
            address = "127.0.0.1"
        if port is None:
            port = 5042

読めばわかる方が多いかもしれませんが、環境変数にPORTがあれば、そのPORTの値をport番号として代入し、pythonコード内でapi.runの引数にaddressの記載がなければ、0.0.0.0にすると書いてあります。

ですので、Dockerfileの中にENV PORT ‘3001’と今は記載してあるので、api.run()の引数を全て外し、PORT:3001, address:0.0.0.0の状態にします。

じゃあなんでなんだろう?

どうしてIPアドレスを0.0.0.0指定しているのにアクセスできないのか

これですが結論から書くとdocker runコマンドが間違っていました。

これが間違った実行コマンドです。

yuki$ docker run docker_responder_init

このとき立ち上がったコンテナに対して、docker inspectで調べてみると、せっかく待っているコンテナ側の3001ポートに結びつく、ローカルのポートが設定されていませんでした。

            "Ports": {
                "3001/tcp": null
            }

 

正しいdocker runコマンドは下です。

yuki$ docker run -p 3001:3001 docker_responder_init

ローカルの3001ポートをコンテナの3001ポートと結びつける必要があるのです。こうするとdocker inspectで確認した時に、対応するポートの記述があります。

            "Ports": {
                "3001/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "3001"
                    }
                ]
            },
つまりめちゃくちゃ基本的なことでした。
でもdockerの基本を復習できたのでおっけー!!
responder docker 無事起動できた様子

まとめ

responderのaddress指定とport指定

  • 環境変数にPORTがなく、pythonコード内でもportとaddressを指定しない場合、デフォルトでport=5042とaddress=127.0.0.1が割り振られる。
  • 環境変数にPORTがあると、自動的にその値をポート番号に設定する。その時のaddressはデフォルトで’0.0.0.0’が入る。(全てのIPアドレスから待ち受ける)
  • python内でportを指定しても環境変数のPORTが優先される。
  • pythonコード内で指定したaddressは、再優先される。
docker responder

Dockerコンテナで起動するには

  • Dockerfileの中にPORT番号を指定する
  • pythonコード側でaddressとport番号の指定は不要(してもいい)
  • 起動する時はDockerfileに記述したport番号を指定してdocker run -p <port番号>:<port番号> docker_responder_init
お読みいただきありがとうございました。

以前までの記事はこちらにまとめております。

Python responder
初心者がPythonのresponderを学ぶ旅まとめ記事です Pythonのresponderを触ってみた記事を一覧でまとめており、随時更新中(2019/07/12)です。 ...
ABOUT ME
hirayuki
今年で社会人3年目になります。 日々体当たりで仕事を覚えています。 テーマはIT・教育です。 少しでも技術に親しんでもらえるよう、noteで4コマ漫画も書いています。 https://note.mu/hirayuki