とりあえず自分が慣れてるんで docker-compose
のymlの書式で書きますが、コマンドラインからでも同じことはできるはずでやんす。
tl; dr
-
MYSQL_DATABASE
をenvに指定すれば1個は作ってくれるけど、複数個のDBは作ってくれない -
/docker-entrypoint-initdb.d
にあるSQLは勝手に突っ込んでくれるしシェルがあればシェルを実行してくれる -
/docker-entrypoint-initdb.d
にいい感じのシェルを書くといい感じにDBが作れるというか、なんでもできる。
やりたいこと
以下のような docker-compose.yml
を用意して、2つのDBを作りたいと思っています。
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は1つ作られてますが、
同じコンテナの中に second_database
というDBを作りつつ、
first_database
と second_database
それぞれにダンプを投げつけたい、
というのが今回の目的になります。
DockerのMySQLの初期化処理
「DockerのMySQL」って言葉は多分に語弊があるんですが、いわゆるオフィシャルのMySQLのことを指してます。
基本的には MYSQL_DATABASE
であったり MYSQL_USER
といったenvを指定して起動することで、初回起動時にそこで指定したDBやユーザーを作ってくれます。
多くのケースでは1つのDBで十分でしょうし、
複数のDBを使いたい場合は、そもそも別のDockerのMySQLインスタンス立てればいいですので、
この機能だけでだいたい大丈夫だと思います。
が、どうしても、どうしても1つのホストに複数の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を作ったりすることはできません。
シェルを配置する
というわけで、以下のようなシェルを用意します。
#!/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[@]}
みたいなよくわからない変数がでてきたりしてカオスですが、
1行目で second_database
を作成し、
2行目で second_database
に対して MYSQL_USER
env で指定したユーザーに権限を与え
3行目で FLUSH PRIVILEGES
し、
5行目で 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です。
この辺のハンドリング行ってるシェルの処理を見たほうが理解は早いと思います。
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_
のように適当な文字列で終わらせてます。
(閑話休題)
で、最終的には以下のようになります。
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やシェルが動いているのがわかると思います。