インフラエンジニアの金澤です。
以前ブログでお話しました、ランサーズのNginx+PHP-FPM化が一段落したのでまとめました。
移行の目的
以前は、Apache 2.2 + mod_php の構成で動作していました。
以前のブログでもお話ししましたように、PHP 5.3からバージョンアップする過程で、今までのhttpd.confをそのまま使うことができなくなったため、この機会に設定を一から見直し、Nginx + PHP-FPM 構成に移行しました。
移行時に行ったこと
Nginxのインストール
ランサーズは、Amazon Linuxで稼働しています。
yumパッケージのNginxには欲しい機能が含まれていなかったため、ソースからインストールしました。
configure時に以下のオプションでコンパイルすることで、yumパッケージでインストールした場合とほぼ同じディレクトリ構成になります。
./configure \
--user=nginx \
--group=nginx \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-log-path=/var/log/nginx/access.log \
--with-http_addition_module \
--with-http_gzip_static_module \
--with-http_gunzip_module \
--with-http_realip_module \
--with-http_v2_module \
--with-http_ssl_module \
--with-http_stub_status_module
PHP-FPMとの連携
PHP-FPMとはunix socketで連携します。
server unix:/var/run/php-fpm/php-fpm.sock;
ELB対策
ELBのSSL Termination機能を利用しているため、https通信も、EC2内部ではhttpで処理しています。
https経由のアクセスであったかどうかを判定するために、X-Forwarded-Proto環境変数を利用しています。
set $elb_https off;
if ($http_x_forwarded_proto = https) {
set $elb_https on;
}
location ~ \.php {
...
fastcgi_param HTTPS $elb_https;
...
}
そして、アクセスログがELBのプライベートアドレスにならないように、以下の設定を行いました。
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
プロキシ設定
http://www.lancers.jp/magazine/ 等、いくつかのURLをWordPressサーバーにプロキシしています。
Apache(httpd)の場合は、rewriteやProxyPassで手軽にプロキシ設定できましたが、Nginxの場合は、resolverにDNSのIPアドレスを設定する必要があります。
AWSの場合、10.0.0.0/16 のVPC上のresolverは10.0.0.2になります。
location ~ ^/magazine/(.*)$ {
resolver 10.0.0.2;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
set $endpoint "magazine.lancers.jp";
proxy_pass http://$endpoint/magazine/$1?$args;
}
開発環境ではhostsを利用しているので、dnsmasqをインストールし、hostsをDNSサーバーとして稼働させています。
このときのresolverのIPアドレスは 127.0.0.1 になります。
Apache機能依存関数の置き換え
Apache(httpd)機能依存のPHP関数を置き換える必要がありました。
apache_note関数
アクセスログに、ログインユーザーidを記録するために利用していました。
apache_note('userId', $this->loginUser['id']);
Apache → Nginx移行時にエラーにならないように、暫定的に以下のようにソースを修正しました。
if (strpos($_SERVER['SERVER_SOFTWARE'], 'pache') !== false) {
apache_note('userId', $this->loginUser['id']);
} else { // nginxの場合はResponse Headerを利用
header('X-User-ID: ' . $this->loginUser['id']);
}
getallheaders関数
このブログの記事を参考にさせていただきました。
app/config/bootstrap.php に以下のように実装しました。
Nginxに移行した場合に、こちらで実装したgetallheaders関数が実行されるようになります。
if (!function_exists('getallheaders')) {
function getallheaders()
{
$headers = array();
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
return $headers;
}
}
チューニング設定
※AWSのEC2 c4.2xlarge(8コア、15GB)インスタンスで稼働させた場合の設置値です。
Apache + mod_phpの設定
Apache + mod_phpの時代は、試行錯誤しながら、最終的に以下の値で運用していました。
StartServers 100
MinSpareServers 100
MaxSpareServers 120
ServerLimit 350
MaxClients 350
MaxRequestsPerChild 30
MaxRequestsPerChildのデフォルトは10000ですが、かなり低く設定しています。
低く設定すると、すぐにプロセスがなくなるため、forkの量が増え、CPU負荷が増加します。
高く設定すると、プロセスのforkが少なくなりますが、アクセスの度にプロセスのメモリ使用量が増加していきます。
CPU能力の高いc4系のインスタンスを利用しているため、メモリ容量と比較してCPUに余裕があります。
CakePHP等のフレームワークを使ったサービスの場合、GCによるメモリ解放があまり期待できないので、こまめにプロセスを切ることでメモリを確保していました。
Nginx + PHP-FPMの設定
Nginxはイベント駆動のため、基本的にはCPU数分だけプロセスを稼働する運用になります。
インスタンスタイプの変更時に動的にプロセス数が変更されるように、worker_processesをautoに設定しました。
worker_processes auto;
※結果、現在、以下の10プロセスが稼働しています。
ID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
814 nginx 20 0 73920 6952 3148 S 0.0 0.0 0:00.98 nginx
815 nginx 20 0 73920 6964 3148 S 0.0 0.0 0:01.78 nginx
816 nginx 20 0 73920 6704 3148 S 0.0 0.0 0:02.59 nginx
817 nginx 20 0 74168 6832 3148 S 0.0 0.0 0:05.45 nginx
819 nginx 20 0 75604 8380 3148 S 0.0 0.1 0:15.86 nginx
820 nginx 20 0 75948 8856 3148 S 0.0 0.1 1:04.30 nginx
822 nginx 20 0 78564 11m 3148 S 0.0 0.1 5:00.41 nginx
823 nginx 20 0 77844 10m 3148 S 0.0 0.1 4:53.30 nginx
824 nginx 20 0 72920 5056 2448 S 0.0 0.0 0:00.20 nginx
2017 root 20 0 72920 5928 3416 S 0.0 0.0 0:04.44 nginx
PHP-FPMのプロセス数は100に設定しました。
ランサーズの場合、PHP-FPM のメモリ消費量は、1プロセスあたり最大で約100MBほど消費していました。
100プロセスの場合、PHP-FPMのプロセスだけで約10GBのメモリ消費となります。
pm_max_requests は、ApacheのMaxRequestsPerChild にあたる設定です。
Apacheのときと同様に30に設定しました。
pm = dynamic
pm.max_children = 100
pm.start_servers = 100
pm.min_spare_servers = 100
pm.max_spare_servers = 100
pm.max_requests = 30
※すべての設定を100に統一したので、今後はpm = static にしても良いかもしれません。
移行結果
※AWSのEC2 c4.2xlarge(8コア、15GB)インスタンスで稼働させた結果です。
リソース消費量
移行前(apache + mod_php)
移行後(Nginx + PHP-FPM)
CPU使用率は、100%~300%の間で変動するようになりました。
PHP-FPM 100プロセスの設定で、EC2のメモリ15GBをほぼリミットまで利用。
※その後、slabキャッシュを定期的に削除するようにしたところ、5GB程度の利用に収まっています。
サーバーレスポンス
Apache(httpd)は、mod_phpを利用するため、preforkで動作していました。
静的ファイルもpreforkのApache(httpd)プロセスで処理していたため、イベント駆動のnginxのほうがパフォーマンスは向上しそうですが、前段でCDN(CloudFront)を挟んでキャッシュしているため、EC2への静的ファイルへのアクセス頻度が低く、差が出にくいと考えています。
mod_phpとPHP-FPMは、ランサーズにおいては、レスポンス上はほとんど差がでませんでした。
ただし、今回はパフォーマンス向上ではなく、バージョンアップの前準備が目的でしたので、極端にパフォーマンスが劣化しなければOKという方針で取り組んでいます。
また、現在PHP-FPM 5.3で稼働していますが、今後、PHP-FPM 5.6にバージョンアップしたらどうなるかも計測していきたいと思います。
PHP、CakePHPのバージョンアップ状況
以前のブログで、以下のフェーズに分けてバージョンアップを行うと話していました。
Apache + mod_php + PHP 5.3 + CakePHP 1.3
↓(第1フェーズ)
Nginx + PHP-FPM + PHP 5.3 + CakePHP 1.3
↓(第2フェーズ)
Nginx + PHP-FPM + PHP 5.6 + CakePHP 1.3
↓(第3フェーズ)
Nginx + PHP-FPM + PHP 5.6 + CakePHP 2
↓(第4フェーズ)
Nginx + PHP-FPM + PHP 5.6 + CakePHP 3
↓(第5フェーズ)
Nginx + PHP-FPM + PHP 7.x + CakePHP 3
その後、計画の詳細を詰め、現在は以下のフェーズに分けて進めています。
※インフラ側では、PHP 5.6のバージョンアップ検証を進めていましたが、その前にCakePHPを2.8までバージョンアップする方向で進めることになりました。
Apache + mod_php + PHP 5.3 + CakePHP 1.3
↓(第1フェーズ)
Nginx + PHP-FPM + PHP 5.3 + CakePHP 1.3
↓(第2フェーズ)
Nginx + PHP-FPM + PHP 5.3 + CakePHP 2.8
↓(第3フェーズ)
Nginx + PHP-FPM + PHP 5.6 + CakePHP 2.8
↓(第4フェーズ)
Nginx + PHP-FPM + PHP 5.6 + CakePHP 2.9
↓(第5フェーズ)
Nginx + PHP-FPM + PHP 7.x + CakePHP 2.9
↓(第6フェーズ)
Nginx + PHP-FPM + PHP 7.x + CakePHP 3
↓(第7フェーズ)
Nginx + PHP-FPM + PHP 7.x + CakePHP 4
そして現在、第2フェーズの、CakePHP 1.3 → 2.8 バージョンアップ作業を進めています。
PHPカンファレンスに弊社エンジニアが登壇します
10/8 に開催される、PHPカンファレンス2017に弊社エンジニアの秋山が登壇します。
https://joind.in/event/japan-php-conference-2017/session23-lancers
上でお話しました、バージョンアップ状況の詳細についてもお話しする予定です。