Docker Compose をはじめよう

読む時間の目安: 7 分

このページでは、簡単な Python ウェブアプリケーションを Docker Compose 上に作り上げます。 このアプリケーションは Flask フレームワークを利用し、Redis によりアクセスカウンターを管理します。 サンプルでは Python を用いていますが、ここで示す考え方は Python に不慣れな方でも理解できるようにしています。

前提条件

Docker EngineDocker Compose をインストールしておいてください。 Python や Redis はインストールする必要はありません。 これはいずれも Docker イメージとして提供されます。

ステップ 1: セットアップ

アプリケーションの依存パッケージを定義します。

  1. プロジェクト用のディレクトリを生成します。

    $ mkdir composetest
    $ cd composetest
    
  2. プロジェクトディレクトリ内にapp.pyというファイルを生成して、以下の記述を書き写してください。

    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)
    

    この例においてredisとは、このアプリケーションネットワーク上の redis コンテナーのホスト名です。 Redis のデフォルトポートとして6379を利用します。

    一時的なエラーの取り扱い

    get_hit_countという関数がどのように書かれているかを見てください。 この単純なリトライのループにより、redis サービスが起動していなかったとしても、リクエストを何度でも送信できます。 アプリケーションが起動する最中に対して、この方法が適していますが、さらにはこのアプリの動作中に redis サービスを再起動する必要が発生した場合も、アプリが柔軟に対応できる方法です。 クラスターを構成している場合、ノード間でのネットワークの瞬断を制御することもできます。

  3. プロジェクト用のディレクトリにもう一つrequirements.txtという名称のファイルを作成し、次のようにします。

    flask
    redis
    

ステップ 2: Dockerfile の生成

このステップでは、Docker イメージを構築する Dockerfile を作ります。 そのイメージには依存するすべてのもの、つまり Python と Python アプリケーションが含まれます。

プロジェクト用のディレクトリ内で Dockerfile という名称のファイルを作成し、次の内容にします。

# syntax=docker/dockerfile:1
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]

これは Docker に対して以下の指示を行います。

  • Python 3.7 イメージを使って当イメージを構築する。
  • 作業用ディレクトリを /code に指定する。
  • flaskコマンドにより用いられる環境変数を設定する。
  • gcc や依存パッケージをインストールする。
  • requirements.txtをコピーして Python 依存パッケージをインストールする。
  • イメージにメタデータを追加して、コンテナーがポート 5000 をリッスンするように記述する。
  • このプロジェクトのカレントディレクトリ.を、イメージ内のワークディレクトリ.にコピーする。
  • コンテナーに対するデフォルトのコマンドを flask run にする。

Dockerfile の書き方の詳細については、Docker ユーザーガイドDockerfile リファレンス をご覧ください。

ステップ 3: Compose ファイル内でのサービス定義

プロジェクト用のディレクトリ内でdocker-compose.ymlという名称のファイルを作成し、次の内容にします。

version: "3.9"
services:
  web:
    build: .
    ports:
      - "8000:5000"
  redis:
    image: "redis:alpine"

この Compose ファイルは webredis という 2 つのサービスを定義します。

ウェブサービス

web サービスは、カレントディレクトリ内のDockerfileからビルドされたイメージを利用します。 そしてコンテナーとホストマシンを、公開用ポート8000でつなぎます。 このサービス例では、Flask ウェブサーバーのデフォルトポートである5000を利用するものです。

Redis サービス

redis サービスには Docker Hub レジストリから取得した、公開の Redis イメージを利用します。

ステップ 4: Compose によるアプリケーションの構築と実行

  1. プロジェクト用のディレクトリでdocker-compose upによりアプリケーションを起動します。
   $ docker-compose up

   Creating network "composetest_default" with the default driver
   Creating composetest_web_1 ...
   Creating composetest_redis_1 ...
   Creating composetest_web_1
   Creating composetest_redis_1 ... done
   Attaching to composetest_web_1, composetest_redis_1
   web_1    |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
   redis_1  | 1:C 17 Aug 22:11:10.480 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
   redis_1  | 1:C 17 Aug 22:11:10.480 # Redis version=4.0.1, bits=64, commit=00000000, modified=0, pid=1, just started
   redis_1  | 1:C 17 Aug 22:11:10.480 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
   web_1    |  * Restarting with stat
   redis_1  | 1:M 17 Aug 22:11:10.483 * Running mode=standalone, port=6379.
   redis_1  | 1:M 17 Aug 22:11:10.483 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
   web_1    |  * Debugger is active!
   redis_1  | 1:M 17 Aug 22:11:10.483 # Server initialized
   redis_1  | 1:M 17 Aug 22:11:10.483 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
   web_1    |  * Debugger PIN: 330-787-903
   redis_1  | 1:M 17 Aug 22:11:10.483 * Ready to accept connections

Compose は Redis イメージを取得し、コードが動作するイメージを構築した上で、定義されているサービスを開始します。 この例ではビルド時において、コードがイメージ内に静的にコピーされます。

  1. ブラウザーでhttp://localhost:8000/を開き、アプリケーションの動作を確認します。

    Docker を Linux、Docker Desktop for Mac、Docker Desktop for Windows で直接使っている場合、ウェブアプリは Docker デーモンのホスト上でポート 5000 を開いています。 ブラウザーから http://localhost:8000 にアクセスして、Hello Worldメッセージが表示されることを確認してください。 接続できなければhttp://127.0.0.1:8000も試してください。

    ブラウザーには以下のメッセージが表示されます。

    Hello World! I have been seen 1 times.
    

    ブラウザー上の hello world

  2. ページを更新します。

    数値が更新されたはずです。

    Hello World! I have been seen 2 times.
    

    ブラウザー上の hello world

  3. 別の端末画面を開いてdocker image lsを実行し、ローカルのイメージ一覧を表示します。

    この時点で一覧表示されるイメージにrediswebが含まれます。

    $ docker image ls
    
    REPOSITORY        TAG           IMAGE ID      CREATED        SIZE
    composetest_web   latest        e2c21aa48cc1  4 minutes ago  93.8MB
    python            3.4-alpine    84e6077c7ab6  7 days ago     82.5MB
    redis             alpine        9d8fa9aa0e5b  3 weeks ago    27.5MB
    

    docker inspect <tag または id>によってイメージを確認することもできます。

  4. アプリケーションを停止させます。 2 つめに開いた端末画面上のプロジェクトディレクトリにおいてdocker-compose downを実行します。 またはアプリを開始したはじめの端末画面上において CTRL+C を入力します。

ステップ 5: Compose ファイルにバインドマウントを追加

プロジェクトディレクトリ内にある docker-compose.yml を編集して、web サービスへの バインドマウント を追加します。

version: "3.9"
services:
  web:
    build: .
    ports:
      - "8000:5000"
    volumes:
      - .:/code
    environment:
      FLASK_ENV: development
  redis:
    image: "redis:alpine"

新しい volumes というキーは、ホスト上のプロジェクトディレクトリ(カレントディレクトリ)を、コンテナー内にある /code ディレクトリにマウントします。 こうすることで、イメージを再構築することなく、実行中のコードを修正できるようになります。 environmentキーには環境変数FLASK_ENVを設定しています。 これは開発モードでflask runを実行し、コード変更時にリロードするよう指示します。 このモードは開発時にのみ用いるようにします。

ステップ 6: Compose によるアプリの再構築と実行

プロジェクトディレクトリにて docker-compose up を入力する際に、Compose ファイルが更新されていると、アプリは再構築され実行されます。

$ docker-compose up

Creating network "composetest_default" with the default driver
Creating composetest_web_1 ...
Creating composetest_redis_1 ...
Creating composetest_web_1
Creating composetest_redis_1 ... done
Attaching to composetest_web_1, composetest_redis_1
web_1    |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
...

Hello World メッセージをもう一度確認してみます。 再読み込みをすると、さらにカウンターが増えるはずです。

フォルダ、ボリューム、バインドマウントの共有

  • プロジェクトを Users ディレクトリ(cd ~)以外に置いている場合、利用している Dockerfile やボリュームのドライブ、ディレクトリは、共有できるようにしておく必要があります。 実行時に、アプリケーションファイルが見つからない、ボリュームマウントが拒否される、サービスが起動できない、といったランタイムエラーが発生した場合は、ファイルやドライブを共有にすることを試してください。 C:\Users(Windows の場合)または /Users(Mac の場合)ではないディレクトリにあるプロジェクトがある場合は、ボリュームマウントを共有ドライブにする必要があります。 これはまた、Linux コンテナー を利用する Docker Desktop for Windows のプロジェクトでも同様です。 詳しくは Docker for Mac における ファイル共有 を参照してください。 また一般的な利用例に関しては コンテナーでデータ管理 を参照してください。

  • 比較的古い Windows OS 上において Oracle VirtualBox を利用している場合は、VB trouble ticket に示されている共有フォルダに関する問題が起こるかもしれません。 より新しい Windows システムであれば、Docker Desktop for Windows の要件を満たすため、VirtualBox は必要としません。

ステップ 7: アプリケーションの更新

アプリケーションのコードは、ボリュームを利用してコンテナー内にマウントされたため、コードへの変更とその確認はすぐにできます。 イメージを再構築することは必要なくなりました。

app.py内のメッセージを変更して保存します。 たとえばHello World!メッセージをHello from Docker!に変更することにします。

return 'Hello from Docker! I have been seen {} times.\n'.format(count)

ブラウザーにてアプリを再読み込みします。 メッセージは更新され、カウンターも加算されているはずです。

ブラウザー上の hello world

ステップ 8: その他のコマンドを試す

サービスをバックグラウンドで実行したい場合は docker-compose up-d フラグ(”デタッチ”モード用のフラグ)をつけます。 docker-compose ps を実行して、現在動いているものを確認します。

$ docker-compose up -d

Starting composetest_redis_1...
Starting composetest_web_1...

$ docker-compose ps

       Name                      Command               State           Ports
-------------------------------------------------------------------------------------
composetest_redis_1   docker-entrypoint.sh redis ...   Up      6379/tcp
composetest_web_1     flask run                        Up      0.0.0.0:8000->5000/tcp

docker-compose run コマンドを使えば、サービスに対してのコマンド実行を行うことができます。 たとえば web サービス上でどのような環境変数が利用可能であるかは、以下のコマンドを実行します。

$ docker-compose run web env

docker-compose --help を実行すれば、その他のコマンドを確認できます。 bash や zsh シェルにおいて コマンド補完をインストールしている場合は、利用可能なコマンドを確認することもできます。

docker-compose up -d により Compose を起動していた場合は、サービスを停止させるために以下のコマンドを実行します。

$ docker-compose stop

コンテナーも完全に削除し、すべてを終わらせる場合には down コマンドを使います。 --volumes を指定すれば Redis コンテナーにおいて利用されているデータボリュームも削除することができます。

$ docker-compose down --volumes

ここまで Compose の基本動作について見てきました。

次に読むものは

documentation, docs, docker, compose, orchestration, containers