PHP 7.0 & HTTP2 アップグレードへの道

PHP7 がリリースされ、nginx も HTTP2 に対応するようになり、ともにサイトの高速化が期待できるとのことで、早速導入してみました。

PHP 7.0 の導入

PHP7 を入れるための remi リポジトリを追加。依存関係にある EPEL リポジトリも追加。
wget http://ftp.iij.ad.jp/pub/linux/fedora/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
rpm -Uvh epel-release-7-5.noarch.rpm
wget http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
rpm -Uvh remi-release-7.rpm

yum で インストール
yum -y install --enablerepo=remi-php70 php php-devel php-common php-mbstring php-mysqlnd php-pdo php-mcrypt php-pear php-xml php-gd php-pecl-apcu-bc php-fpm

でも、エラーでこけました。いくつかのパッケージで

要求: libc.so.6(GLIBC_2.15)(64bit)

と出てくるのでどうやら glibc 2.15 (64bit) が必要なようです。 調べてみると現時点の CentOS 6 系では yum で glibc は 2.12 までしかアップデートできませんでした。

CentOS のバージョンを 7 にするのは影響がデカくて流石に避けたいところです。
ソースからビルドすれば大丈夫そうですが、yum 管理から外れるのもなぁ…とひとしきり悩んだ結果、 入れ直しついでにせっかくなので PHP の管理は phpBrew で行うようにしてみました。
phpBrew は各バージョンをビルドから行うので、おそらく CentOS 6 のままでもイケるであろうと期待して…

公式の手順に沿って、Cent OS 用の phpBrew インストールの必要環境を構築します。

まずはPHPに特化したリポジトリ、webtaticを追加。
rpm -Uvh http://mirror.webtatic.com/yum/el6/latest.rpm
基本パッケージとしてPHP5.3をインストールします。
yum install --enablerepo=webtatic php php-xml

普通にインストールされたものの、phpをコールするたびに

PHP Warning: PHP Startup: Unable to load dynamic library ‘/usr/lib64/php/modules/apc.so’ - /usr/lib64/php/modules/apc.so: cannot open shared object file: No such file or directory in Unknown on line 0

とエラーがでて鬱陶しいので、APCを有効にするべく、PECLに必要なライブラリをインストールします。
yum install --enablerepo=webtatic php-pear php-devel httpd-devel pcre-devel

そしてAPCをインストール
pecl install APC

引き続き、phpBrew の必要環境の準備を行います。

rpmforgeリポジトリを登録
wget http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm
rpm -Uvh rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm

phpBrewの依存パッケージのインストール
yum install --enablerepo=rpmforge re2c libmhash

準備が完了したので phpBrew 経由で
phpbrew install 7.0.1 +default+dbs+mcrypt+gd+fpm
と PHP 7.0.1 をインストールしてみたものの、途中でいろいろエラーが出てコケまくります。
コンパイルに必要なライブラリがないことが原因のようです。
結局、試行錯誤した結果、以下、PHP のビルドに足らないライブラリを yum で追加インストールしておくことになりました。
yum --enablerepo=epel install libxml2-devel openssl-devl bzip2-devel readline-devel libxslt-devel libjpeg-devel libpng-devel libXpm-devel freetype-devel

今度こそ、と思いきやOpen SSLのライブラリがないというエラーが…

configure: error: Cannot find OpenSSL’s libraries

また、phpBrew の公式には以下のような記述があり、指定しないと後でGD拡張を有効にしようとしてもエラるので、

GD拡張を指定してPHPをビルドするには、libpng dirとlibjpeg dirを指定する必要があります。 例えば、 $ phpbrew install php-5.4.10 +default +mysql +intl +gettext +apxs2=/usr/bin/apxs2 \ -- --with-libdir=lib/x86_64-linux-gnu \ --with-gd=shared \ --enable-gd-natf \ --with-jpeg-dir=/usr \ --with-png-dir=/usr

最終的な結果として以下のようなコマンドになりました。

phpbrew install 7.0.1 +default+sqlite+mysql+mcrypt+gd+fpm+openssl=/usr -- --with-libdir=lib64 --with-gd=shared --enable-gd-natf --with-jpeg-dir=/usr --with-png-dir=/usr

ビルドに時間はかかるけど、何とか動作するところまではこぎつけられました。

拡張も有効化して完了です。
phpbrew ext install gd
phpbrew ext install opcache
phpbrew ext install apcu

OPcacheの設定
vim ~/.phpbrew/php/php-7.0.1/var/db/opcache.ini

zend_extension=/path/to/.phpbrew/php/php-7.0.1/lib/php/extensions/no-debug-non-zts-20151012/opcache.so
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1

APCUの設定
vim ~/.phpbrew/php/php-7.0.1/var/db/apcu.ini

extension = apcu.so
apc.enabled=1
apc.shm_size=128M
apc.ttl=86400
apc.gc_ttl=86400

HTTP2 の有効化

nginx 1.9.5 から HTTP2 をサポートするようになったので、まずは nginx のバージョンを上げます。
現時点では nginx 1.9 系 は Stable ではなく mainline バージョンなので、 以前登録した nginx repo の baseurl を変更します。
vim /etc/yum.repos/nginx.repo

-baseurl=http://nginx.org/packages/centos/6/$basearch/
+baseurl=http://nginx.org/packages/mainline/centos/6/$basearch/

あとは通常通り、アップデートすればOKでした。
yum --enablerepo=nginx update nginx

また、php-fpm と nginx の参照を unix socketで接続すると Bad Gateway が発生してどうにもならなかったので、 127.0.0.1:9000 に書き直したところ、正常にアクセスできました。

既にStartSSL導入済みのサイトでは niginx.conf の以下の点を変更することでHTTP2を有効にできました。

vim /etc/nginx/conf.d/www.example.com.conf

-listen 443 ssl;
+listen 443 ssl http2;
-ssl_ciphers  HIGH:!aNULL:!MD5;
+ssl_ciphers  AESGCM:HIGH:!aNULL:!MD5;

また、ちょうど Let’s Encrypt が公開されたので新規のSSLはこちらはで取得してみることにします。

まずは任意の場所で公式のリポジトリからデータを取得します。
git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt

letsencrypt-auto コマンドを実行すると依存ライブラリが自動的にインストールされます。
./letsencrypt-auto --help
# 問答無用で突然インストールが始まるので心臓に悪いです…

インストール中にものすごい数の warning がでるのですが、これは動作には Python2.7 が必要なためです。
自分の環境は CentOS 6.7 で、 yum の標準リポジトリ経由では 2.6 までしかアップデートできないため、IUS リポジトリを追加して、Python 2.7 をインストールしました。
rpm -UhV https://centos6.iuscommunity.org/ius-release.rpm
yum install --enablerepo=ius python27 python27-devel python27-pip python27-setuptools python27-virtualenv

これで warnning も出ずに平穏に証明書の生成ができます。
証明書の発行コマンドを打ち込むと

./letsencrypt-auto certonly --webroot -d www.example.com --webroot-path /path/to/public_html

何かエラーが返ってきました。

IMPORTANT NOTES:
 - The following 'urn:acme:error:unauthorized' errors were reported by
   the server:

   Domains: www.example.com
   Error: The client lacks sufficient authorization

これは nginx.conf で ドットから始まるリソースへのアクセスを制限しているために発生したエラーでした。

  location ~ /\. {
    deny all;
    log_not_found off;
    access_log off;
  }

こんな記述をしていると403エラーで正常に処理が完了しません。

nginx.conf に letsencrypt が生成して利用する .well-known/acme-challenge ディレクトリのみ、アクセスを開放する記述を追記します。

  location ^~ /.well-known/acme-challenge/ {
    allow all;
  }

再度証明書の発行コマンドを実行したら、めでたく証明書が発行されました。

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/www.example.com/fullchain.pem. Your cert will
   expire on YYYY-MM-DD. To obtain a new version of the certificate in
   the future, simply run Let's Encrypt again.

nginx.conf の server 以下、発行された証明書を参照するSSLの記述を追加します。

-listen 443 ssl;
+listen 443 ssl http2;

+ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
+ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
+ssl_session_cache shared:SSL:1m;
+ssl_session_timeout 5m;
+ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;
+ssl_prefer_server_ciphers on;

nginx を再起動してサイトがSSLでアクセスできることを確認できました。
service ngingx restart

また、Let’s Encrypt の証明書は3か月で有効期限が切れるため、crontab で定期的に証明書の更新コマンドを実行するようにします。
crontab -e
0 0 1 * * /[letsencryptのインストールパス]/letsencrypt-auto certonly --webroot --agree-tos -w /path/to/public_html -d www.example.com --renew-by-default && service nginx reload

参考サイト