本番運用に備えたPHP7.3.0のコンテナを作る

# はじめに

年末にひさしぶりにPHPのプロジェクトを作る機会がありました。

いつの間にかPHP7.3がリリースされており、
いつの間にかPHPの公式コンテナも出ていたので、
ノウハウを共有するため、ひととおり本番運用を想定したコンテナイメージの作成方法をまとめました。

長くなったので、実際にプロジェクトとして動かすための手順は以下の記事でまとめました。
[dockerでPHP7.3+Laravel環境を15分で作る~2019年版](https://qiita.com/tomoyamachi/items/991a0d149e9585cac15f)
## 構築環境のバージョン情報

- MacOS High Siera 10.13.6
- Docker Desktop : Version 2.0.0.0-mac81 (29211)

## 最終的なDockerfile

**2019/01/26 追記**
@hokutoasari さんの[記事](https://qiita.com/hokutoasari/items/4db320e519d87186974f)を参考に `echo 'opcache.optimization_level=0x7FFFBBFF'` を追加しました。

```dockerfile
FROM php:7.3.0-fpm-alpine3.8
RUN apk upgrade --update && \
  apk --no-cache add icu-dev autoconf make g++ gcc

RUN docker-php-ext-install  -j$(nproc) iconv intl mbstring pdo_mysql opcache && \
  pecl install xdebug-2.7.0beta1 && \
  docker-php-ext-enable xdebug

RUN { \
  echo 'short_open_tag = On'; \
  echo 'fastcgi.logging = 1'; \
  echo 'opcache.enable=1'; \
  echo 'opcache.optimization_level=0x7FFFBBFF' ; \
  echo 'opcache.revalidate_freq=0'; \
  echo 'opcache.validate_timestamps=1'; \
  echo 'opcache.memory_consumption=128'; \
  echo 'opcache.interned_strings_buffer=8'; \
  echo 'opcache.max_accelerated_files=4000'; \
  echo 'opcache.revalidate_freq=60'; \
  echo 'opcache.fast_shutdown=1'; \
  echo 'xdebug.remote_enable=1'; \
} > /usr/local/etc/php/conf.d/overrides.ini

COPY . /var/www/html
```

開発環境でデバッグするためにxdebugは入れていますが、本番用のコンテナでは入れないようにしましょう。

# 解説

## ベースとなるコンテナを選択
https://hub.docker.com/_/php/
公式のdockerhubを見ると、いくつか種類があります。
今回は、PHP-FPMがあるものの中から選びます。

将来的に本番でも利用したいので、できるだけ軽量のalpineベースのイメージを利用します。 ※ ベースとなるOSが変わると、パッケージの依存関係の解消が面倒になることがあるので、ベースイメージは本番/開発ともに同じものを利用します。

|Image名|OS|Size|
|:--|:--|:--|
|php:7.3.0-fpm|Debian|372MB|
|php:7.3.0-fpm-alpine|Alpine|78MB|

```dockerfile
FROM php:7.3.0-fpm-alpine3.8
```
できる限りバージョンは指定しましょう。

## モジュールのインストール
### docker-php-ext-xxx について
`docker-php-ext-configure`と`docker-php-ext-install`は、コンテナイメージに事前に圧縮されて入っているエクステンションを操作できます。

- configure : 設定をカスタマイズしたいものの設定を行う ex) gd, intl 
- install : 解凍してインストール

configureを指定したエクステンションは、のちほどinstallをする必要があります。

`docker-php-ext-enable` は、peclで入れたextensionを有効にします。 ※ peclコマンドもコンテナにデフォルトで入っています。

たとえばこんな感じです。

```dockerfile
RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
    docker-php-ext-install -j$(nproc) gd intl mbstring pdo_mysql opcache && \
    pecl install xdebug-2.7.0beta1 && \
    docker-php-ext-enable xdebug
```

`-j` オプションは、makeに渡す引数です。 並列ではしらせるジョブの上限を指定します。
https://www.gnu.org/software/make/manual/html_node/Parallel.html
利用可能なスレッドの数は環境に応じて変わるので、 `$nproc` を入れています。

一応、あとで出来上がったコンテナにの端末で、moduleを確認してみましょう。

```terminal

$ docker run --rm --interactive --tty {container} sh

> コンテナの端末に接続
/var/www # php -m
[PHP Modules]
Core
ctype
curl
date
dom
fileinfo
filter
ftp
gd
hash
iconv
intl
json
libxml
mbstring
mysqlnd
openssl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
readline
Reflection
session
SimpleXML
sodium
SPL
sqlite3
standard
tokenizer
xdebug
xml
xmlreader
xmlwriter
Zend OPcache
zlib

[Zend Modules]
Xdebug
Zend OPcache
```

モジュールが認識されていることが確認できました。

### 利用可能なモジュールの確認

利用できるext-nameは以下の方法で確認するのが確実です。

```shell
$ docker run --rm php:7.3.0-fpm-alpine docker-php-ext-install
usage: /usr/local/bin/docker-php-ext-install [-jN] ext-name [ext-name ...]
   ie: /usr/local/bin/docker-php-ext-install gd mysqli
       /usr/local/bin/docker-php-ext-install pdo pdo_mysql
       /usr/local/bin/docker-php-ext-install -j5 gd mbstring mysqli pdo pdo_mysql shmop

if custom ./configure arguments are necessary, see docker-php-ext-configure

Possible values for ext-name:
bcmath bz2 calendar ctype curl dba dom enchant exif fileinfo filter ftp gd gettext gmp hash iconv imap interbase intl json ldap mbstring mysqli oci8 odbc opcache pcntl pdo pdo_dblib pdo_firebird pdo_mysql pdo_oci pdo_odbc pdo_pgsql pdo_sqlite pgsql phar posix pspell readline recode reflection session shmop simplexml snmp soap sockets sodium spl standard sysvmsg sysvsem sysvshm tidy tokenizer wddx xml xmlreader xmlrpc xmlwriter xsl zend_test zip

Some of the above modules are already compiled into PHP; please check
the output of "php -i" to see which modules are already loaded.
```

**Possible values for ext-name** 以下に利用可能なモジュールが記載されています。

### xdebugの導入

PHP7.3用のxdebugは `2.7.0beta1` です。 (2019/1/3 時点)
https://pecl.php.net/package/xdebug
バージョン指定して `pecl install` します。

本番で利用するコンテナにビルドする際は、xdebugが入らないようにしましょう。

## php.iniの設定

ファイルを作成して、イメージ内にCOPYしてもいいんですが、Dockerfileだけで済ませたいのでファイルにechoします。

```dockerfile
RUN { \
  echo 'upload_max_filesize = 100M'; \
  echo 'post_max_size = 108M'; \
  echo 'short_open_tag = On'; \
  echo 'fastcgi.logging = 1'; \
  echo 'opcache.enable=1'; \
  echo 'opcache.optimization_level=0x7FFFBBFF'; \
  echo 'opcache.revalidate_freq=0'; \
  echo 'opcache.validate_timestamps=1'; \
  echo 'opcache.memory_consumption=128'; \
  echo 'opcache.interned_strings_buffer=8'; \
  echo 'opcache.max_accelerated_files=4000'; \
  echo 'opcache.revalidate_freq=60'; \
  echo 'opcache.fast_shutdown=1'; \
  echo 'xdebug.remote_enable=1'; \
} > /usr/local/etc/php/conf.d/overrides.ini
```

## 補足 : composerの導入

composer用のコンテナを立てて、composerのコマンドはそこで実行するのがベストプラクティスです。これについては後述します。
ただ、あとから取り除いても大きな影響はないので、とりあえず開発を初めたい人はcomposerを直接コンテナにいれてもいいと思います。 

```dockerfile
RUN php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer && \
  apk --no-cache add git zip
```


## パッケージインストール

基本的にはモジュールベースで考えると思うので最後に持ってきました。
それぞれのモジュールに必要なパッケージを入れましょう。

```dockerfile
RUN apk upgrade --update && \
    apk --no-cache add icu-dev autoconf make g++ gcc
```


## 注意点

### より小さいコンテナサイズを目指す
本気でコンテナファイルを作成するときは、できるだけコンテナサイズを小さくするためにmulti stage buildをして、本番利用しないファイルはすべてコンテナに入らないよう調整します。

参考記事 : https://qiita.com/minamijoyo/items/711704e85b45ff5d6405

### composerは別コンテナで稼働させる
前述しましたが、通常、composerはcomposer専用のコンテナを通して実行することもできます。
たとえば ローカルのディレクトリに、composer installしたvendorファイルを追加したい場合は以下のように行います。

```terminal
$ docker run --rm --interactive --tty -v $(pwd):/app \
   composer composer install

or 

$ docker run --rm --interactive --tty -v $(pwd):/app \
   composer bash -c "composer install"
```

### 本番/開発コンテナの違い
本番/開発コンテナは基本的には分けたほうがいいと考えています。
本番用でビルドする場合は、以下のような違いをつけます。

- xdebugを入れない
- `composer install --no-dev` でパッケージを分ける
- 本番には不要なファイル群をコンテナから取り除く
    - テストファイルやデータ系ファイルなど
    - .dockerignoreやmulti stage buildを利用

# 最後に

とりあえず PHP7.3で一通り最初のプロジェクト作成で必要な状態にはなりました。
現実で利用できるようなPHP-FPMコンテナは別の記事で~~書きます 。~~書きました。
[dockerでPHP7.3+Laravel環境を15分で作る~2019年版](https://qiita.com/tomoyamachi/items/991a0d149e9585cac15f)