docker-composeでmysql使うとき初回起動時に複数のDBを作る方法

とりあえず自分が慣れてるんで `docker-compose` のymlの書式で書きますが、コマンドラインからでも同じことはできるはずでやんす。

## tl; dr
* `MYSQL_DATABASE` をenvに指定すれば１個は作ってくれるけど、複数個のDBは作ってくれない
* `/docker-entrypoint-initdb.d` にあるSQLは勝手に突っ込んでくれるしシェルがあればシェルを実行してくれる
* `/docker-entrypoint-initdb.d` にいい感じのシェルを書くといい感じにDBが作れるというか、なんでもできる。

## やりたいこと

以下のような `docker-compose.yml` を用意して、２つのDBを作りたいと思っています。

```yaml:docker-compose.yml
version: '2'
services:
  db:
    image: mysql
    environment:
      MYSQL_DATABASE: first_database
      MYSQL_USER: username
      MYSQL_PASSWORD: secret
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - db-data:/var/lib/mysql

volumes:
  db-data:
    driver: local
```

この段階で `first_database` というDBは１つ作られてますが、
同じコンテナの中に `second_database` というDBを作りつつ、
`first_database` と `second_database` それぞれにダンプを投げつけたい、
というのが今回の目的になります。

## DockerのMySQLの初期化処理

「DockerのMySQL」って言葉は多分に語弊があるんですが、いわゆるオフィシャルのMySQLのことを指してます。

https://hub.docker.com/_/mysql/

基本的には `MYSQL_DATABASE` であったり　`MYSQL_USER` といったenvを指定して起動することで、初回起動時にそこで指定したDBやユーザーを作ってくれます。

多くのケースでは１つのDBで十分でしょうし、
複数のDBを使いたい場合は、そもそも別のDockerのMySQLインスタンス立てればいいですので、
この機能だけでだいたい大丈夫だと思います。

が、どうしても、どうしても１つのホストに複数のDBを作りたいねん・・・といったことには、このenvの設定だけでは実現できません。

また、`CREATE DATABASE hoge` だけじゃなくて、テーブル群も作ってほしい！ということにも、これらenvは対応していません。

## Initializing a fresh instance

では、どうするか、ということで `Initializing a fresh instance` という項目で説明があるように、「最初の起動時にいい感じにする機能」が用意されています。

> When a container is started for the first time, a new database with the specified name will be created and initialized with the provided configuration variables. Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order.
> 
> 初回のコンテナの起動時に、指定された名前で新しいデータベースが作成されたり、設定された値で初期化処理が行われます。
> さらに、 `/docker-entrypoint-initdb.d` にある `.sh` `.sql` `.sql.gz` ファイルが、ファイル名のアルファベット順で実行されます。

というわけで、 `/docker-entrypoint-initdb.d` にSQLファイルを置いたりすればいい感じにできます。
が、SQLファイルでは、 `MYSQL_DATABASE` に指定したDBに対してダンプファイルを流すだけですので、他のDBを作ったりすることはできません。


## シェルを配置する

というわけで、以下のようなシェルを用意します。

```bash:001_second_database.sh
#!/bin/sh

echo "CREATE DATABASE IF NOT EXISTS \`second_database\` ;" | "${mysql[@]}"
echo "GRANT ALL ON \`second_database\`.* TO '"$MYSQL_USER"'@'%' ;" | "${mysql[@]}"
echo 'FLUSH PRIVILEGES ;' | "${mysql[@]}"

"${mysql[@]}" < /docker-entrypoint-initdb.d/second_database.sql_
```

`${mysql[@]}` みたいなよくわからない変数がでてきたりしてカオスですが、
１行目で `second_database` を作成し、
２行目で `second_database` に対して　`MYSQL_USER` env で指定したユーザーに権限を与え
３行目で `FLUSH PRIVILEGES` し、
５行目で `second_database` に対してダンプしたSQLである `second_database.sql_` を流し込んでる感じです。

### ファイル構成

まだ、若干イメージついてないところがあると思いますので、ファイル構成を見てみましょう。

```ディレクトリ構成
.
├── docker-compose.yml
└── sql
    ├── 000_first_database.sql
    ├── 001_second_database.sh
    └── second_database.sql_
```

ポイントは、 `second_database.sql_` の末尾が `_` で終わってることです。
`_` じゃなくてもいいのですが、とにかく `.sql` `.sh` で終わらなければOKです。

この辺のハンドリング行ってるシェルの処理を見たほうが理解は早いと思います。

引用: https://github.com/infosiftr/mysql/blob/dbb83a94ee0a994651dd430d27caeac652cf9e78/5.7/docker-entrypoint.sh#L108-L116

```bash
for f in /docker-entrypoint-initdb.d/*; do
  case "$f" in
    *.sh)     echo "$0: running $f"; . "$f" ;;
    *.sql)    echo "$0: running $f"; "${mysql[@]}" < "$f"; echo ;;
    *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${mysql[@]}"; echo ;;
    *)        echo "$0: ignoring $f" ;;
  esac
  echo
done
```

こんな感じで処理されてるので、ファイル名の末尾に気をつければ、無視されるので、 `.sql_` のように適当な文字列で終わらせてます。

（閑話休題）

で、最終的には以下のようになります。

```yaml:docker-compose.yml
version: '2'
services:
  db:
    image: mysql
    environment:
      MYSQL_DATABASE: first_database
      MYSQL_USER: username
      MYSQL_PASSWORD: secret
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - ./sql:/docker-entrypoint-initdb.d
      - db-data:/var/lib/mysql

volumes:
  db-data:
    driver: local
```

`volumes` に `./sql` のマウントの記述を追記しました。
これで初回起動時に、 `./sql`以下のSQLやシェルが実行され、やりたいことを実現できました。やったぜ。

また、これは「初回起動時」限定の動作なので、すでに起動しちゃっていて配布するのでなければ、直接ダンプを流し込めばいいと思います。
配布用にバチッっと初回処理として作りたいのであれば、とりあえずボリュームを消したあとに起動すれば、`./sql` 内のSQLやシェルが動いているのがわかると思います。