実習で学ぶDocker入門 | 4. Docker Composeを使用して複数コンテナをデプロイする
これまでは1つのイメージまたはコンテナを扱う方法を進めてきた。しかし実際の開発では、データベースやWebサーバーなど複数の工程を組み合わせてシステムを構成することが一般的である。Dockerを使ったシステムでは、1つの関心事を1つのコンテナに割り当てる(Each container should have only one concern)ことが公式ドキュメントでも推奨されており、必然的に複数コンテナを扱う場合がよくある。本書では、開発およびテスト環境向けにローカルホストで複数コンテナを起動する方法として、Docker Composeというツールを紹介する。
複数コンテナ管理ツールDocker Composeの概要
Docker Composeは、複数コンテナで構成されたDockerアプリケーションの設定を作成し、実行するためのツールである。
複数コンテナ(データベース、キャッシュ、Web APIなど)で構成されたシステムをdockerコマンドだけで起動する場合、個別コンテナの設定をパラメータとして指定しながら、コンテナ依存関係の順序に沿ってコマンドを実行しなければならない。
一方、Docker Composeを使用すると、複数コンテナの設定と依存関係を設定ファイルに記述し、1つのコマンドで複数コンテナをまとめて実行できる。
Docker Composeが使用される主なケース
開発環境の標準化
チームメンバーがルールどおりに開発環境を手動で整備すると、時間がかかるだけでなく手作業のミスを引き起こす可能性がある。Docker Composeを使用して設定ファイルを渡せば、開発環境の詳細を気にせず、1つのコマンドで環境を整備できる。これにより開発環境整備作業を簡素化できる。
テスト環境の自動化
CI(Continuous Integration)とCD(Continuous Deployment)を行う場合、end to endテスト(すべての構成要素を結合してテストすること)を自動化するには、理想的にはテストごとに独立したテスト環境を提供する必要がある。Docker Composeを使用すれば、必要なテスト環境をコマンド1つで起動でき、検査が完了したら簡単に破棄することもできる。
単一ホストデプロイ
運用環境が単一サーバーの場合、Docker Composeを使用してデプロイできる。詳しくはドキュメントを参照してほしい。
ただし、アプリケーションを拡張するために複数ノードへデプロイする場合は、一般的にDocker EngineのSwarm modeという機能、またはAWS ECSやGoogle Container Engineのようなクラウドクラスタマネージャーのほうが適している。
単一コンテナからDocker Composeを理解する
Docker Compose設定ファイル(docker-compose.yml)の作成と動かし方を知るため、Dockerイメージビルドページで作成したイメージを使用し、単一コンテナを起動・停止する方法を紹介する。事前に実習していない場合は、先に実習してから進めてほしい。
docker-compose.ymlの作成
次は前に作成したイメージからコンテナを起動するための設定ファイルである。この設定ファイルをdocker-compose.ymlという名前で保存する。
version: '3'
services:
myfirstapp:
image: myfirstapp
ports:
- "8888:5000"
この設定ファイルの内容は次のとおりである。
- Docker Composeファイルフォーマットバージョンは
3である。 - サービス名は
myfirstappである。servicesの下にある項目は外部から認識できる機能単位であり、「サービス」と呼ぶ。- ただしサービスはコンテナに接続されるため、サービスの実体はコンテナである。
- サービスに接続されるコンテナは
myfirstappというイメージから起動される。 - コンテナの5000番ポートをホストの8888番にマッピングする。これにより、コンテナのWebサーバーへ
https://localhost:8888でアクセスできる。
Docker Composeでコンテナを起動する
docker-compose.ymlがあるディレクトリで次のコマンドを実行するとコンテナが起動する。
$ docker-compose up
.. 中間省略 ...
Creating network "myfirstapp_default" with the default driver
Creating myfirstapp_1
Attaching to myfirstapp_1
Dockerイメージビルドで示したように、https://localhost:8888/へアクセスするとWebアプリケーションが表示される。
これは次のdockerコマンドを実行するのと同じ意味である。
% docker run -p 8888:5000 myfirstapp
dockerコマンドと比較したdocker-composeの良い点は、設定をおおまかに設定ファイルへ記述できる点にある。主なユースケースの節で紹介したとおり、Docker EngineおよびDocker Composeをインストールする環境さえあれば、docker-compose upで同じ環境を構築できる。
Docker Composeがサポートするコマンド
docker-compose upは、docker-compose.ymlに書かれたコンテナの作成や起動などをまとめて処理できる。
オプションを付けずに実行するとフォアグラウンドで実行され、ログが標準出力に表示される。次のように-dオプションを付けるとバックグラウンドで実行される。
% docker compose up -d
コンテナを停止するには次のコマンドを実行する。
% docker compose stop
コンテナを再起動するには次のコマンドを実行する。
% docker compose start
特定のコンテナを指定して起動することもできる。
% docker compose start myfirstapp
コンテナの停止と削除をまとめて行うには次のコマンドを実行する。
% docker compose down
その他のコマンドの詳細は公式ドキュメントを参照してほしい。
複数コンテナを起動してみる
Docker Composeの使い方が分かったところで、より実用的な例としてDocker公式サンプルのvoting app(投票アプリ)を紹介する。
このアプリケーションは5つのサービスで構成されている。

voting appの構成(github.com/dockersamples/example-voting-appから引用)
注意: ここではDocker Composeの使い方やdocker-compose.ymlの書き方に焦点を当てて説明する。サンプルの各コンテナ実装の詳細はGitHubリポジトリを参照してほしい。
| サービス | 名前 | 内容補足 |
|---|---|---|
| voting-app | ユーザーに投票を促すWebページを表示するアプリケーション。 | データはRedisに保存する。PythonとFlaskによるWebアプリケーション |
| redis | 投票結果を一時的に保存するキャッシュ。 | Redis |
| worker | 投票結果を取得してPostgresデータベースに保存するワーカー。 | .NET |
| db | 投票結果を保存するデータベース。データはDocker volumeに保管する。 | Postgres |
| result-app | リアルタイム投票結果を表示するWebアプリケーション。 | Node.jsによるWebアプリケーション |
voting appを起動する
アプリケーションの動作を確認するため、次のようにGitHubリポジトリからソースコードを取得し、docker-compose upコマンドでコンテナを起動する。初回はイメージビルドが走るため時間がかかる点に注意しよう。
% git clone https://github.com/dockersamples/example-voting-app.git
% cd example-voting-app
% docker-compose up
コンソール出力が止まり、コンテナが起動したらブラウザでhttps://localhost:5000へ接続する。次のような投票画面が表示される。CATSとDOGSのどちらかを選択する。
投票画面
ブラウザでhttps://localhost:5001へ接続する。投票結果画面が表示される。投票画面で選択したほうに投票が入っていることを確認できる。
投票結果画面
複数コンテナを扱うdocker-compose.ymlを書く
ここではDocker Composeの機能と設定ファイル作成について説明する。設定ファイルの詳細な書き方は公式ドキュメントを参照してほしい。
voting-appの設定ファイルは次のとおりである。設定ファイルには次が定義されている。
- サービス(services)が5つ(vote、result、worker、redis、db)
- volumeが1つ(db-data)
- networkが2つ(front-tier、back-tier)
version: "3"
services:
vote:
build: ./vote
command: python app.py
volumes:
- ./vote:/app
ports:
- "5000:80"
networks:
- front-tier
- back-tier
result:
build: ./result
command: nodemon --debug server.js
volumes:
- ./result:/app
ports:
- "5001:80"
- "5858:5858"
networks:
- front-tier
- back-tier
worker:
build:
context: ./worker
networks:
- back-tier
redis:
image: redis:alpine
container_name: redis
ports: ["6379"]
networks:
- back-tier
db:
image: postgres:9.4
container_name: db
volumes:
- "db-data:/var/lib/postgresql/data"
networks:
- back-tier
volumes:
db-data:
networks:
front-tier:
back-tier:
services - サービス定義
単一コンテナの例で説明したように、外部から認識できる機能単位を「サービス」と呼ぶ。サービスはコンテナに接続されるため、サービスの実体はコンテナである。
volumes - volume(データ永続化領域)の定義
volumeは、コンテナのライフサイクルが終了した後でもデータを保管できるデータ領域である。特徴は次のとおりである。
- データ永続性を目的とした機能であるため、コンテナが削除されてもvolumeが明示的に破棄されない限り、volume内のデータは保持される。
- volumeは特定コンテナ専用のvolumeだけでなく、複数コンテナから参照できるvolumeも作成できる。
- ホスト側のディレクトリをvolumeとしてコンテナにマウントできる。
- この機能はホストとコンテナの間でファイルを渡すときに有効である。
各サービスのvolumesには、サービス別のvolume設定を記述している。たとえば、voteサービスはホスト側のディレクトリ./voteを自動作成されるコンテナ専用volumeとして、コンテナディレクトリ/appにマウントする。これにより、アプリケーション実行に必要なファイルをコンテナへ渡す。
services:
(省略)
vote:
(省略)
volumes:
- ./vote:/app
一方、dbサービスはdocker-compose.yml最上位のvolumesに名前を付けて定義したdb-dataという名前のvolumeを、コンテナのディレクトリ/var/lib/postgresql/dataにマウントする。db-dataのようにdocker-compose.yml最上位に名前を付けて宣言されたvolumeを「named volume」と呼ぶ。
services:
(省略)
db:
(省略)
volumes:
- "db-data:/var/lib/postgresql/data"
volumes:
db-data:
- voteサービスのvolumeのように、サービス定義にホストおよびコンテナディレクトリパスだけを指定して宣言したvolumeは、そのサービス専用のvolumeになる。
- 一方、db-dataのようなnamed volumeは複数コンテナから参照できる。
networks - ネットワーク設定
networkはサービスが属するネットワークである。
voting-appでは、ホスト側からデータベースへ直接アクセスして意図しないデータ変更が発生することを防ぐため、データベースをホストから直接アクセスできないback-tierネットワークに配置し、Webサーバーはホストからアクセスするfront-tierネットワークに配置している。
このように各サービスが所属するネットワークを指定するには、まずdocker-compose.ymlの最上位にネットワークを宣言する。
networks:
front-tier:
back-tier:
そして各サービス定義のnetworksに、サービスが参加するネットワークを指定する。次の場合、voteサービスはback-tierとfront-tierに属し、redisサービスはback-tierにだけ属する。
vote:
(省略)
networks:
- front-tier
- back-tier
(省略)
redis:
(省略)
networks:
- back-tier
同じネットワークに属するサービス同士は、ホスト名としてサービス名を指定して相手サービスへ接続できる。たとえば、voteサービスとredisサービスはどちらもback-tierに属するため、voteサービスからホスト名redisでredisサービスにアクセスできる。実際にvoteサービスの実装(app.py)を見ると、ホスト名にredisを指定していることが分かる(ポートは省略され、デフォルトポート6379が使用される)。
def get_redis():
if not hasattr(g, 'redis'):
g.redis = Redis(host="redis", db=0, socket_timeout=5)
return g.redis
またDocker Composeは、最初にdocker-compose upするときにデフォルトネットワークを1つ作成し、各サービスをデフォルトネットワークに所属させる。たとえばvoting-appの例なら、voting_defaultが作成されている。
$ docker-compose up
(省略)
Creating network "voting_default" with the default driver
その他のネットワーク設定の詳細は、以下の公式ドキュメントを参照してほしい。
ports - ホストとコンテナ間のポートマッピング
portsはホストとコンテナ間のポートマッピングを指定する。コンテナの80番ポートをホストの5000番ポートへマッピングすると、ブラウザでhttps://localhost:5000/へ接続したときにvoteサービスのWebアプリケーションへアクセスできる。
ports:
- "5000:80"
image - 使用するイメージの指定
imageで使用するイメージを指定する。次の場合、redisサービスは既存のイメージredis:alpineを使用していることが分かる。
redis:
image: redis:alpine
build - イメージビルド
既存イメージでコンテナの要件が満たされない場合、buildでイメージビルド設定を指定する。buildを指定すると、ローカルキャッシュにイメージがない場合、コンテナ起動前にイメージビルドが実行され、イメージが作成される。
次の場合、voteサービスは./voteディレクトリにビルドに必要な構成ファイルがあることを指定している。
services:
vote:
build: ./vote
実際にexample-voting-app/vote/を開くと、Webアプリケーションに必要な複数のファイルがあり、その中にDockerイメージを構築するためのDockerfileがある。Dockerイメージビルドについては「イメージコンテナ管理メカニズム」を参照してほしい。
なお、imageとbuildを両方指定すると、ビルド結果のイメージ名はimageで指定したイメージ名になる。
command - コンテナ起動コマンド
commandはコンテナ起動コマンドを指定する。voteサービスはFlaskというPython用Webフレームワークで実装されている。ここではWebアプリケーションのエントリポイントであるapp.pyを実行している。
command: python app.py
Docker Composeの説明はここまでである。ここではDocker EngineとDocker Compose機能の一部だけを扱った。ぜひサンプルコードと公式ドキュメントを見て内容を把握し、自分のサービスに活用してほしい。
結論
本記事ではDocker公式サンプルであるvoting-appを例に、Docker Compose設定ファイルの作成とコマンドの実行方法について紹介した。
ミドルウェアを提供するとき、利用者にDockerイメージとDocker Compose設定ファイルを提供すれば、環境セットを簡単に構築できる点は、ソフトウェアの提供元にとっても利用者にとって非常に有用である。
ただし前述のとおり、Docker Composeは単一ノードへのデプロイに使用するツールであるためスケールアウトできず、プロダクション環境には適していない。次はプロダクション環境に適したクラウドクラスタマネージャーを紹介する。