本番運用のための理想的なDockerfileについての考察

# はじめに

以前、私の携わっていた案件ではアプリケーションを動作させるDockerイメージが重いことが常に問題になっていました。

Dockerイメージが重いと、良いことは一つもありません。
私の経験上で主要な具体例を上げると…

- **稼働アプリケーションのパフォーマンス低下 (無駄なリソース消費)**
- **ビルド(CI)にかかる時間の増大**
- **ローカル開発が重くてストレス (開発にかかる運用負荷の増大)**

ということが実際に発生してきます。

どういうコンテナを作るべきかは、Googleがベストプラクティスを提唱しているものが有名で、以下のatmarkITの記事などでも紹介されています。

https://www.atmarkit.co.jp/ait/articles/1807/13/news054.html

# どんな環境を想定しているか

具体的には、本項では**ステートレスなWEBサービスのフロントエンド**について考察します。

先に進むほど具体的な内容について記しますが、最終的には `Linux + Apache + PHP` のような一般的なWEBサーバのフロントとなるコンテナを実現します。

DBやキャッシュ周りは、SaaSや他のコンテナに任せるものと想定します。

# 本番運用するにあたりレイヤーをどうするか

先ほどのGoogleのプラクティスにもある通り、公開されているイメージをそのまま自分の (自社の) サービスに適用できるケースは少ないでしょう。

何がしかの公開イメージをもとに、足りないライブラリ・パッケージを導入するDockerfileを書き、ビルドしてサービス投入することになるはずです。

ここでまず考えなくてはならないポイントは、**ビルドするレイヤーは通常1つでは収まらない**ということです。

具体的には、CIについて考慮する必要があります。

- OSやミドルウェアなど、基本的な環境を定義してビルドする (1層目)
- CIを回して git clone を行い、最新のアプリ状態をビルドする (2層目)

という、**最低2層の構成**とすべきでしょう。

レイヤーは少なければ少ないほど、完成するイメージは軽くなります。
ただし、CIを回してビルドするイメージを毎回最初から作っていたのでは、どんなに軽くできるテクニックを駆使していても、毎回相応のビルド時間が発生するので現実的ではありません。

「1層目」について、**同様の環境で動作するいくつかのアプリケーションは、同じイメージを共用**する方が良いでしょう。

これは、**セキュリティにとっても利点**があります。
例えばOSに脆弱性が出たときに、アプリケーション側のDockerfileの `FROM` を書き換えることで、各アプリケーション一律にセキュリティ対応が行われた状態で、すぐデプロイし直せることがその理由です。

実際、私がかつて携わっていた案件でも「1層目」を共通化すること自体はネガティブな要素や意見はありませんでした。

場合によっては、アプリケーションごとの差を吸収するために3層としなければならないケースもあるかも知れません。詳しくは個別的な検討が必要でしょう。

また、これより先は、

- 1層目のビルドイメージ　…　ベースイメージ
- 2層目のビルドイメージ　…　CIイメージ

という呼び方で話を進めたいと思います。

# アンチパターン

全くの私見ではありますが、私の過去の経験をもとに先に良くないベースイメージの構成の仕方を挙げていきます。

1. **VMで稼働してるインフラの作り方をDockerfileに適用する**
1. **言語のバージョンをもってイメージを場合分けする**
1. **公開されているイメージを生かさない**
1. **OSのディストリビューションにこだわる**
1. **Dockerfileとして軽量にできる記法/手法を使わない**

主にこの5点だと考えます。
カテゴリごとというよりも、ハマりそうな順に書いてます。。。

一文ではよく分からないと思いますので、下記で個別的に詳細説明します。

## 1. VMで稼働してるインフラの作り方をDockerfileに適用する

これから本番でDockerを採用しようとする企業 (チーム) にとって、最もあると思われるパターン。例えば AWS EC2 のオートスケール環境で稼働しているアプリケーションをコンテナ化する…などの要件ではないかと思います。

一見すると、Dockerfileはシェルスクリプトとほぼ同様に読めるので、VMで『安定稼働』しているサーバインフラの作りをそのまま、あるいは似たものを移植したくなるかも知れません。

これはアンチパターンです。

本項では主にPHP環境を扱うのですが、PHPのインストールのやり方としては、基本的には以下のような3つの方法が考えられます。

- ソースインストール (公式推奨)
- yum や apt などOSのリポジトリ管理ツールからインストール
- .rpm や .deb などのパッケージファイルを使用してインストール

VMで構築する場合は、おそらくこのいずれかを採用するはずですが、Dockerで同じことをするべきではありません。

**いずれの方法を取っても、処理的には高コストなのでイメージを重くします。**

詳しくは後ほどまた触れていきます。

## 2. 言語のバージョンをもってイメージを場合分けする

先ほど、「同様の環境で動作するいくつかのアプリケーションは、同じイメージを共用する方が良い」と書きました。

結論から書くと、**これは、たとえばPHP5.6で開発されたアプリケーション群と7.0で開発されたアプリケーション群の差異…のことを指しているのではありません。**

ここでのアンチパターンは、以下の内容を指しています。

同じPHP7.0のアプリケーションでも、フレームワークが異なると必要なライブラリは異なってきます。
そのため、**複数のフレームワークに対応させたイメージは、多数のライブラリを盛り込む必要があり、より重量級になりがちです。**

また、同言語の同バージョンを後から別のフレームワークに対応させようとした場合、追加で別のソフトウェアを導入する必要も生じます。
**これは結果的に、他のアプリケーションに影響を与え安定性を損なう原因になったり、Dockerfileの複雑化・さらなる重量化をもたらします。**

言語のバージョンよりも、

- **フレームワークごと**
- **フレームワークのバージョンごと**

という、2つの視点で分けながら共通化を図る方が自然と考えます。

<font color="red">ただし、フレームワークを利用しない言語やフレームワーク自体の開発が遅い (言語のバージョンのみ上げたい要件が頻発する) 場合などはこの限りではないでしょう。**別のスキームの検討が必要と考えます。**</font>

## 3. 公開されているイメージを生かさない

1. で書いた通り、言語環境を一から構成する処理は重いです。

Docker Hubには多数のイメージが用意されています。
**軽いDockerイメージを作るコツは、実現したい環境に近い公式イメージを基にしてDockerfileを書くことです。**

https://hub.docker.com/_/php

具体的には、上記のようにアプリケーションを動作させるための言語環境を基準にした公式イメージを使用するのがより良いと考えます。

「Dockerイメージをなぜ作るのですか？」

という問いに、「アプリケーションを動かすため」と私たちは明確に答えるはずです。目的に適合する最適なイメージを選定することが、私たちに必要な第一歩だと思います。

基となる軽量で最適なイメージをよく考慮しないこと、これがアンチパターンです。

Node.js, Python, Go, Ruby…　いずれも言語に応じた公式がありますので、先にそちらを覗いてみるべきでしょう。

https://hub.docker.com/_/node/
https://hub.docker.com/_/python/
https://hub.docker.com/_/golang/
https://hub.docker.com/_/ruby/

話をPHPの環境構築に戻すと、PHPのエクステンションは歴史的に非常に複雑化しており、大まかには以下の3パターンに分かれます。

1. **PHPのソースに同梱されるもの**
1. **外部提供されるC言語で開発されたもの** (`pecl` コマンドでインストール )
1. 外部提供されるPHPで開発されたもの (`composer` コマンドでインストール )

PHPの本番運用を考えるにあたって、 `composer` に任せられない **1. 2. を扱うプラクティスは持っておく必要があります。**


なお、https://hub.docker.com/_/php には以下のように書かれており、

> We provide the helper scripts docker-php-ext-configure, docker-php-ext-install, and docker-php-ext-enable to more easily install PHP extensions.

また、例として以下のようなDockerfileが紹介されています。

```Dockerfile
FROM php:7.4-fpm
RUN apt-get update && apt-get install -y \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libpng-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd
```

ここではエクステンションとして、 php-gd のインストールが例として触れられているのですが、これだけ取ってもOSのディストリビューションやPHP自体のインストール時に行うなど、一般的には複数の方法があります。
https://www.php.net/manual/ja/image.installation.php

これを docker-php-ext-install などのコマンドだけで扱えるなら、よりメリットがありそう…と考えたのですが、**結論としてはこの方法は上手くいきませんでした……詳しくは後述します。**

## 4. OSのディストリビューションにこだわる

現サービスを安定運用しているエンジニアにとって (ことさらに**運用系に深く関わる**エンジニアにとって) 、今まで使用してきた愛着あるLinuxディストリビューションは無視できないポイントでしょう。

しかしながら、3. で書いたようにコンテナ環境を作る唯一の意味は「アプリケーションを動かす」ことのみにあります。**OSの種類は問題ではありません**。

コンテナのような定義的 (宣言的) なインフラにおいては、ロジックとして正常に動作すると確認できたものは安定運用できると考えて差しつかえありません。
**VM環境からコンテナへサービスを移行するときは、機能テストを網羅的に行うことにこそ特に注力すべきです。**
(もちろん、適切なスケーリングあるいはオートスケールは当然必要ですが…)

**本項では、軽量Linuxディストリビューションの一つ <font color="red">Alpine</font> を利用します。**

Alpineは、ここ数年でDocker界隈ではかなり注目されているとともに、Docker HubのPHP公式内を俯瞰してみたときに、 (標準であるDebianと比較すると) **非常に軽量**なためです。

## 5. Dockerfileとして軽量にできる記法/手法を使わない

ここでは、WEB検索すると多く出てくる一連の情報をメインに紹介します。

現運用しているなどDockerfileの構成をすぐに大きく変更できない場合でも、**もし以下のようなことをまだ行っていない場合は、相応の軽量化が期待できます。**

- インストールキャッシュを削除する (apt, yum, apk など利用する場合)

```
例：
 (※ 実際にはDockerfileの形式に合わせた最適化が必要)

# apt clean
　または
# rm -rf /var/cache/apt/*
```

- ソースインストールを行う場合は、tarボールやビルドしたソースを削除する  
→　一般のサーバ運用であれば保持しておくべきですが、コンテナでは継続運用しないため。

```
例：
 (※ 実際にはDockerfileの形式に合わせた最適化が必要)

# cd /usr/local/src/httpd-2.4.9
# ./configure --prefix=/opt/httpd/httpd-2.4.9 --with-apr=/opt/apr/apr-1.5.1 --with-apr-util=/opt/apr-util/apr-util-1.5.3
# make
# make install

// この部分が該当
# make clean
# rm -rf /usr/local/src/httpd-2.4.9/
```

- 場合によりマルチステージビルドを活用する
→　一般に公式イメージは軽いのと、コンパイルのステージを分離して複数イメージを利用することが軽量化に効果を発揮する場合があります。


- ADD より COPY を利用する 
→　詳細は公式に任せて割愛しますが、機能が違うので `COPY` を使いましょう。
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#add-or-copy

- RUN, COPY などの処理はなるべくまとめる
→　具体的には、 **`\` や `&&` で結合しRUNは1回**にします。
　　RUNを次々に並べて書くようなやり方はスマートでないだけでなく、複雑な処理になるとイメージサイズに影響する場合があるようです。

- ビルドディレクトリ配下に余計なものを置かない
→　COPY の使い方しだいで運ばれてしまいます…
　　何かの事情で避けられない場合は .dockerignore を書きましょう。


# Laravel 7.x が稼働するベースイメージを作ってみる

手元に Laravel 7.x を利用して開発したテストアプリケーションがあるため、これを本番相当で動作可能とするユースケースを考えていきます。

まず、Docker HubのPHP公式から適当と思われるイメージを見繕います。

前提として `PHP >= 7.2.5` という要件があるので、最低限に近いところでPHPのバージョンは `7.3` としたいと思います。
https://readouble.com/laravel/7.x/ja/installation.html

ここで、考えるべきポイントは4つ。

- AlpineがベースOSとなっていること
- 2.4系のApacheが最初から入っていると、なおベスト
- ZTSは必要ない
- worker系のMPMは使用しないので、FPMも必要ない

ざっくり合いそうなものは以下の感じでした。

- php:7.3-alpine3.12
- php:7.3-apache

ポイントに合わせた観点で行くと、**AlpineかつApacheインストール済みのものはありません**でした。php:7.3-apache については、OSは Debian 10 (buster) となります。

一旦、これらを手元のPCでpullして素のイメージのサイズ比較を行います。

```
$ docker pull php:7.3-alpine3.12
$ docker pull php:7.3-apache
```

```
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
php                 7.3-alpine3.12      898191dd6734        8 days ago          74.3MB
php                 7.3-apache          4c443398a62c        8 days ago          410MB
```

正直なところ、全く比較になりません…

いくらApacheが入っていないとはいえ、<font color="red">Alpineが劇的に軽すぎる</font>ためかなりのパッケージを積み込んでもbusterのイメージに届くことは、まず絶対にないでしょう。

ここは、 php:7.3-alpine3.12 のイメージを採用することで進めます。

## php:7.3-alpine3.12 の素のイメージに足りないものを確認

Laravel 7.x のインストール要件に戻ります。
https://readouble.com/laravel/7.x/ja/installation.html

以下のように記載されています。

- PHP >= 7.2.5
- BCMath PHP拡張
- Ctype PHP拡張
- Fileinfo PHP拡張
- JSON PHP拡張
- Mbstring PHP拡張
- OpenSSL PHP拡張
- PDO PHP拡張
- Tokenizer PHP拡張
- XML PHP拡張

素のイメージからコンテナを起動し、足りないものを確認していきます。

```
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
php                 7.3-alpine3.12      898191dd6734        8 days ago          74.3MB
php                 7.3-apache          4c443398a62c        8 days ago          410MB
$ docker run -it -d -p 8000:80 898191dd6734        // http://127.0.0.1:8000 でPC目視確認できるよう設定
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
b7a35b6625d7        d6096778d65d        "docker-php-entrypoi…"   7 seconds ago       Up 5 seconds        0.0.0.0:8000->80/tcp   wizardly_meitner
```

ここまでは手元のPCで実行しました。
次にコンテナにログインし、ライブラリの状況を調査します。

```
$ docker exec -it b7a35b6625d7 /bin/sh

/ # php -m
[PHP Modules]
Core
ctype
curl
date
dom
fileinfo
filter
ftp
hash
iconv
json
libxml
mbstring
mysqlnd
openssl
pcre
PDO
pdo_sqlite
Phar
posix
readline
Reflection
session
SimpleXML
sodium
SPL
sqlite3
standard
tokenizer
xml
xmlreader
xmlwriter
zlib

[Zend Modules]

```

bcmath 以外は入っているようです… が、結論としてはこれだけでは情報が不足です。

yum や apt を利用する時と異なり、**公式イメージはPHPがソースからビルドされているため、結局各ライブラリのインストールが必要となります。**

このことを確認するためには、 `php -i` を利用するか phpinfo を設置しWEBブラウザで確認しながら考慮すると良いでしょう。

```/var/www/localhost/htdocs/phpinfo.php
<?php
phpinfo();
?>
```

以下のように、ビルドされたエクステンションが **enabled** となっていることが必要です。
![bcmath_support_enable.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631706/21b1a3e8-7bbf-55a6-08f3-bf9d884fdf49.png)

また、以下についても確認しました。

```
/ # date
Sat Jun 20 12:11:15 UTC 2020
/ # which composer        // 出力なし
/ # which npm             // 出力なし
```

タイムゾーンがUTCになっているのと、Laravel 7で必要なコマンドがないのでこの辺りを調整することにします。
またMySQL接続は必要なので、それらのライブラリも追加でインストールが必要です。

## 実際に書いたDockerfile

```Dockerfile
FROM php:7.3-alpine3.12

# Document Root
RUN mkdir /application

# Package Install
RUN apk update && apk add --no-cache git tzdata apache2 npm \
    php7-common php7-apache2 php7-bcmath php7-ctype  php7-fileinfo php7-json \
    php7-mbstring php7-openssl php7-pdo php7-mbstring php7-tokenizer php7-xml \
    php7-session php7-pdo_mysql php7-mysqli \
  && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
  && apk del tzdata

# Copy php.ini
COPY ./etc/php7/php.ini /etc/php7/

# Copy httpd.conf
COPY ./etc/apache2/httpd.conf /etc/apache2/

# Composer Install
RUN curl -sS https://getcomposer.org/installer | php -- \
  --install-dir=/usr/local/bin --filename=composer --version=1.10.6
```

- --no-cache オプションについて :  
/var/cache/apk/ 配下のインストールキャッシュを自動で消してくれます。

ある程度は可読性に配慮しつつ、上記のようにまとめました。
git clone するアプリケーションは `/application` をドキュメントルートとして配置する想定です。

php.ini および httpd.conf は内容を検討し、Dockerfileからの相対パスに配置しておく必要があります。
また、 `composer` コマンドは常に最新のものが入るので、挙動を一致させ可読性を高める意味でバージョン指定を行うこととしました。

手元のPCでビルドして、内容を確認します。

```
$ docker build -t docker-image-base-laravel7x ./
$ docker images
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
docker-image-base-laravel7x   latest              b239a1771f04        7 seconds ago       177MB
```

**177MBに収まりました！**

<font color="red">私のかつての担当案件では、当初のアプリイメージ (CIイメージ) が **3GB** 近くあったため、かなり重くなっていました。その後、基本的なつくりは変えずに軽量化を試みて **約半分1.5GB** 弱までダイエット。</font>

<font color="red">それでもローカル開発が重い…というあたりの感覚値です。
**見込みとしては、CIイメージが 1GB を切ると色々と改善されてくる可能性が高いです。**</font>

その前提からすると、177MBはかなり良好な数値と言えます。

コンテナに入って状況を確認します。

```
$ docker run -it -d b239a1771f04
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
d87924fc9320        b239a1771f04        "docker-php-entrypoi…"   8 seconds ago       Up 6 seconds                            epic_mccarthy
$ docker exec -it d87924fc9320 /bin/sh
```

コンテナ内で変更点をチェックしていきます。

```
/ # date
Sun Jun 21 19:02:24 JST 2020
/ # apk list | grep "php" | grep "installed"
php7-openssl-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-tokenizer-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-pdo_mysql-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-common-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-mysqlnd-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-fileinfo-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-mbstring-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-session-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-json-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-xml-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-apache2-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-ctype-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-bcmath-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-pdo-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
php7-mysqli-7.3.19-r0 x86_64 {php7} (PHP-3.01 BSD LGPL-2.0-or-later MIT Zend-2.0) [installed]
/ # which composer
/usr/local/bin/composer
/ # composer -V
Composer version 1.10.6 2020-05-06 10:28:10
/ # which npm
/usr/bin/npm
/ # npm -v
6.14.4
```

問題なく適用されています。

なお、 php7-session というのを含めていますが、これを入れないと以下のようなエラーログが出てLaravel環境のサービスの正常性が確認できませんでした。

```/var/log/apache2/access.log
[Fri Jun 26 19:30:49.089145 2020] [php7:error] [pid 240] [client 172.17.0.1:42184] PHP Fatal error:  Interface 'SessionHandlerInterface' not found in /application/testapp/vendor/laravel/framework/src/Illuminate/Session/FileSessionHandler.php on line 10
[Fri Jun 26 19:30:49.394184 2020] [php7:error] [pid 236] [client 172.17.0.1:42204] PHP Fatal error:  Interface 'SessionHandlerInterface' not found in /application/testapp/vendor/laravel/framework/src/Illuminate/Session/FileSessionHandler.php on line 10
```

# PECL でインストールするものを考える

まず memcached について下記のような方法を検討しました。

```Dockerfile
# Install Memcached
RUN apk add --no-cache autoconf \
  libmemcached-dev zlib-dev \
  gcc g++ make \
&& pecl install memcached \
&& docker-php-ext-enable memcached
```

先ほど docker-php-ext-install は結論としては使えないと書きましたが、その理由として**ソースインストールされたPHPへのエクステンションの組込みの問題**があります。

`apk` を使うことでこの部分をよきに計らってくれ、以下のようなiniファイルが生成されるのです。

```
/ # ls -la /etc/php7/conf.d/
total 72
drwxr-xr-x    1 root     root          4096 Jul  4 09:48 .
drwxr-xr-x    1 root     root          4096 Jul  3 10:49 ..
-rw-r--r--    1 root     root            20 Jun 12 21:27 00_bcmath.ini
-rw-r--r--    1 root     root            19 Jun 12 21:27 00_ctype.ini
-rw-r--r--    1 root     root            22 Jun 12 21:27 00_fileinfo.ini
-rw-r--r--    1 root     root            18 Jun 12 21:27 00_json.ini
-rw-r--r--    1 root     root            22 Jun 12 21:27 00_mbstring.ini
-rw-r--r--    1 root     root            21 Jun 12 21:27 00_openssl.ini
-rw-r--r--    1 root     root            17 Jun 12 21:27 00_pdo.ini
-rw-r--r--    1 root     root            21 Jun 12 21:27 00_session.ini
-rw-r--r--    1 root     root            23 Jun 12 21:27 00_tokenizer.ini
-rw-r--r--    1 root     root            17 Jun 12 21:27 00_xml.ini
-rw-r--r--    1 root     root            21 Jun 12 21:27 01_mysqlnd.ini
-rw-r--r--    1 root     root            20 Jun 12 21:27 02_mysqli.ini
-rw-r--r--    1 root     root            23 Jun 12 21:27 02_pdo_mysql.ini
-rw-r--r--    1 root     root            22 Jan 23 02:29 10_igbinary.ini
```

実際には `pecl` コマンドを使用してインストールするプラクティスを持つものも `apk` を経由しておよそインストール可能と見えます。

```
/ # apk search pecl
php7-pecl-imagick-dev-3.4.4-r3
php7-pecl-mailparse-3.1.0-r0
php7-pecl-imagick-3.4.4-r3
php7-pecl-uploadprogress-doc-1.1.3-r1
php7-pecl-amqp-1.10.2-r0
php7-pecl-yaml-2.1.0-r1
php7-pecl-memcache-4.0.5.2-r0
php7-pecl-psr-1.0.0-r0
php7-pecl-redis-5.2.2-r1
php7-pecl-ast-1.0.6-r0
php7-pecl-xdebug-2.9.6-r0
php7-pecl-couchbase-2.6.2-r0
php7-pecl-uploadprogress-1.1.3-r1
php7-pecl-apcu-5.1.18-r0
php7-pecl-timezonedb-2020.1-r0
php7-pecl-igbinary-3.1.2-r0
php7-pecl-uuid-1.1.0-r1
php7-pecl-gmagick-2.0.5_rc1-r5
php7-pecl-lzf-1.6.8-r0
php7-pecl-msgpack-2.1.0-r0
php7-pecl-vips-1.0.10-r1
php7-pecl-zmq-1.1.3-r6
php7-pear-7.3.19-r0
php7-pecl-mcrypt-1.0.3-r0
php7-pecl-xhprof-2.2.0-r0
php7-pecl-oauth-2.0.5-r0
php7-pecl-ssh2-1.2-r0
php7-pecl-memcached-3.1.5-r0
tesseract-ocr-4.1.1-r3
php7-pecl-protobuf-3.12.2-r0
php7-pecl-event-2.5.6-r0
php7-pecl-xhprof-assets-2.2.0-r0
```

総合的に考えると `apk` に寄せてしまい、 docker-php-ext-install/enable 系のコマンドは使わなくても良いと判断しました。

redis (phpredis) は特によく使われるので、memcached とあわせてこれらを含んだDockerfileにしてみたものが以下です。

```Dockerfile
FROM php:7.3-alpine3.12

# Document Root
RUN mkdir /application

# Package Install
RUN apk update && apk add --no-cache git tzdata apache2 npm \
    php7-common php7-apache2 php7-bcmath php7-ctype  php7-fileinfo php7-json \
    php7-mbstring php7-openssl php7-pdo php7-mbstring php7-tokenizer php7-xml \
    php7-session php7-pdo_mysql php7-mysqli \
    php7-pecl-memcached php7-pecl-memcache php7-pecl-redis \
  && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
  && apk del tzdata

# Copy php.ini
COPY ./etc/php7/php.ini /etc/php7/

# Copy httpd.conf
COPY ./etc/apache2/httpd.conf /etc/apache2/

# Composer Install
RUN curl -sS https://getcomposer.org/installer | php -- \
  --install-dir=/usr/local/bin --filename=composer --version=1.10.6
```

各コマンドはざっくり省略しますが、イメージサイズは以下の通りです。

```
$ docker images
REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
docker-image-base-laravel7x-cache                  latest              a928f55b49a6        40 seconds ago      180MB
```

**180MB**…
全くのところ、<font color="red">劇的に軽い</font>です。
Dockerfileはこの作り方が正解なのではないかと考察します。

## より高速なWEBのために

以前の担当案件では、イメージにPHPを高速化するためのアクセラレータ APCu, OPcache などが入っていました。

このあたりも案件は物理サーバから移植した構成であったため、(当初に) ほとんどそのまま入れたのだと思います。こうしたツールがコンテナにどの程度なじむかは、ちょっと注意・検証が必要かな…と思います。

本項ではこうした所に突っ込むつもりはないので、環境や言語によりけりで確認する必要はあるでしょう。

そもそもWEBを高速化したいなら…

- **より一般的なキャッシュ (CDN, フレームワーク, DB, redisに寄せる… etc)**
- **ボトルネックを見つけて対応 (モデル/SQLまわり, 処理ロジック… etc)**
- **ネットワークやサーバキャパシティプランニングの問題 (インフラ系)**


などのセオリーがあるので、まず内部のキャッシュツールを導入するより、個人的には先にそういった原因調査から行う方がいいのではないかな…と。。
調査の過程で有用性が認められればあるいは、というのが本来の順序と考えます。

PHPに関しては言語自体以前よりはるかに高速化しているので、言語のバージョンとフレームワーク如何の組み合わせにも着目する必要があるでしょう。

# おわりに

Dockerは検証や開発の環境作りにとても便利です。
また、Kubernetesなどでこれを運用することで、安定した本番環境を得ることもできます。

ただし、本番環境はとにかく軽くしておかないとアプリケーションの運用年数が長くなってきた時に、冒頭に書いたようなさまざまな不利益を被ることになります。**さらに悪いことに、Dockerfileはコードであるためにサービスイン後にこれを変更することは (特に調整面で…) 手数も多く**、当然危険も伴います。

コンテナアプリケーションのサービスイン時には、

- **より軽くシンプルなDockerfileの検討**
- **機能テストを網羅的に行うこと**
- **キャパシティプランニングとスケーラビリティの調整**
- **現実的なシナリオを想定した負荷試験**

などをバランス良く実施しながら、一定の時間をかけ慎重に行った方が賢明でしょう。

Fin ❤︎