Docker Composeを利用してみる
Docker Composeを触ってみました。公式のクイックスタート・ガイドがあるので、それを参考に作業してみます。
Get started with Docker Compose | Docker Documentation
FlaskとRedisの環境を、Docker Composeで作成します。
環境情報
- Windows10 Pro
- Virtualbox 5.2.8 r121009 (Qt5.6.2)
- Docker toolbox(Docker version 18.03.0-ce, build 0520e24302)
- Docker Compose(version 1.20.1, build 5d8c71b2)
Docker toolboxであれば、インストール時に合わせてDocker Composeもインストールされているはずです。
実際の作業
Windowsのホームフォルダ(C:\Users)配下に、本プロジェクト用の適当なフォルダを作成して、flask用のファイルを作成します。ファイル名は app.py
としています。
#!/usr/bin/env python3 import time import redis from flask import Flask app = Flask(__name__) cache = redis.Redis(host='redis', port=6379) def get_hit_count(): retries = 5 while True: try: return cache.incr('hits') except redis.exceptions.ConnectionError as exc: if retries == 0: raise exc retries -= 1 time.sleep(0.5) @app.route('/') def hello(): count = get_hit_count() return 'Hello World! I have been seen {} times.\n'.format(count) if __name__ == "__main__": app.run(host="0.0.0.0", debug=True)
同階層に、pip実行時のrequirementファイルを作成します。ファイル名は requirements.txt
としています。
flask redis
同階層に、flask用コンテナ作成用の Dockerfile
を作成します。
FROM python:3.7 ADD . /code WORKDIR /code RUN pip install -r requirements.txt CMD ["python", "app.py"]
同一階層に docker-compse.yml
ファイルを作成します。
version: '3' services: web: build: . ports: - "5000:5000" volumes: - .:/code depends_on: - redis redis: image: "redis:latest"
まさにここがDocekr Composeになる訳ですね。下記がリファレンスになります。
Compose file version 3 reference | Docker Documentation
web部分では、用意されたDockerfileをbuildして、ポートマッピングして、ボリュームマウントする設定になっています。 depends_on
でredisを指定しているため、redis側のコンテナが起動してから、web側のコンテナを起動する設定となっています。
以下が最終的なフォルダ構造です。
. ├── Dockerfile ├── app.py ├── docker-compose.yml └── requirements.txt
同じ階層で以下コマンドを実行することで、コンテナが起動します。
> docker-compose up > >docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a0a432e67e77 dockercompose_web "/bin/sh -c 'python …" 5 hours ago Up 5 hours 0.0.0.0:5000->5000/tcp dockercompose_web_1 1eadd7cb0361 redis:latest "docker-entrypoint.s…" 5 hours ago Up 5 hours 6379/tcp dockercompose_redis_1
WEBサーバが起動したはずなので、docker-machineのIPを調べてWEBページにアクセスします。redis側でcounterを管理しているので、アクセスする度にサイト上のカウントがアップします。
> docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS default * virtualbox Running tcp://192.168.99.100:2376 v18.09.3 > > curl http://192.168.99.100:5000 Hello World! I have been seen 1 times.
トラブルシューティングその1
docker compose up
時に、以下のエラーが出る時。
> docker compose up ... python: can't open file 'app.py': [Errno 2] No such file or directory
GithubのIssueによると、プロジェクトフォルダの作成パスがまずいようです。
It's most likely a volume mounting issue. If your app directory is outside your home directory (C:\Users\whatever), then volumes won't mount from your host machine. Is that the case?
理由ですが、下記のQiita投稿にて詳しく説明してくれていました。
Dockerにホストのフォルダをマウントしたい! - Qiita
docker-machine (旧 boot2docker)は Windows環境であれば、
C:\Users
をVirtualBox共有フォルダとしてdockerホストの/c/Users
に自動マウントしてくれます。
とあり、確認してみると確かにマウントしてくれています。
> VBoxManage showvminfo default ... Shared folders: Name: 'c/Users', Host path: '\\?\c:\Users' (machine mapping), writable ...
つまり、Docker上のコンテナでボリュームマウントしている際には、VirutalBoxのこの設定によりDockerホストにマウントされたパスを介して、Windows上のファイルシステムを見てくれているわけですね。だからホームディレクトリから外れると、 app.py
ファイルが見つからんと言ってくる訳ですね。
トラブルシューティングその2
docker compose up
時に、以下のエラーが出る時。
> docker compose up ... OSError: [Errno 8] Exec format error
シェルスクリプトで動いちゃってるからシバン行を追加しろ、とStackOverflowが教えてくれました。
Docker Composeを利用しない場合
最後に、仮にDocker Composeを利用しない場合には、どういった処理が必要となるのかを書いておきます。
まず、redis用のコンテナを起動します
> docker run --name redis -d redis:latest
続いて、webにあたるコンテナイメージをビルドします。
> docker build . -t web:sample
で、そのコンテナを起動します。linkオプションにて、redisコンテナとコンテナ間接続できるようにしています。
> docker run --name web -d -p 5000:5000 -v /code --link redis:redis web:sample
これでWEBページは表示されるようになります。が、Docker Composeにて実施されている処理とは、実は異なります。現在、linkオプションの利用はすでに非推奨となっており、Docker Networkを利用すべきとなっています。Docker Composeでは、このDocker Networkの設定を暗黙的に実施してくれています。
実際にDocker Networkの設定を確認すると、Docker Composeで起動したコンテナ用のNetwork設定を確認できます。
> docker network ls NETWORK ID NAME DRIVER SCOPE e594e8665523 bridge bridge local edc81f5a2fe9 dockercompose_default bridge local 213160a9c5bb host host local 3f11e8bd77a7 none null local > > docker network inspect edc81f5a2fe9 [ { "Name": "dockercompose_default", "Id": "edc81f5a2fe986f4a04c38a5b2b439237462cb14a7f52a0d8cbb00c32faa21a0", "Created": "2019-04-04T20:54:27.920459768Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.18.0.0/16", "Gateway": "172.18.0.1" } ] }, "Internal": false, "Attachable": true, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "31835ef1c159e399a7cabf53919cfb1574787e6f1adc8efd9102a1efb088d3f1": { "Name": "dockercompose_redis_1", "EndpointID": "f22e4c639e98f9fd5efcc616b7f6e9121f5a769722edb09affeeb8b85aa537fe", "MacAddress": "02:42:ac:12:00:02", "IPv4Address": "172.18.0.2/16", "IPv6Address": "" }, "88d20d41b334d2f4708f4c3082de33cadd4de58579185b8ff20f3855b2ddac7f": { "Name": "dockercompose_web_1", "EndpointID": "2aa9ec9dfd96fc32d7009dc4876649ecff6f99191fd109771cc70f65ebe1bbd9", "MacAddress": "02:42:ac:12:00:03", "IPv4Address": "172.18.0.3/16", "IPv6Address": "" } }, "Options": {}, "Labels": { "com.docker.compose.network": "default", "com.docker.compose.project": "dockercompose" } } ]