かどうかは別として、こんな構成もできるよ。というネタと思っていただければ。(笑)
ご注意: この構成は所属先の見解ではなく、また、実運用の実績があるものではありません。あくまでも情報の一つとしてお読みください。
php-fpm(fastcgi)によるAPIサーバを建てるにあたって、LBをフロントエンドに置いて負荷分散。
というのは当たり前すぎる構成ですね。
さて、ここからが本題。
APIサーバということは、全てのリクエストはフレームワークを経由することになります。
最近のフレームワークはpublic/index.php
を単一のフロントコントローラとしているものがほとんどです。
LaravelやLumenなどがそうですね。
ということは、バックエンド側にはwebサーバは不要だと思いませんか? phpの処理系であるphp-fpm
デーモンだけが立っていれば良いと思いませんか?
図にするとこんな感じ。
php-fpm
はTCP/IPをlistenできるので、元々こういう使い方も想定されているわけですね。
この構成だとwebサーバが1つ減るのでオーバヘッドが減ります。CPU処理コスト、メモリ消費、アクセスログのディスクI/O、(webサーバ → php-fpmの)内部接続コストがなくなります。サーバ間の役割がはっきり分離できるのもポイントだと思いませんか?
静的ファイルについてはフロントエンド側で対応するか、静的専用のwebサーバを建てるか、S3のwebホスティングを使うか、CDNを使えば十分ですね。
具体的な設定方法
nginxをフロントエンド(LB)にした場合の設定例です。
httpsでリクエストを受けてSSL terminationをした上で、複数のバックエンドにラウンドロビンでロードバランスする構成です。
php-fpm(バックエンド側)
こちらは極めて単純。
listenアドレスに0.0.0.0
を指定してデーモンを再起動するだけ。
listen = 0.0.0.0:9000
nginx(フロントエンド側)
upstream backends {
server [backend IP 1]:9000;
server [backend IP 2]:9000;
}
server {
listen 443 ssl http2;
server_name site.localdomain;
charset utf-8;
root /var/www/html;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# SSL terminationをする場合
ssl on;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
try_files $uri /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass backends;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME [php-fpm側のindex.phpがあるディレクトリのpath]$fastcgi_script_name;
include fastcgi_params;
}
}
ポイントは3つ。
- バックエンド側の
php-fpm
サーバを、upstreamとして登録する。 - nginx側のrootディレクトリ(
/var/www/html
)に、ダミーのindex.phpファイルを作成しておく。(重用) -
SCRIPT_FILENAME
は php-fpm側のindex.phpがあるディレクトリを指定する。
ダミーのindex.phpが必要なのは、try_files
でリクエストURLが/index.php
に変換されて存在チェックが入るからです。
このチェックが通らないと、location ~ \.php$
に制御が渡らず404 NotFound
となってしまいます。
index.php
ファイルは存在している事自体が重要なので、中身は空でもかまいません。
touch /var/www/html/index.php
このあたりがもう少しエレガントに設定できれば良いのですが、他の方法が思いつきませんでした。
一応手元で環境構築してテストしたところ、ちゃんとラウンドロビンされてることと、バックエンドサーバを停止したらそこへの振り分けが停止され、バックエンドサーバを再開したら再度振り分け対象になったことは確認できました。