Apache Prefork+mod_phpからEvent+PHP-FPMへの切り替えた記録(CentOS7)

Apache のPrefork MPM + mod_phpで運用していたシステムを、Apache Event MPMに変更したので、その際の記録を残したいと思います。

ApacheのPrefork MPMは「プロセス数=同時接続数」です。しかし、Apacheに割り当てられるメモリには当然限りがあり、Preforkで増やせるプロセスに制限をかける必要があります。つまり接続数に限界がでてくることになります。

KeepAlive Offだと、性能頭打ちするし。。。KeepAlive Onにすると、パラメータ調整が難しく、大丈夫だと思っても、いきなり不安定になるときがあったり。。。設定しては負荷検証を繰り返し繰り返しと戦ったあげく、アーキテクチャをEvent MPMにすることにしました。

このあたりは、似たようなことされているこちらのブログも参考になるかと。

Apacheをpreforkからeventに切り替える | 純規の暇人趣味ブログ

Event MPMにすると何が良いのかを端的に知りたい場合は、以下のスライドのNginx部分をApache EventMPMに読み替えるとイメージつかめるかと思います。

PreforkからEventにMPMを変更するにあたっての記録を残しておきます。

バージョン情報

CentOS7 + Apache 2.4.23 + PHP-FPM 7.0.x

です。

PHP-FPM Systemd化

remiリポジトリからyumで入れました。systemd化は以下のように書きました。

$ cat /etc/systemd/system/php-fpm.service
[Unit]
Description=The PHP FastCGI Process Manager
After=syslog.target network.target

[Service]
Type=simple
PIDFile=/var/run/php-fpm.pid
ExecStart=/opt/remi/php70/root/usr/sbin/php-fpm --nodaemonize --fpm-config /etc/opt/remi/php70/php-fpm.conf
ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target

一応Reloadも定義していますが、あまり期待しない方が良いかも知れないです。

php-fpmはgraceful restartしません(回避策あり) - White Box技術部

既存資産のRewriteRuleを活かしたい

Apache EventMPM + PHP-FPMの構成の場合、Apacheはリバースプロキシとして動きます。

設定方法はいくつかありますが、Apacheの既存RewriteRuleも活かしたかったので SetHandler を使う方法にしました。

以下のように書くことで既存のRewriteRuleをそのまま流用できました。

#既存のRewriteRule
RewriteRule ^/(.*)$  /index.php/$1 [L]

<FilesMatch \.php$>
    SetHandler "proxy:fcgi://127.0.0.1:9000"
</FilesMatch>

Apache PHP-FPM間通信 Unix SocketTCP

ApachePHP-FPMの通信にはTCPだけではなく、Unix Socketを利用することができます。

Unix Socketの方が早いという話も聞いていたので、実際に両方のパターンで負荷テストを行ってみました。

環境依存な部分もあると思いますが、今回使っていたシステムの場合は性能面での差が出ることはありませんでした。

むしろ、システムリソースの限界を超えた負荷をかけた場合には、Unix Socketの方がエラーが増えてしまいました。

結果、今回はTCPを利用することにしました。

ちなみにUnix Socketを使う場合は、前述の SetHandler を以下のような設定にします。

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

PHP-FPMの Unix Socket 設定は以下のような感じです。

listen = /var/run/php-fpm.sock
listen.owner = apache
listen.group = apache
listen.allowed_clients = 127.0.0.1

PHP情報をApacheログに出力する場合の変更

PHPapache_setenv とか使ってApacheのログをカスタマイズしている場合は、レスポンスヘッダーを使うことで実装できます。

[Apache] レスポンスヘッダーの内容をログに出力して、なおかつヘッダーから削除する方法

Apacheのバージョンは要注意です。 2.4.7 以上が必要なのですが、記事執筆時点のCentOS7のリポジトリでは足りませんでした。

最新安定版の 2.4.23 を入れた時の記事がこちらです。

yomon.hatenablog.com

他にもApacheに依存している部分は見直した方が良いです。例えば、 apache_request_headers は以下のワークアラウンドが役立つはず。

php - Is there a apache_request_headers alternatve for displaying HTTP Headers - Stack Overflow

他にもアプリケーション依存の修正は諸々あると思いますが、残りは割愛で。

PHP-FPMの並列数

PHPを走らせるPHP-FPMですが並列数を調整する必要がります。

手順としては、 ps コマンドにて php-fpm 1プロセス当たりの実メモリ利用量(RSS)と並列数をかけて、全プロセスのメモリ利用量を確認します。

以下のような感じです。

$ ps aux | grep php-fpm | grep www | awk '{sum += $6}END{print "Total(KB)  :\t",sum,"\nAverage(KB):\t",sum/NR}'
Total(KB)  :     403452
Average(KB):     20172.6

この情報を元にPHP-FPMのメモリ制約を考えます。あとは実際に負荷テストなどをして、他のミドルウェアやCPUリソースなどとの兼ね合いを確認します。

設定例は以下のような感じです。考え方はApacheと近いです。PHP-FPMの設定ページ確認してください。

pm = static
pm.max_children = 90
pm.start_servers = 90
pm.min_spare_servers = 90
pm.max_spare_servers = 90
pm.max_requests = 500

PHP-FPMのモニタリング

PHP-FPMにもApacheで言う、server-status 的なものが存在します。

設定ファイルに以下の項目を追加することで利用できるようになります。

pm.status_path = /phpfpm_status

Apache側の設定例は以下のような感じ。

<Location /phpfpm_status>
  Require all denied
  Require ip 127.0.0.0/8
  Require ip 192.168.10.0/24
  Deny from 192.168.20.0/24
  SetHandler "proxy:fcgi://127.0.0.1:9000/phpfpm_status"
</Location>

PHP-FPMの状況みたい場合はコンソールから以下のコマンド打って確認できます。

$ curl http://localhost/phpfpm_status
pool:                 www
process manager:      static
start time:           17/Nov/2016:16:17:16 +0900
start since:          191346
accepted conn:        1695725
listen queue:         0
max listen queue:     38
listen queue len:     511
idle processes:       19
active processes:     1
total processes:      70
max active processes: 70
max children reached: 2
slow requests:        1

この設定するとこちらのMuninプラグインも使えるようになります。

GitHub - tjstein/php5-fpm-munin-plugins: A set of Munin plugins for PHP5-FPM

PHPからMySQLへの接続並列数

PHP-FPMのプロセス数が決まってきたら、それに合わせて、MySQLのMax Connectionも調整しました。

mysql> show global variables like 'max_connections';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | xxxx  |
+-----------------+-------+

PHP-FPMのプロセス数にとDB数から算出できます。

PHPのプロセス数 × DB数

以下のSQL文の結果とPHP-FPMのプロセス数を見比べると、よりわかりやすいかもしれないです。

select DB,count(*) from information_schema.processlist group by DB order by count(*) desc;

ただ、コネクション数はそれなりにメモリ食うので、少し運用してみて、以下のパラメータ含めて調整してみると良いと思います。

mysql> show global status like 'Max_used_connections';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | XXXXX  |
+----------------------+-------+
1 row in set (0.00 sec)

(番外)ELB経由のApacheログにClient IPを出力

ELB配下のApacheの場合、クライアントPCなどのIPを X-Forwarded-For というヘッダーの情報を元に取得してログに出力できるのですが、なんか項目が複数になってします。

以下のようにApacheに設定することで、

RemoteIPHeader X-Forwarded-For

このようなLogFormatを、

LogFormat "%{X-Forwarded-For}i ....."

こんな感じに書き換えるとうまくログに欲しい情報が出力されるようになりました。

LogFormat "%a......"

こちらを参考にさせていただきました。

mod_extract_forwardedと多段 Proxy - ブログ - ワルブリックス株式会社

参考URL

  • FastCGI Process Manager (FPM)設定項目

PHP: 設定 - Manual