Amazon Linux AMI 2017.09で今こそApacheをhttp/2対応にする手順

ローリングリリースで提供されるAmazon Linux AMIが先日バージョン2017.09となりました。数年ぶりにOpenSSLのバージョンが更新される(1.0.1k → 1.0.2k)等、それなりに変更点の大きいリリースとなっており、これによりディストリビューション標準のパッケージのみでApache/Nginxのhttp/2に対応できるようになりました。ソースコンパイルを伴うミドルウェアは実運用においては、メンテナンス性に難が有りましたが、今後は簡単に導入が可能なはずです。「今こそ」、なタイミングかと思いますので対応していきます。

Amazon Linux AMIのバージョン確認

# cat /etc/system-release
 Amazon Linux AMI release 2017.09

まだ2017.09になっていなければ(問題無い事を確認したうえで)yum updateしてください。
古いリリースはセキュリティ修正も行われません。

前提

Webサーバー

Apache 2.4を使います。WordPressを動かす為にPHP 7.1を使っています。

SSL/TLSへの対応

既にWebサイトは(常時)SSL/TLS対応になっているものとして進めます。
まだであれば、Let’s Encryptなど各種方法で対応して下さい。

ロードバランサ

ELB(ALB/CLB/NLB)は未導入のものとして話を進めます。

PHP-FPMの導入

なぜいきなりPHPの話になるかというと、Apacheでhttp/2に対応するためには、Prefork MPMではなく別のMPMを使う必要が有るからです※1。その為、PHPをApacheモジュール版ではなく、
php-fpmで動作させる必要があります。

※1: Apache 2.4.27以降はmod_http2がprefork MPMに対応しない。

導入手順

php-fpmをyumからインストール。PHP 7.1を利用しているので php71-fpm になっています。

# yum install php71-fpm

Apache側の現在のMPMを確認

# /usr/sbin/httpd -V | grep MPM
 Server MPM: prefork

preforkで動いています。

/etc/httpd/conf.modules.d/00-mpm.conf

で設定されているので上記ファイルを開き、

LoadModule mpm_prefork_module modules/mod_mpm_prefork.so

を#でコメントアウト

#LoadModule mpm_event_module modules/mod_mpm_event.so

のコメントアウトを外しevent MPMを使うように修正

構文チェック

# /usr/sbin/httpd -t
 AH00513: WARNING: MaxRequestWorkers of 64 is not an integer multiple of ThreadsPerChild of 25, decreasing to nearest multiple 50, for a maximum of 2 servers.
 Syntax OK

スレッド数の部分で警告が出ていますが、これは後で修正します。

バーチャルホストの設定

PHPファイルの実行をphp-fpmに渡すための設定を追加する。
Apache(やNginx)とphp-fpmの接続は、UNIXドメインソケットと127.0.0.1:9090へのTCP/IP接続が選べますが、ApacheとPHPを同じサーバーで実行する場合は、応答速度の速いUNIXドメインソケットの方で設定します。
対象のバーチャルホスト設定に以下を追記。拡張子がphpのファイルをphp-fpmに渡します。

<FilesMatch \.php$>
    SetHandler "proxy:unix:/var/run/php-fpm/php-fpm.sock|fcgi://localhost"
</FilesMatch>

FilesMatchにマッチしたファイルしかphp-fpmに渡されない事に注意。

構文チェック

# httpd -t
 AH00513: WARNING: MaxRequestWorkers of 64 is not an integer multiple of ThreadsPerChild of 25, decreasing to nearest multiple 50, for a maximum of 2 servers.
 Syntax OK

php-fpm側の設定

ユーザーの設定やソケットファイル名の設定など修正。

# cp /etc/php-fpm.d/www.conf /etc/php-fpm.d/www.conf.orig
# vim /etc/php-fpm.d/www.conf
# diff /etc/php-fpm.d/www.conf.orig /etc/php-fpm.d/www.conf
38c38
< listen = /var/run/php-fpm/www.sock
---
> listen = /var/run/php-fpm/php-fpm.sock
48,49c48,49
< ;listen.owner = nobody
< ;listen.group = nobody
---
> listen.owner = apache
> listen.group = apache
55c55
< listen.acl_users = apache,nginx
---
> ;listen.acl_users = apache,nginx

php-fpmのログレベルをnoticeからwarningに変更する。

# vim /etc/php-fpm.conf

;log_level = notice

を以下に修正

log_level = warning

php-fpmの起動とhttpdの再起動

# /etc/init.d/php-fpm start
 Starting php-fpm-7.1: [ OK ]

# /etc/init.d/httpd restart
 Stopping httpd: [ OK ]
 Starting httpd: AH00513: WARNING: MaxRequestWorkers of 64 is not an integer multiple of ThreadsPerChild of 25, decreasing to nearest multiple 50, for a maximum of 2 servers.
 [ OK ]

まずはこの時点でWebサイトが正常に表示されることを確認する。
次にMPMの確認

# /usr/sbin/httpd -V | grep MPM
 Server MPM: event

あとはドキュメントルートに適当にphpinfoを実行するファイルを置いて確認。
下記ファイルをWebブラウザで開いた際に、Server APIに
「Apache handler 2.0」ではなく「FPM/FastCGI」と表示されていればOK。

phptest.php
<?php
    phpinfo();

※確認後はこのファイルの消し忘れにご注意ください。

これでApacheがevent MPMかつ、php-fpmでPHPが動作する環境になりました。
MPMがeventになった事により、多数のアクセスに対しても(適切にチューニングをすれば)preforkよりもスケールする事が出来ると思われます。

http2/対応

ここまでくればhttp/2対応は簡単です。
Amazon Linuxバージョンアップの時点で、mod_http2はすでに導入されているので

# ls -l /etc/httpd/modules/ | grep http2
 -rwxr-xr-x 1 root root 253544 Sep 25 08:20 mod_http2.so

対象のバーチャルホストに以下を追記

Protocols h2 http/1.1

し、httpdを再起動

# /etc/init.d/httpd restart

これでもうhttp2対応です。お疲れ様でした。

なお、Apacheで出ている、MaxRequestWorkersやThreadsPerChildのワーニングについては、MaxRequestWorkersがThreadsPerChildの整数倍になるように、各自調整を行ってください。ここの調整はアクセス数などによっても異なります。


ci-phpunit-testが動かない理由がphp7ccだった話

Composerで導入できる kenjis/ci-phpunit-test は Codeigniter 3.x で快適にPHPUnitによるテストが書ける便利なツールです。

その ci-phpunit-test にはクラスのメソッド、PHP標準の関数、定数などを書き換える事が出来る、モンキーパッチ機能が有るのですが。なぜか動かなくなり悪戦苦闘していました。

原因はComposerでグローバルにインストールしたphp7ccでした。

sstalle/php7cc はそのPHPアプリケーションが PHP 7.0 で動作するうえで問題が無いかを静的解析するツールなのですが、どちらも nikic/php-parser というPHPで書かれたPHPパーサに依存しており、違うバージョンが入ってしまうことが原因のようです。

nikic/php-parser v1.2.1 の composer.json の記述(抜粋)
nikic/php-parser: ~1.4,

kenjis/ci-phpunit-test v0.15.0 の composer.json の記述(抜粋)
nikic/php-parser: ^2.1|^3.0

とりあえずグローバルに入っている古いバージョンをremoveする事で、問題なく動くようになりました。

なお、php7cc の代わりになりそうな物としては wimg/PHPCompatibility があります。php7cc でカバーしていた範囲をすべてチェック出来るかはまだ分からないのですが、チェックのターゲットとして、PHP 5.6 以前のバージョンであったり、PHP 7.1 にも対応してるようです。


Windows環境のComposerを複数PHPバージョンで使い分ける

Windows環境におけるPHP複数バージョン導入は比較的簡単です。例えばCドライブ配下に複数のPHPをzip展開、php.iniの設定を行うだけでOKです。

c:\php\php56\php.exe
c:\php\php70\php.exe
c:\php\php71\php.exe

実際に使う際はこの中からデフォルトとして使うバージョンに環境変数を通しておきます。

c:\php\php56\php.exe
c:\php\php70\php.exe ←環境変数を通してデフォルトで使われるPHPにしておく。
c:\php\php71\php.exe

例えばPHPのビルトインWebサーバーを使う際、デフォルトで使うバージョン以外を使いたいプロジェクトではフルパスで別パスのphpを起動してやればいいです。

c:\php\php71\php.exe -S localhost:8080

ここで、Composerが問題になってきます。WindowsにインストールしたComposerは環境変数に設定されたデフォルトのPHPで実行されるので、PHP 7.1を使っているプロジェクトでPHP 7.1を求めるライブラリを導入したい場合などに、ComposerのチェックでPHPのバージョンが足りないと言われエラーになってしまいます。また、適切にcomposer.jsonが設定されていなければPHP 5.6のプロジェクトで7.0が必須のライブラリをインストールしてしまうかもしれません。

このComposerコマンドの実行ファイルは実際にはバッチファイルになっています。このファイルはインストール時に設定を変えていなければ

C:\ProgramData\ComposerSetup\bin

にインストールされています。
(コマンドプロンプトであれば where コマンドでコマンドのインストール先が分かります)

中に記載されているのは

@ECHO OFF
php “%~dp0composer.phar” %*

だけですので、このファイルを複製し

@ECHO OFF
c:\php\php71\php.exe “%~dp0composer.phar” %*

のようにしてあげれば、PHP 7.1で起動するComposerコマンドを作る事が出来ます。

PHP 7.1のプロジェクト上でComposerコマンドを利用する際はcomposerコマンドではなく

composer_php71.bat install

のように利用すればOKです。

なお、Composerがプロジェクトごとではなくグローバルにインストールするパッケージについては全てのプロジェクトに影響が及びますので、その点はご注意ください。

ちなみに phpunit コマンドも同様にバッチファイルになっているので、同様に複数PHPバージョンのバージョンを用意しておくことができます。