ランサーズ(Lancers)エンジニアブログ > PHP > CakePHP > ランサーズのNginx+PHP-FPM化

ランサーズのNginx+PHP-FPM化

kanazawa|2017年09月05日
Apache

インフラエンジニアの金澤です。
以前ブログでお話しました、ランサーズの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)

CPUは8コア(800%)中、200%手前でほぼ安定。

メモリ15GBをほぼリミットまで利用していました。

移行後(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
上でお話しました、バージョンアップ状況の詳細についてもお話しする予定です。