PHP-FPM gère les processus workers PHP dans vos applications. Sa configuration est souvent copiée sans véritable compréhension, ce qui cause des problèmes sérieux en production : particulièrement dans les environnements Kubernetes où chaque pod a des limites de ressources strictes.
Cet article explique comment adapter la configuration PHP-FPM à votre infrastructure en combinant le mode static avec le HPA Kubernetes.
Les trois modes du process manager PHP-FPM
pm = dynamic (par défaut)
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500
PHP-FPM démarre avec un nombre minimal de workers et en crée de nouveaux selon la charge.
- Avantages : efficacité mémoire en faible trafic, adaptation automatique à la charge
- Inconvénients : latence lors des pics soudains, tuning complexe (4 paramètres à équilibrer), comportement imprévisible sous forte charge
pm = ondemand
pm = ondemand
pm.max_children = 50
pm.process_idle_timeout = 10s
Les workers sont créés à la demande et tués après un délai d'inactivité. Idéal pour les workloads très sporadiques, mais avec une latence de démarrage à chaque requête après un creux d'activité.
pm = static
pm = static
pm.max_children = 10
Un nombre fixe de workers démarre au lancement du conteneur et ne change jamais. Simple, prévisible, parfait pour Kubernetes.
Le problème de dynamic dans Kubernetes
Sur une VM, le mode dynamic a du sens. Dans Kubernetes, chaque pod a des limites de ressources explicites :
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
Avec pm = dynamic, PHP-FPM peut créer des workers jusqu'à pm.max_children, ce qui risque de déclencher un OOMKill si les limites mémoire sont dépassées. La scalabilité horizontale est le rôle du HPA, pas celui de PHP-FPM.
L'approche recommandée : static + HPA
L'idée est simple : un pod = un nombre fixe de workers. Quand plus de capacité est nécessaire, Kubernetes crée plus de pods.
Le contrat mémoire : memory_limit × pm.max_children
; php.ini
memory_limit = 128M
; php-fpm.conf
pm = static
pm.max_children = 3
# Kubernetes Deployment
resources:
limits:
memory: "512Mi"
Avec un pod de 512Mi et environ 64Mi de surcharge système :
(512 - 64) / 128 ≈ 3,5 → arrondi à 3 workers
Dans le pire des cas (chaque worker consommant sa limite mémoire) : 3 × 128Mi + ~64Mi = ~448Mi, bien en dessous de la limite de 512Mi.
Configuration HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-app
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Comparaison
| Aspect | dynamic sur K8s |
static + HPA |
|---|---|---|
| Latence au démarrage | Variable | Aucune |
| Prévisibilité mémoire | Faible | Élevée |
| Complexité de tuning | 4 paramètres | 1 paramètre |
| Responsabilité de la scalabilité | PHP-FPM | Kubernetes |
| Risque OOMKill | Élevé | Contrôlé |
Erreurs courantes
- Copier
pm.max_children = 50sans réfléchir : 50 workers × 128M = 6,4 Go potentiels sur un pod de 512Mi = OOMKill garanti. - Laisser
pm = dynamicpar défaut dans Kubernetes : ce mode n'a jamais été conçu pour des conteneurs avec des limites de ressources fixes. - Définir
memory_limit = -1: impossible de prédire l'utilisation mémoire du pod. - Oublier
pm.max_requests: sans cette directive, les fuites mémoire s'accumulent au fil du temps.pm.max_requests = 500 - Scaler uniquement sur le CPU : pour les workloads I/O-bound, exposer
pm.status_pathet utiliser les métriques PHP-FPM dans le HPA est plus pertinent.
Optimiser la Readiness Probe
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
Avec pm = static, les workers sont prêts immédiatement au démarrage du conteneur. Pas besoin d'un initialDelaySeconds élevé.
Conclusion
La règle d'or :
- VM / bare metal →
pm = dynamic - Kubernetes →
pm = static+ HPA
La clé est que memory_limit, pm.max_children et la limite mémoire du pod forment un contrat interdépendant. Définissez l'un, déduisez les autres. Cette séparation claire des responsabilités rend le système prévisible et élimine une catégorie entière de bugs mémoire.