苦手なシェルスクリプト読み
Dockerを使うにあたって露呈してきたのが、Linuxの設定やソフトのインストールやコンパイルに関しての知識不足。そして圧倒的なシェルスクリプトの苦手意識。
そのせいか、そんな知識とシェルスクリプトのテクニックの塊のようなDockerfileの内容って苦手だったので敬遠していたんですよね…。
今回はそんな知識不足と苦手意識を何とかするためにDockerfileの内容を読んで理解しようとしてみました。
@MINOはよくPHPを使うので、公式PHPのDockerfileを読んでみたいと思います。
今回はその中でもapacheが入っているPHP7.0.4のDockerfileを選んでみました。GitHubでは以下です。
ベースイメージ
FROM debian:jessie
公式PHPはdebianがベースイメージになっています。結構多くのイメージでdebianが使われているのですが、なんか理由があるのでしょうか?歴史が古いからなのかな。
このベースのdebianですが、余計なものは何も入っていない素のdebianと言った感じです。
debianのDockerfileのベースは空のベースであるscratchとなっております。そのためDockerfileを見ても公式のdebianイメージの詳細がわかりません。
しかし親切なことにビルドログが置かれていて、公式のdebianイメージに何が含まれているかを確認することができます。ビルドログは以下です。
これを読むのも結構勉強になりますね。興味深い…。
docker-brew-debian/jessie/build.log
公式のdebianのDockerfileはrootfs.tar.xz
をADD
することによってブートストラップにしてdebianのビルドを行う仕組みになっているようです。
phpizeの依存関係
次にapt-get
でいろいろパッケージをインストールしています。
# phpize deps
RUN apt-get update && apt-get install -y
autoconf
file
g++
gcc
libc-dev
make
pkg-config
re2c
--no-install-recommends && rm -r /var/lib/apt/lists/*
コメントのdeps
はおそらくdependencies
(依存性)の略ではないかと。つまりphpize
依存パッケージってことですね。多分。
phpizeってなんや…。
phpize
はCで書かれたPHP用のライブラリ(エクステンションとか拡張モジュールとか)をPHP本体を再コンパイルなしでビルドするために必要なツールのようです。…よくわかんない。 これがあれば後付でエクステンションを入れることができるということかな?
インストールされているパッケージの役割も調べてみました。
パッケージ | 役割 |
---|---|
autoconf | configureを生成するためのプログラム |
file | file は、各引数のファイルの識別を行う。ファイルシステムの検査、マジックナンバーの検査および 言語の検査 |
g++ | C++ コンパイラコマンド(C++コンパイラ) |
gcc | GNUコンパイラコレクション |
libc-dev | GNU C ライブラリ(仮想パッケージ) |
make | GNU Make は、プログラムのソースファイルからプログラムの実行ファイルやその他の 対象ファイルを生成する作業を制御するユーティリティ |
pkg-config | automake や autoconf が利用するコンパイルフラグや リンクフラグを管理 |
re2c | C 言語ベースの認識エンジンを生成するためのツールコマンドラインエディタライブラリ |
いくつかは知っているものもありますが、初耳(初見?)のパッケージもあります。知らなすぎだな…。
ともかくこれらがphpize
との依存パッケージみたいです。
apt-age update
して--no-install-recommends
(おすすめパッケージは入れない)でパッケージインストール後、パッケージリストをrm
で消していますね。
このようにインストールしては後片付けという手順を踏んでいるのは余計なファイルやディレクトリを残さないようにする定石みたいです。
persistent / runtimeの依存関係
またいろいろパッケージをインストールしています。
# persistent / runtime deps
RUN apt-get update && apt-get install -y
ca-certificates
curl
libedit2
libsqlite3-0
libxml2
--no-install-recommends && rm -r /var/lib/apt/lists/*
persistentってなに?
調べてみたけど良くわからない…。英語としては持続的とか永続的みたいな意味のようだけど…。 イメージの中に残すパッケージってことなのかな。
とりあえずパッケージの詳細を調べてみました。
パッケージ | 役割 |
---|---|
ca-certificates | SSL ベースのアプリケーションが SSL 接続時の認証で接続先 を確認するための CA 証明書の PEM ファイル |
curl | curl は URL 文法でデータを転送するコマンドラインツール |
libedit2 | コマンドラインエディタライブラリ |
libsqlite3-0 | SQL データベースエンジンを実装する C ライブラリ |
libxml2 | GNOME XML ライブラリ |
libxml2はこの後にインストールするapacheが依存しています。 curlはのちのちにPHPのソースコードを取得するために使っています。
他はよくわからない…。確かにそれぞれ便利なツールみたいだけど、なんの依存があるんだろう?
PHP.iniの場所指定とエクステンションのiniの場所決め
ここでENV
コマンドを使って環境変数としてphp.ini
の場所を出力しています。また早速その変数をつかってディレクトリを作成しています。
ENV PHP_INI_DIR /usr/local/etc/php
RUN mkdir -p $PHP_INI_DIR/conf.d
apacheのインストール
ここでapacheのインストールを行っています。debianのapacheパッケージってまんまapacheって名前なんですね。CentOSだとhttpdって名前だったりしますよね。
RUN apt-get update && apt-get install -y apache2-bin apache2.2-common --no-install-recommends && rm -rf /var/lib/apt/lists/*
ここでも最小限指向のために全部入りのapache2パッケージを使わずに、最低限必要なapache2-binパッケージをインストールするのみとなっています。apache2.2-commonは互換性のために存在するパッケージのようです。
パッケージ | 役割 |
---|---|
apache2-bin | Apache HTTP Server本体 |
apache2.2-common | apache2-bin への移行用パッケージ |
ちなみにapache2-binパッケージはインスタンス設定ができないのでapacheの複数起動はできないとのことです。複数起動したいならDockerコンテナを複数立ち上げろということなんでしょうか?
apacheのための所有権の編集
この段階でディレクトリの所有権の編集をおこなっています。
最初に/var/www/html
をばっさり捨てているのは余計なファイルを捨てるためだと思います。その後必要になるディレクトリを作って(/var/www/html
も)います。
そしてchown
でそれらのディレクトリの所有権をapacheのユーザであるwww-data
に移行しています。
RUN rm -rf /var/www/html && mkdir -p /var/lock/apache2 /var/run/apache2 /var/log/apache2 /var/www/html && chown -R www-data:www-data /var/lock/apache2 /var/run/apache2 /var/log/apache2 /var/www/html
マルチプロセッシングモジュールの切り替え
RUN a2dismod mpm_event && a2enmod mpm_prefork
正直あんまりわかっていませんが、mpm_event
はマルチプロセス+マルチスレッドを実現するモジュールでmpm_prefork
は従来のプロセスフォーク(親から子プロセスを作る方法)のモジュールだったと思います。
mpm_event
でapacheを動かしていると、いくつかのPHP拡張モジュール(つまりスレッドセーフではないモジュール)が動かないそうで、apacheとPHPを連携させるにはmpm_prefork
を使うのが今のところ定石のようです。
ともかくも、この段階でmpm_event
からmpm_prefork
に変更を行っています。
apacheモジュールの設定について
ここでちょっとdebianのapacheに関して少し説明してみたいと思います。
debianのapacheモジュールの有効無効の設定方法はRedHat系とは違うようです。
具体的にはa2dismod
スクリプトで無効設定、a2enmod
スクリプトで有効設定を行います。
debianではapache2パッケージをインストールすると/etc/apache2
以下に以下のようなディレクトリが作られます。
ディレクトリ | 役目 |
---|---|
mods-available | 利用できるモジュールの設定とロード用設定ファイルが入る |
mods-enabled | 有効にしたモジュールの設定ファイルのシンボリックリンク(mods-avilable内のファイルより)が入る |
sites-available | 利用できるバーチャルホストの設定と実体が入る |
sites-enabled | 有効にしたバーチャルホストの設定ファイルのシンボリックリンク(sites-available内のファイルより)が入る |
設定の本体はmods-available
ディレクトリに入っていますが、mods-enabled
にシンボリックリンクが入っているかいないかで有効無効の判断としています。
a2enmod
はシンボリックリンクを作るスクリプト、a2dismod
はシンボリックリンクを削除するスクリプトです。自力でシンボリックリンクを作ったり削除することでも有効無効の設定はできます。
RedHat系で慣れている人は戸惑うかと思いますが、かなり利便な仕組みだと思います。
apache設定の初期化
この段階でapache設定の初期化を行っています。
RUN mv /etc/apache2/apache2.conf /etc/apache2/apache2.conf.dist && rm /etc/apache2/conf-enabled/* /etc/apache2/sites-enabled/*
COPY apache2.conf /etc/apache2/apache2.conf
先ほどのモジュールの有効無効と同じく設定ファイルに関しても切り分けができる仕組みになっています。
ディレクトリ | 役目 |
---|---|
conf-available | 利用できる設定が入る |
conf-enabled | 有効にしたい設定ファイルのシンボリックリンク(conf-avilable内のファイルより)が入る |
apacheの大本の設定であるapache2.conf
を別名で確保しています(mv /etc/apache2/apache2.conf /etc/apache2/apache2.conf.dist)。
そのあとconf-enabled
以下とsites-enabled
以下のファイルを削除して設定を初期化しています。
最後に自前のapache2.conf
をCOPY
コマンドで入れています。
このapache2.conf
もGitHubにありますので、確認してみてください。余計なコメントがなくて読みやすいです。
PHPビルド用の環境変数設定
ENV PHP_EXTRA_BUILD_DEPS apache2-dev
ENV PHP_EXTRA_CONFIGURE_ARGS --with-apxs2
この段階ではPHPをビルドする際に必要なるパッケージ名と引数を環境変数にしています。
本来apacheではビルド時にモジュールの組み込みが必要なのですが、apxs
と(mod_so
)を使うことにで動的にモジュール読み込みができるとのことです。--with-apxs2
はapxs
をビルド時に組み込む為の引数です。
GPGチェック用の環境変数設定
ここではGPGキーとダウンロードするPHPのソースコードファイル名などを環境変数に出しています。
ENV GPG_KEYS 1A4E8B7277C42E53DBA9C7B9BCAA30EA9C0D5763
ENV PHP_VERSION 7.0.4
ENV PHP_FILENAME php-7.0.4.tar.xz
ENV PHP_SHA256 584e0e374e357a71b6e95175a2947d787453afc7f9ab7c55651c10491c4df532
PHPのGPGとファイル名はこちらから確認できます。 Current Stable PHP 7.0.4 http://php.net/downloads.php
PHPのビルド
さてやっとPHP本体のビルドです。以下は結構長いシェルスクリプトになっていますが、順に追っていきたいと思います。
デベロッパツールの準備
RUN set -xe
&& buildDeps="
$PHP_EXTRA_BUILD_DEPS
libcurl4-openssl-dev
libedit-dev
libsqlite3-dev
libssl-dev
libxml2-dev
xz-utils
"
&& apt-get update && apt-get install -y $buildDeps --no-install-recommends && rm -rf /var/lib/apt/lists/*
PHPのビルドに必要になるデベロッパツール等をインストールしています。パッケージの指定に変数をつかっているのはなぜなのかわかりません。
ビルドにはヘッダファイルなどが必要になるのでデベロッパツールパッケージが必要です。
パッケージ | 役目 |
---|---|
libcurl4-openssl-dev | libcurlのデベロッパツール |
libedit-dev | editline および履歴機能ライブラリのデベロッパツール |
libsqlite3-dev | sqlite3のデベロッパツール |
libssl-dev | OpenSSLのデベロッパツール |
libxml2-dev | libxml2のデベロッパツール |
xz-utils | XZ 形式の圧縮ユーティリティ |
PHPソースコードのダウンロードとGPGキーの確認
curlでソースを取得。その後事前に用意していたGPGキーを使ってファイル改変のチェックをおこなっています。
&& curl -fSL "http://php.net/get/$PHP_FILENAME/from/this/mirror" -o "$PHP_FILENAME"
&& echo "$PHP_SHA256 *$PHP_FILENAME" | sha256sum -c -
&& curl -fSL "http://php.net/get/$PHP_FILENAME.asc/from/this/mirror" -o "$PHP_FILENAME.asc"
&& export GNUPGHOME="$(mktemp -d)"
&& for key in $GPG_KEYS; do
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key";
done
&& gpg --batch --verify "$PHP_FILENAME.asc" "$PHP_FILENAME"
&& rm -r "$GNUPGHOME" "$PHP_FILENAME.asc"
PHPソースコードのビルド本番
PHPソースコードのビルドです。
&& mkdir -p /usr/src/php
&& tar -xf "$PHP_FILENAME" -C /usr/src/php --strip-components=1
&& rm "$PHP_FILENAME"
&& cd /usr/src/php
&& ./configure
--with-config-file-path="$PHP_INI_DIR"
--with-config-file-scan-dir="$PHP_INI_DIR/conf.d"
$PHP_EXTRA_CONFIGURE_ARGS
--disable-cgi
# --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself)
--enable-mysqlnd
--with-curl
--with-libedit
--with-openssl
--with-zlib
&& make -j"$(nproc)"
&& make install
&& { find /usr/local/bin /usr/local/sbin -type f -executable -exec strip --strip-all '{}' + || true; }
&& make clean
インストール先のディレクトリ/usr/src/php
を作っています。
先ほどダウンロードしたソースコードを/usr/src/php
として解凍しています。
解凍した後にはソースコードを削除してお片づけ。
解凍した/usr/src/php
に移動して./configure
を実行。先に用意していたphp.iniやエクステンション.iniの設定、mysqlnd、curl 、libedit、openssl、zlibを有効にしています。
またcgiは無効にしていますね。
make -j"$(nproc)"
でもし可能なら並列ビルドできるようにしています。
nproc
コマンドはCPUコア数が返り、make
の-j
オプションは指定したCPUコア数で並列ビルドを行うための設定です。
謎のスクリプト
{ find /usr/local/bin /usr/local/sbin -type f -executable -exec strip --strip-all '{}' + || true; }
は何をやっているんでしょうか?
意味としては「/usr/local/bin
と/usr/local/sbin
から実行可能ファイル(-type f -executable
)を探し、シンボルをはずして(-exec strip --strip-all
)表示('{}' +
)」が失敗したら(||
)true
コマンド実行ですかね…。
ちゃんとインストールされていることの確認なのかな〜。イメージビルド時のログ用?わっかんね〜な〜。
用なしパッケージの削除
またまたお片付けです。PHPのビルドが終わったのでビルドだけに必要だったパッケージを削除しています。
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false -o APT::AutoRemove::SuggestsImportant=false $buildDeps
apt-get purgeは指定したパッケージを設定ファイルごと削除するコマンドかと思いますが、AutoRemove::RecommendsImportant=false
とAutoRemove::SuggestsImportant=false
は初めて見ました。
おそらく重要な推奨パッケージと需要な提案パッケージの削除は行わないということだと思います。
$buildDepsにはapache2-dev、libcurl4-openssl-dev、libedit-dev、libsqlite3-dev、libssl-dev、libxml2-dev、xz-utilsのことなので、これらが削除されるということになります。
便利スクリプトのコピー
COPY docker-php-ext-* /usr/local/bin/
この段階ではスクリプト等を入れています。
docker-php-ext-*
はdocker-php-ext-configure
、docker-php-ext-enable
、docker-php-ext-install
の3つのスクリプトのことで、PHPエクステンションを簡単にインストール・有効化するために用意されたものです。
スクリプト | 役目 |
---|---|
docker-php-ext-configure | 引数を元にphpizeやconfigure実行してくれる |
docker-php-ext-install | 引数を元にエクステンションをインストールしてくれる |
docker-php-ext-enable | 引数を元にエクステンションを有効にしてくれる |
こんな感じで使います。gdエクステンションを入れる場合です。
$ docker-php-ext-configure gd --with-jpeg-dir=/usr/local/something
$ docker-php-ext-install gd
$ docker-php-ext-enablegd
それぞれのスクリプトはGitHubに置かれていますので確認してみてください。
php/7.0/apache/docker-php-ext-configure
php/7.0/apache/docker-php-ext-install
php/7.0/apache/docker-php-ext-enable
apacheのフォアグランド化スクリプトのコピー
COPY apache2-foreground /usr/local/bin/
コンテナはフォアグランドでなんらかのプロセスが動いていないと直ぐに終了してしまいます。なのでapacheをフォアグランドで動かすようにしているようです。
実際には
rm -f /var/run/apache2/apache2.pid
exec apache2 -DFOREGROUND
プロセス識別子ファイルを消してまっさらにしてから、-DFOREGROUNDオプションでフォアグランド起動しています。
これもGitHubにスクリプトが置かれているので確認してみてください。
php/7.0/apache/apache2-foreground
作業ディレクトリの設定・ポートの設定・コンテナ起動時の引数の設定
WORKDIR /var/www/html
EXPOSE 80
CMD ["apache2-foreground"]
最後に作業ディレクトリの設定(/var/www/html
)をして、apacheのポートをport80
に設定(EXPOSE 80)をしています。
CMD ["apache2-foreground"]
でコンテナ起動時にapacheが立ち上がる用になっています。
まとめ
Dockerファイルも一つ一丁寧に読むと、知識不足のせいではあるのですが、理解するまでに結構時間がかかりますね。いくつかの点は分からない状態ではありますが…。
今回の記事がお役に立つかどうかはわかりませんが、@MINOとしては得るもの(知識)が多く満足しています。
これでもっとDockerfileを読むことができるようになりそうです。