traduit depuis : http://blog.foaa.de/2010/11/php-apache-and-fastcgi-a-comprehensive-overview
Je me suis récemment retrouvé dans des problemes avec notre configuration mod_fastcgi + PHP et j'ai dû faire beaucoup de recherches pour decortiquer ce qu'il se passe et comment le réparer. Ce post résume les résultats de cette recherche. Il couvre tout ce que vous devriez vouloir savoir sur PHP + Apache et FastCGI, quels modules sont ils, comment vous les utilisez, ce qui est planifié et les problèmes réels auxquels vous devez vous attendre. Aussi ce qui est nouveau avec PHP 5.3 sur FPM et comment vous pouvez en tirer bénéfice.
En court : le serveur apache délègue les requètes entrantes vers des processus PHP (externes) persistants et récupère/renvoie la sortie au client. Les processus externes exécutant PHP sont contrôlés par un gestionnaire de processus (php-pm ou php-fpm) qui peuvent forker les processus enfants. Tous les enfants descendants du même processus maître sont capables de partager des ressources comme un cache opcode comme APC. C'est différent de la façon dont mod_php (le runtime PHP est démarré avec le processus apache) et les fonctionnements normaux de CGI (un nouveau process est démarré pour chaque requête). En contraste avec mod_php, vos processus peuvent être exécutés sous différents (Unix/OS) utilisateurs par suexec - tout en restant persistant - ce qui réduit le danger d'être compromis par une magnitude.
La plupart des implémentations FastCGI fournissent aussi (un autre) gestionnaire de processus, qui tente lui même de supporter les processus enfants et peut interférer sur eux par PHP.
Tout d'abord, il y a mod_fastcgi qui est une sorte de mère de toutes les implémentations FastCGI dans apache. Dans des circonstances normales, cela fonctionne comme un charme. Cependant, il y a de sérieux problèmes si vous voulez exécuter certains scénarios comme avec des processus php qui s'exécutent plus longtemps que le timeout s'y attend. Alors il y a le tout neuf mod_fcgid, qui est une implémentation plus récente de FastCGI et fournit de meilleures méthodes pour obtenir de la stabilité et du contrôle. Le mauvais côté est qu'il ne peut pas utiliser un serveur FastCGI démarré en externe. A côté de ces "majors", vous pouvez utiliser mod_fastcgi_handler, une version allégée (pas de réel gestionnaire de processus car PHP possède le sien) et ré-implémentation amincie du problème. Malheureusement c'est un peu expérimental/beta. Depuis Apache 2.3+ vous pouvez aussi essayer mod_proxy_fcgi, basé sur mod_proxy, aussi sans aucun process gérant les capabilities.
Le but de chaque implémentation sera toujours d'avoir un support PHP par un attachement au suffixe de fichier, ce qui veut dire que tous les fichiers avec .php sont exécutés avec le processus FastCGI. Je ne tiens pas compte des approches basées sur les dossiers (ex: /fcgi-bin). Tout exemple suivant devra fonctionner avec php-cgi de PHP 5.2′s et PHP 5.3 – mais pas avec PHP FPM, dont je parlerai plus tard.
Le bon côté est que c'est tres stable et assez capable de suivre (et tuer) des processus morts ou en panne. Le mauvais côté est que vous ne voulez pas l'utiliser si vous avez besoin d'un cache opcode (comme APC ou XCache). Il délègue chaque nouvelle requête à un autre gestionnaire de processus PHP - pas à ses enfants. Donc vous voulez servir plus qu'une requête à la fois, vous devez redémarrer de multiples processus maîtres qui pourraient avoir des enfants mais qui cacherait le cache entre eux. Si vous n'avez pas besoin d'un cache opcode, tout va bien (donc de nouveau : si c'est le cas, vous n'avez pas besoin de d'un PHP persistant et performant dû à un manque de requêtes). Aussi, il NE fonctionnera PAS de toute façon avec PHP 5.3 sur FPM! Ne vous embêtez pas à essayer, ça ne peut pas car il n'implémente pas de méthode pour communiquer par socket ou par ip/port avec un processus externe démarré.
Vous avez besoin d'un script de démarrage pour votre application car nous utilisons suexec (qui lance le processus php sous un autre utilisateur que www-data) et ses paramètres de sécurité nécessitent d'avoir les scripts exécutables sous /var/www (tant que vous ne re-compilez pas vous même).
Wrapper /var/www/wrapper/fcgid
#!/bin/bash exec /usr/bin/php-cgi
S'assure que le script est exécutable et que lui même ainsi que le dossier supérieur appartiennent à l'utilisateur et groupe que vous spécifiez avec SuexecUserGroup dans le VirtualHost. Ci dessous, un VirtualHost minimal :
FCGIWrapper /var/www/wrapper/fcgid .php AddHandler fcgid-script .php SuexecUserGroup uk uk ServerName some-host DocumentRoot /var/www/sites/some-site
C'est tout. Vous pouvez maintenant configurer mod_fcgid pour ne pas démarrer X processus et/ou toujours garder Y processus disponible etc... Cependant, ce n'est pas amusant tant que ces processus ne forkent pas et donc ne partagent pas de ressources. C'est pourquoi je ne vais pas aller plus loin avec. Utilisez mod_fcgid pour Perl, Ruby, Python ou tout ce que vous préférez... mais pas PHP.
En utilisant ce module, vous voulez probablement désactiver la plupart des fonctionnalités de contrôle du gestionnaire de processus et laisser PHP supporter le reste. Selon le nombre de processus sur tout votre serveur, vous devez ajuster globalement la quantité maximum de processus exécutables. ex : /etc/apache2/http.conf:
FastCgiConfig -killInterval 60 -maxClassProcesses 1 -maxProcesses 50 -minProcesses 0 -startDelay 5
Les parts importantes ici sont maxClassProcesses (d'un point de vue de mod_fastcgi, chaque VirtualHost avec FastCGI utilisera seulement un processus (maître)) et maxProcesses, qui détermine le montant maximum de processus à travers tout votre serveur. (penser: 50 VHosts chacun en maître).
Maintenant dites au module the mod_fastcgi (debian: /etc/apache2/mods-enabled/fastcgi.conf) d'utiliser suexec wrapper:
AddHandler fastcgi-script .fcgi FastCgiWrapper /usr/lib/apache2/suexec FastCgiIpcDir /var/lib/apache2/fastcgi
Vous avez aussi besoin d'un script conteneur. Pour les mêmes raisons que ci dessu mais aussi pour déterminer la quantité d'enfants forkés. On tient compte qu'il est dans /var/www/wrapper/fastcgi:
#!/bin/bash # L'utilisateur uk possède son propre fichier ini dans /var/www/ini/uk/php.ini #export PHPRC="/var/www/ini/uk" # trop d'enfants forkés export PHP_FCGI_CHILDREN=3 exec /usr/bin/php5-cgi
Ensuite vous pouvez configurer le VirtualHost:
FastCgiServer /var/www/wrapper/fastcgi -processes 1 -user uk -group uk -idle-timeout 310 -flushScriptAlias /php-fastcgi-uk /var/www/wrapper/fastcgi Action php-fastcgi-uk /php-fastcgi-uk AddHandler php-fastcgi-uk .php SuexecUserGroup uk uk ServerName some-host DocumentRoot /var/www/sites/some-site
Ce que nous faisons ici est démarrer le FastCgiServer (depuis apache) via la commande wrapper command en tant qu'utilisateur uk et groupe uk en disant à FastCGI PM qu'il doit y avoir un seul process. Le -flush est optionnel, il s'assures que la requête sera passée non bufferisée aux serveurs FastCGI – j'ai vécu des processus se figeant sans cela. La partie la plus importante est le -idle-timeout dans lequel je rentrerai dedans plus tard.
Ici vous n'avez pas besoin d'un script conteneur mais un script de démarrage car mod_fastcgi_handler peut seulement négocier avec des processus externes démarrés. Allez y :
#!/bin/bash # User uk has it's ini file in /var/www/ini/uk/php.ini #export PHPRC="/var/www/ini/uk" # trop d'enfants forkés export PHP_FCGI_CHILDREN=3 exec /usr/bin/php5-cgi -b 127.0.0.1:9000
Ensuite dans votre VirtualHost:
SetHandler fcgi:127.0.0.1:9000 SuexecUserGroup uk uk ServerName some-host DocumentRoot /var/www/sites/some-site
Apres le (re) démarrage d'apache, vous devez démarrer le script externe manuellement. ex : via start-stop-daemon.
Je ne l'ai pas essayé n'ayant pas d'apache 2.3 fonctionnant. Dans tous les cas, je pense qu'il serait assez simple - si vous pouvez construire votre apache vous même, vous n'aurez pas de problèmes à le configurer. Pour le moment ce n'est pas un choix valide car 2.3 est loin d'être stable donc pas déployé dans la plupart des distros.
Depuis PHP 5.3.3 FPM est inclus dans PHP et, bien c'est une grande avançée, car c'est version de PHP PM la plus aboutie et (autant que j'aie pu experimenter) stable. Il doit être démarré externalement ce qui exlut mod_fcgid des le début et nous laisse avec mod_fastcgi* et – tant que 2.3 est stable – mod_proxy_fcgi. Mais il ramène aussi de nouvelles fonctionnalités sur la table : rlimit pour max open files, forçant max_execution_time des processus PHP et la gestion de processus dynamiques avec qui vous pouvez réduire le montant des processus en attente et par cela le gaspillage de mémoire.
Avant que j'aille dans l'implémentation actuelle, je veux partager un problème significatif avec PHP et FastCGI dans lequel vous allez foncer et qui m'a coûté des heures et des heures de recherches jusqu'à ce que je m'aperçoive que le problème etait à sa place de départ.
Avec php-cgi et mod_fastcgi (ou mod_fcgid) vous avez deux timeouts séparés pour vos processus : un de FastCGI PM (idle-timeout dans mod_fastcgi) et un du processus PHP (max_execution_time). Tant que cette dernière est inférieure à la première – tout fonctionne bien. Mais vous pouvez (vos utilisateurs) modifier max_execution_time pendant l'exécution (ok, ini_set pourrait être désactivé, mais ceci briserait la plupart des applications installables) ce qui ensuite laisserait le idle-timeout de FastCGI PM faire son terrible travail. Pas seulement maintenant je je suis pratiquement sûr de ce qu'il se passe exactement, mais laissez moi vous montrer à quoi ça ressemble :
clock_gettime(CLOCK_MONOTONIC, {267231, 352083330}) = 0
clock_gettime(CLOCK_MONOTONIC, {267231, 352083330}) = 0
clock_gettime(CLOCK_MONOTONIC, {267231, 352083330}) = 0
poll([{fd=5, events=POLLIN|POLLPRI}], 1, 1000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}], 1, 0) = 0 (Timeout)
La pire chose est qu'il n'affecte pas seulement un enfant forké, mais tous. C'est comme si FastCGI PM coupait seulement le flux IO vers les processus PHP et le laisse comme ça, se demandant où est ce que tout le monde est allé.
Vous pourriez ajuster idle-timeout de mod_fastcgi à une valeur trés forte et ce problème ne viendra pas – mais si il est vraiment requis de redémarrer les processus depuis le PM, vous aurez un problème. C'est là où intervient FPM. Il fournit une option de configuration appellée request_terminate_timeout, qui force la terminaison des processus PHP quoi qu'il en soit. Ce que vous pouvez faire maintenant, c'est d'ajuster max_execution_time à une valeur raisonnable, ensuite ajuster request_terminate_timeout à quelque chose de plus long, qui ne peut pas être écrasé par l'utilisateur puis ajuster le FastCGI idle-timeout juste au dessus.
Je vais vous montrer comment l'utiliser avec mod_fastcgi, car mod_fastcgi_handler se plaignait d'erreurs dans la réponse FastCGI de FPM, mod_proxy_fcgi est toujours une partie de Apache 2.3 et mod_fcgid ne fonctionnera pas. C'est alors un peu succint à l' implémenter avec PHP 5.2.x et php-cgi mais une fois que vous avez compris l'idée générale, ce n'est pas si difficile.
Commençons avec FPM lui même. FPM a besoin d'un fichier de configuration (one per user) et optionnellement un chemin vers un dossier contenant un php.ini customisé. Voici le fpm.conf:
[global] pid = /tmp/fpm-uk.pid error_log = /var/log/apache2/fpm-uk.log log_level = error [www] listen = /tmp/fpm-uk.sock listen.owner = uk listen.group = www-data listen.mode = 0660 user = uk group = uk pm = dynamic pm.max_children = 10 pm.start_servers = 2 pm.min_spare_servers = 3 pm.max_spare_servers = 10 pm.max_requests = 0 request_terminate_timeout = 305 slowlog = /var/log/apache2/fpm-slow-uk.log
Les parties les plus importantes sont les réglages de listen et user :
Lisez le documentation file configuration guide pour plus d'informations détaillées.
Maintenant ça devient étroit car nous avons besoin d'une configuration quelque peu étrange pour apache. Laissez moi d'abord vous montrer à quoi cela ressemblera puis vous expliquer comment ça marche.
FastCgiExternalServer /var/www/sites/virtual-some-site -socket /tmp/fpm-uk.sock -user uk -group uk -idle-timeout 310 -flushOptions +ExecCGI AddHandler php5-fpm-uk .php Action php5-fpm-uk /virtual-some-site Alias /virtual-some-site /var/www/sites/virtual-some-site/www SuexecUserGroup uk uk ServerName some-host DocumentRoot /var/www/sites/some-site
La bidouille est de configurer un second dossier dans lequel existe un lien vers l'actuel DocumentRoot. Le premier argument de la directive FastCgiExternalServer dit : exécute tout sous ce dossier comme du PHP. Nous ne pouvons pas utiliser le document root (/var/www/sites/some-site) actuel directement car ceci en viendrait à tout tunnelliser (.jpg, .html, ..) par le handler. Avec la directive Alias nous connections le dossier lié virtual-some-site vers le dossier actuel. Action déclare alors une nouvelle action avec le nom php5-fpm-uk avec le dossier lié et lie AddHandler tous les fichiers php avec l'action. Le bloc Files s'assure à ce que tous les fichiers .php soient exécutables pour CGI. Il y a d'autres configurations fonctionnelles mais celle ci fait le travail spécialement avec une large utilisation de directives RewriteRule, ce qui va briser la plupart des autres configurations FPM.
Voici comment vous configurez le dossier virtuel :
mkdir -p /var/www/sites/virtual-some-site cd /var/www/sites/virtual-some-site ln -s ../some-site www chown -h uk:uk www
Si vous rencontrez des problèmes, assurez vous que vous avez activé globalement SymLinksIfOwnerMatch ou sur le dossier :
Options +SymLinksIfOwnerMatch
Ayant fait cela, vous pouvez redémarrer apache et démarrer le serveur FPM:
php5-fpm -c /var/www/config/uk/ini -y /var/www/config/uk/fpm.conf
Vous n'avez pas encore terminé car il pourrait casser votre application car les variables d'environnement ($_SERVER) SCRIPT_NAME, SCRIPT_FILENAME et PHP_SELF contiennent le chemin virtuel. Donc vous devez les retirer lors de l'exécution. Ceci peut être automatisé par un fichier prepend via le php.ini:
# ajouter à votre php.ini auto_prepend_file = /var/www/config/uk/prepend.php
Puis dans le fichier prepend (/var/www/config/uk/prepend.php):
Ceci n'es pas aussi propre que je l'aurais voulu mais cela fonctionne. J'ai joué avec beaucoup de configurations et ce fut la seule fonctionnant en toutes circonstances (comme mentionné au dessus avec beaucoup de RewriteRules vous pouvez finir avec des boucles infinies). Une chose à mentionner : vérifiez que le nom de votre dossier virtuel est unique (pas seulement “virtual” ou “php”) comme cela, vous pouvez facilement le remplacer sans casser votre application...
PHP sur FastCGI possède des atouts et inconvénients mais aussi longtemps que vous les connaissez, vous pouvez travailler autour. Dans tous les cas, c'est plus sécurisé que mod_php dans un environnement partagé. Cependant, si vous êtes encore un fan de mod_php ou si vous pensez que l'effort ne vaille pas le résultat vous devriez jeter un oeil sur Apache MPM ITK. Ce MPM vous autorise à assigner un user/group pour mod_php. Le mauvais côté est que vous ne pouvez toujours pas contrôler la quantité de ressources utilisées (comme dans: 5 PHP processes max) et c'est un fork de MPM prefork (deux fois moins rapide que MPM worker pour les fichiers statiques).
Comme toujours : aucune garantie n'est donnée. Assurez vous de tester tout cela dans un environnement de développement sécurisé