← Blog

PHP-FPM & Kubernetes : arrêtez de mal configurer votre process manager

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émarrageVariableAucune
Prévisibilité mémoireFaibleÉlevée
Complexité de tuning4 paramètres1 paramètre
Responsabilité de la scalabilitéPHP-FPMKubernetes
Risque OOMKillÉlevéContrôlé

Erreurs courantes

  1. Copier pm.max_children = 50 sans réfléchir : 50 workers × 128M = 6,4 Go potentiels sur un pod de 512Mi = OOMKill garanti.
  2. Laisser pm = dynamic par défaut dans Kubernetes : ce mode n'a jamais été conçu pour des conteneurs avec des limites de ressources fixes.
  3. Définir memory_limit = -1 : impossible de prédire l'utilisation mémoire du pod.
  4. Oublier pm.max_requests : sans cette directive, les fuites mémoire s'accumulent au fil du temps.
    pm.max_requests = 500
  5. Scaler uniquement sur le CPU : pour les workloads I/O-bound, exposer pm.status_path et 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 metalpm = dynamic
  • Kubernetespm = 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.