Docker Composeで複数コンテナを跨ぐヘルスチェックを実装してみた

# はじめに

社内で現在担当しているプロジェクトではローカル環境には`docker compose`、CI環境には`codebuild`を使用しています。

今回は`codebuild`内でも`docker compose`を流用していて、自動テストを回す際にAPサーバーとDBサーバーの疎通確認をする必要があり、そこでハマった事や最終的に採用した方法を共有したいと思います。

あくまで多くある解決策の内の一つですので、オプションとして見ていただければ嬉しいです🙇‍♂️

# この記事でわかる事
- PHPやRailsなどのAPサーバーとして使うコンテナから、MySQLなどのDBサーバーとして使うコンテナへ確実に疎通できる事を保証する方法(connectionがないとか怒られない)

# 今回解決したい事
- CI環境でテストを自動実行している時に、DBサーバー(MySQL)のヘルスチェックが終了する前にAPサーバー(PHP)がテストを回してしまって、CIが失敗してしまうのを防ぎたい。

# 検証環境
- docker compose(version 3.9)
    - APサーバー
        - PHP 8.0
    - DBサーバー
        - mysql/mysql-server:5.7

# 実装

## 検証その1 depends_onを使う
とりあえず最初は何も制御せずに試しました笑
とはいえ、何も考えなしと言う訳でもなく`docker comopose`の`depends_on`と言う記載をしていれば制御してくれていると思っていたのでそのままの状態で問題ないだろうと言う考えでした。
下記のようなコードです↓

``` docker-compose.yml
  php:  
    depends_on:
      - db
```
ただ、この考えは間違っていました。

`depends_on`の正しい挙動は、コンテナの起動順序とシャットダウンの順序を制御してくれると言うものでした。
詳しくは下記の公式ドキュメントに譲りますが、上記の例だと

- docker comopse up した時はdbコンテナ => PHPコンテナの順番で立ち上がる
- docker compose down した時はPHPコンテナ => dbコンテナの順でコンテナが停止する

この2つの挙動のみなので、今回の目的である**コンテナ同士の通信が正常に疎通するか** という部分は `depends_on`では制御できませんでした。なのでこの案は没です。

`depends_on`のドキュメントはこちら↓
https://docs.docker.com/compose/startup-order/

## 検証その2 depends_on + condition
さて次はどうしようかという事で、docker composeのみで何か制御する方法があるのではと思い公式ドキュメントを漁っていました。するとなんとやりたい事とバッチリ一致しているのが、出てきました..!
どうやら、`depends_on`の`condition`というオプションと、`helthcheck`と言う機能を使えば実装できるという事です。コードは下記になります↓

```docker-compose.yml
  php:  
    depends_on:
      db:
        condition: service_healthy
```
簡単ですね！`condition`が増えただけです！
`service_healthy`というのは、dockerの持つhelthcheck機能でhealthyになっているかどうかを待ってから起動してくれると言うものです。めちゃ便利です..!

今回は、`mysql/mysql-server:5.7`のdockerイメージにhealthcheckの機能が含まれていたので、その起動確認をしています。こんなやつです↓

<img width="464" alt="スクリーンショット 2022-02-15 16.02.59.png (7.0 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/02/15/103690/9a3c8512-edf3-401b-bba9-37c1e7c0bf4b.png">

[mysql/mysql-server:5.7](https://hub.docker.com/layers/mysql/mysql-server/5.7/images/sha256-db2fc5a6c8282b9cc758ff8471ceb7d8f53dd206ccaedaea31ec2305d217f061?context=explore)

これでdocker-compose upすると
<img width="462" alt="スクリーンショット 2022-02-15 16.04.42.png (4.5 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/02/15/103690/ad5a8180-b0a8-4c05-93fa-43442d3b953c.png">

はい！ちゃんと`healthy`になっているのでこれで間違いなく疎通できますね！

ただしこの方法は、docker compose公式ドキュメントのversion2.1には記載があるものの、version3では削除されていました。。
心配になりググってみると

> Version 3 no longer supports the condition form of depends_on.

と言う文言がかなりの数出てきました。
公式ドキュメントではその記述は現時点では発見できなかったのですが、ドキュメントに載っていないものを使用するのは気が引けるなと思い、この方法は断念しました。

## 検証その3 シェルスクリプト(採用)

結構時間を潰しましたが、最終的に答えは公式ドキュメントにも載っていました。
`wait-for-it.sh`と言うものを使えという事でどうやらシンプルにシェルスクリプトを使えと言う感じらしいです。
いくつかリポジトリが紹介されていましたが、そこまで大それた制御を求めていなかったので、今回は自作しました。
リポジトリが気になる方は以下で紹介されてます↓

https://docs.docker.jp/compose/startup-order.html

実際に使用したコードは↓です

``` wait-for-it.sh
#!/usr/bin/env ash

set -e

MAX_ATTEMPTS=10
for ((i=1; i <= $MAX_ATTEMPTS; i++)); do
		SLEEP_TIME=$(( 6 * $i ))
		HELTHCHECKTSTATE=$(docker inspect --format='{{json .State.Health}}' {dbコンテナ名} | jq -r '.Status')
		echo $HELTHCHECKTSTATE
		if [ "$HELTHCHECKTSTATE" = "healthy" ]; then
			echo "ready for test!!!"
			break
		fi
		sleep $SLEEP_TIME
done;

```

ポイントと言うほどのことはありませんが、最大60秒間待ってつながらなければ、流石に何かがおかしいだろうと言うところで、最大60秒間まで、6秒ずつ待ち時間を増やしていくシェルスクリプトを記載しました。

また、healthcheckの値は、`docker inspect`で確認ができるので、それを `jq`で加工して取り出しています。
実際のcodebuildの結果は、↓になります

<img width="475" alt="スクリーンショット 2022-02-15 17.12.07.png (12.3 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2022/02/15/103690/233c3134-6e06-416d-8982-257c2578d050.png">

しっかり`healthy`になって処理が終了していますね！

# おわりに
最終的には弊社では最後の方法を採用しましたが、おそらく他にも色々な方法があると思います！
もっといい方法があるよ！とか、このオプションを使えば、シェルスクリプトなしでもできる！とかあればどんどんコメントなど頂ければと思います🙇‍♂️

ありがとうございました！