Docker Compose クィックスタート

このチュートリアルでは Docker Compose の基本的な考え方について説明します。 簡単な Python ウェブアプリケーションの開発を通じてガイドを進めていきます。

このアプリケーションは Flask フレームワークを利用し Redis によりアクセスカウンターを管理します。 このアプリを通じて Docker Compose をウェブ開発シーンにどのように適用するのか、という実践的な例を示すものです。

ここで示す考え方は Python に不慣れな方でも理解できるようにしています。

これは標準的な例というわけではないため、Compose のコアな機能を実現するものではありません。

前提条件

以下を行っておくことが必要です。

手順 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 f'Hello World! I have been seen {count} times.\n'

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

    メモ

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

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

    flask
    redis
  4. Dockerfile を生成し、次のコードを記述します。

    # syntax=docker/dockerfile:1
    FROM python:3.10-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", "--debug"]

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

    • Python 3.10 イメージを使ってこのイメージをビルドします。
    • ワーキングディレクトリを /code に設定します。
    • flask コマンドが利用する環境変数を設定します。
    • gcc やその依存パッケージをインストールします。
    • requirements.txt をコピーして Python 依存パッケージをインストールします。
    • イメージにメタデータを追加して、コンテナーがポート 5000 をリッスンするようにします。
    • このプロジェクトのカレントディレクトリ . をイメージ内のワークディレクトリ . にコピーします。
    • コンテナーのデフォルトコマンドを flask run --debug にします。
    重要

    Dockerfile には .txt などのような拡張子がないことを確認してください。 エディターの中には自動的に拡張子をつけてしまうものがあり、その場合にはアプリケーション実行時にエラーとなってしまいます。

    Dockerfile の書き方の詳細は Dockerfile リファレンス を参照してください。

手順 2: Compose ファイルでのサービス定義

Compose はアプリケーションスタック全体の制御を単純化します。 つまりサービス、ネットワーク、ボリュームを、分かりやすいただ一つの YAML 設定ファイルを使って管理できます。

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

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

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

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

redis サービスには Docker Hub レジストリに公開されている Redis イメージを取得して利用します。

compose.yaml ファイルの詳細については Compose はどのように動作するか を参照してください。

手順 3: Compose によるアプリのビルドと実行

たった 1 つのコマンドを使うだけで、設定ファイルに基づいたサービスのすべてを生成して起動します。

  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/ を開き、アプリケーションの動作を確認します。

    うまくいかなかったら 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 を入力します。

手順 4: Compose Watch 利用のための Compose ファイル修正

プロジェクトディレクトリにある compose.yaml ファイルを修正して watch を利用するようにします。 ソースコードの編集や保存を行った際に、起動中の Compose サービスが自動的に更新される様子を見ることができます。

services:
  web:
    build: .
    ports:
      - "8000:5000"
    develop:
      watch:
        - action: sync
          path: .
          target: /code
  redis:
    image: "redis:alpine"

ファイルを変更したら、コンテナー内部の /code 配下のファイルとの同期が行われます。 コピーを行えば bundler が実行中のアプリケーションを再起動することなく更新します。

Compose Watch がどのようにして動作するのかについては Compose Watch の利用 を参照してください。 数々のオプションについては コンテナーでのデータ管理 にも説明があります。

メモ

この例においては Dockerfile--debug オプションをつけています。 Flask に対して --debug オプションを設定すると、コードの再ロードが自動化され、再起動やコンテナーの再ビルドをしなくても、バックエンド API を動作させることができます。 .py ファイルを編集した後は、その後の API 呼出は新たなコードを用いるようになります。 ただしこの単純な例においては、ブラウザー上の UI は自動更新が行われません。 たいていのフロントエンド開発サーバーでは、Compose を使って動作する、ネイティブのライブリロード機能がサポートされています。

手順 5: Compose を使ったアプリの再ビルドと実行

プロジェクトディレクトリにおいて docker compose watch または docker compose up --watch と入力してください。 アプリのビルドと起動を行い、ファイル監視モード (watch mode) がスタートします。

$ docker compose watch
[+] Running 2/2
 ✔ Container docs-redis-1 Created                                                                                                                                                                                                        0.0s
 ✔ Container docs-web-1    Recreated                                                                                                                                                                                                      0.1s
Attaching to redis-1, web-1
         ⦿ watch enabled
...

もう一度ウェブブラウザー上の Hello World メッセージを確認してください。 画面更新を行うと、カウンターが更新されるはずです。

手順 6: アプリケーションのアップデート

Compose Watch の動作を確認するには以下を行います。

  1. app.py 内にあるあいさつ文を変更して保存します。 たとえば Hello World!Hello from Docker! とします。

    return f'Hello from Docker! I have been seen {count} times.\n'
  2. ブラウザー上のアプリをリフレッシュします。 あいさつ文が更新されます。 さらにカウンターも更新されるはずです。

    ブラウザー上の hello world
  3. すべてを終えたら docker compose down を実行します。

手順 7: サービスの分割

Compose ファイルを複数に分けると、さまざまな環境やワークフロー向けに Compose アプリケーションをカスタマイズできます。 これは何十ものコンテナーを利用するような大きなアプリケーションにとって非常に便利です。 その管理を複数チームに分かれて行うことができます。

  1. プロジェクトフォルダーにおいて infra.yaml という Compose ファイルを新規生成します。

  2. compose.yaml ファイル内の Redis サービスの部分を切り取って、新ファイル infra.yaml の中に貼り付けます。 ファイルの最上段にはトップレベル属性である services をつけます。 infra.yaml ファイルは以下となります。

    services:
      redis:
        image: "redis:alpine"
  3. compose.yaml ファイルにはトップレベル属性 include を用いて infra.yaml ファイルへのパスを記述します。

    include:
       - infra.yaml
    services:
      web:
        build: .
        ports:
          - "8000:5000"
        develop:
          watch:
            - action: sync
              path: .
              target: /code
  4. 作り替えた Compose ファイルを使って docker compose up によりアプリをビルドおよび実行します。 ブラウザー上に Hello world メッセージが表示されます。

これは単純化した例にすぎません。 それでも include の基本的な考え方を示していて、複雑なアプリケーションを Compose サブファイルに分割し、簡単にモジュラー化する方法を紹介しています。 include を使った複数 Compose ファイルの利用に関する詳細は 複数 Compose ファイルの利用 を参照してください。

手順 8: 他のコマンドを試す

  • サービスをバックグランド実行したい場合は docker compose up に対して -d フラグ (「デタッチ detached」モードの意味) をつけて実行します。 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 --help の実行によって、これ以外に利用できるコマンドを確認できます。

  • docker compose up -d を使って Compose を起動した場合は、作業終了の後は以下によりサービスを終了します。

    $ docker compose stop
    
  • docker compose down コマンドによって、すべてを停止させコンテナーの完全削除までを行います。

次に読むものは