Dockerfileを読んで理解したい
シェルスクリプトに慣れ親しんでる人なら苦労しないのだと思うのですが、@MINOのように経験が浅いとDockerfileはチンプンカンプンです。
すこしでもシェルスクリプトを読み解く力を付けるために、Dockerfileを読む努力をしています。
でも難しす…。
公式MySQLのDockerfileの内訳
今回読んでみたのは以下です。
Dockerfile本体とdocker-entrypoint.shというシェルスクリプトで構成されています。
Dockerfile本体では言うまでもなく主に環境構築が記述されていますが、docker-entrypoint.shでは主にデータベースのセットアップ処理が記述されています。
そのため公式MySQLを理解するにはDockerfike本体を読むだけでは不足で、docker-entrypoint.shも読んで理解する必要があります。
正直なところdocker-entrypoint.shの方が読むのが難しかったです…。
docker-entrypoint.sh編は記事が長くなりすぎたので別記事にしました。もしいち早くdocker-entrypoint.shを読みたいということでしたら、以下の記事を参考にしていただけると幸いです。
Docker公式MySQLのDockerfileを頑張って読んで理解しようとしてみた[docker-entrypoint.sh編]
それではDockerfile本体編のスタートです。
ベースイメージ
FROM debian:jessie
MySQLのベースイメージはいまのところdebian8(jessie)です。将来的にはalpineとかも使われるんでしょうか?
ユーザ、グループの作成・スクリプト実行ディレクトリの作成
# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r mysql && useradd -r -g mysql mysql
RUN mkdir /docker-entrypoint-initdb.d
さてここではユーザとディレクトリを1つ作っています。
ユーザ・グループを最初につくる理由
コメントでは
# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
ユーザとグループを最初につくるのは、どのような依存性にもかかわらず、いつも一貫したIDを得たい為です。
とあるので、最初にユーザとグループをつくっているんですね。
MySQLをインストールするときに自動で作られるユーザとグループの場合IDをコントロールすることは難しいのですが、こうしておけばいつも同じIDのユーザとグループ作れるんですね。
スクリプト実行ディレクトリとは
次にディレクトリを1つ作っています。これはデータベースの初期化に使うシェルスクリプトやSQLを入れるようのディレクトリです。
このディレクトリにそれらを入れておくとデータベースが作られる際に自動的に実行される仕組みになっています。(その仕組みはdocker-entrypoint.shの方で実装されています)
perlとpwgenのインスト−ル
# FATAL ERROR: please install the following Perl modules before executing /usr/local/mysql/scripts/mysql_install_db:
# File::Basename
# File::Copy
# Sys::Hostname
# Data::Dumper
RUN apt-get update && apt-get install -y perl pwgen --no-install-recommends && rm -rf /var/lib/apt/lists/*
ここではperl
とpwgen
をインストールしています。
コメントにはperl
のモジュールがない場合のエラーが書かれていますね。
後に使うdebconf
もたしかperl
で書かれていますので、いずれにしてもperl
が必要になるようです。
pwgen
はパスワード生成プログラムです。これはDockerfile上ではなく、docker-entrypoint.shの方でデータベース用のパスワード生成の際に活用されます。
インストールではオススメパッケージは入れない( --no-install-recommends
)のとインストール後にパッケージリストを削除してお掃除してなるべくコンテナの状態が小さくなるようにしています。
パッケージのチェック
# gpg: key 5072E1F5: public key "MySQL Release Engineering <mysql-build@oss.oracle.com>" imported
RUN apt-key adv --keyserver ha.pool.sks-keyservers.net --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5
ここではGPGキーを使ってのパッケージの正当性のチェックを行っています。
MySQLの本体のインストール
ENV MYSQL_MAJOR 5.7
ENV MYSQL_VERSION 5.7.12-1debian8
RUN echo "deb http://repo.mysql.com/apt/debian/ jessie mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list
# the "/var/lib/mysql" stuff here is because the mysql-server postinst doesn't have an explicit way to disable the mysql_install_db codepath besides having a database already "configured" (ie, stuff in /var/lib/mysql/mysql)
# also, we set debconf keys to make APT a little quieter
RUN { \
echo mysql-community-server mysql-community-server/data-dir select ''; \
echo mysql-community-server mysql-community-server/root-pass password ''; \
echo mysql-community-server mysql-community-server/re-root-pass password ''; \
echo mysql-community-server mysql-community-server/remove-test-db select false; \
} | debconf-set-selections \
&& apt-get update && apt-get install -y mysql-server="${MYSQL_VERSION}" && rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql
バージョン定義
ENV
コマンドでメジャ−バージョンとパッケージバージョンを定義しています。
ENV MYSQL_MAJOR 5.7
ENV MYSQL_VERSION 5.7.12-1debian8
リポジトリ追加
次にMySQLがあるリポジトリをリストに加えています。
RUN echo "deb http://repo.mysql.com/apt/debian/ jessie mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list
/var/lib/mysqlの役目
結構な長文コメントですな。
# the "/var/lib/mysql" stuff here is because the mysql-server postinst doesn't have an explicit way to disable the mysql_install_db codepath besides having a database already "configured" (ie, stuff in /var/lib/mysql/mysql)
/vat/lib/mysql(の処理)をここにいれているのは、mysql-server postinstはデータベースが作成されたという場合を除いてに明確にmysql_install_dbのコードパスを無効にする方法が無い
ちょっと訳に自信がありませんが 、おそらくdocker-entrypoint.sh内での処理に関係しているのでは無いかと思います。
公式MySQLでは「データベースが無い場合(初回起動run時)」か「すでにデータベースがある場合(docker startなどで再起動させた場合)」で処理を条件分岐をさせている場所があります。
データベースが作られると/vat/lib/mysql/mysql
というディレクトリができる(データディレクトリがデフォルトなら)という状況を利用して、その条件分岐では/vat/lib/mysql/mysql
が無いということでデータベースが無いという判断をしています。
MySQLをインストールした直後にはおそらくmysql_install_db
のおかげで、権限データベースができてしまっている為(つまり/vat/lib/mysql/mysql
がある状態)にこれを削除する以外に、初回起動か2回目の起動を判断することができない、という意味なのではないと思います。
なので以下のようにして全て削除してまっさらな状態にしているのだと思います。
&& rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql
debconf設定
# also, we set debconf keys to make APT a little quieter
RUN { \
echo mysql-community-server mysql-community-server/data-dir select ''; \
echo mysql-community-server mysql-community-server/root-pass password ''; \
echo mysql-community-server mysql-community-server/re-root-pass password ''; \
echo mysql-community-server mysql-community-server/remove-test-db select false; \
} | debconf-set-selections \
少し話が前後しましたが、ここではdebconfの設定を行っています。
debconfとはパッケージのインストールの際の対話に自動的に答えるためのプログラムです。予め聞かれる事柄はわかっているので、事前に設定してしまおうという便利な機能です。
下記はデータディレクトリの指定の設定です。なにも設定していない(''
)なのでデフォルトの/vat/lib/mysql/
になりま。
echo mysql-community-server mysql-community-server/data-dir select ''; \
次にrootパスワードですが、これもここでは決めていません。docker-entrypoint.shの方で(コンテナとしての)rootならパス無しでログインできる様にしています。
echo mysql-community-server mysql-community-server/root-pass password ''; \
echo mysql-community-server mysql-community-server/re-root-pass password ''; \
テストデータベースを消すかどうかには消さないを選択しています。あとで/vat/lib/mysql/
を一気にrm
してしまうからここでは消さなくてもいいということなんでしょうか?
echo mysql-community-server mysql-community-server/remove-test-db select false; \
これらは;
でつながっていますので、一連の文章としてパイプでdebconf-set-selections
に渡されます。これでMySQLをインストールする際は自動的にこれらが設定されますのでインストールが中断されることはありません。
MySQLインストール
あとはインデックスをアップデートしてMySQLパッケージをインストール、リストを削除してお片づけしています。
&& apt-get update && apt-get install -y mysql-server="${MYSQL_VERSION}" && rm -rf /var/lib/apt/lists/* \
my.cnfの書き換え
# comment out a few problematic configuration values
# don't reverse lookup hostnames, they are usually another container
RUN sed -Ei 's/^(bind-address|log)/#&/' /etc/mysql/my.cnf \
&& echo 'skip-host-cache\nskip-name-resolve' | awk '{ print } $1 == "[mysqld]" && c == 0 { c = 1; system("cat") }' /etc/mysql/my.cnf > /tmp/my.cnf \
&& mv /tmp/my.cnf /etc/mysql/my.cnf
ここではMySQLの設定ファイルであるmy.cnf
の書き換えを行っています。
まずsed
の処理でbind-address
およびlog
が先頭の行をコメントアウトしています。bind-address
はデフォルトで127.0.0.1
になりますが、別途コンテナにIPが与えられるのでこの設定は邪魔になります。
sed -Ei 's/^(bind-address|log)/#&/' /etc/mysql/my.cnf
さらにawk
で[mysqld]
の直下にskip-host-cache
とskip-name-resolve
を挿入する処理を行っています。逆引きとホストのキャッシュをスキップする設定です。コメントにも「それを使うなら別なコンテナにやらせてね」とあります。
echo 'skip-host-cache\nskip-name-resolve' | awk '{ print } $1 == "[mysqld]" && c == 0 { c = 1; system("cat") }' /etc/mysql/my.cnf > /tmp/my.cnf
&& mv /tmp/my.cnf /etc/mysql/my.cnf
その結果my.cnfは以下のようになります。
# Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# The MySQL Community Server configuration file.
#
# For explanations see
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html
[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
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
explicit_defaults_for_timestamp
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
#bind-address = 127.0.0.1
#log-error = /var/log/mysql/error.log
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
# * IMPORTANT: Additional settings that can override those from this file!
# The files must end with '.cnf', otherwise they'll be ignored.
#
!includedir /etc/mysql/conf.d/
ボリュームの作成
VOLUME /var/lib/mysql
公式のMySQLではデータが永続化するようにデータディレクトリをVOLUME
にしています。これでコンテナが一旦止まってもデータが失われることはありません。
シェルスクリプトの実行準備
COPY docker-entrypoint.sh /usr/local/bin/
RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat
ENTRYPOINT ["docker-entrypoint.sh"]
別途紹介しますが、docker-entrypoint.shを/usr/local/bin/にCOPY
してリンクを作っています。そして ENTRYPOINT
として設定しています。
これでコンテナ起動時にはdocker-entrypoint.shが実行されることになります。
ポートの設定
EXPOSE 3306
ポートの設定です。3306はMySQLの標準的なポートです。素直に3306なんですね。
引数の設定
CMD ["mysqld"]
最後に引数の設定です。run時に何も引数を指定しない場合は、このmysqldがENTRYPOINT
であるdocker-entrypoint.shに渡されることになります。
まとめ
さて足早ではありますが、Docker公式MySQLのDockerfileを読んでみました。
正直なところそれほど難しくはなかったかなという印象です。内容も短めですしね。
しかし、Dockerfileは難しくなくても、docker-entrypoint.shの方が@MINOにとっては難度が高かったです。心を折られる感じの内容が結構あります…。
もしシェルスクリプトに慣れていなくて、それでもDockerfileを読んで理解したいという奇特な方はいっしょに心を折られましょう…。
docker-entrypoint.sh編は以下です。
Docker公式MySQLのDockerfileを頑張って読んで理解しようとしてみた[docker-entrypoint.sh編]