DockerのHealthCheckでコンテナの実行順序を制御する。

## はじめに
プログラミング学習コミュニティ「progaku」で行っているチーム開発で取り組んだ内容になります。

## コンテナの実行順序の制御が必要になった場面
Dockerで環境構築を行い、初めてDB作成のコマンド実行する際に以下のようなエラーが発生
```
docker compose run backend rails db:create
```

エラー内容
```terminal
[+] Building 0.0s (0/0)                                                                                                              docker:desktop-linux
[+] Creating 3/0
 ✔ Network [ネットワーク名]   Created                                                                                                         0.0s
 ✔ Volume [ボリューム名]  Created                                                                                                         0.0s
 ✔ Container pa_database             Created                                                                                                         0.0s
[+] Running 1/1
 ✔ Container pa_database  Started                                                                                                                    0.1s
[+] Building 0.0s (0/0)                                                                                                              docker:desktop-linux
Can't connect to server on 'db' (115)
Couldn't create 'app_development' database. Please check your configuration.
rails aborted!
ActiveRecord::ConnectionNotEstablished: Can't connect to server on 'db' (115)


Caused by:
Mysql2::Error::ConnectionError: Can't connect to server on 'db' (115)

Tasks: TOP => db:create
(See full trace by running task with --trace)
```


## 原因
`[+] Running 1/1
 ✔ Container pa_database  Started  `
 ここでデータベースは起動されているけど
 `Mysql2::Error::ConnectionError: Can't connect to server on 'db' (115)`
 データベースに接続ができなかったと言われている

docker-compose.ymlは以下の通り
```docker-compose.yml
version: "3"

services:
  backend:
    container_name: pa_backend
    build:
      context: .
      dockerfile: ./infra/backend/Dockerfile
    volumes:
      - ./backend:/usr/src/app
    stdin_open: true
    tty: true
    env_file:
      - ./infra/env/backend.env
    ports:
      - 3000:3000
    command: sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    depends_on:
      - db
  db:
    image: mysql:8.0
    platform: linux/amd64
    container_name: pa_database
    volumes:
      - db-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
    ports:
      - 3306:3306
volumes:
  db-data:
    driver: local
```

調べるとデータベースは起動してから設定ファイルの読み込み等行うため、実際に接続完了となるまでに少し時間がかかるようです。


## depends_onの仕様を理解する

https://dev.mysql.com/doc/refman/8.4/en/windows-server-first-start.html

ただ、現在すでに
「depends_on」オプションを指定してサービス間の起動順序を指定するようになっています。
```
    depends_on:
      - db
```
depends_onはサービス間の依存関係を設定するものです。
上記の例ではbakendのコンテナはdbのコンテナに依存しています。

なので
1. db
1. backend
の順番に実行されるようになっています。

では何故、接続に失敗してしまうのか？

https://docs.docker.jp/compose/compose-file/compose-file-v3.html#depends-on

調べてみるとドキュメントにまさに注意点として書かれていました。笑
>depends_on 使用時に注意すべき点 
depends_on では、 web を開始する前に db と redis の「準備」が整うのを待ちません。単に、順番通り開始するだけです。


## condtionオプション、healthcheckで実行順序を制御

準備が整うまでに待たせるために、depends_onオプションには
conditionというサブオプションがあります。

https://docs.docker.jp/compose/compose-file/


condtionの仕様
>condition ：依存関係を満たしているとみなす状態
service_started ：前述の短い構文のものと同等
service_healthy ：依存先のサービスを起動する前に、依存元のサービスが「 正常healthy 」（ healthcheck で示す）な状態を指定
service_completed_successfully ：依存先のサービスを起動する前に、依存元のサービスは正常に実行済みの状態を指定

service_healthyを指定し、条件を指定することで、その条件を満たすまで待機させることができるようです。

helthcheckの仕様
>healthcheck
このサービスのコンテナが「 正常healthy 」かどうかを判断するために実行する、確認用コマンドを設定します。
```
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost"]
  interval: 1m30s
  timeout: 10s
  retries: 3
  start_period: 40s
```
helthcheckは依存先の方、今回で言うと「db」コンテナの方に書きます。
各項目の意味ですが
test = 確認用のコマンドを指定
interval = 確認コマンドを実行する間隔を指定
timeout = タイムアウト時間を指定
retries = リトライ、つまり再実行を行う数を指定
start_period = helthcheck開始後、失敗を無視する期間を指定
となっています。

今回はmysqlが接続できる状態になっていることを確認できれば良いのでそのコマンドを指定する必要があります。

mysqlでは`ping`というコマンドで接続可能かの確認ができるようです

>ping
サーバーが使用可能かどうかをチェックします。 サーバーが稼働中の場合は mysqladmin のリターンステータスは 0 になり、稼働していない場合は 1 になります。 Access denied のようなエラーの場合でも 0 となります。これは、サーバーは稼働しているが接続を拒否したことを意味しており、サーバーが稼働していない状態とは異なるからです。

https://dev.mysql.com/doc/refman/8.0/ja/mysqladmin.html

## 設定を反映
```docker-compose.yml
version: "3"

services:
  backend:
    container_name: pa_backend
    build:
      context: .
      dockerfile: ./infra/backend/Dockerfile
    volumes:
      - ./backend:/usr/src/app
    stdin_open: true
    tty: true
    env_file:
      - ./infra/env/backend.env
    ports:
      - 3000:3000
    command: sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    depends_on:
      db:
        condition: service_healthy
  db:
    image: mysql:8.0
    platform: linux/amd64
    container_name: pa_database
    volumes:
      - db-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
    ports:
      - 3306:3306
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "db", "-u", "root", "-proot"]
      interval: 5s
      timeout: 10s
      retries: 5
volumes:
  db-data:
    driver: local

```

## 変更点
backend側にconditionオプションを追加しました。
service_healthyを指定
```yml
    depends_on:
      db:
        condition: service_healthy
```

db側にはhealthcheckオプションを追加

```yml
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "db", "-u", "root", "-proot"]
      interval: 5s
      timeout: 10s
      retries: 5
```
CMDは「,」区切りで書くようです。

とりあえず
intervalは5秒感覚
timeoutは10秒
retriesは5回
に設定しました。

## 再度実行

```
docker compose run backend rails db:create

[+] Building 0.0s (0/0)                                                                                                              docker:desktop-linux
[+] Creating 1/1
 ✔ Container pa_database  Recreated                                                                                                                  0.8s
[+] Running 1/1
 ✔ Container pa_database  Started                                                                                                                    0.1s
[+] Building 0.0s (0/0)                                                                                                              docker:desktop-linux
Created database 'app_development'
```

無事、接続可能状態になるまで待機して、db:createコマンドを実行することができました。


## まとめ
以上になります。
何となくではなく１つひとつのコマンド、処理の仕様を理解することの大切さを改めて学びました。焦ってとりあえず動いたら良いで進めてしまいがちになってしまうので気をつけていきたいです。
初学者なので、間違っている点などあれば教えていただけると幸いです。