Apache, mode Event et PHP-FPM

14 mars 2016

Suite Ă  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 :

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

Références