Une autre façon d’utiliser PHP-FPM

Salut à tous, le blog de Guillaume n’est pas (encore) complètement mort. Mon nouveau job me prend énormément de temps, plus pleins d’autres chamboulements dans ma vie personnelle font que je n’ai plus une minute à consacrer ni à Sonerezh, ni à mon blog. Deux projets qui me sont pourtant très chers…

J’ai quand même réussi à bloquer quelques heures pour vous écrire cet article un peu plus technique que d’habitude. Et nous allons nous intéresser aux différentes façons qu’il existe d’utiliser PHP-FPM, notamment avec Nginx.

En effet, si vous cherchez sur le web, vous trouverez un grand nombre de sites / blogs qui vous proposeront les mêmes modèles de configuration ou presque, or il faut savoir qu’on peut faire autrement, en fonction de ses besoins ou de l’infrastructure que nous avons à gérer.

Nous allons voir notamment quelles sont les alternatives possibles à pm = dynamic et comment faire pour avoir plusieurs processus PHP-FPM maîtres (ou pères).

Avant d’aller plus loin, sachez que je travaille comme d’habitude sur une Debian 8.1 Jessie Stable tout juste installée, et avec les paquets par défaut proposés par les dépôts Debian.

Gestion des processus ‘ondemand’, ou ‘dynamic’ ?

Comme je vous le disais, la plupart des configurations proposées sur Internet à propos de PHP-FPM ressemblent à cela :

pm = dynamic
pm.max_children = 35
pm.start_servers = 10
pm.min_spare_servers = 10
pm.max_spare_servers = 15
pm.max_requests = 500

Que l’on retrouve notamment sous Debian 8 dans /etc/php5/fpm/pool.d/www.conf. Avec des nombres plus ou moins variants, j’admets que cet extrait provient du serveur qui héberge le blog. Cette configuration vous permet de laisser à PHP-FPM la gestion de ses processus, sachant qu’il doit en avoir au moins 10 (min_spare_servers), pas plus de 15 (max_spare_servers) et démarrer avec 10 processus. Au niveau de ps cela donne quelque chose comme :

root     19216  0.0  0.3 268940 15404 ?        Ss   juil.07   0:58 php-fpm: master process (/etc/php5/fpm/php-fpm.conf)                    
www-data 15598  0.2  2.3 359956 93104 ?        S    juil.22   3:59  \_ php-fpm: pool www                                                       
www-data 15599  0.2  2.3 360484 94824 ?        S    juil.22   4:18  \_ php-fpm: pool www                                                       
www-data 15600  0.2  2.3 361192 95660 ?        S    juil.22   4:39  \_ php-fpm: pool www 
[...]

Dans mon cas j’ai 10 processus qui tournent en permanence, même si aucun de mes sites n’est visité. Or il faut savoir que PHP-FPM est parfaitement capable de gérer ses processus à la demande, via la directive pm = ondemand. Il suffit ensuite d’indiquer à PHP-FPM jusqu’à combien d’enfants il peut créer. La configuration ci-dessus deviendrait :

pm = ondemand
pm.max_children = 35
pm.process_idle_timeout = 10s
pm.max_requests = 500

Les valeurs process_idle_timeout et max_request étant respectivement le temps d’inactivité d’un enfant et le nombre maximum de requête qu’il peut traiter avant d’être réinstancié. Si je refais un ps faux | grep php :

root     19394  0.0  0.3 268940 15404 ?        Ss   juil.07   0:58 php-fpm: master process (/etc/php5/fpm/php-fpm.conf)

Je n’ai plus qu’un processus maître, en attente de créer des enfants au fur et à mesure qu’il reçoit des requêtes. Par exemple deux requêtes en parallèle = deux processus enfants, tués au bout de 10 secondes d’inactivité.

root     19216  0.0  0.6 268940 25768 ?        Ss   juil.07   0:58 php-fpm: master process (/etc/php5/fpm/php-fpm.conf)                    
www-data 19742 17.1  1.0 273468 42012 ?        S    22:04   0:02  \_ php-fpm: pool www                                                       
www-data 19743 11.3  0.9 273416 37588 ?        D    22:04   0:01  \_ php-fpm: pool www
Cette configuration est intéressante dans le cas d’un site à faible ou moyen trafic, comme le blog de Guillaume par exemple, où l’on n’a pas un grand nombre de visites en continu. Cela permet d’économiser de la mémoire vive lorsque le site n’est pas fréquenté 🙂

Des processus parents séparés, pour des caches opcode isolés

Nous allons maintenant pousser le vice un peu plus loin. Souvenez-vous je vous avais déjà parlé des différents niveaux de cache entre PHP-FPM et Nginx, et j’avais notamment évoqué le cache opcode.

Vous ne serez pas surpris d’apprendre que ce cache est géré par le processus père dans le cas de PHP-FPM et que par conséquent, les processus enfants n’ont aucun moyen d’en modifier les paramètres, ni via ini_set(), ni via php_admin_value. Tout est issu dans les fichiers .ini. Cela signifie aussi que ce cache opcode est donc partagé en mémoire entre tous les enfants, et peut-être à l’origine de potentielles failles de sécurité. Ou encore, si vous avez à gérer un grand nombre de site, faire exploser la taille du cache du processus père.

Une solution à ce problème est donc de créer un processus père par site ou par application, afin de les isoler un peu plus les uns des autres. Cette technique à un autre avantage, c’est qu’elle permet aussi de gérer la maintenance de ces applications de façon indépendante. J’ai besoin de redémarrer le pool PHP de mon site A, cela n’affecte pas mon site B sur son propre pool. Notez que cela permet aussi d’avoir des configurations adaptées à chacunes de nos applications. Moi qui partage mon serveur avec deux autres amis, c’est très intéressant si l’un d’entre nous souhaite bricoler sans gêner les autres. Le seul inconvénient (si s’en est un), c’est qu’il va falloir écrire un script de démarrage pour chacun de nos processus, qui auront aussi chacun leur propre pid. Mais heureusement pour nous, nous sommes entrés dans l’air Systemd.

Il est clair que ce type d’environnement est beaucoup plus facile à maintenir avec un gestionnaire de configuration comme Puppet, Ansible ou encore Chef. Cela peut devenir très vite laborieux à la main.

Préparer la configuration du processus père

Là c’est très simple, nous allons honteusement copier la configuration par défaut, en adaptant quelques noms bien évidemment. Libre à vous de perfectionner ce qui suit. Je propose de placer cette configuration dans /etc/php5/fpm/php-fpm.guillaume.conf.

; FPM Configuration for Guillaume's websites
[global]
pid = /run/php5-fpm.guillaume.pid
error_log = /var/log/php5-fpm.guillaume.log
 
[www-guillaume]
listen = /var/run/php5-fpm.guillaume.sock
listen.owner = guillaume
listen.group = guillaume
listen.mode = 0666
 
user = guillaume
group = guillaume
 
pm = ondemand
pm.max_children = 35
pm.process_idle_timeout = 10s
pm.max_requests = 500

Je n’ai vraiment mis que l’essentiel : quelques droits, les chemins du pid et du socket. N’oubliez pas de créer le fichier de log 😉

Ajouter le nouveau pool dans Systemd

Là aussi nous allons honteusement copier la configuration par défaut, qui se trouve dans /etc/systemd/system/multi-user.target.wants. On copie / colle les fichiers et on crée les liens symboliques qui vont bien.

sudo cp /lib/systemd/system/php5-fpm.service /lib/systemd/system/php5-fpm.guillaume.service
sudo ln -vs /lib/systemd/system/php5-fpm.guillaume.service /etc/systemd/system/multi-user.target.wants/php5-fpm.guillaume.service
« /etc/systemd/system/multi-user.target.wants/php5-fpm.guillaume.service » -> « /lib/systemd/system/php5-fpm.guillaume.service »

Puis on adapte la configuration. Je propose :

# /etc/systemd/system/multi-user.target.wants/php5-fpm.guillaume.service
[Unit]
Description=The PHP FastCGI Process Manager
After=network.target
 
[Service]
Type=notify
PIDFile=/var/run/php5-fpm.guillaume.pid
ExecStartPre=/usr/lib/php5/php5-fpm-checkconf
ExecStart=/usr/sbin/php5-fpm --nodaemonize --fpm-config /etc/php5/fpm/php-fpm.guillaume.conf
ExecReload=/bin/kill -USR2 $MAINPID
 
[Install]
WantedBy=multi-user.target

J’aimerais vous parler un peu plus de cette partie mais Systemd ne m’est pas assez familier pour le moment… En tout cas il faut recharger sa configuration :

sudo systemctl daemon-reload

Et on peut démarrer tout ce petit monde !

sudo systemctl start php5-fpm.guillaume.service
sudo systemctl status php5-fpm.guillaume.service
● php5-fpm.guillaume.service - The PHP FastCGI Process Manager
   Loaded: loaded (/lib/systemd/system/php5-fpm.guillaume.service; enabled)
   Active: active (running) since jeu. 2015-07-23 22:35:25 CEST; 1s ago
  Process: 23276 ExecStartPre=/usr/lib/php5/php5-fpm-checkconf (code=exited, status=0/SUCCESS)
 Main PID: 23281 (php5-fpm)
   Status: "Ready to handle connections"
   CGroup: /system.slice/php5-fpm.guillaume.service
           └─23281 php-fpm: master process (/etc/php5/fpm/php-fpm.guillaume.conf)

Me voilà donc avec un pool, à mon nom, qui a sa propre configuration et son propre cache. Je n’ai plus qu’à indiquer à Nginx qu’il doit transmettre les requêtes au bon pool (par exemple celles de mon blog) et tout sera bon.


Ces sources m’ont aidé à écrire cet article :

Cet article vous a plu ? Partagez-le sur les réseaux sociaux !

Twitter Facebook Google Plus email