この投稿は グレンジ Advent Calendar 2021 の 7日目の記事です。
こんにちは、株式会社グレンジでサーバーサイドエンジニアをしています。
新卒の島田(@konnyaku256)です。
最近、Docker版のMariaDBを使う機会があり、その際に初期化SQLを実行しようとして詰まりました。
この記事では、その話についてまとめたいと思います。
MariaDBの初期化について
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, .sql.gz, .sql.xz and .sql.zst that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order. .sh files without file execute permission are sourced rather than executed. You can easily populate your mariadb services by mounting a SQL dump into that directory and provide custom images with contributed data. SQL files will be imported by default to the database specified by the MARIADB_DATABASE / MYSQL_DATABASE variable.
引用:https://hub.docker.com/_/mariadb
Docker HubのDescriptionによると、Docker版のMariaDBでは、/docker-entrypoint-initdb.d に .sql,.sh, .sql.gzファイルを配置しておくと、コンテナの起動時にそれらのファイルを読み込んで実行してくれる、という説明が書かれています。
この仕組みを使えば、例えば、DBやユーザの作成といった初期化処理を実行することができそうです。
しかし...
初期化SQLが実行されない
次の構成でイメージを作成し、コンテナを起動します。
ディレクトリ構成
$ tree
.
├── Dockerfile
└── init
└── init.sql
初期化SQL(最終行は改行です)
CREATE DATABASE `test`;
Dockerfile
FROM mariadb:5.5.40
COPY init/* /docker-entrypoint-initdb.d/
CMD ["mysqld"]
一見すると、良さそうに見えます。
イメージをビルドして、コンテナを起動してみます。
$ docker build -t mariadb-example ./ && docker run -d -e MYSQL_ROOT_PASSWORD=password --name mariadb-example mariadb-example
コンテナの状態を確認します。
$ docker ps -a --no-trunc -f name=mariadb-example
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6395ad036dfa74597853a2cf1e62d73f30b7446547d2a262a887dc1614c47b2c mariadb-example "/docker-entrypoint.sh mysqld" 6 minutes ago Up 6 minutes 3306/tcp mariadb-example
ステータスを見る限り、正常に起動できていそうです。
コンテナにログインして詳細を確認します。
$ docker exec -it mariadb-example /bin/bash
MariaDBにログインします。
root@0b4910572e26:/# mysql -ppassword
データベースの一覧を取得して、testというDBが作成されているか確認します。
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)
あれ!? 初期化SQLに記述したDBがありません!
原因はdocker-entrypoint.sh
docker ps
の結果から、今回作成したMariaDBコンテナでは起動時に docker-entrypoint.sh が実行されていることがわかります。
COMMAND
"/docker-entrypoint.sh mysqld"
つまり、docker-entrypoint.sh を見れば、なぜ初期化SQLが実行されないかわかりそうです。
シェルスクリプトの中身を確認します。
root@0b4910572e26:/# cat docker-entrypoint.sh
#!/bin/bash
set -e
if [ ! -d '/var/lib/mysql/mysql' -a "${1%_safe}" = 'mysqld' ]; then
if [ -z "$MYSQL_ROOT_PASSWORD" ]; then
echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set'
echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ?'
exit 1
fi
mysql_install_db --user=mysql --datadir=/var/lib/mysql
# These statements _must_ be on individual lines, and _must_ end with
# semicolons (no line breaks or comments are permitted).
# TODO proper SQL escaping on ALL the things D:
TEMP_FILE='/tmp/mysql-first-time.sql'
cat > "$TEMP_FILE" <<-EOSQL
DELETE FROM mysql.user ;
CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
DROP DATABASE IF EXISTS test ;
EOSQL
if [ "$MYSQL_DATABASE" ]; then
echo "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE ;" >> "$TEMP_FILE"
fi
if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" >> "$TEMP_FILE"
if [ "$MYSQL_DATABASE" ]; then
echo "GRANT ALL ON $MYSQL_DATABASE.* TO '$MYSQL_USER'@'%' ;" >> "$TEMP_FILE"
fi
fi
echo 'FLUSH PRIVILEGES ;' >> "$TEMP_FILE"
set -- "$@" --init-file="$TEMP_FILE"
fi
chown -R mysql:mysql /var/lib/mysql
exec "$@"
今回配置した初期化SQLが実行されない原因がわかりました。
/docker-entrypoint-initdb.d に関する処理が何もありません。
公式ドキュメントの嘘つき!と言いたいところですが、おそらくこのバージョンが公開された時点では /docker-entrypoint-initdb.d を使った初期化機能は実装されていなかったのでしょう。
docker-entrypoint.shを置き換える
/docker-entrypoint-initdb.d が使えないとわかったので、代替案を考えます。
今回は、手っ取り早く docker-entrypoint.sh を置き換えることにします。
ディレクトリ構成
$ tree
.
├── Dockerfile
├── docker-entrypoint.sh
└── init
└── init.sql
次のようなdocker-entrypoint.shを追加します。
#!/bin/bash
set -e
if [ ! -d '/var/lib/mysql/mysql' -a "${1%_safe}" = 'mysqld' ]; then
if [ -z "$MYSQL_ROOT_PASSWORD" ]; then
echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set'
echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ?'
exit 1
fi
mysql_install_db --user=mysql --datadir=/var/lib/mysql
# These statements _must_ be on individual lines, and _must_ end with
# semicolons (no line breaks or comments are permitted).
# TODO proper SQL escaping on ALL the things D:
TEMP_FILE='/tmp/mysql-first-time.sql'
cat > "$TEMP_FILE" <<-EOSQL
DELETE FROM mysql.user ;
CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
DROP DATABASE IF EXISTS test ;
EOSQL
if [ "$MYSQL_DATABASE" ]; then
echo "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE ;" >> "$TEMP_FILE"
fi
if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" >> "$TEMP_FILE"
if [ "$MYSQL_DATABASE" ]; then
echo "GRANT ALL ON $MYSQL_DATABASE.* TO '$MYSQL_USER'@'%' ;" >> "$TEMP_FILE"
fi
fi
# 初期化SQLを実行
cat ./initdb/init.sql >> "$TEMP_FILE"
echo 'FLUSH PRIVILEGES ;' >> "$TEMP_FILE"
set -- "$@" --init-file="$TEMP_FILE"
fi
chown -R mysql:mysql /var/lib/mysql
exec "$@"
置き換え前との差分は次の行です。
既存のシェルスクリプトではDBの初期化に変数$TEMP_FILE
を使っているようだったので、初期化SQLの中身を$TEMP_FILE
の末尾に追加しています。
# 初期化SQLを実行
cat ./initdb/init.sql >> "$TEMP_FILE"
ファイルの権限は755に変更しておきます。
$ chmod 755 docker-entrypoint.sh
Dockerfileを次のように修正します。
FROM mariadb:5.5.40
COPY init/* /initdb/
COPY docker-entrypoint.sh /
CMD ["mysqld"]
イメージのビルドからMariaDBにログインまで再度実行します。
データベースの一覧を取得して、testというDBが作成されているか確認します。
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| test |
+--------------------+
4 rows in set (0.00 sec)
今度はうまく作成されました!🙌
まとめ
- Docker版 MariaDB 5.5.40(かなり古い)では /docker-entrypoint-initdb.d ディレクトリにsqlファイルを配置しても初期化を実行できなかった
- 代わりに docker-entrypoint.sh を置き換えて、初期化SQLが実行されるようにした
構成が似ているので、MySQLでも古いバージョンでは類似の問題に遭遇するかもしれないですね。
なかなか踏む機会はないと思いますが、どなたかの参考になれば幸いです。
明日は、gamuさんの記事が投稿される予定です。 お楽しみに!