PHP, FastCGI, FPM et Apache
Le sujet n'est pas nouveau... A l'époque de la sortie de PHP 5.0 on se posait déjà la question : "Comment vais-je faire pour faire tourner mon vieux PHP 4 et mon nouveau PHP 5 ?". Aujourd'hui la même question se repose mais pour faire tourner PHP 5.2.x et PHP 5.3.x ensemble (et même bientôt avec PHP 5.4). Certains se demanderont quel en est l'intérêt. Il est en réalité multiple car ce cas de figure couvre un certain nombre de cas d'utilisation, allant du poste de développement à l'hébergement.
De l'intérêt d'une installation PHP multiple
Voici en vrac quelques cas d'utilisation :
- Développement de plusieurs projets ayant des contraintes d'hébergement différentes (l'hébergeur du client A accepte PHP 5.3 mais pas celui du client B car il préfère rester sur sa vieille RHEL moisie).
- Je veux voir comment mon site réagit à la dernière RC de PHP 5.4, histoire de me tenir prêt.
- J'héberge sur mon serveur dédié plusieurs sites eZ Publish dont des versions non compatibles PHP 5.3 (< eZ Publish 4.2), mais je ne veux pas pénaliser les plus récents.
On pourrait en trouver d'autres, j'en suis certain.
Apache et son module PHP
Le principal problème auquel on se heurte, c'est le type d'installation standard d'un serveur web Apache avec PHP, qui consiste à brancher PHP en tant que module Apache. De cette manière, les deux compères se retrouvent mariés et indissociables, mais ce n'est pas sans inconvénient. En effet, PHP (ou plutôt ses nombreuses librairies) n'étant pas threadsafe, il est indispensable d'utiliser le mode prefork-mpm d'Apache 2.x, l'évitant d'utiliser des threads séparés en forkant ses processus à la place comme le faisait le vénérable Apache 1.3 (cf documentation de PHP sur le sujet ). En théorie, cela ne pose pas de problème, mais dans la pratique ce n'est pas vraiment un modèle de performance car Apache a vite tendance à saturer CPU et RAM dès qu'il y a un peu trop de trafic, générant ralentissements, timeouts et autres désagréments sympathiques. Et pour ne rien arranger, cette installation classique reste celle mise en avant par de nombreuses distributions Linux via ses paquets pré-compilés (notamment Debian/Ubuntu).
Alors quoi ? Certains advantistes vous diront "Apache c'est pour les vieux cons, passe à Nginx ou au pire Lighttpd". Oui certes cette solution est intéressante mais je connais encore trop peu d'administrateurs système en entreprise (comprendre "chez les très conservateurs hébergeurs des clients") qui sont prêts à sauter le pas. Beaucoup préfèrent encore rester sur Apache pour plein de raisons plus ou moins avouables et légitimes.
Heureusement il existe une solution alternative : FastCGI.
Le messie FastCGI et son copain PHP-FPM (et vice-versa)
Mais d'abord, c'est quoi FastCGI ? L'article Wikipedia nous apprend qu'il s'agit d'une "technique permettant la communication entre un serveur HTTP et un logiciel indépendant". En gros, c'est une évolution du vieux protocole CGI, qui offre notamment la possibilité de recycler les processus et donc d'éviter d'avoir à les réinitialiser à chaque demande.
Il existe deux modules principaux pour Apache : mod_fcgid et mod_fastcgi . Je ne parlerai que de mod_fastcgi car il offre plus de fonctionnalités qui nous seront indispensables, notamment la possibilité de définir un Process Manager externe. Ces modules permettent donc de faire le pont entre Apache et PHP en lançant des processus (ou workers) PHP et de les recycler au besoin, sans pour autant que PHP soit intégré à Apache.
Ils ont donc 2 rôles :
- Passer les requêtes demandant une interprétation depuis le serveur vers un worker PHP.
- Gérer les workers, leur nombre et leur recyclage.
Et bien sûr, vous l'aurez compris, plus besoin du mode prefork-mpm d'Apache, on peut enfin le passer en mode worker-mpm, bien plus efficace.
Seulement voilà, en terme de gestion de processus, c'est bien, mais ce n'est pas non plus la panacée car tout est géré via un script shell (que nous verrons en détail) afin de lancer l'exécutable php-cgi. Certes il est possible de définir un certain nombre de paramètres, mais on a vu mieux, même si les performances restent au rendez-vous... Heureusement, PHP vient à notre secours avec PHP-FPM (pour FastCGI Process Manager), intégré nativement depuis la version 5.3.3 (à activer à la compilation). Il s'agit à l'origine d'un patch aux sources de PHP qui a rapidement su séduire notamment de part son efficacité. Vous en apprendrez plus sur le vieux site de PHP-FPM .
Oui mais voilà... PHP-FPM n'est disponible que depuis PHP 5.3.3, et je veux aussi faire tourner PHP 5.2.x. Qu'à cela ne tienne, on utilisera PHP-FPM pour 5.3+ et le FastCGI classique pour les versions antérieures :-).
Au boulot !
Passons maintenant à la pratique.
Notre objectif sera d'installer 2 versions de PHP (5.2.17 et 5.3.8) en FastCGI sur une Ubuntu server 8.04 (ça marche aussi avec une version plus récente). Il nous sera pour cela nécessaire de compiler nous-même PHP (les paquets n'étant pas à jour).
Pré-requis
Nous ferons toutes les opérations suivantes en tant que root.
sudo su -
Pour la compilation, nous allons avoir besoins de quelques outils essentiels :
aptitude install build-essential libxml2-dev autoconf2.13 libpng12-* libjpeg* zlib1g gawk bison flex ^libxml2-* mcrypt libmcrypt-dev perl libcurl4-gnutls-dev libicu-dev libxslt1-dev libcurl4-openssl-dev
Une fois ces outils installés, il ne faut pas non plus oublier Apache, en mode worker bien sûr, ainsi que son mod_fastcgi :
aptitude install apache2 apache2-mpm-worker libapache2-mod-fastcgi
Pour la suite, nous compilerons PHP respectivement dans les répertoires /opt/php52 et /opt/php53. Nous garderons les sources dans /opt/src/php.
mkdir -p /opt/php52 /opt/php53 /opt/src/php
Compilation de PHP 5.2.17
Téléchargeons les sources de PHP.
cd /opt/src/php wget -O php-5.2.17.tar.bz2 http://fr2.php.net/get/php-5.2.17.tar.bz2/from/fr.php.net/mirror tar -xvjf php-5.2.17.tar.bz2
Nous voici maintenant en présence d'u répertoire php-5.2.17/ contenant les sources de PHP. Nous allons maintenant préparer PHP à la compilation en activant les options que l'on souhaite (adaptez à ce que vous souhaitez). Au besoin, n'hésitez pas à lancer ./configure --help depuis le répertoire des sources.
cd php-5.2.17/ ./configure \ --prefix=/opt/php52 \ --with-readline \ --with-mysql \ --with-mysqli \ --with-pdo-mysql \ --with-gd \ --with-freetype-dir \ --with-jpeg-dir \ --with-png-dir \ --with-zlib-dir \ --enable-gd-native-ttf \ --with-curl \ --enable-bcmath \ --enable-mbstring \ --enable-exif \ --with-ldap \ --enable-pcntl \ --enable-soap \ --with-xsl \ --enable-zip \ --with-config-file-path=/opt/php52/conf \ --with-config-file-scan-dir=/opt/php52/conf.d \ --enable-fastcgi \ --enable-discard-path \ --enable-force-cgi-redirect
Ici quelques explications s'imposent.
- --prefix : Le "préfixe" de PHP, où il sera installé
- --enable-fastcgi : Active le support FastCGI de PHP
- --enable-discard-path : Permet de placer le binaire php-cgi en dehors du dossier web
- --enable-force-cgi-redirect : Option de sécurité pour CGI/FastCGI concernant les redirections internes au serveur
Si votre serveur se plaint au sujet de readline et/ou de freetype, essayez d'installer les paquets libreadline-dev et libfreetype6-dev.
Si le configure ne vous a pas engueulé, vous pouvez passer à la compilation (vous avez le temps de prendre un café) :
make make install
Reste à copier le php.ini et à l'éditer à votre convenance (notamment en renseignant le date.timezone, chez moi à Europe/Paris).
cp /opt/src/php/php-5.2.17/php.ini-production /opt/php52/conf/php.ini
Je prends ici la version "production", mais si vous êtes sur un poste de développement, prenez plutôt le php.ini-development ;-).
Compilation de PHP 5.3.8
Même punition pour PHP 5.3.8, à quelques détails près. En effet, les options de configuration ne sont pas tout à fait les mêmes et nous souhaitons installer en plus PHP-FPM.
cd /opt/src/php wget -O php-5.3.8.tar.bz2 http://fr2.php.net/get/php-5.3.8.tar.bz2/from/fr.php.net/mirror tar -xvjf php-5.3.8.tar.bz2 cd php-5.3.8/ ./configure \ --prefix=/opt/php53 \ --with-readline \ --with-mysql=mysqlnd \ --with-mysqli=mysqlnd \ --with-pdo-mysql=mysqlnd \ --with-gd \ --with-freetype-dir \ --with-jpeg-dir \ --with-png-dir \ --with-zlib-dir \ --enable-gd-native-ttf \ --with-curl \ --enable-bcmath \ --enable-mbstring \ --enable-exif \ --with-ldap \ --enable-pcntl \ --enable-soap \ --with-xsl \ --enable-zip \ --with-config-file-path=/opt/php53/conf \ --with-config-file-scan-dir=/opt/php53/conf.d \ --with-iconv-dir=/opt/local \ --enable-fpm \ --with-fpm-user=www-data \ --with-fpm-group=www-data
Ici nous configurons sensiblement de la même façon à quelques détails près :
- --enable-fpm : Ceci active le support de PHP-FPM
- --with-fpm-user=www-data : PHP-FPM utilisera par défaut l'utilisateur www-data (celui d'Apache donc)
- --with-fpm-group=www-data : Même chose pour le groupe
- --with-mysql=mysqlnd (idem pour mysqli et pdo-mysql) : On force l'utilisation du driver MySQL natif fourni nativement avec PHP 5.3 (bien plus performant). Il vous faudra juste indiquer le chemin vers la socket du serveur MySQL dans le php.ini (sections MySQL, MySQLi et Pdo_mysql)
Si tout s'est bien passé, on passe à la compilation :
make make install
N'oublions pas non plus le php.ini.
cp /opt/src/php/php-5.3.8/php.ini-production /opt/php53/conf/php.ini
Voilà, nous avons maintenant 2 beaux PHP tout neufs qu'il faut désormais faire fonctionner avec notre serveur Apache.
Apache et mod_fastcgi
Nous avons déjà installé le module mod_fastcgi, mais il n'est pas encore activé ni configuré.
a2enmod fastcgi vi /etc/apache2/mods-available/fastcgi.conf
Ce fichier va devoir ressembler à cela :
<IfModule mod_fastcgi.c> AddHandler fastcgi-script .fcgi FastCgiIpcDir /var/lib/apache2/fastcgi FastCgiConfig -autoUpdate -singleThreshold 100 -killInterval 300 -maxProcesses 10 -maxClassProcesses 1 AddHandler php5-fcgi .php AddType application/x-httpd-php .php ScriptAlias /cgi-bin/ /var/www/cgi-bin/ <Directory "/var/www/cgi-bin"> Options +ExecCGI SetHandler fastcgi-script Order Deny,Allow Allow from env=REDIRECT_STATUS </Directory> # PHP 5.3 + FPM par defaut <Location "/cgi-bin/php53.fpm"> Order Deny,Allow Deny from all Allow from env=REDIRECT_STATUS </Location> FastCgiExternalServer /var/www/cgi-bin/php53.fpm -host 127.0.0.1:9000 Action php5-fcgi /cgi-bin/php53.fpm </IfModule>
A nouveau, quelques explications s'imposent.
- FastCgiIpcDir : Il s'agit du répertoire où seront stockées les sockets de connexion FastCGI par le biais desquelles Apache va communiquer avec ses workers. Ce répertoire doit impérativement exister (il a probablement été créé lors de l'installation du module avec aptitude).
- FastCgiConfig : C'est la configuration du process manager de mod_fastcgi par défaut. Elle sera utilisée pour les environnements en PHP 5.2 (ceux en PHP 5.3 utiliseront PHP-FPM à la place). L'option -maxProcesses indique le nombre maximum TOTAL de workers (pour tout le serveur). Quant à -maxClassProcesses, il indique le nombre maximum par VirtualHost; il doit impérativement rester à 1 (nous verrons après pourquoi). Pour les autres options, je vous renvoie à la doc de mod_fastcgi .
- AddHandler php5-fcgi .php : Ajoute un handler nommé php5-fcgi pour tous les fichiers .php.
- Allow from env=REDIRECT_STATUS : Avec la commande Action utilisée pour CGI/FastCGI (voir plus bas), Apache définit automatiquement la variable d'environnement REDIRECT_STATUS. Le fait de n'autoriser que les connexions ayant cette variable d'environnement permet de sécuriser en évitant les connexions directes non souhaitées.
- Le répertoire /var/www/cgi-bin est ensuite configuré pour autoriser l'exécution des scripts CGI et n'autorise que les connexions ayant le statut REDIRECT_STATUS.
- La location /cgi-bin/php53.fpm est configurée pour n'autoriser que les connexions ayant le statut REDIRECT_STATUS (le fichier n'a pas besoin d'exister)
- FastCgiExternalServer : Configuration de PHP-FPM sur une socket réseau répondant à l'adresse IP 127.0.0.1 sur le port 9000
- Action php5-fcgi /cgi-bin/php53.fpm : L'action FastCGI par défaut est de rediriger vers la socket réseau, et donc vers PHP-FPM (voir plus bas)
Nous utiliserons donc par défaut PHP 5.3 via PHP-FPM. Mais si nous voulons configurer un virtual host en PHP 5.2, il suffira de rajouter dans le VirtualHost :
Action php5-fcgi /cgi-bin/php52.fcgiCela définira l'action du handler php5-fcgi vers le script /var/www/cgi-bin/php52.fcgi. Ce script, lui, a besoin d'exister.
mkdir /var/www/cgi-bin vi /var/www/cgi-bin/php52.fcgi
Et en voici le contenu :
#!/bin/sh PHP_FCGI_CHILDREN=5 export PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS=1000 export PHP_FCGI_MAX_REQUESTS exec /opt/php52/bin/php-cgi
Il s'agit donc d'un script shell définissant 2 variables d'environnement puis faisant appel au binaire php-cgi (la requête HTTP lui sera transmise par mod_fastcgi).
- PHP_FCGI_CHILDREN : C'est le nombre maximum de processus enfants que PHP a le droit de créer (ici 5, ce qui n'est pas énorme). Ces enfants seront recyclés au fil des requêtes.
- PHP_FCGI_MAX_REQUESTS : Nombre maximum de requêtes que peut gérer un processus enfant avant d'être renouvelé
La conséquence de ce script est que c'est PHP qui gère lui-même ses processus en fonction de ces variables d'environnement. Laisser PHP gérer permet notamment d'avoir un cache d'Opcode commun à ces processus enfants uniquement (si vous utilisez APC), c'est pourquoi il est important de laisser l'option -maxClassProcesses de FastCgiConfig à 1. Si votre machine peut encaisser plus de processus, augmentez la valeur de PHP_FCGI_CHILDREN.
Vous avez aussi la possibilité de passer des options au binaire php-cgi si vous le souhaitez, pour indiquer par exemple un php.ini alternatif via l'option -c (utile pour du virtual hosting).
Ce script doit être exécutable, nous lui donnons donc les droits nécessaires :
chmod +x /var/www/cgi-bin/php52.fcgi
Configuration de PHP-FPM
Le fichier de configuration de PHP-FPM se trouve dans /opt/php53/etc, mais il faut d'abord le renommer.
mv /opt/php53/etc/php-fpm.conf.default /opt/php53/etc/php-fpm.conf
Dans ce fichier, il va nous falloir activer le PID :
pid = run/php-fpm.pid
Cela va positionner le fichier PID dans /opt/php53/var/run/php-fpm.pid.
Il nous faut maintenant configurer notre pool de connexions entrantes (un peu comme nous l'avons fait en shell pour PHP 5.2). Toujours dans le même fichier, déplacez vous à la section [www] pour définir les paramètres suivants (vous pouvez en configurer d'autres si vous le souhaitez) :
[www] ; Prefixe pour le pool, sert notamment pour le chroot si vous l'activez ; Note : Le dossier doit impérativement exister ! prefix = /opt/php53/var/fpm/pools/$pool ; Les utilisateurs/groupes utilisés par les workers user = www-data group = www-data ; La socket réseau que nous avons configuré au niveau Apache listen = 127.0.0.1:9000 ; On n'accepte que les connexions locales listen.allowed_clients = 127.0.0.1 pm = dynamic ; Le nombre maximum de workers simultanés, équivalent de PHP_FCGI_CHILDREN pm.max_children = 10 ; On démarre 5 workers au démarrage pm.start_servers = 5 ; Le nombre minimum de serveurs de backup en idle pm.min_spare_servers = 3 ; Idem, mais maximum pm.max_spare_servers = 5 ; Le nombre maximum de requêtes par enfant ; Equivalent de PHP_FCGI_MAX_REQUESTS pm.max_requests = 1000
Libre à vous de modifier d'autres variables de configuration, fiez-vous aux commentaires dans le fichier de configuration. Vous pouvez notamment définir des php_admin_value et autres php_flag.
Il est tout à fait possible d'utiliser ce même pool pour plusieurs virtual hosts, mais il peut être intéressant d'en définir un pour chaque.
Il ne reste qu'à démarrer PHP-FPM. Pour ce faire, PHP nous fournit un script init.d prêt à l'emploi, il suffit juste de le placer au bon endroit.
cp /opt/src/php/php-5.3.8/sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm /etc/init.d/php-fpm start
C'est terminé ! Il ne reste plus qu'à démarrer votre serveur Apache.
Pour vérifier que tout est bien configuré, il suffit de lancer un phpinfo().
Rien ne vous empêche maintenant de tester PHP 5.4 ;-)
Précautions sur les règles de réécriture
Si vous utilisez un CMS ou un framework utilisant des règles de réécriture de façon assez intense, comme c'est le cas avec eZ Publish, il convient alors d'éviter que le répertoire de script virtuel /cgi-bin/ soit interprété par d'autres RewriteRule. Pour ce faire, il suffit de rajouter une règle simple au-dessus de toutes vos autres règles :
RewriteRule ^/cgi-bin - [L]Du côté des performances
J'ai pu effectuer quelques benchmarks rapides, notamment en comparaison avec une installation en module Apache, et le FastCGI (sans FPM) tient ses promesses malgré l'utilisation d'un script Shell intermédiaire. Les performances sont équivalentes (nombre équivalent de requêtes délivrées par seconde) mais ce qui est intéressant c'est que le serveur tient beaucoup mieux la charge dans le sens où Apache ne se noie pas et ne fait pas monter votre serveur à 100% de CPU et de RAM. Ce sont les processus séparés de PHP qui font monter les ressources dans les tours, ce qui est bien plus gérable.
Avec PHP-FPM, c'est encore plus efficace car PHP pilote tout ! Il offre par ailleurs bien plus d'options de configuration et de log.
On pourrait aller encore plus loin et mettre en place un serveur beaucoup plus léger comme Nginx ou Lighttpd car après tout, Apache ne sert ici plus qu'à servir les ressources statiques ! Cela fera peut-être l'objet d'un nouvel article :-).








Commentaires
Toujours intéressant ! (par Nicolas Martinez)
Ca c'est du post (par Arnaud Lafon)
Super billet ! (par Plopix)
Merci (par Jérôme Vieilledent)
re... (par Nicolas Martinez)
En effet (par Jérôme Vieilledent)
Ah yes! (par Nicolas Martinez)
yeah (par Maxime THOMAS)
Nginx / Lighttpd (par Jérôme Vieilledent)
NginX (par Maxime THOMAS)
yeah 2 (par Maxime THOMAS)
Il manque un package (par Maxime THOMAS)
Packages manquants (par Jérôme Vieilledent)
Et le module apache ? (par Maxime THOMAS)
yeah 3 (par Maxime THOMAS)
yeah 4 (par Maxime THOMAS)
Mici :) (par Yannick K.)
en effet (par lam@accessoire informatique)
Re..... (par Vidéosurveillance alarme)
Oui ça marche très bien sur Apache (par Jérôme Vieilledent)
Socket inet vs socket unix (par geotrouvetou)
Très bon!! (par polux)
Mim (par tutaj)
sudo su - (par Yo)