kakakakakku blog

Weekly Tech Blog : Keep on Learning 👍

事前にデータ投入をした MySQL Docker イメージを作る場合は /docker-entrypoint-initdb.d を活用すると便利

事前にデータ投入をした MySQL Docker イメージが必要になり,最初は「Dockerfile で頑張る感じかなぁ...」なんて考えながら調査をしていたら,公式の MySQL Docker イメージに「カスタムスクリプトを実行する機能」が用意されていることを知って,全て解決した.今までも MySQL を Docker で動かす場面はあったけど,今回の機能は知らなくて勉強になった.

CI で使うためにマイグレーション実行済の MySQL Docker イメージを用意しても良いし,新メンバーのために開発用の初期データを投入した MySQL Docker イメージを用意しても良いし,ハンズオンイベントのためにテストデータを投入した MySQL Docker イメージを用意しても良いと思う.今回の仕組みを知っておくと便利な場面は多そう.

公式の Dockerfiledocker-entrypoint.sh を調べつつ,GitHub の Readme を読んでいたら,今回の機能に気付いた.

After the database is created, the entrypoint script will execute any .sh or .sql scripts found in /docker-entrypoint-initdb.d/ If you wish to execute any custom initialization scripts (e.g. for extra database or user creation), map them to this location the first time you start up the image.

github.com

github.com

動作確認

最初に動作確認をした結果を載せておこうと思う.今回まず Dockerfile と投入データが入った world.sql.gz を用意した.データは MySQL から公開されている world database にした.

.
├── Dockerfile
└── world.sql.gz

Dockerfile はシンプルで,以下のようになる.今回はサンプルとして MySQL 5.7 をベースにし,事前に world.sql.gz を投入しておくような設定にした.ファイルをコンテナ内部の /docker-entrypoint-initdb.d にコピーしている点がポイントで,ここに置いておくだけで,自動的に実行してくれる.

FROM mysql:5.7

COPY world.sql.gz /docker-entrypoint-initdb.d/world.sql.gz

実際にコンテナイメージをビルドして,コンテナを動かしてみると(オプションなどは必要に応じて),正常にデータが投入されていることを確認できた.便利!

$ docker run -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -it kakakakakku/mysql-57-world-database
$ docker exec -it xxxxxxxxxxxx /bin/sh

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| world              |
+--------------------+
5 rows in set (0.00 sec)

mysql> USE world;

mysql> SHOW TABLES;
+-----------------+
| Tables_in_world |
+-----------------+
| city            |
| country         |
| countrylanguage |
+-----------------+
3 rows in set (0.00 sec)

/docker-entrypoint-initdb.d とは?

もう少し /docker-entrypoint-initdb.d の仕組みを調べてみようと思う.まず docker-entrypoint.sh に以下のような実装があった.

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

よって,サポートされている拡張子と動作は以下となる.

  • .sh : そのまま実行する
  • .sql : mysql コマンドに流し込む
  • .sql.gz : 解凍してから mysql コマンドに流し込む

拡張子別に試してみた

さらに grant.sqlinit.sh も追加して,全ての拡張子を試してみた.

.
├── Dockerfile
├── grant.sql
├── init.sh
└── world.sql.gz

grant.sql では,以下の通り,新規ユーザーを作成して権限を付与している.

GRANT ALL PRIVILEGES ON world.* TO 'kakakakakku'@'localhost' IDENTIFIED BY 'kakakakakku';

init.sh では,単純に MySQL のバージョンを表示している.

#!/bin/sh

mysql --version

Dockerfile は,以下の通り.

FROM mysql:5.7

COPY init.sh /docker-entrypoint-initdb.d/init.sh
COPY world.sql.gz /docker-entrypoint-initdb.d/world.sql.gz
COPY grant.sql /docker-entrypoint-initdb.d/grant.sql

この状態でコンテナを起動すると,以下のようにログが出力されていて,正常に実行されていた.

/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/grant.sql

/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/init.sh
mysql  Ver 14.14 Distrib 5.7.20, for Linux (x86_64) using  EditLine wrapper

/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/world.sql.gz

新規ユーザーも作成できていた.

mysql> SELECT Host, User FROM mysql.user;
+-----------+-------------+
| Host      | User        |
+-----------+-------------+
| %         | root        |
| localhost | kakakakakku |
| localhost | mysql.sys   |
| localhost | root        |
+-----------+-------------+
4 rows in set (0.00 sec)

実行順序に気を付ける

もし,ファイルの実行順序に依存関係がある場合,今回の仕組みではファイル名の昇順で実行されてしまうため,問題になる場合がある.その場合は Dockerfile で数字などの接頭辞を付けて COPY することで,意図した順番で実行できるようになる.マイグレーションなどは,基本的にファイル名にタイムスタンプなどが入っていると思うので,大丈夫なはず.

FROM mysql:5.7

COPY init.sh /docker-entrypoint-initdb.d/1_init.sh
COPY world.sql.gz /docker-entrypoint-initdb.d/2_world.sql.gz
COPY grant.sql /docker-entrypoint-initdb.d/3_grant.sql

まとめ

  • 事前にデータ投入をした MySQL Docker イメージが必要な場合は /docker-entrypoint-initdb.d を使う
  • .sql だけではなく .sh.sql.gz もサポートしている
  • 実行順序に依存関係がある場合,ファイル名に気を付ける

今回,検証として world database を投入した MySQL Docker イメージを作って Docker Hub に公開してみた.MySQL のハンズオンなどに活用できそう.

github.com

関連記事

2016年に作った MySQL ハンズオン資料でも world database を使ったけど,Homebrew で MySQL をインストールして,データを投入するところから始まるので,ここを Docker イメージで置き換えてあげれば,もっと簡単に進行できそう!

github.com