Après l'installation de ce premier site WordPress, j'ai fait face à quelques soucis lors de l'installation automatique des thèmes et extensions : WP exige en effet que les fichiers appartiennent à l'utilisateur exécutant les scripts PHP. Ce qui n'était pas mon cas…
J'aurais simplement pu faire un "chown -R apache" , mais j'ai préféré en profiter pour me lancer dans la mise en place de suEXEC.
Installation
J'utilisais à l'origine l'association Apache + mod_php disponible par défaut sur ma Gentoo. Cependant, celle-ci ne permet pas d'exécuter les scripts PHP avec un utilisateur différent de celui du serveur web (ou alors peut-être avec le MPM "peruser", à vérifier).
Pour compiler Apache avec le support suEXEC, il suffit d'ajouter le use-flag suivant suexec. La configuration par défaut de suEXEC exige que tous les documents soientt dans /var/www. Dans mon cas, je préfère /home. Il est donc nécessaire d'ajouter la variable SUEXEC_DOCROOT dans /etc/make.conf :
echo 'www-servers/apache suexec' >> /etc/portage/package.use
echo 'SUEXEC_DOCROOT="/home"' >> /etc/make.conf
emerge apache
Afin de pouvoir utiliser les modes CGI ou FastCGI, il faut aussi désactiver le mod_php. Cela peut se faire en supprimant la directive "-D PHP5" du fichier /etc/conf.d/apache2. Mais pour faire des tests sans impacter tout le serveur (sur un VirtualHost ou un répertoire par exemple), il est aussi possible de le désactivé depuis les fichiers de configuration apache :
<VirtualHost *:80>
...
php_admin_flag engine off
...
</VirtualHost>
Mode CGI
Première étape : le mode CGI basique.
<VirtualHost *:80>
ServerName tech.poirsouille.org
DocumentRoot "/home/poirsouille/public_html/tech"
CustomLog /var/log/apache2/tech_access_log combined
ErrorLog /var/log/apache2/tech_error_log
SuexecUserGroup poirsouille poirsouille
ScriptAlias /cgi-bin/ "/home/poirsouille/public_html/cgi-bin/"
php_admin_flag engine off
AddHandler php-script .php
Action php-script /cgi-bin/php-cgi
<Directory "/home/poirsouille/public_html/cgi-bin">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>
<Directory "/home/poirsouille/public_html/tech">
Options Indexes FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
La commande SuexecUserGroup active suEXEC pour les appels CGI. Le ScriptAlias définit un répertoire contenant des cgi exécutables. Celui-ci doit impérativement être contenu dans la DOC_ROOT de suEXEC. AddHandler et AddScript permettent d'associer les fichiers .php au binaire php-cgi.
suEXEC exige que le programme appelé appartienne à l'utilisateur, et qu'il ne soit modifiable que par lui. Les liens symboliques hors de DOC_ROOT ne sont pas supportés. Afin d'éviter de copier le binaire pour chaque utilisateur, le plus simple est d'utiliser un simple wrapper, en veillant à ce qu'il ait des droits corrects :
/home/poirsouille/public_html/cgi-bin/php-cgi
#!/bin/sh
/usr/bin/php-cgi $@
Après un redémarrage d'Apache, les scripts PHP devraient fonctionner correctement.
Cependant, côté serveur, un nouveau processus /usr/bin/php-cgi est créé pour chaque requête, ce qui cause une importante dégradation des performances face à mod_php.
Mode FastCGI
C'est alors qu'intervient FastCGI. Au lieu de créer un processus par requête, il ré-utilise toujours le(s) même(s). La maintenance de ces processus est assurée par Apache (un peu comme pour Apache lui-même avec le MPM prefork).
Voici donc comment utiliser PHP en mode FastCGI. Il existe deux modules Apache pour cela : mod_fstcgi et mod_fcgid. J'ai choisi le second :
emerge mod_fcgid
Voici ma configuration FastCGI :
/etc/apache2/modules.d/20_mod_fcgid.conf
<IfDefine FCGID>
LoadModule fcgid_module modules/mod_fcgid.so
SocketPath /var/run/fcgidsock
SharememPath /var/run/fcgid_shm
FcgidIdleTimeout 120
FcgidProcessLifeTime 240
FcgidMaxProcessesPerClass 100
FcgidConnectTimeout 60
FcgidIOTimeout 120
# Ces deux lignes ne sont nécessaire que si mod_php est désactivé totalement
AddHandler fcgid-script .php
FCGIWrapper /usr/bin/php-cgi .php
</IfDefine>
Et on modifie le VirtualHost précédent :
<VirtualHost *:80>
ServerName tech.poirsouille.org
DocumentRoot "/home/poirsouille/public_html/tech"
CustomLog /var/log/apache2/tech_access_log combined
ErrorLog /var/log/apache2/tech_error_log
SuexecUserGroup poirsouille poirsouille
php_admin_flag engine off
FCGIWrapper /home/poirsouille/public_html/cgi-bin/php-fcgi .php
<Directory "/home/poirsouille/public_html/tech">
Options Indexes FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
Options +ExecCGI
</Directory>
</VirtualHost>
Les directives FCGIWrapper et +ExecCGI font leur apparition. Voici le simple wrapper PHP :
/home/poirsouille/public_html/cgi-bin/php-fcgi
#!/bin/sh
#export PHPRC="/etc/php.ini"
#export PHP_FCGI_CHILDREN=4
exec /usr/bin/php-cgi
On noter l'utilisation de la fonction exec de bash, qui permet de remplacer le processus de bash par celui appelé. Cela évite la création d'un processus fils, qui ne serait pas correctement tué par Apache. Une nouvelle fois, on s'assure que ce script a les droits corrects pour suEXEC :
web ~ # chown poirsouille:poirsouille /home/poirsouille/public_html/cgi-bin/php-fcgi
web ~ # chmod 755 /home/poirsouille/public_html/cgi-bin/php-fcgi
web ~ # ll /home/poirsouille/public_html/cgi-bin/php-fcgi
-rwxr-xr-x+ 1 poirsouille poirsouille 91 Oct 10 15:31 /home/poirsouille/public_html/cgi-bin/php-fcgi
Après redémarrage d'Apache on constate que les processus php-cgi survivent aux requêtes. Cela apporte de bien meilleures performances que le mode CGI, au détriment de l'utilisation mémoire (puisque l'interpréteur PHP reste en mémoire en attendant la prochaine requête).
Afin de minimiser la mémoire utilisée, j'ai désactivé mod_php globalement une fois mes tests terminés. Ainsi les processus Apache n'incluent plus mod_php et consomment moins de mémoire.
Performances
Les résultats suivants ont été obtenus en utilisant Apache bench avec les paramètres suivants :
ab -n100 -c10 http://tech.poirsouille.org/
La page d'accueil de ma nouvelle installation de WP m'a servi de référence.
mod_php
Concurrency Level: 10
Time taken for tests: 25.674 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 589000 bytes
HTML transferred: 565100 bytes
Requests per second: 3.89 [#/sec] (mean)
Time per request: 2567.396 [ms] (mean)
Time per request: 256.740 [ms] (mean, across all concurrent requests)
Transfer rate: 22.40 [Kbytes/sec] received
Ma configuration initiale comme référence.
CGI
Concurrency Level: 10
Time taken for tests: 72.616 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 586800 bytes
HTML transferred: 565100 bytes
Requests per second: 1.38 [#/sec] (mean)
Time per request: 7261.626 [ms] (mean)
Time per request: 726.163 [ms] (mean, across all concurrent requests)
Transfer rate: 7.89 [Kbytes/sec] received
En passant PHP en CGI on note une nette dégradation des performances, comme prévu.
CGI + suEXEC
Concurrency Level: 10
Time taken for tests: 72.299 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 586800 bytes
HTML transferred: 565100 bytes
Requests per second: 1.38 [#/sec] (mean)
Time per request: 7229.882 [ms] (mean)
Time per request: 722.988 [ms] (mean, across all concurrent requests)
Transfer rate: 7.93 [Kbytes/sec] received
L'activation de suEXEC en plus de CGI n'influe pratiquement pas sur les performances.
mod_fcgid + suEXEC
Concurrency Level: 10
Time taken for tests: 27.193 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 948188 bytes
HTML transferred: 926054 bytes
Requests per second: 3.68 [#/sec] (mean)
Time per request: 2719.334 [ms] (mean)
Time per request: 271.933 [ms] (mean, across all concurrent requests)
Transfer rate: 34.05 [Kbytes/sec] received
Le passage en mode FastCGI permet de retrouver des performances presque équivalentes à mod_php.
Remarques
L'authentification HTTP de PHP ne fonctionne qu'avec mod_php. Il existe toutefois des workarounds en utilisant mod_rewrite.
Il faudra que je jette un œil a PHP-FPM, qui devrait permettre de se passer de suEXEC.
Références