2016/10/18 20:29:49

Docker公式MySQLのDockerfileを頑張って読んで理解しようとしてみた[docker-entrypoint.sh編]

dokcer
目次(クリックするとジャンプします)
  • 1:Dockerfileが読みたいの
  • 2:エラー時の挙動設定
  • 3:オプションの確認
  • 3.1:拡張的な変数展開
  • 3.2:特殊変数の書き換え
  • 4:特定のオプションがあった場合
  • 4.1:しらないarg
  • 4.2:ヘルプなどのオプションがあったら…
  • 5:条件分岐
  • 6:ゲットこんふぃぐ
  • 6.1:データディレクトリの取得
  • 7:$DATADIR/mysqlの存在分岐
  • 7.1:データベースがまだ作られていない場合…
  • 8:設定値の不足処理
  • 8.1:情報が不足していたら…
  • 9:ディレクトリの作成・ユーザ割り当て
  • 10:データベースイニシャライズ
  • 11:mysqldの起動およびネットワークの無効化・pidの取得
  • 12:クライアントの起動の準備
  • 12.1:コマンドの格納
  • 12.2:どんな出力になるのか
  • 12.3:結局なにが代入された?
  • 13:クライアント起動
  • 13.1:コマンドのうまい使い方
  • 13.2:確認用SQL文
  • 14:時刻の設定
  • 14.1:タイムゾーンデータの取得
  • 15:ランダムパスワードの生成
  • 16:データベースの準備
  • 16.1:バイナリログの無効化
  • 16.2:rootユーザの作成
  • 17:rootパスワードの追加
  • 18:データベースの作成
  • 19:ユーザを作った際の処理
  • 20:データベースの独自初期化用処理
  • 21:ワンタイムパスワードが有効な場合
  • 22:プロセスの終了
  • 23:分岐の最後
  • 24:まとめ

Dockerfileが読みたいの

今回は公式のMySQLのDockerfileを読んでみようと頑張りました。この記事は長すぎたので2つに分割しています。こちらの記事はDockerfileに付随しているdocker-entrypoint.sh編です。

今回読んでみたのは以下です。

docker-library/mysql/5.7/

Dockerfile本体の記事はこちらです。ぜひ参照してください。

Docker公式MySQLのDockerfileを頑張って読んで理解しようとしてみた[Dockerfile本体編]

それではdocker-entrypoint.sh編の始まりです。

エラー時の挙動設定

set -eo pipefail

このシェルスクリプトではパイプ中のエラーでもちゃんと終了するようにpipefailオプションをつけています。ともかくちゃんと落ちろということのようです。

オプションの確認

まずは冒頭の部分

# if command starts with an option, prepend mysqld
if [ "${1:0:1}" = '-' ]; then
    set -- mysqld "$@"
fi

コメントでは

# if command starts with an option, prepend mysqld
もしオプションがあったらmysqldを先頭に足します

とあるのでオプション関係の処理のようですね。

拡張的な変数展開

さっそく${0:1:0}の部分からわからなくてハマりましたが、この${0:1:0}は「bashの拡張的な変数展開」というものらしく、変数を細かくハンドリングしたい時に有用なのだそうです。

意味としては以下の様になります。

${変数:位置:個数}

${1:0:1}の場合、$1(引数の1個目)の最初の文字(0)から1個分(1)という意味になります。

引数がuncoだったらuを表すことになりますし、--set-server-caracterだったら-になります。

if [ "${1:0:1}" = '-' ]; then

-と比較してならという意味ですから、$1(引数の一個目)の最初の文字(0)から1個分(1)が-だったらという意味になります。

つまりこのコードでは-があるかどうかでオプションの有無を確認しているようです。

特殊変数の書き換え

さて、この条件にかなっていたら次は

set -- mysqld "$@"

を行います。

setはシェルオプションの設定等に使いますが、ここでは変数の内容を変更するために使っています。

--という不思議なオプションがありますが、これは「これ以降はオプションじゃないよ」という意味になります。これもなかなか情報が見つからず苦労しました。

setはオプションが何もなくて値を与えられると、与えられた値は位置パラメーターに代入されます。つまり$@$1などです。

この処理でmysqld$@の展開が新たに$@の内容になります。結果的に引数の先頭にmysqldが足された格好になるわけです。なるほど。わかりにくい。

特定のオプションがあった場合

# skip setup if they want an option that stops mysqld
wantHelp=
for arg; do
    case "$arg" in
        -'?'|--help|--print-defaults|-V|--version)
            wantHelp=1
            break
            ;;
    esac
done

コメントでは

# skip setup if they want an option that stops mysqld
もしMySQLを止めるオプションがあったらセットアップをスキップします。

とあります。何らかのオプションがある時にはセットアップしない処理のようですね。

しらないarg

for arg;

さて、まずargってどっからでできたんだよ。argって引数ってことだよね?でもどこにも定義されていないぞ?

argには何がはいるんだ〜!!????

散々調べた結果、驚きの答え…。

通常for文は

for 変数 in 配列

と書きますが、inを省略すると$@がforの対象になるそうです。

え〜…。ちゃんと書いたほうが読みやすくない?みんなこうやって書くの?

ともかくargには引数が全部入るわけですね。

ヘルプなどのオプションがあったら…

もし以下のようなヘルプやバージョン確認などのオプションが与えられていたらwanthelpに1が代入されるようになっています。 そしてbreakで抜けてますね。wanthelpはのちの条件式で使われます。

-'?'|--help|--print-defaults|-V|--version

DockerfileではCMDがmysqldになっているので、本来はなにも引数を与えなくてもMySQLが起動するようになっています。

しかしもしオプションを付けて起動したいなら、CMDが上書きされてしまうので、

mysqld -オプション

という風にmysqldを付けて起動する必要があるのですが、このスクリプトのおかげでオプションだけでもMySQLが起動するようになっているのです。親切なのかこれ?

条件分岐

if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then

ここは簡単ですね。「引数の1番目が(文字列の)’mysqld’でかつ(-a)$wantHelpが空白の場合(-z)」を表しています。

先ほど処理していた特定のオプションがあった場合はここで弾かれるようになっているのですね。

これ以降がメインの処理になっていきます。

ゲットこんふぃぐ

# Get config
    DATADIR="$("$@" --verbose --help 2>/dev/null | awk '$1 == "datadir" { print $2; exit }')"

さて何やら複雑な感じです…。焦らずに解読します。

データディレクトリの取得

DATADIRに何らかの値を代入しようとしているようですね。

まずこのラインは$()でくくられているのでコマンド結果展開ですね。

"$@" --verbose --help 2>/dev/null

ここでは引数を展開してなおかつ、--verbose --helpの2つのオプションを付けて実行しパイプで次に送っています。

$@は通常であればmysqldですから

mysqld --verbose --help

が実行されてパイプに渡される訳ですね。$@にはちゃんとオプションも入りますから安心です。

さて--verboseを付けてhelpを開くと、 以下の様にMySQLのさまざまな設定値などが出力されます。 (ものすごくたくさんの項目があるので、以下は一部です)

Variables (--variable-name=value)
and boolean options {FALSE|TRUE}                             Value (after reading options)
------------------------------------------------------------ -------------
abort-slave-event-count                                      0
allow-suspicious-udfs                                        FALSE
archive                                                      ON
auto-increment-increment                                     1
auto-increment-offset                                        1
autocommit                                                   TRUE
automatic-sp-privileges                                      TRUE
avoid-temporal-upgrade                                       FALSE
back-log                                                     80
basedir                                                      /usr
big-tables                                                   FALSE
bind-address                                                 *
binlog-cache-size                                            32768
binlog-checksum                                              CRC32
binlog-direct-non-transactional-updates                      FALSE
binlog-error-action                                          ABORT_SERVER
binlog-format                                                ROW
binlog-group-commit-sync-delay                               0
binlog-group-commit-sync-no-delay-count                      0
binlog-gtid-simple-recovery                                  TRUE
binlog-max-flush-queue-time                                  0
binlog-order-commits                                         TRUE

次にawkdatadirでマッチする行の2コラム目を取得しています。

 awk '$1 == "datadir" { print $2; exit }'

先ほど出力されたなかには以下のような項目があります。これはコンテナに入ってみて確認してみました。

datadir     /var/lib/mysql/

つまりawk/var/lib/mysql/を取得しているのです。このディレクトリはデータベースのデータが格納される場所のはずです。

こうして得られたDATADIRは以降でも様々に使われます。

$DATADIR/mysqlの存在分岐

if [ ! -d "$DATADIR/mysql" ]; then

ここも簡単ですね。$DATADIR/mysqlがディレクトリじゃないなら(! -d)という分岐です。

データベースがまだ作られていない場合…

つまり 「/var/lib/mysql/mysqlがディレクトリじゃない時〜」ってことになるのだと思います。

公式のMySQLではコンテナが起動されると普通はデータベースを自動的に作られます(はず)。

データベースが作られると/var/lib/mysql/の直下にmysqlディレクトリが作られるようになっているはずです。

なのでこのディレクトリを確認することですでにデータベースが作ってあるコンテナなのかどうかの分岐をさせられるようになっているようです。2回め以降はデータベースを作る必要がないですからね。

こういう分岐がないといつも「データベースつくってええええ」という気味の悪いコンテナになっちゃいマス。

設定値の不足処理

if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
            echo >&2 'error: database is uninitialized and password option is not specified '
            echo >&2 '  You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD'
            exit 1
        fi

ここでは設定値が不足していた場合の分岐を行っています。公式のMySQLコンテナは少くともMYSQL_ROOT_PASSWORDMYSQL_ALLOW_EMPTY_PASSWORDMYSQL_RANDOM_ROOT_PASSWORD のどれかを設定しなければいけません。

root のパスワードがない状態ではこの後に行うデータベースの生成が行えないですからね。

情報が不足していたら…

条件に$MYSQL_ROOT_PASSWORD$MYSQL_ALLOW_EMPTY_PASSWORD$MYSQL_RANDOM_ROOT_PASSWORDのすべて(-a)が空白だった場合(-z)に、エラーメッセージを出すようになっていますね。

最後にexit 1でエラー終了させています。

runの時に実際に試すとどういう挙動になるかわかりますよ。

ディレクトリの作成・ユーザ割り当て

mkdir -p "$DATADIR"
chown -R mysql:mysql "$DATADIR"

ここも難しくないですね。先ほどのDATADIRを使ってディレクトリをつくって、所有権をmysqlに与えています。

データベースイニシャライズ

echo 'Initializing database'
"$@" --initialize-insecure
echo 'Database initialized'

--initialize-insecureを実行すると、rootであればパスワード無しでログインできるとのことです。コンテナ内からのログインはパスワード無しでOKってことかな?

mysqldの起動およびネットワークの無効化・pidの取得

"$@" --skip-networking &
pid="$!"

--skip-networkingでネットワーク越しアクセスを禁止しているようです。コンテナですから外からアクセスされても困りますしね。

"$@" --skip-networking&が付いていますが、これはバックグラウンド実行をさせるためです。

pid="$!"でプロセスIDを取得していますが、$!はバックグラウンドで実行された直前のプロセスのプロセスIDを保持しています。

ですから$!には、先ほどの"$@" --skip-networking(mysqld –skip-networking) をバックグラウンド実行した際のmysqldのプロセスIDが入っているはずです。それをpidに代入しているのです。

pidは後の処理で使われマス。

クライアントの起動の準備

さて次のこの部分。かなりハマりました。良くわからない。

mysql=( mysql --protocol=socket -uroot )

コマンドの格納

まず右辺の部分ですが、

( mysql --protocol=socket -uroot )

()でくくられているのでコマンド実行ですよね。

mysqlですからクライアント起動をしているのだと思います。

--protocolはプロトコルの指定でsocketにしています。そしてrootでログインするということdayone、これはわかりますが…。

左辺のこれ。

mysql=

だ、代入?しているよ?なにが入るの?

なにが代入されるのか確かめるために以下のようにしてコンテナを起動しました。これであればスクリプトは実行されない状態でコンテナが起動します。

$ docker run -it --name db mysql /bin/bash

まず試しに右辺だけ実行してみました。

# mysql --protocol=socket -uroot

そしたら…

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

あ?あんだこら?どうやらmysqld.sockが無いと怒られたようです。

公式MySQLコンテナに出力されるmy.cnfの抜粋ですが、通信用のソケットの指定がされているのがわかるかと思います。(socket=/var/run/mysqld/mysqld.sock)ここにソケットがないとあかんのです。

[client]
port            = 3306
socket          = /var/run/mysqld/mysqld.sock

[mysqld_safe]
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock
nice            = 0

[mysqld]
skip-host-cache
skip-name-resolve
user            = mysql
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock

mysqld.sockとはUNIXドメインソケットのことで、プロセス間通信に用いられるファイルディスクリプタです。localhostの場合はこのファイルを介して通信するんですね。

いろいろ調べてみましたが、mysqldを起動させないとmysqld.sockは生成されないそうです。なるほど。いやあたりまえか。

どんな出力になるのか

実際先ほどのテスト用コンテナで

mysqld --skip-networking &

を実行すると以下のような出力がありました。(抜粋)

2016-04-16T02:56:41.480641Z 0 [Note] mysqld: ready for connections.
Version: '5.7.10'  socket: '/var/run/mysqld/mysqld.sock'  port: 0  MySQL Community Server (GPL)

一番下にソケットができた旨の出力がありますよね。

mysqldがバックグラウンドで起動している状態で(mysqld: ready for connections)ソケットができたので接続可能になったということのようです。

結局なにが代入された?

で前置きが随分長かったですが、

mysql=( mysql --protocol=socket -uroot )

にはなにが代入されたんでしょうか?実際出力させてみると…

mysql

え?これだけ?もっとゴイスーな数値が入ったりしないの?変数にいれる必要あるの?

しかしこれはこの後に出てくる更に難解な変数の序章でした…。

クライアント起動

for i in {30..0}; do
    if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then
        break
    fi
    echo 'MySQL init process in progress...'
    sleep 1
done
if [ "$i" = 0 ]; then
    echo >&2 'MySQL init process failed.'
    exit 1
fi

さてこの箇所では何をやっているんでしょう?

この部分から察するに何かを30回試行するみたいですね。

for i in {30..0}; do

コマンドのうまい使い方

さて問題はお次の箇所。

if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then
    break
fi

${mysql[@]}←ファッ?

なにこれ?変数展開ということはギリ理解できるが[@]ってなんやねん…難解。

調べた、ググった、全然[@]の情報がヒットしない…。記号って検索しにくいな…。

というわけで自分でシェルスクリプトを書いて挙動を確かめました。

結論から言うと[@]は「引数を伴った変数展開」を表すようです。

例えば

list=( ls -a -l )
echo "${list}"

ls

が出力されました。オプションは出力されないんですね。一方こちらは

list=( ls -a -l )
echo "${list[@]}"

以下のように出力されます。

ls -a -l

オプションも出力されていますね。

先ほど

mysql=( mysql --protocol=socket -uroot )

の部分で変数mysqlには”mysql”が格納されました。

これは@MINOが勘違いしていたのですが、( )でくくるのは「サブシェルでのコマンド実行」であり、「実行結果」を表すものではなかったのです。

実行結果は$( )で表します。試しに以下の様にしてみると…

#/bin/bash                                                                                                                                 
list=$(ls -a -l)                                                                                                                                 
echo "${list}"

以下のように出力されます。「実行結果」ですよね。

合計 1368
drwxr-xr-x 13 mino mino   4096  4月 16 13:06 .
drwxr-xr-x 60 mino mino   4096  4月 16 12:36 ..
drwx------ 25 mino mino   4096  4月  2 15:05 DesignPattern

一方( )はコマンド自体が代入されるという認識でいいのかな?

ともかく

mysql=( mysql --protocol=socket -uroot )

のmysqlにはmysql --protocol=socket -urootが格納され、オプションも出力するときには"${mysql[@]}"という書き方をするということのようです。

いやハマった…。

確認用SQL文

さてハマりついでにもうひとハマり。簡単そうに見えるけど…。

'SELECT 1' 

なんだこれ。こんなん必要なの?

結論から言うとこれSQL文なんですよ…。こんなんあるんですね。しらなんだ。

このセレクト文では整数値の1が取得できます。ただそれだけ。でも1が返ってくることでmysqldにつながったことが明確にわかるのです。

データベースも何もない状態ですから、mysqldがまっさらな状態でも返せるSQL文ということなんでしょうね。。

つまり接続確認の意味合いのようですよ。

"${mysql[@]}"にパイプで'SELECT 1'を実行して成功したらステータスコード0が返るはずですから、

if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then

は真となり、breakするわけですね。シェルスクリプトは0が真になるので若干ややこしす。

30回試行して失敗したら

if [ "$i" = 0 ]; then
    echo >&2 'MySQL init process failed.'
    exit 1
fi

$i0になりますからこの部分が真になり、接続できなかった旨を表示して終了します。

時刻の設定

if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then
    # sed is for https://bugs.mysql.com/bug.php?id=20545
    mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/'
fi

$MYSQL_INITDB_SKIP_TZINFOが空なら(ーz)実行するようになっていますね。

タイムゾーンデータの取得

ysql_tzinfo_to_sqlはmysql データベースに、タイムゾーンテーブルをロードするプログラムだそうです。

tz databaseという世界各地域の標準時データ(別名zoneinfo)があるそうで、LinuxやMacで利用されています。つまりこのデータがあれば全世界の時刻を扱うことができるということですね。

mysql_tzinfo_to_sqltz databaseをデータベースに取り込むためのプログラムということのようです。

さて、mysql_tzinfo_to_sqlをパイプでsedにつないでいますが、この箇所の処理はどうも不具合に対する処理のようです。

コメントにもあるとおりhttps://bugs.mysql.com/bug.php?id=20545を確認してみてください。英語ですが。

バージョンによってはLocal time zone must be set--see zic manual pageという記述がなにやら悪さをするそうで、それを書き換えているということです。

以下のような部分を

INSERT INTO time_zone_transition_type (Time_zone_id, Transition_type_id, Offset, Is_DST,    Abbreviation) VALUES (@time_zone_id, 0, 0, 0, 'Local time zone must be set--see zic manual page');

こんなふうに書き換えているわけですね。

INSERT INTO time_zone_transition_type (Time_zone_id, Transition_type_id, Offset, Is_DST,    Abbreviation) VALUES (@time_zone_id, 0, 0, 0, 'FCTY');

正直なにがダメなのかよくわからないです…ごめんなさい。

ランダムパスワードの生成

if [ ! -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
    MYSQL_ROOT_PASSWORD="$(pwgen -1 32)"
    echo "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD"
fi

ここはすんなりわかりました。docker run時に-e MYSQL_RANDOM_ROOT_PASSWORDを指定している場合、ランダムでパスワードをつくってくれます。その処理がここで行われています。

ここでは32文字のパスワードを生成しています。ちなみにこんな感じのパスワードになります。

$ pwgen -1 32
shuw5Ohmae6cuow5ohsei2Dahfahvai7

データベースの準備

"${mysql[@]}" <<-EOSQL
    -- What's done in this file shouldn't be replicated
    --  or products like mysql-fabric won't work
    SET @@SESSION.SQL_LOG_BIN=0;
    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 ;
    FLUSH PRIVILEGES ;
EOSQL

さてこの部分ではいくつかのSQLをクライアントを通して実行しています。

バイナリログの無効化

まずはこの部分。

SET @@SESSION.SQL_LOG_BIN=0;

SESSION.SQL_LOG_BINはバイナリログにログを残すかどうかの設定です。バイナリログというのは、テーブルやデータへの操作、変更などがイベントとして残るそうです。

いくつかのデータリカバリ処理にはこのバイナリログが必要になる場合があるそうです。

この場合バイナリログを取らない設定にしているということですね。

いろいろ調べたところ、どうやらレプリケーションをもちいるときにこのバイナリログが悪さをする可能性があるみたいです。

レプリケーションとは負荷分散などの目的の為にデータを複数複製する機能です。マスターデータがあってスレーブをたくさん持てるので、スレーブにアクセスしてもらうことで一極集中せずに済みます。

例えばマスターでユーザを増やした場合などにバイナリログが有効になっていると、スレーブで必要ないマスターのユーザ情報が伝わってしまい、なにやら不吉なこと(スレーブが止まるとか)が起きるらしいです。

おそらくこれ以降でユーザ操作をおこなっているので必要な設定なのではないかと思います。

rootユーザの作成

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 ;
FLUSH PRIVILEGES ;

ここはそれほど難しくないですね。一旦ユーザを全消ししてrootユーザを作っています。rootユーザのパスワードにはMYSQL_ROOT_PASSWORDが当てられていますね。

あとはGRANTrootにすべての権限を与え、テストデータベースを削除して、権限を更新しています。

rootパスワードの追加

if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then
    mysql+=( -p"${MYSQL_ROOT_PASSWORD}" )
fi

ここでは変数mysqlにrootパスワードを追加しています。先ほどrootを作っていますから、これでパスワードをつかってログインできるようになります。

データベースの作成

if [ "$MYSQL_DATABASE" ]; then
    echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}"
    mysql+=( "$MYSQL_DATABASE" )
fi

ここでデータベースを作成しています。docker run時に指定した-e MYSQL_DATABASEがデータベース名になります。

CREATE DATABASE IF NOT EXISTS

は「もしこの名前のデータベースがなかったら作る」という意味です。このSQLを${mysql[@]}にパイプで渡して実行、その後に変数mysqlにデータベース名を足しています。

いままでの処理で変数mysqlは以下のようになっているはずです。順次作られたデータベースにログインする為のコマンドに仕上げられていますね。

mysql --protocol=socket -uroot -p password dbname 

ユーザを作った際の処理

if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
    echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" | "${mysql[@]}"

    if [ "$MYSQL_DATABASE" ]; then
                echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;" | "${mysql[@]}"
    fi

    echo 'FLUSH PRIVILEGES ;' | "${mysql[@]}"
fi

公式のMySQLではroot以外にユーザを作ることも可能です。docker run時に-e MYSQL_USER-e MYSQL_PASSWORDを指定しているとrootではなくこのユーザでログインすることもできるようになります。

作ったデータベースで全権をユーザに与える処理がされていますね。

データベースの独自初期化用処理

echo
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ファイルでデータベースを初期化できるようにしてあります。

/docker-entrypoint-initdb.d/shsqlsql.gzに該当するファイルがあるとそれらを実行してくれる様になっています。

ここでも"${mysql[@]}"が大活躍です。便利だなこいつ。

たとえば何らかのデータを起動時に入れてしまいたい場合はsql等でぶっこむことが可能になっています。

具体的にはdocker-entrypoint-initdb.dをデータボリューム等でバインドしてファイルを突っ込む方法を取るかと思います。

起動と同時に叩き込む!!!!

ワンタイムパスワードが有効な場合

if [ ! -z "$MYSQL_ONETIME_PASSWORD" ]; then
    "${mysql[@]}" <<-EOSQL
        ALTER USER 'root'@'%' PASSWORD EXPIRE;
    EOSQL
fi

ここではdocker run時にMYSQL_ONETIME_PASSWORDを有効にしていた場合の処理です。

PASSWORD EXPIRE

はパスワードに期限を付けます。次回ログインには別なパスワードを設定する必要があります。

プロセスの終了

if ! kill -s TERM "$pid" || ! wait "$pid"; then
    echo >&2 'MySQL init process failed.'
  exit 1
fi

echo
echo 'MySQL init process done. Ready for start up.'
echo

おぼえていますでしょうか?以下の部分でmysqldのプロセスID取得していましたよね。

"$@" --skip-networking &
pid="$!"

変数pidをつかってmysqldを終了しようとしています。

if ! kill -s TERM "$pid" || ! wait "$pid"; then

kill -s TERM "$pid"のTERMシグナルはプロセスを正常終了させる為のシグナルです。条件には!が付いているので、「正常終了できなかった時」という意味デス。

さらに! wait "$pid"waitはプロセスが終了するのを待つコマンドです。これも!が付いているので、「正常に待てなかった時」という意味になるかと思います。

つまり正常終了できないか、プロセスを終了させられない場合には失敗したと判断してエラーをだしてセットアップを終了するようにしています。

ちゃんと終了させられた場合は

echo
echo 'MySQL init process done. Ready for start up.'
echo

を出力してセットアップが正常に終わったことを表示します。

分岐の最後

chown -R mysql:mysql "$DATADIR"
fi

exec "$@"

さて今までは初回起動時にデータベースを作る為の処理でした。

2回目以降の起動はデータベースがすでに作られているはずなので、セットアップ処理はされません。

最後にexec "$@"がありますが、これは初回起動でも2回目の起動でも、mysqldを起動させるコマンドです。

DockerfileのCMDが引数になりますので、”mysqld”が実行されるわけですね。

こうしてMySQLコンテナは起動するわけです。

まとめ

読んで理解するのは大変ですね。コードを書くより人のコードを読む方が難しく感じます。ともかく時間はかかりましたが、ある程度理解できてよかったです。

はたしてこんな記事が役に立つかどうかはわかりませんが、暇つぶしにはなるかもしれません。

Dokcerfileを読んだことで…

  • シェルスクリプトは読むのが難しい
  • コマンドを変数に格納して実行する方法が勉強になった
  • いろんな作法をしることができた
  • Dockerfileに恐怖感を感じにくくなった