LoginSignup
8
2

More than 1 year has passed since last update.

【Docker】MariaDBで初期化SQLを実行しようとして詰まった話

Posted at

この投稿は グレンジ 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(最終行は改行です)

init.sql
CREATE DATABASE `test`;

Dockerfile

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
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を追加します。

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を次のように修正します。

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さんの記事が投稿される予定です。 お楽しみに!

参考

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2