Menu
Menu

Nous contacter

01 82 83 51 70 infos@globalis-ms.com

6B rue Auguste Vitu

75015 Paris

Tribune Glob'codeur

Le 2 avril 2016 par Armel Fauveau

Apache 2.4 et multiples versions de PHP en mode FPM

« Retour

Ce billet explique comment déployer Apache 2.4 et de multiples versions de PHP en mode FPM (Fast Process Manager). L’idée est de tirer profit des dernières évolutions d’Apache, mais surtout, de pouvoir faire cohabiter les différentes versions de PHP simultanément, tout en profitant des performances remarquables de la SAPI FPM.

Cette procédure a été testée et approuvée sur la distribution Slackware (13.37.0). J’utilise cette distribution depuis 1993. Donc, ne pas s’attendre à l’utilisation de commandes apt-get et autres yum. On va partir des sources d’Apache et de PHP, compiler et configurer l’ensemble, écrire des fichiers rc, etc.

Installation d’Apache 2.4

Pour débuter, commençons par télécharger et compiler Apache.

$ wget http://wwwftp.ciril.fr/pub/apache//httpd/httpd-2.4.18.tar.gz
$ tar xvfz httpd-2.4.18.tar.gz
$ cd httpd-2.4.18
# => Téléchargement des sources d'Apache, unarchivage et déplacement dans le répertoire source
$ cd srclib
$ wget http://wwwftp.ciril.fr/pub/apache//apr/apr-1.5.2.tar.gz
$ tar xvfz apr-1.5.2.tar.gz
$ mv apr-1.5.2 apr
$ wget http://wwwftp.ciril.fr/pub/apache//apr/apr-util-1.5.4.tar.gz
$ tar xvfz apr-util-1.5.4.tar.gz
$ mv apr-util-1.5.4 apr-util
# => Ajout de APR et APR util
$ cd ..
$ './configure'
'--prefix=/usr/local/apache' '--enable-module=so'
'--enable-rewrite' '--enable-deflate' '--enable-expires'
'--enable-logio' '--with-included-apr' '--with-mpm=worker'
$ make
# => Compilation d'Apache en mode mpm_worker

Apache propose plusieurs Modules Multi-Processus (MPM). Ici, j’ai choisi worker, mais il est possible d’opter pour prefork ou event.


$ './configure'
'--prefix=/usr/local/apache' '--enable-module=so'
'--enable-rewrite' '--enable-deflate' '--enable-expires'
'--enable-logio' '--with-included-apr' '--with-mpm=prefork'
$ make
# => Compilation d'Apache en mode mpm_prefork
$ './configure'
'--prefix=/usr/local/apache' '--enable-module=so'
'--enable-rewrite' '--enable-deflate' '--enable-expires'
'--enable-logio' '--with-included-apr' '--with-mpm=event'
$ make
# => Compilation d'Apache en mode mpm_event

On peut maintenant installer Apache via la commande make install.

Il reste alors à éditer le ficher httpd.conf et veiller à activer LoadModule proxy_module modules/mod_proxy.so et LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so. Ces modules seront essentiels pour utiliser PHP en mode FPM. En fonction des besoins, on peut aussi en profiter pour éditer les directives DirectoryIndex, ServerName, Listen, la prise en comptes des fichiers .htaccess, etc.

On peut alors démarrer Apache.

$ /usr/local/apache/bin/apachectl start
# => Démarrage d'Apache

Installation de PHP

Nous allons maintenant installer différentes versions de PHP que nous ferons cohabiter ensemble.

$ wget http://fr2.php.net/get/php-7.0.5.tar.gz/from/this/mirror
$ tar xvfz php-7.0.5.tar.gz
$ cd php-7.0.5
# => Téléchargement des sources de PHP, unarchivage et déplacement
dans le répertoire source
$ './configure'
'--enable-fpm' '--with-fpm-user=daemon' '--with-fpm-group=daemon'
'--prefix=/usr/local/php-70' '--with-config-file-path=/usr/local/php-70'
'--with-mysqli=mysqlnd' '--enable-pdo' '--with-pdo-mysql=mysqlnd'
'--with-openssl' '--enable-bcmath' '--with-bz2' '--with-pic'
'--enable-calendar' '--enable-ctype' '--enable-ftp' '--with-gd'
'--with-freetype-dir' '--with-png-dir' '--with-jpeg-dir=/usr'
'--with-gmp' '--enable-sockets' '--enable-sysvsem' '--enable-sysvshm'
'--disable-debug' '--with-zlib=/usr' '--with-pear' '--enable-simplexml'
'--enable-mbstring=all' '--with-pspell' '--with-curl' '--with-xsl'
'--with-mcrypt' '--enable-soap' '--enable-zip' '--enable-pcntl'
'--with-readline' '--enable-opcache' '--without-pear' '--with-libdir=lib64'
$ make
# => Compilation de PHP en mode FPM

PHP est compilé ici en mode FPM. À noter les paramétrages --prefix et --with-config-file-path. Ils sont importants et vont permettre de parfaitement isoler les différentes versions de PHP entre elles. On peut maintenant installer PHP via la commande make install.

Il reste encore quelques opérations de post-installation à effectuer.
$ mv /usr/local/php-70/sbin/php-fpm /usr/local/php-70/sbin/php-fpm-70
# => Renommage du binaire php-fpm
$ mkdir /usr/local/php-70/extensions
# => Création d'un répertoire pour accueillir les extensions dans le répertoire d'installation
$ cp modules/opcache.so /usr/local/php-70/extensions/
# => Copie du module opcache.so
$ cp php.ini-development /usr/local/php-70/php.ini
# => Copie du fichier php.ini-development dans le répertoire d'installation

On peut maintenant éditer le fichier php.ini placé dans le répertoire d’installation afin d’initialiser correctement quelques directives comme extension_dir = /usr/local/php-70/extensions, date.timezone = Europe/Paris ou encore charger le cache d’opcode en ajoutant zend_extension = opcache.so.

Il reste à dérouler la même procédure avec les différentes version de PHP que l’on désire faire cohabiter: PHP 5.5, PHP 5.6, etc. Il faut juste bien penser à isoler les différentes versions entre elles en jouant avec les paramétrages --prefix et --with-config-file-path lors de la compilation de PHP et bien cibler le bon répertoire d’installation dans la suite des opérations.

Configuration et lancement de PHP en mode FPM

La configuration et le lancement des pools FPM passent par la mise en place de 2 fichiers.

Par exemple pour PHP 7.0, un premier fichier que nous appelerons /etc/php-fpm-70.conf, servira à la configuration.
[global]
pid = /var/run/php-fpm-70.pid
error_log = /var/log/php-fpm-70.log
[www]
user = daemon
group = daemon
listen = 127.0.0.1:9070
pm = dynamic
pm.max_children = 40
pm.start_servers = 10
pm.min_spare_servers = 10
pm.max_spare_servers = 20
pm.max_requests = 1000
chdir = /

Bien noter encore ici le soin particulier apporté à l’isolation dans les chemins pid et error_log, mais aussi au niveau listen dans le choix du port réseau à écouter. Il conviendra d’adapter et affiner les autres paramétres en fonction de vos besoins.

Un second fichier, que nous appellerons /etc/rc.d/rc.php-fpm-70, servira à lancer le pool.


#! /bin/sh
### BEGIN INIT INFO
# Provides:          php-fpm
# Required-Start:    $remote_fs $network
# Required-Stop:     $remote_fs $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts php-fpm
# Description:       starts the PHP FastCGI Process Manager daemon
### END INIT INFO
prefix=/usr/local/php-70
exec_prefix=${prefix}
php_fpm_BIN=${exec_prefix}/sbin/php-fpm-70
php_fpm_CONF=/etc/php-fpm-70.conf
php_fpm_PID=/var/run/php-fpm-70.pid
php_opts="--fpm-config $php_fpm_CONF"
wait_for_pid () {
    try=0
    while test $try -lt 35 ; do
        case "$1" in
            'created')
            if [ -f "$2" ] ; then
                try=''
                break
            fi
            ;;
            'removed')
            if [ ! -f "$2" ] ; then
                try=''
                break
            fi
            ;;
        esac
        echo -n .
        try=`expr $try + 1`
        sleep 1
    done
}
case "$1" in
    start)
        echo -n "Starting php-fpm "
        $php_fpm_BIN $php_opts
        if [ "$?" != 0 ] ; then
            echo " failed"
            exit 1
        fi
        wait_for_pid created $php_fpm_PID
        if [ -n "$try" ] ; then
            echo " failed"
            exit 1
        else
            echo " done"
        fi
    ;;
    stop)
        echo -n "Gracefully shutting down php-fpm "
        if [ ! -r $php_fpm_PID ] ; then
            echo "warning, no pid file found - php-fpm is not running ?"
            exit 1
        fi
        kill -QUIT `cat $php_fpm_PID`
        wait_for_pid removed $php_fpm_PID
        if [ -n "$try" ] ; then
            echo " failed. Use force-quit"
            exit 1
        else
            echo " done"
        fi
    ;;
    force-quit)
        echo -n "Terminating php-fpm "
        if [ ! -r $php_fpm_PID ] ; then
            echo "warning, no pid file found - php-fpm is not running ?"
            exit 1
        fi
        kill -TERM `cat $php_fpm_PID`
        wait_for_pid removed $php_fpm_PID
        if [ -n "$try" ] ; then
            echo " failed"
            exit 1
        else
            echo " done"
        fi
    ;;
    restart)
        $0 stop
        $0 start
    ;;
    reload)
        echo -n "Reload service php-fpm "
        if [ ! -r $php_fpm_PID ] ; then
            echo "warning, no pid file found - php-fpm is not running ?"
            exit 1
        fi
        kill -USR2 `cat $php_fpm_PID`
        echo " done"
    ;;
    *)
        echo "Usage: $0 {start|stop|force-quit|restart|reload}"
        exit 1
    ;;
esac

Et voilà. On peut alors lancer notre pool de parseurs PHP 7.0.

$ /etc/rc.d/rc.php-fpm-70 start
# => Lancement du pool FPM de parseurs PHP 7.0

Comme précédemment, dérouler la même procédure avec les différentes version de PHP que l’on désire faire cohabiter.

Voici ce que doit produire la commande pstree avec les versions PHP 5.4, 5.5, 5.6 et 7.0

$ pstree | grep php
|-php-fpm-54---10*[php-fpm-54]
|-php-fpm-55---10*[php-fpm-55]
|-php-fpm-56---10*[php-fpm-56]
|-php-fpm-70---10*[php-fpm-70]
# => Exécution de la commande pstree permettant de visualiser les pools

Compilation d’une extension PHP

Lorsque l’on compile une extension PHP, le binaire produit dépend de la version de PHP (du Zend Engine). C’est pour cela que le binaire d’une extension compilée pour PHP 5.4 ne fonctionnera pas avec PHP 5.5 ou 5.6, le moteur interne de PHP ayant changé. C’est aussi pour cela que nous avons compilé PHP avec les paramétrages --prefix et --with-config-file-path. Cela va simplifier la compilation de nouvelles extensions. Voici la marche à suivre avec, par exemple, la compilation de xDebug pour PHP 7.0.


$ wget https://xdebug.org/files/xdebug-2.4.0.tgz
$ tar xvfz xdebug-2.4.0.tgz
$ cd xdebug-2.4.0
# => Téléchargement des sources de xDebug, unarchivage et déplacement
dans le répertoire source
$ /usr/local/php-70/bin/phpize
$ ./configure --with-php-config=/usr/local/php-70/bin/php-config
$ make
# => Compilation de xDebug
$ cp modules/xdebug.so /usr/local/php-70/extensions/
# => Copie de l'extension dans le répertoire d'extensions

Et voilà, une fois encore, dérouler la même procédure avec les différentes version de PHP en prenant soin de pointer vers les bonnes versions de phpize et php-config.

Un dernier mot sur la mise en oeuvre et la performance

Afin d’activer le parseur de telle ou telle version de PHP, il suffit d’utiliser un simple fichier .htaccess. Par exemple, pour activer le parseur de PHP 7.0.
<FilesMatch .php$>
SetHandler "proxy:fcgi://127.0.0.1:9070"
</FilesMatch>

On peut aussi ajouter ces précédentes lignes directement dans le httpd.conf, en global ou au niveau de tel ou tel virtualhost.

Rappelons que l’utilisation de PHP en mode FPM implique d’avoir recours à un fichier .user.ini si l’on désire affiner les paramétrages de PHP dans tel ou tel répertoire.

En terme de performance, à serveur constant, l’utilisation d’Apache 2.4 et de la SAPI FPM se montre de loin la combinaison la plus performante, comparativement à PHP en module ou en CGI. Cette combinaison fait également globalement jeu égal avec Nginx d’après mes observations et tests de montée en charge.

Article précédent Article suivant