2016/10/18 20:47:36

Dockerの公式PHPのDockerfileを頑張って読んで理解しようとしてみた

dokcer
目次(クリックするとジャンプします)
  • 1:苦手なシェルスクリプト読み
  • 2:ベースイメージ
  • 3:phpizeの依存関係
  • 3.1:phpizeってなんや…。
  • 4:persistent / runtimeの依存関係
  • 4.1:persistentってなに?
  • 5:PHP.iniの場所指定とエクステンションのiniの場所決め
  • 6:apacheのインストール
  • 7:apacheのための所有権の編集
  • 8:マルチプロセッシングモジュールの切り替え
  • 8.1:apacheモジュールの設定について
  • 9:apache設定の初期化
  • 10:PHPビルド用の環境変数設定
  • 11:GPGチェック用の環境変数設定
  • 12:PHPのビルド
  • 12.1:デベロッパツールの準備
  • 12.2:PHPソースコードのダウンロードとGPGキーの確認
  • 12.3:PHPソースコードのビルド本番
  • 12.4:謎のスクリプト
  • 12.5:用なしパッケージの削除
  • 13:便利スクリプトのコピー
  • 14:apacheのフォアグランド化スクリプトのコピー
  • 15:作業ディレクトリの設定・ポートの設定・コンテナ起動時の引数の設定
  • 16:まとめ

苦手なシェルスクリプト読み

Dockerを使うにあたって露呈してきたのが、Linuxの設定やソフトのインストールやコンパイルに関しての知識不足。そして圧倒的なシェルスクリプトの苦手意識。

そのせいか、そんな知識とシェルスクリプトのテクニックの塊のようなDockerfileの内容って苦手だったので敬遠していたんですよね…。

今回はそんな知識不足と苦手意識を何とかするためにDockerfileの内容を読んで理解しようとしてみました。

@MINOはよくPHPを使うので、公式PHPのDockerfileを読んでみたいと思います。

今回はその中でもapacheが入っているPHP7.0.4のDockerfileを選んでみました。GitHubでは以下です。

php/7.0/apache/Dockerfile

ベースイメージ

FROM debian:jessie

公式PHPはdebianがベースイメージになっています。結構多くのイメージでdebianが使われているのですが、なんか理由があるのでしょうか?歴史が古いからなのかな。

このベースのdebianですが、余計なものは何も入っていない素のdebianと言った感じです。

debianのDockerfileのベースは空のベースであるscratchとなっております。そのためDockerfileを見ても公式のdebianイメージの詳細がわかりません。

しかし親切なことにビルドログが置かれていて、公式のdebianイメージに何が含まれているかを確認することができます。ビルドログは以下です。

これを読むのも結構勉強になりますね。興味深い…。

docker-brew-debian/jessie/build.log

公式のdebianのDockerfileはrootfs.tar.xzADDすることによってブートストラップにして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.confCOPYコマンドで入れています。

このapache2.confもGitHubにありますので、確認してみてください。余計なコメントがなくて読みやすいです。

php/7.0/apache/apache2.conf

PHPビルド用の環境変数設定

ENV PHP_EXTRA_BUILD_DEPS apache2-dev 
ENV PHP_EXTRA_CONFIGURE_ARGS --with-apxs2

この段階ではPHPをビルドする際に必要なるパッケージ名と引数を環境変数にしています。

本来apacheではビルド時にモジュールの組み込みが必要なのですが、apxsと(mod_so)を使うことにで動的にモジュール読み込みができるとのことです。--with-apxs2apxsをビルド時に組み込む為の引数です。

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=falseAutoRemove::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-configuredocker-php-ext-enabledocker-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を読むことができるようになりそうです。