Docker ComposeでDBコンテナが準備状態になるまで待つ

アプリケーションの開発・運用にDockerを利用し始めると、本番環境で使用するDockerfileでCI(自動テスト)したくなると思います。
その自動テストが単一のコンテナで完結しない場合、利用するCI環境によりますがDocker Composeを利用するのがお手軽かと思います。

例えばGithub ActionsをCI環境として使った場合はこんな感じのワークフローを設定することになります。

steps:
- name: Docker Compose Up
  run: docker-compose -f docker-compose.ci.yml up -d
- name: Run Tests
  run: docker-compose -f docker-compose.ci.yml run app ./test.sh

docker-compose.ci.yml にはアプリケーションコンテナ(app)とデータベースコンテナ(db)が存在し、 test.sh には自動テストを実行するスクリプトが存在すると思って見てみてください。

これで設定としては十分に見えますが、多くの場合この自動テストは失敗します。
なぜなら、Docker Composeはコンテナの起動順は制御してくれますがその中のプロセスの準備までは待ってくれないからです。上の場合、自動テストが実行されるタイミングでdbコンテナ内のデータベースプロセスが起動していることが保証されないため、自動テスト側でデータベースの待ち合わせを行っていない限り失敗するでしょう。
(例えばMySQLの場合プロセスの起動までだいたい8秒くらいかかっていたのでまず失敗します)

以下のドキュメントにもその旨が書いてあります。

解決策として、上記ドキュメントにあるように自動テスト実行前にデータベースプロセスを待ち合わせます。
待ち合わせ方法は様々あると思いますが、上記ドキュメントで紹介されているようなライブラリを使用するのがお手軽なのではないでしょうか。

ですが、自動テストでは本番環境と同じDockerfileを使いたい。となると自動テストでプロセスを待ち合わせるためだけに本番環境にこれらのライブラリを入れるのは嫌な感じです。

ということでこれらのライブラリを実行するコンテナを別で用意しプロセスの待ち合わせを行ってもらった上で自動テストを行うようにします。
例えば上記にあげた wait-for-it を利用する場合はまずDocker Compose の設定ファイルに以下のように wait-for-it を実行するコンテナを追加してあげます。

services:
  db:
    image: mysql:5.7
  app:
    build: 
      dockerfile: ./Dockerfile
  # 以下を新規追加
  wait:
    image: willwill/wait-for-it:latest

あとは最初に例示したワークフローにデータベースプロセスの待機処理を追加すればOKです。

steps:
- name: Docker Compose Up
  run: docker-compose -f docker-compose.ci.yml up -d
# 以下を新規追加
- name: Wait Database
  run: docker-compose -f docker-compose.ci.yml run wait db:3306 -- echo "Database is up"
- name: Run Tests
  run: docker-compose -f docker-compose.ci.yml run app ./test.sh

自分が試したのはGitHub Actionsだけですが、おそらく他のCIサービスでも同じ動作をすると思うのでお困りの際は試してみてください。

この記事が気に入ったらサポートをしてみませんか?