ざっくりまとめ
Docker Composeの depends_on と healthcheck を組み合わせると、DBコンテナが完全に起動してからAPIコンテナを起動させられる。
課題
DBとAPIからなるWebアプリケーションの実行環境をDocker Composeで用意しているとき、DBが完全に起動し終わる前にAPIが起動してリクエストを処理すると 500 (Internal Server Error) を返してしまう問題がある。
ローカル環境などでの開発時であれば大きな問題はないが、CI時にコンテナスタックの起動後即座にAPIテストが走る前提だとこの問題を踏んで「なぜか稀にAPIテストが500で落ちる」という現象に悩まされることになる。
毎回起こるわけではないため対応の優先度付けが難しく、またAPIテストが落ちるとなったら普通はAPIに不具合があることをを疑ってしまい本当の原因に気づきづらい。
解決方法
Docker Composeの depends_on と healthcheck を組み合わせて、DBコンテナの完全起動を待ってからAPIコンテナを起動させる。
(1) depends_on
depends_on は、あるコンテナが別のコンテナに依存していることを表明する設定。 docker compose up を叩いた際のコンテナ起動順に影響があり、この設定をしておくと依存先から順に起動してくれる。
例えばAPIコンテナがDBコンテナに依存しているとき、下記のように depends_on で db と記載しておくと db コンテナを起動後に api コンテナが起動されるようになる。
...
services:
api:
...
+ depends_on:
+ - db
db:
...
ただし、ここでいう "起動" とは「CMDで指定されているコマンドを実行して即座に異常終了しなかった」という状態を指しているので、この設定だけではDBのウォームアップ中にAPIコンテナが起動して500を返し得てしまうのでもう少し工夫が必要。
(2) healthcheck
healthcheck は、正常状態かどうかの検証方法をDockerに検証させるための設定。コンテナのウォームアップが終わってリクエストを正常に受け入れられる状態かどうかの検証方法をここで設定する。
例えばDBコンテナが完全起動したら mysqladmin ping -u root とか mysql -u root -e "SELECT 1;" コマンドが通るはずなので、下記のように test に検証コマンドを記載する。
services:
db:
...
+ healthcheck:
+ test: ["CMD", "mysql", "-u", "root", "-e", "SELECT 1"]
+ interval: 6s
+ timeout: 1s
+ retries: 5
- see: https://docs.docker.com/compose/compose-file/compose-file-v3/#healthcheck
- see: https://docs.docker.com/engine/reference/builder/#healthcheck
(3) depends_onとhealthcheckを組み合わせる
依存関係を表明する depends_on と、完全起動状態を検証する healthcheck を組み合わせると、DBコンテナが完全起動してからAPIコンテナを起動させられる。
...
services:
api:
...
+ depends_on:
+ db:
+ condition: service_healthy
db:
...
+ healthcheck:
+ test: ["CMD", "mysql", "-u", "root", "-e", "SELECT 1"]
+ interval: 6s
+ timeout: 1s
+ retries: 5
depends_on を書くときに condition: service_healthy と指定しないといけないのがややトラップ。