
🌐 Aussi en: English · Deutsch · Español
Deep Dive de la série autour de l’Ansible Tour. Niveau : administration Linux avancée. Le tour explique les rôles ; les deep dives expliquent la technique en dessous – comment elle fonctionne, pourquoi elle est là, ce que cette installation en fait exactement, comment l’exploiter au quotidien et ce qui aurait existé d’autre.
WordPress et Nextcloud sont des applications PHP, et dans les deux pods chaque requête dynamique aboutit dans un worker PHP-FPM (partie 3, partie 4). Le nombre de workers, la durée qu’un worker peut tourner et la quantité de code compilé qu’ils partagent décident de la vitesse des sites et de ce qui se passe sous charge. Cet article explique la mécanique, les réglages choisis ici, les mesures derrière – et deux valeurs par défaut qui bridaient Nextcloud sans que personne ne le remarque.
🧭 Comment fonctionnent PHP-FPM et OPcache
Maître, pool, workers
PHP-FPM (« FastCGI Process Manager ») est un processus maître qui démarre et surveille des processus workers. Chaque worker traite exactement une requête à la fois, du début à la fin. nginx, dans le même pod, sert lui-même les fichiers statiques et transmet chaque requête .php en FastCGI au port 9000. Un pool est un groupe de workers avec ses propres réglages ; les deux applications utilisent l’unique pool www des images officielles.
Combien de workers
pm = static: un nombre fixe de workers, toujours en marche.pm = dynamic: entremin_spare_serversetmax_spare_serversworkers inactifs sont tenus prêts ; de nouveaux démarrent jusqu’àmax_children.pm = ondemand: les workers ne démarrent qu’à l’arrivée d’une requête et s’arrêtent après un délai d’inactivité.
pm.max_children est la vraie limite de concurrence : la requête numéro max_children + 1 attend dans la listen queue qu’un worker se libère. pm.max_requests recycle un worker après autant de requêtes, ce qui borne la lente croissance mémoire due à des extensions qui fuient.
Deux sortes de limites de temps
max_execution_time est la limite propre à PHP et, sous Linux, ne compte que le temps CPU du script – une requête qui attend la base ou un verrou ne la consomme pas. request_terminate_timeout est la limite de FPM sur le temps réel écoulé : à son expiration, le maître tue le worker. Seule la seconde libère un worker bloqué.
OPcache : compiler une fois
PHP compile chaque script en bytecode avant de l’exécuter. OPcache garde ce bytecode en mémoire partagée, utilisée ensemble par tous les workers d’un pool ; un fichier est donc compilé une fois par pod au lieu d’une fois par requête. Trois réglages le dimensionnent : memory_consumption (le segment), interned_strings_buffer (les chaînes identiques stockées une seule fois) et max_accelerated_files (le nombre d’emplacements de sa table de hachage). Ce dernier est arrondi en interne au nombre suivant d’une liste fixe de nombres premiers – 10 000 devient 16 229, 20 000 devient 32 531. Quand les emplacements ou la mémoire sont pleins, les fichiers suivants ne sont tout simplement pas mis en cache et sont compilés à chaque requête.
validate_timestamps décide si OPcache vérifie les dates de modification des fichiers (toutes les revalidate_freq secondes). Désactivé, c’est plus rapide, mais un fichier modifié reste alors de l’ancien bytecode jusqu’au redémarrage de FPM.
APCu : un cache pour l’application
APCu est un second segment de mémoire partagée que l’application remplit elle-même – Nextcloud y range ses recherches d’apps et de configuration comme cache local. Comme OPcache, il vaut par pod, pas entre pods ; tout ce qui doit rester cohérent entre processus et pods relève de Redis.
🛡️ Pourquoi il est là
- C’est ce que livrent les images officielles.
wordpress:fpmetnextcloud:fpmcontiennent toutes deux PHP-FPM ; nginx devant est l’installation documentée. - Une concurrence prévisible. Une limite haute fixe de workers, adaptée à la limite mémoire du conteneur, vaut mieux qu’un serveur qui accepte tout puis se fait tuer par l’OOM killer.
- Un contrôle fin. Taille du pool, délais, page de statut et slow log se règlent par application, indépendamment du serveur web.
🔧 Ce que fait ce dépôt
| Blog | Nextcloud A (plus grand) | Nextcloud B | |
|---|---|---|---|
| PHP | 8.4 | 8.5 | 8.5 |
pm | dynamic | dynamic | dynamic |
max_children | 12 | 30 | 20 |
| start / min spare / max spare | 4 / 2 / 8 | 8 / 4 / 16 | 6 / 3 / 12 |
max_requests | 500 | 500 | 500 |
request_terminate_timeout | 300 s | 3 600 s | 3 600 s |
| Limite du conteneur | 1 Gio, 2 cœurs | 6 Gio, 3 cœurs | 5 Gio, 2 cœurs |
| OPcache | 128 Mo, 10 000 fichiers, dates vérifiées toutes les 60 s | 256 Mo, 20 000 fichiers, dates non vérifiées | idem |
| APCu | – | 128 Mo | 128 Mo |
Les réglages du pool se trouvent dans une ConfigMap montée comme z-monitoring.conf dans php-fpm.d/ – le « z » garantit qu’elle est lue en dernier et l’emporte sur le www.conf de l’image. Les valeurs de Nextcloud viennent de variables d’hôte avec des valeurs par défaut dans le template, si bien qu’un petit hôte de test et les deux serveurs de production partagent un seul template.
La valeur par défaut qui bridait Nextcloud à cinq
Dans la première version de la pile Nextcloud, le pool n’avait aucun réglage propre. Le conteneur tournait donc avec la valeur d’origine de FPM, pm.max_children = 5 – quelle que soit sa limite mémoire de 4 Gio. Clients de synchronisation, applications mobiles et rappels WOPI de Collabora occupent tous des workers ; la sixième requête PHP simultanée attendait. Corrigé en juillet 2026 avec un pool de 12, puis 20 ; en octobre, Prometheus montrait des pics de 17 workers actifs sur le plus grand serveur, la limite initiale de cinq était donc bien réelle. La passe de réglage du 16 octobre 2026 a porté les pools à 30 et 20. La règle : la mémoire par worker fois max_children, plus OPcache et APCu, doit rester nettement sous la limite du conteneur.
Le cache de bytecode trop petit
L’image Nextcloud fixe max_accelerated_files = 10000, soit 16 229 emplacements en interne. Nextcloud avec ses apps compte 15 486 fichiers PHP sur le plus grand serveur et 17 811 sur le plus petit. Sur le plus petit, plus de 1 500 fichiers n’entraient donc jamais dans le cache et étaient recompilés à chaque requête qui les utilisait. Depuis le 16 octobre, les deux serveurs tournent avec 20 000 (32 531 emplacements) et 256 Mo. Le blog, avec 4 452 fichiers PHP, est loin de sa limite.
Dates non vérifiées – et ce que cela coûte
Sur Nextcloud, validate_timestamps = 0 : aucun stat() par fichier et par requête. Le prix : un fichier modifié n’est remarqué qu’au redémarrage du pod. Les mises à jour d’image redémarrent le pod de toute façon, mais occ upgrade et occ app:update écrasent des fichiers PHP sur place. Le playbook de mise à jour redéploie donc le pod Nextcloud une seconde fois après eux. Pour cet article, il est apparu que la mise à jour optionnelle des apps dans le playbook principal n’avait pas ce second redémarrage – les apps mises à jour auraient continué à tourner avec leur ancien code. Corrigé. Le blog garde la vérification des dates avec un intervalle de 60 secondes, car WordPress met à jour les extensions sur place depuis sa propre page d’administration.
Limites dures et slow log
[www]
pm.status_path = /fpm-status
slowlog = /proc/1/fd/2
request_slowlog_timeout = 5s
request_slowlog_trace_depth = 0
request_terminate_timeout = 3600s ; blog: 300srequest_terminate_timeoutn’était pas non plus réglé avant le 16 octobre : une requête bloquée sur un verrou ou un stockage externe gardait son worker jusqu’au redémarrage du pod. 3 600 s sur Nextcloud, car le téléchargement en flux d’un dossier peut légitimement durer de longues minutes ; 300 s sur le blog.- Le slow log va sur la sortie d’erreur du conteneur et donc dans le journal du pod. Une trace de pile exigerait
ptrace, dont un conteneur sans capabilities ne dispose pas ; la profondeur 0 n’enregistre que la ligne « executing too slowly ». FPM essaie quand même de s’attacher et écrit unfailed to ptraceinoffensif par requête lente – accepté, carSYS_PTRACEaffaiblirait le conteneur. - La page de statut n’est pas exposée par nginx du tout ; un sidecar
php-fpm_exporterdans le même pod la lit en FastCGI et fournit les chiffres à Prometheus (tableau de bord Grafana 4912).
📊 Mesuré
| 7 derniers jours | Blog | Nextcloud A | Nextcloud B |
|---|---|---|---|
| Requêtes PHP par jour | ~21 700 | ~23 600 | ~16 200 |
| Pic de workers actifs | 8 sur 12 | 13 sur 30 | 9 sur 20 |
max_children atteint | 0 | 0 | 0 |
| Requêtes lentes (> 5 s) | 0 | 0 | 0 |
| Mémoire par worker (RSS / part propre PSS) | ~78 / ~16 Mo | 108–157 / 14–49 Mo | 71–116 / 8–27 Mo |
| Pic mémoire du conteneur | 538 Mio sur 1 Gio | 763 Mio sur 6 Gio | 277 Mio sur 5 Gio |
| Périodes CPU bridées | 2,1 % | 2,2 % | 0,2 % |
Trois lectures. D’abord, le RSS d’un worker surestime son coût : il compte dans chaque worker les pages partagées d’OPcache et d’APCu. Le PSS répartit les pages partagées entre les processus qui les utilisent et donne le chiffre honnête – 16 Mo par worker du blog, pas 78. Ensuite, les pools Nextcloud sont désormais généreux : le pic en a utilisé moins de la moitié, et les limites des conteneurs sont bien au-dessus des pics. Enfin, le blog a connu une valeur aberrante : une seule minute, le 16 octobre, avec 8 workers actifs et 30 requêtes en attente dans la file – une rafale d’environ 40 requêtes simultanées. Aucun journal de ce jour n’a survécu (la collecte des journaux a commencé trois jours plus tard), la cause reste donc inconnue. C’était une minute sur une semaine, et depuis la file est restée à zéro.
🧰 Exploitation au quotidien
# FPM status as the exporter sees it (it talks FastCGI to port 9000 itself)
k3s kubectl -n nextcloud exec deploy/nextcloud -c nginx -- \
curl -s http://127.0.0.1:9253/metrics | grep -E '^phpfpm_(active|idle|listen_queue|max_children)'
# slow requests
k3s kubectl -n nextcloud logs deploy/nextcloud -c nextcloud-fpm | grep -i 'executing too slow'
# memory per worker, honestly (PSS, in MB)
for p in $(pgrep -f '^php-fpm: pool www'); do
awk '/^Pss:/{s+=$2} END{printf "%d ", s/1024}' /proc/$p/smaps_rollup; done; echo
# new PHP code after an in-place update (Nextcloud)
k3s kubectl -n nextcloud rollout restart deployment/nextcloud- Dimensionner les workers. Mesurer le PSS sous charge, le multiplier par le
max_childrenprévu, ajouter OPcache et APCu, comparer à la limite du conteneur. Puis surveiller « max_children reached » et la listen queue dans Grafana ; si les deux restent à zéro, le pool est assez grand. - Vider OPcache.
opcache_reset()en ligne de commande vide le cache propre de la CLI, pas celui de FPM. Pour FPM, la méthode fiable est un redémarrage – sous Kubernetes, un rollout. - Un site qui bloque. Les métriques par processus de l’exporteur montrent depuis combien de temps chaque worker est occupé ; avec le slow log, c’est généralement plus rapide que de fouiller les journaux d’accès.
🆘 Antisèche : symptôme → commande
La version courte de cet article, pour le moment où quelque chose ne va pas. À lancer sur l’hôte en root ; <…> sont des espaces réservés.
| Symptôme | Commande |
|---|---|
| Tous les workers occupés, requêtes en attente | k3s kubectl -n nextcloud exec deploy/nextcloud -c nginx -- \ |
| « server reached pm.max_children » | k3s kubectl -n nextcloud logs deploy/nextcloud -c nextcloud-fpm | grep max_children |
| Quelles requêtes sont lentes ? | k3s kubectl -n nextcloud logs deploy/nextcloud -c nextcloud-fpm | grep -i 'executing too slow' |
| 502 Bad Gateway | k3s kubectl -n nextcloud logs deploy/nextcloud -c nginx --tail=30 |
| Combien de mémoire un worker utilise-t-il vraiment ? | for p in $(pgrep -f '^php-fpm: pool www'); do |
| L’ancien code tourne encore après une mise à jour | k3s kubectl -n nextcloud rollout restart deployment/nextcloud # OPcache: validate_timestamps=0 |
| La même chose sur le blog | k3s kubectl -n wordpress logs deploy/wordpress -c wordpress-fpm --tail=30 |
🔭 La suite
- PHP 8.5 pour le blog dès que l’image officielle de WordPress le recommande ; Nextcloud tourne déjà en 8.5.
- Le JIT est désactivé. Pour des applications web limitées par les E/S comme celles-ci, il apporte peu ; ce serait une expérience avec des mesures avant et après, pas un réglage par défaut.
- Une alerte sur la file d’attente – fait le 21 octobre 2026 : elle envoie un mail quand des requêtes attendent un worker libre pendant cinq minutes, ce qui n’est jamais arrivé sur les 14 jours mesurés.
⚖️ Alternatives – et pourquoi PHP-FPM
| Option | Point fort | Pourquoi pas ici |
|---|---|---|
| Apache avec mod_php | L’installation la plus simple, un seul type de processus | PHP vit dans chaque processus du serveur web, y compris ceux qui servent des fichiers statiques ; pas de contrôle de pool séparé. |
| FrankenPHP / RoadRunner | Workers de longue durée, l’application reste en mémoire | Exige une application conçue pour cela ; WordPress et Nextcloud attendent un état neuf à chaque requête. |
| Swoole | PHP asynchrone | Incompatible avec ces applications. |
| PHP-FPM + nginx | Ce que livrent les images, contrôle fin, bien connu | Choisi. |
Rien dans cet article n’est exotique. Les deux vrais gains – vingt-cinq workers de plus et un cache où le code tient – sont venus de la vérification des valeurs par défaut réelles, pas d’un réglage astucieux. Les valeurs par défaut sont la supposition de quelqu’un d’autre sur votre charge ; avec PHP-FPM, deux d’entre elles se trompaient d’un facteur quatre pour Nextcloud.


