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?

python: can't open file 'app.py': [Errno 2] No such file or directory · Issue #3157 · docker/compose · GitHub

理由ですが、下記のQiita投稿にて詳しく説明してくれていました。

Dockerにホストのフォルダをマウントしたい! - Qiita

docker-machine (旧 boot2docker)は Windows環境であれば、 C:\UsersVirtualBox共有フォルダとして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が教えてくれました。

python - Flask CLI throws 'OSError: [Errno 8] Exec format error' when run through docker-compose - Stack Overflow

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"
        }
    }
]