docker-composeでDBの起動完了を待ってからWebアプリを実行する

# はじめに

docker-composeで複数のコンテナを管理するとき、ほぼWebアプリとDBを一緒に使います。
大抵はWebアプリ側に`depends_on`でDBのコンテナを指定して起動順序を制御しますが、あくまで起動順序だけなのでDBの起動完了前にWebアプリがDBにアクセスしてしまい起動失敗する事があります。

せっかくdocker-composeで1つにまとめて管理しているのに便利さが半減してしまうとモヤモヤしていたら、[公式](https://docs.docker.com/compose/startup-order/)ではWebアプリ側でチェックせよと解説していました。


## depends_on/condition

**2021/07/21追記**
depends_onで指定したサービスのhealthcheckが通ってから起動できるconditionが追加されました。これを使えばチェック用スクリプトを使わずにすみます。詳しくは[Docker Compose の depends_on の使い方まとめ](https://gotohayato.com/content/533/)を参照してください。

# GitBucket + MySQLの場合

上述の公式の解説ではPostgreSQLを対象にしているのですが、MySQLをよく使うので書き換えてみました。

ファイル構成はこんな感じです。

```txt:files
docker-compose.yml
  + gitbucket/
      + Dockerfile
      + wait.sh
```

ベースイメージはalpine版なのでapkでmysql-clientをインストールしています。

```txt:gitbucket/Dockerfile
FROM java:8-jre-alpine
MAINTAINER shiena
ENV GITBUCKET_HOME /var/gitbucket
VOLUME ["${GITBUCKET_HOME}"]
EXPOSE 8080
RUN apk add --no-cache mysql-client
COPY wait.sh /wait.sh
ENV GITBUCKET_VERSION 4.7.1
ADD https://github.com/gitbucket/gitbucket/releases/download/${GITBUCKET_VERSION}/gitbucket.war /gitbucket.war
```

## depends_on/condition版

- versionを`3`にします。
- dbに`healthcheck/test`を追加してチェック用コマンドを追加します。
- gitbucketの`depends_on/db`をリストから要素に変更してその子要素に`condition: service_healthy`を追加します。

これでdbのhealtcheck/testで指定したコマンドが成功するとgitbucketが起動するようになります。

```yaml:docker-compose.yml
version: '3'
services:
  gitbucket:
    build: gitbucket
    image: gitbucket:4.7.1
    ports:
      - "8080:8080"
    volumes:
      - ./var/gitbucket:/var/gitbucket
    depends_on:
      db:
        condition: service_healthy
    command: java -jar /gitbucket.war

  db:
    image: mysql:5.7
    ports:
      - "3306:3306"
    environment:
      - MYSQL_RANDOM_ROOT_PASSWORD=1
      - MYSQL_DATABASE=gitbucket
      - MYSQL_USER=gitbucket
      - MYSQL_PASSWORD=gitbucket
      - TZ=Asia/Tokyo
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    healthcheck:
        test: mysqladmin ping -h 127.0.0.1 -u$$MYSQL_USER -p$$MYSQL_PASSWORD
```

MySQLは`mysqladmin `コマンドでポートチェックできますが、httpやtcpなどは以下のような方法や他のコマンドも利用できます。

- httpチェックを `curl -f http://localhost:8080`
- tcpのチェックをbashで `bash -c "echo > /dev/tcp/127.0.0.1/80"`
- tcpのチェックをnetstatで `netstat -tnl | awk '$$1 ~ /^tcp/ && $$4 ~ /:80$$/{rc=1}END{exit !rc}'`

## チェックスクリプト版

今回のメインとなる`wait.sh`は引数からDBのホスト名、ユーザ名、パスワードを受け取って生存チェック後にWebアプリを実行します。

```sh:gitbucket/wait.sh
#!/bin/sh

set -e

host="$1"
shift
user="$1"
shift
password="$1"
shift
cmd="$@"

echo "Waiting for mysql"
until mysql -h"$host" -u"$user" -p"$password" &> /dev/null
do
        >&2 echo -n "."
        sleep 1
done

>&2 echo "MySQL is up - executing command"
exec $cmd
```

最後にdocker-compose.ymlです。
wait.shにDBの情報を渡す必要があるため、`command`でGitBucketを起動します。

```yaml:docker-compose.yml
version: '2'
services:
  gitbucket:
    build: gitbucket
    image: gitbucket:4.7.1
    ports:
      - "8080:8080"
    volumes:
      - ./var/gitbucket:/var/gitbucket
    depends_on:
      - db
    command: sh /wait.sh db gitbucket gitbucket java -jar /gitbucket.war

  db:
    image: mysql:5.7
    ports:
      - "3306:3306"
    environment:
      - MYSQL_RANDOM_ROOT_PASSWORD=1
      - MYSQL_DATABASE=gitbucket
      - MYSQL_USER=gitbucket
      - MYSQL_PASSWORD=gitbucket
      - TZ=Asia/Tokyo
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
```

以上、このようなスクリプトを挟む事でDBの起動完了を待ってからWebアプリを起動できるようになります。

# 参考

* [サービスの完成する順番が前後する問題 - docker-compose depends_onとlinksの違い](http://qiita.com/sivertigo/items/9baa73d922a68788402b#サービスの完成する順番が前後する問題)
* [Controlling startup order in Compose](https://docs.docker.com/compose/startup-order/)
* [DreamItGetIT/wait-for-mysql](https://github.com/DreamItGetIT/wait-for-mysql)
* [vishnubob/wait-for-it](https://github.com/vishnubob/wait-for-it)
* [jwilder/dockerize](https://github.com/jwilder/dockerize)
* [Eficode/wait-for](https://github.com/Eficode/wait-for)
* [Docker Compose の depends_on の使い方まとめ](https://gotohayato.com/content/533/)