Apache, mode Event et PHP-FPM
14 mars 2016Suite Ă mon article sur les raisons de mon retour sous Apache, voici comme promis un tuto sur l’installation de Apache en mode Event avec PHP-FPM. Mais avant la pratique, un peu de thĂ©orie.
Explications
Qu’est-ce que le mode « event » de Apache ? Pourquoi l’utiliser ? Et pourquoi PHP-FPM ? Pour rĂ©pondre Ă ces questions, il faut comprendre comment Apache fonctionne.
TL;DR : Le mode event est plus performant que le mode prefork utilisĂ© par dĂ©faut, ceci grĂące Ă une meilleure gestion de la mĂ©moire. PHP-FPM permet de faire tourner PHP dans un processus sĂ©parĂ©, sous des utilisateurs diffĂ©rents, et puis toute façon on a pas le choix de l’utiliser ou non Ă cause du mode event qui nous l’impose.
Historiquement, et dans la plupart des cas aujourd’hui encore, Apache est utilisĂ© par dĂ©faut en mode prefork. Dans ce mode, un processus parent Apache est lancĂ©, et il lance Ă son tour un nombre limitĂ© de processus enfants. Chaque processus enfant est plus ou moins une instance Ă part entiĂšre de Apache, qui chacun charge toute la configuration, les modules, etc. Ainsi, lorsqu’un client fait une requĂȘte HTTP, celle-ci est prise en charge par un processus qui va lui ĂȘtre entiĂšrement dĂ©diĂ©. Si un autre client fait une requĂȘte, celle-ci va ĂȘtre assignĂ©e Ă un autre processus, ou alors attendre qu’il y en ai un qui se libĂšre s’ils sont tous occupĂ©s. Ce fonctionnement est trĂšs consommateur en mĂ©moire vive, et pas forcĂ©ment ce qui fait de plus rĂ©actif.
Une autre approche, plus intĂ©ressante, est d’utiliser le mode worker. Celui-ci, en plus de lancer plusieurs processus comme le mode prefork, va permettre l’utilisation de plusieurs threads (fils d’exĂ©cution, ou unitĂ©s de traitement) dans chaque processus. L’avantage des _threads_ sur les processus est que ceux-ci se lancent beaucoup plus rapidement, et du fait qu’ils partagent entre-eux le mĂȘme espace d’adressage (et partagent donc les variables, etc.) la communication entre les diffĂ©rents threads est plus rapide et consomme moins de mĂ©moire. Les processus quant Ă eux sont totalement isolĂ©s les uns des autres. Ainsi, chaque communication HTTP est attribuĂ©e Ă un thread diffĂ©rent, plutĂŽt qu’Ă un processus. (Cf. cet article Les processus sous Linux qui explique trĂšs bien le fonctionnement des processus et la diffĂ©rence avec les threads.)
Il y a mieux encore ! Depuis Apache 2.4, le mode event est disponible. Celui-ci fonctionne exactement de la mĂȘme maniĂšre que le mode worker, Ă la diffĂ©rence prĂšs qu’un thread ne va traiter qu’une requĂȘte HTTP au lieu de toute une session HTTP. Une fois la requĂȘte exĂ©cutĂ©e, le thread est immĂ©diatement libĂ©rĂ©. Moins de threads, moins de mĂ©moire.
Il nous reste à répondre à la question : pourquoi PHP-FPM plutÎt que mod_php ?
En mode prefork PHP est chargé via le module Apache mod_php (le fameux paquet libapache2-mod-php5), il fait ainsi partie intégrante du processus Apache, il est entiÚrement géré par lui et en est indissociable. Il hérite donc des problÚmes du mode prefork.
En mode worker ou event, on a pas le choix d’utiliser PHP-FPM, car mod_php (ou plutĂŽt ses diverses bibliothĂšques) n’est pas « thread-safe ». En gros ça signifie qu’il ne sait pas gĂ©rer le multi-threading. Avec PHP-FPM, le problĂšme ne se pose pas, vu que l’exĂ©cution des scripts PHP est sous-traitĂ©e Ă un processus extĂ©rieur Ă Apache via FastCGI.
Bien sĂ»r, il ne s’agit pas uniquement d’une contrainte, l’exĂ©cution des scripts PHP par PHP-FPM permet plus de flexibilitĂ© que via modphp. Elle permet, par exemple, de rĂ©partir la charge de l’exĂ©cution des scripts sur diffĂ©rents serveurs, ou encore d’attribuer des ressources limitĂ©es ou diffĂ©rents utilisateurs Ă diffĂ©rents _pools PHP.
Installation
Les instructions ci-dessous sont obsolĂštes, vous pouvez vous reporter Ă la version mise Ă jour de cet article pour continuer l’installation.
Afin de pouvoir installer le module FastCGI de Apache, qui nous sera nĂ©cessaire pour faire fonctionner PHP-FPM, il nous faut d’abord activer les dĂ©pĂŽts contrib et non-free dans le ficher /etc/apt/sources.list :
deb http://security.debian.org/ jessie/updates main contrib non-free
deb http://debian.mirrors.ovh.net/debian/ jessie-updates main contrib non-free
deb http://debian.mirrors.ovh.net/debian/ jessie main contrib non-free
Ensuite on met Ă jour la liste de paquets, et on installe Apache2 et PHP5-FPM :
apt update
apt install apache2-mpm-event libapache2-mod-fastcgi php5-fpm
On devrait dores-et-dĂ©jĂ pouvoir accĂ©der Ă la page par dĂ©faut de Apache2, qui nous donne notamment quelques informations intĂ©ressantes sur l’architecture de sa configuration.
Pour un Apache qui Ă©tait dĂ©jĂ installĂ©, le mode prefork doit ĂȘtre dĂ©sactivĂ© manuellement (et Ă©ventuellement modphp au passage), pour ensuite activer le mode _event :
a2dismod mpm_prefork php5
a2enmod mpm_event
systemctl restart apache2
Configuration Apache2
RĂ©soudre l’erreur de FQDN
Si on regarde les logs de Apache (journalctl -u apache2 -e), on a déjà une erreur :
apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for ServerName
Elle n’est pas problĂ©matique dans le sens oĂč elle n’empĂȘche le processus de dĂ©marrer correctement. Cependant on va tout de mĂȘme tĂącher de s’en dĂ©barrasser pour Ă©viter de polluer les logs.
On créer donc un fichier /etc/apache2/conf-available/fqdn.conf dans lequel on va mettre :
ServerName localhost
On peut éventuellement remplacer localhost par le nom du serveur, srv.example.net.
Ensuite on active notre fichier de configuration et on recharge le service :
a2enconf fqdn
systemctl reload apache2
Activer PHP5-FPM
Pour le moment PHP ne fonctionne pas sur notre serveur, et pour cause, il n’a pas Ă©tĂ© activĂ© et configurĂ©.
On peut le vérifier en créant un fichier /var/www/html/info.php avec pour contenu :
<?php phpinfo(); ?>
Si on essaie d’y accĂ©der (http://srv.example.net/info.php) on se retrouve avec une belle page blanche et des erreurs dans les logs (/var/log/apache2/error.log).
On va donc activer les modules suivants :
fastcgi: qui va nous permettre de sous-traiter l’exĂ©cution de scripts Ă des processus externes (PHP Ă PHP-FPM dans notre cas, mais ce pourrait tout aussi bien ĂȘtre du Python, Perl, C, etc.) ;actions: qui va nous permettre de rediriger les requĂȘtes PHP vers FastCGI ;alias: qui va nous permettre de mapper nos requĂȘtes pour FastCGI vers le processus correspondant.
a2enmod fastcgi actions alias
Ensuite on créer le fichier /etc/apache2/conf-available/php5-fpm.conf :
<IfModule mod_fastcgi.c>
AddHandler php5-fpm .php
Action php5-fpm /php5-fpm
Alias /php5-fpm /usr/lib/cgi-bin/php5-fpm
FastCgiExternalServer /usr/lib/cgi-bin/php5-fpm -socket /var/run/php5-fpm.sock -pass-header Authorization
<Directory /usr/lib/cgi-bin>
AllowOverride All
Require all granted
</Directory>
</IfModule>
Si on veut que PHP soit fonctionnel pour tous les sites de notre serveur, on active globalement cette configuration et on redémarre le service Apache2 :
a2enconf php5-fpm
systemctl restart apache2
Sinon, si on veut que PHP ne soit actif que pour certains sites, au lieu d’activer globalement la configuration, on peut simplement l’ajouter dans le VirtualHost qui le nĂ©cessite :
Include conf-available/php5-fpm.conf
De cette maniĂšre on peut crĂ©er diffĂ©rents pools de PHP-FPM qui tournent avec des utilisateurs diffĂ©rents, par exemple un pour chaque site. Ainsi, si il y a une faille dans le script PHP de l’un des sites, les risques seraient limitĂ©s aux fichiers sur lesquels l’utilisateur spĂ©cifiĂ© dans le pool a les droits.
En attendant d’explorer cette possibilitĂ©, si on rafraĂźchit notre page info.php dans le navigateur on devrait pouvoir voir le contenu de celle-ci ! :-)
Configurer des pools dans PHP-FPM
Pour terminer on va donc crĂ©er un pool qui tournera avec un utilisateur spĂ©cifique. Ceci, comme on le disait, dans le but d’amĂ©liorer la sĂ©curitĂ© de notre serveur.
Avant de crĂ©er notre utilisateur, on va modifier le fichier /etc/skel/.profile pour lui indiquer d’utiliser le umask 027 :
sed -i 's/#umask 022/umask 027/' /etc/skel/.profile
Ăa va permettre Ă ce que tous les fichiers et dossiers créés par nos nouveaux utilisateurs ne soient pas accessible depuis les autres utilisateurs.
On peut maintenant créer notre nouvel utilisateur :
adduser myuser1
On ajoute ensuite l’utilisateur www-data au groupe de notre nouvel utilisateur, sans quoi Apache ne pourra pas lire les fichiers statiques (html, images, css, js, etc.) du site de notre utilisateur :
adduser www-data myuser1
Maintenant on se connecte Ă la session de notre nouvel utilisateur :
su - myuser1
On crĂ©er un rĂ©pertoire www dans lequel on placera les fichiers de son site, on peut d’ailleurs y crĂ©er le mĂȘme fichier de test que prĂ©cĂ©demment :
mkdir www
echo '<?php phpinfo(); ?>' > www/info.php
On se déconnecte maintenant de notre utilisateur, en faisant Ctrl + D ou exit, pour revenir en root. Et on va maintenant créer notre pool dans le fichier /etc/php5/fpm/pool.d/myuser1.conf :
[myuser1]
user = myuser1
group = myuser1
listen = /var/run/php5-fpm_myuser1.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /
On va lui crĂ©er la configuration Apache associĂ©e, c’est Ă dire qui utilise son socket. Par exemple dans le fichier /etc/apache2/conf-available/php5-fpm_myuser1.conf :
<IfModule mod_fastcgi.c>
AddHandler php5-fpm .php
Action php5-fpm /php5-fpm
Alias /php5-fpm /usr/lib/cgi-bin/php5-fpm_myuser1
FastCgiExternalServer /usr/lib/cgi-bin/php5-fpm_myuser1 -socket /var/run/php5-fpm_myuser1.sock -pass-header Authorization
<Directory /usr/lib/cgi-bin>
AllowOverride All
Require all granted
</Directory>
</IfModule>
On l’ajoute dans notre VirtualHost via la directive Include, exemple :
<VirtualHost *:80>
ServerName myuser1.example.net
DocumentRoot /home/myuser1/www
<Directory /home/myuser1/www>
AllowOverride All
Require all granted
</Directory>
Include conf-available/php5-fpm_myuser1.conf
ErrorLog /home/myuser1/error.log
CustomLog /home/myuser1/access.log combined
</VirtualHost>
On recharge PHP-FPM et Apache, et c’est c’est terminĂ© ! :-)
systemctl reload php5-fpm
systemctl reload apache2