気まま研究所ブログ

思ったことをてきとーに書きます。

公式PHP 7をFast CGIで動かす

こんにちは。
前回、Raspbianで公式php7をビルド & インストールするという記事を書きましたが、モジュール版での動作なため、workerやeventでは動作しません。しかし、それらで動作させたい場合もあるかもしれません。
そこで、今回はモジュール版ではなく、FastCGIを用いてevent MPMにて動作させます。 前回同様コンパイル時間が半端なく長いので今回はRaspbianを利用しませんが、前回の「./configure」から再度行うだけなので恐らく同様の手順でいけると思います。
なお、記事中のコマンドや画像はPHP 7.1.7のものですが、諸事情により再確認を行っているのでPHP 7.1.10でも動作確認が取れております。

はじめに

前回はpreforkで構築したため、今回はeventでかつFastCGIを用いて構築していきます。
基本的にやることは前回と変わらないため、細かい部分は省きますのでビルドについては前回の記事をご参照ください。


実験環境

  1. Ubuntu 16.04 LTS
  2. Apache 2.4.18


前提知識

MPMとは

ところで、MPM(マルチプロセッシングモジュール)*5とはリクエストを並行処理する際の処理方式をモジュール化したものです。
リクエストを処理する場合、簡単な機構では1つのリクエストを処理している間プロセスあるいはスレッドはビジー状態となり、その他のリクエストを受け付けることができません。
それを解決する際に用いられる技術がマルチプロセスモデルやマルチスレッドモデル、イベント駆動モデルです。Apache 2ではそういったモデルを独立したモジュールとして管理されています。
それらモデルは3種類存在し、preforkworkereventがあります。

まず、prefork*6マルチプロセスモデルで、親プロセスから子プロセスを生成(fork)し、それらに処理を委託します。単一のプロセスに対して単一のリクエストを処理するため、障害に対して強い傾向にあります。また、スレッドセーフでないモジュールを使用する場合は必然的にこちらになります。
通常forkは重い処理であるため、事前に既定値(設定可能)だけのプロセスをforkしておくため、preforkと呼ばれます。
ただし、リクエスト数分だけのプロセスが必要となり、メモリ空間を圧迫する可能性があり、リクエスト数によっては処理待ちが発生する可能性があります。

次に、worker*7マルチプロセスモデルとマルチスレッドモデルのハイブリッド型の処理方式を提供します。
規定値の子プロセスを用意し、そこに処理を委託する点はpreforkと同じですが、各子プロセスには既定値だけのスレッドが用意されており、それらのスレッドを用いてリクエストを処理します。
これによりメモリ空間の節約や、forkによるパフォーマンス低下が発生しづらくなります。
ただし、単一のプロセスには複数のリクエストが存在する可能性があり、一つのプロセスが障害を起こすことで複数のリクエストに影響を及ぼす可能性があります。
また、スレッドセーフでないモジュールは使うことができません

最後にevent*8ですが、workerをベースとした処理方式ですが、更にイベント駆動モデルを用いたハイブリッドモデルです。
どの部分にイベントを採用しているのか読み解けませんでしたが、とりあえずそういうことだそうです。
特にKeepAlive問題を主にしたもので、その処理を別スレッドに委託することでリソースの無駄遣いを解決します。
ただし、障害への耐久性がworkerと同様にpreforkに比べて低く、スレッドセーフでないモジュールは使用することができません

どちらを採用するかはマシンスペックとウェブサイトの特性により左右されますが、アクセス数が桁違いに多いサイトなどでなければどちらでもいいように思います。


モジュール版とCGI版とは

もう一つ前提としてモジュール版FastCGI Process Manager(FPM)*9通称CGIの違いから見ていきます。

モジュール版Apache 2のプロセスとして動作させる方式です。
Apache 2と同一のプロセス内で処理が完結するため、処理が速いという特徴がります。
ただし、Apache 2のプロセスと同じ権限を持ってしまうため、権限によればセキュリティに問題が発生する可能性があります。
なお、この問題をPHP上で解決するセーフモードなる機能があったようですが、PHP 5.4以降で削除された*10ようなので無いものと考えたほうが良いでしょう。
前回インストールしたような形式のPHPはこちらのモジュール版を指します。
もともと単一のプロセスを対象とするアプリケーション故にスレッドセーフな設計にはなってないのかPHPにおいてマルチスレッドモデルの環境では使用できません。

次にCGIですが、こちらはモジュール版とは異なり、専用の異なるプロセスで実行されます。
これによりApache 2自体をシングルスレッドに制限されず、マルチスレッドモデルにてPHPを運用することが可能となります。
また、権限によるセキュリティ問題が解消されます。(CGIプロセスが同一権限あるいは上位権限を持っている場合はその限りでない)
ただし、モジュール版とは異なりプロセス間通信が発生するためそれがオーバーヘッドとなり、処理速度が低下する場合があります。
なお、CGIFastCGIの違いはCGIプロセスを永続的に動作させるかそうでないかの違いです。
CGIは要求毎にプロセスを生成し、破棄することでパフォーマンスの悪化を招いており、それを解決するのがFastCGIです。

こういった背景からモジュール版はpreforkでしか動作せず、その他のMPMを用いる場合はFastCGIを用いる必要が出てくるというわけです。


作業工程

PHP 7のビルドとインストール

前回とほぼ同じですが、「--enable-fpm」を追加します。
また、PHP7のインストール先をいじるため、「--prefix」と「--with-config-file-path」の値を変更してありますのでご注意ください。
なお、その他のオプションについては公式ページ*1をご参照ください。

$ cd ~/php7/php-7.1.7/
$ ./configure \
    --prefix=/opt/php7 \
    --with-config-file-path=/opt/php7/etc \
    --enable-mbstring \
    --enable-zip \
    --enable-bcmath \
    --enable-pcntl \
    --enable-ftp \
    --enable-exif \
    --enable-calendar \
    --enable-sysvmsg \
    --enable-sysvsem \
    --enable-sysvshm \
    --enable-wddx \
    --with-curl \
    --with-mcrypt \
    --with-iconv \
    --with-gmp \
    --with-pspell \
    --with-gd \
    --with-jpeg-dir=/usr \
    --with-png-dir=/usr \
    --with-zlib-dir=/usr \
    --with-xpm-dir=/usr \
    --with-freetype-dir=/usr \
    --with-t1lib=/usr \
    --enable-gd-native-ttf \
    --enable-gd-jis-conv \
    --with-openssl \
    --with-mysql=/usr \
    --with-pdo-mysql=/usr \
    --with-gettext=/usr \
    --with-zlib=/usr \
    --with-bz2=/usr \
    --with-recode=/usr \
    --with-mysqli=/usr/bin/mysql_config \
    --with-apxs2=/usr/bin/apxs2 \
    --enable-fpm

しばらくかかりますが、終わるまで待ちます。

無事完了すればコンパイルとインストールをしましょう。

make
make test
sudo make install

ここまでは前回とさほど変わりません。


PHP-FPMの設定と起動

次に、PHP-FPMの設定を行います。
まずは必要なファイルをコピーします。
一つは実行用スクリプトで、ソースファイル郡からコピーし、権限を変更します。

$ sudo cp sapi/fpm/init.d.php-fpm.in  /etc/init.d/php-fpm
$ sudo chmod 755 /etc/init.d/php-fpm

次に、PHP 7のインストールディレクトリ内部にある初期状態のコンフィグファイルを認識可能なファイル名でコピーします。

$ sudo cp /opt/php7/etc/php-fpm.conf.default /opt/php7/etc/php-fpm.conf
$ sudo cp /opt/php7/etc/php-fpm.d/www.conf.default  /opt/php7/etc/php-fpm.d/www.conf

次に、php-fpm.d/www.confを書き換えます。
ここでは既にあるユーザ名を設定しますが、とりあえずApacheでの使用なのでwww-dataを指定します。
ここでPHPに与える詳細な権限が設定できるようなので可能なら専用の権限を与えたほうが良いでしょう。

$ sudo nano /opt/php7/etc/php-fpm.d/www.conf
...
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
user = www-data
group = www-data
...

最後に実行用スクリプトを若干書き換えて起動します。

$ sudo nano /etc/init.d/php-fpm
...
prefix=/opt/php7
exec_prefix=

php_fpm_BIN=$prefix/sbin/php-fpm
php_fpm_CONF=$prefix/etc/php-fpm.conf
php_fpm_PID=$prefix/var/run/php-fpm.pid
...

$ sudo /etc/init.d/php-fpm start

エラーも出ずに起動が完了できれば完了です。


Apache2の設定

FPMの設定と起動の次はApacheとの連携です。
モジュール版PHPを使わない場合はリクエストを処理する機構がありませんので、FPMへ飛ばしてやる必要があります。
そこで、プロキシを経由してPHP-FPMへリクエストを送信することでPHPを処理します。

まずはいらないモジュールを無効化し、eventに変えます。

$ sudo a2dismod mpm_prefork
$ sudo a2dismod php7
$ sudo a2enmod mpm_event

次にプロキシモジュールを有効化します。

$ sudo a2enmod proxy
$ sudo a2enmod proxy_fcgi

後は前回同様に新規コンフィグを作成し、中に設定を書き込みます。
なお、細かい部分は環境に合わせて変更してください。

$ cd /etc/apache2/conf-available
$ sudo nano php7_cgi.conf
<Directory "/var/www/">
    <FilesMatch \.(php|phps)$>
        SetHandler "proxy:fcgi://127.0.0.1:9000/"
    </FilesMatch>
</Directory>

$ sudo a2disconf php7
$ sudo a2enconf php7_cgi
$ sudo apache2ctl restart

前回と打って変わって.phpと.phpsに限ってプロキシ経由でFPMへリクエストを飛ばします。
今回は127.0.0.1:9000を設定していますが、FPMのデフォルトポートなので今後変更される可能性がありますので念のためwww.confのlistenオプションを確認しておくと良いでしょう。

あとはphpinfo();を走らせて、Server APIがFPM/FastCGIとなっていれば完了です。 f:id:AonaSuzutsuki:20170824163247p:plain


最後に

思ったより面倒でしたが、なんとかCGIPHP 7の構築ができました。
負荷テストなどはしていませんが、preforkとの切り替えはmpmの切り替えとモジュール版PHP7の有効化、そしてコンフィグファイルを変更すればいいだけなのでここまでやっておけばどっちにでも転がせる状態です。
ネットに転がっている情報が古いのと細かいところが抜けていて若干苦労しましたが、なんとか成功して良かったです。
なかなか楽しかった。


参考


1 中心となる configure オプションのリスト, http://php.net/manual/ja/configure.about.php
2 Apache2.4 を event MPM + FastCGI で動かす, http://norikone.hatenablog.com/entry/2016/02/07/Apache2_4_prefork%2Bmod_php%E3%81%8B%E3%82%89event%2Bphp-fpm%2Bmod_proxy_fcgi%E3%81%B8
3 How to install PHP 7.1.6 as PHP-FPM & FastCGI for ISPConfig 3.1 on Debian 8 (Jessie), https://www.howtoforge.com/tutorial/how-to-install-php-7-on-debian/
4 why is php-fpm running as nobody?, https://serverfault.com/questions/617392/why-is-php-fpm-running-as-nobody
5 マルチプロセッシングモジュール (MPM), http://httpd.apache.org/docs/2.0/ja/mpm.html
6 Apache MPM prefork, http://httpd.apache.org/docs/2.0/ja/mod/prefork.html
7 Apache MPM worker, http://httpd.apache.org/docs/2.0/ja/mod/worker.html
8 Apache MPM event, https://httpd.apache.org/docs/2.4/mod/event.html
9 FastCGI Process Manager (FPM), http://php.net/manual/ja/install.fpm.php
10 PHP: セーフモード, http://httpd.apache.org/docs/2.0/ja/mpm.html