Arrêt gracieux

OxPHP gère SIGTERM et SIGINT afin que les requêtes en cours se terminent avant que le processus ne s'arrête. C'est essentiel pour les déploiements sans interruption de service et les mises à jour progressives dans l'orchestration de conteneurs.

Gestion des signaux

OxPHP répond à deux signaux d'arrêt :

Signal Source Comportement
SIGTERM Orchestrateurs de conteneurs, docker stop, kill Déclenche l'arrêt gracieux
SIGINT Ctrl+C dans le terminal Déclenche l'arrêt gracieux

Les deux signaux déclenchent la même séquence d'arrêt. Seul le premier signal est nécessaire. Le serveur commence immédiatement à drainer.

Séquence d'arrêt

À la réception d'un signal d'arrêt, OxPHP suit cette séquence :

  1. Arrêter d'accepter de nouvelles connexions — le serveur cesse d'accepter de nouvelles connexions TCP sur le port principal. Les workers PHP continuent de tourner pour traiter les requêtes en cours.
  2. Fermer progressivement les connexions actives — les clients HTTP/2 reçoivent une trame GOAWAY et les connexions keep-alive HTTP/1.1 inactives sont fermées, de sorte que les clients basculent vers une instance saine au lieu de multiplexer de nouvelles requêtes vers celle qui se termine. Les flux ouverts sont clôturés rapidement et proprement — toute réponse qui a commencé à vider une sortie en chunks compte, aussi bien les téléchargements finis que le SSE — voir Server-Sent Events.
  3. Drainer les requêtes en cours — les requêtes actives ordinaires sont laissées tranquilles jusqu'à ce qu'elles se terminent avec leurs réponses complètes. Le serveur vérifie leur achèvement toutes les 100 ms. Le serveur interne de santé/métriques reste disponible pendant toute la durée du drainage, afin que les sondes de disponibilité continuent de fonctionner.
  4. Appliquer le délai limite de drainage — les requêtes qui tournent encore après DRAIN_TIMEOUT_SECONDS sont annulées (leurs callbacks register_shutdown_function() s'exécutent tout de même) et disposent d'environ 2 secondes pour se terminer avant que le serveur ne poursuive.
  5. Vider les plugins — les entrées de journalisation des accès et les spans APM mis en tampon pendant la fenêtre de drainage sont vidés.
  6. Arrêter le pool async — le pool de tâches async en arrière-plan est stoppé.
  7. Interrompre le serveur interne — le serveur de santé/métriques est arrêté une fois le drainage terminé.
  8. Sortir — le processus se termine avec le code de statut 0.
Note

Les workers PHP sont arrêtés explicitement lors de l'étape 1 — la méthode shutdown() de l'exécuteur est appelée dans le cadre de l'arrêt du serveur principal, ce qui signale aux threads workers de sortir après avoir terminé toute requête en cours.

Configuration

Variable Défaut Description
DRAIN_TIMEOUT_SECONDS 25 Nombre maximum de secondes accordé aux requêtes en cours pour se terminer avant d'être annulées ; le processus se termine dans les ~2 secondes qui suivent le délai limite. La valeur par défaut laisse une marge pour la fin de traitement post-délai et le vidage de la télémétrie dans la période de grâce de terminaison par défaut de 30 secondes de Kubernetes

Définissez DRAIN_TIMEOUT_SECONDS en fonction de votre requête attendue la plus lente :

  • Serveurs d'API avec des réponses rapides : 1015 secondes
  • Applications avec des envois de fichiers ou des requêtes longues : 3060 secondes
  • Mode worker avec traitement en arrière-plan : ajustez à votre opération attendue la plus longue

Kubernetes

Sous Kubernetes, le déroulement de l'arrêt lors d'une mise à jour progressive est le suivant :

  1. Kubernetes envoie SIGTERM au pod.
  2. Le pod est retiré de la liste des endpoints du Service.
  3. OxPHP draine les connexions en cours dans le délai de DRAIN_TIMEOUT_SECONDS, puis annule les retardataires et sort dans les ~2 secondes supplémentaires.
  4. Si le pod tourne encore après terminationGracePeriodSeconds, Kubernetes envoie SIGKILL.

Fixez terminationGracePeriodSeconds au-dessus de DRAIN_TIMEOUT_SECONDS + 2 pour que le drainage — y compris la fin de traitement post-délai et le vidage de la télémétrie — se termine avant l'arrêt forcé :

yaml
apiVersion: apps/v1 kind: Deployment spec: template: spec: terminationGracePeriodSeconds: 45 containers: - name: oxphp image: ghcr.io/oxphp/oxphp:0.10.0 env: - name: DRAIN_TIMEOUT_SECONDS value: "30"

Hook pre-stop

Si votre service reçoit du trafic depuis des load balancers externes qui propagent lentement les changements d'endpoints, ajoutez un hook pre-stop pour retarder la séquence d'arrêt :

yaml
lifecycle: preStop: exec: command: ["sleep", "5"]

Cela laisse au load balancer le temps de retirer le pod de sa liste de cibles avant qu'OxPHP ne cesse d'accepter les connexions.

Docker

Docker envoie SIGTERM lorsque vous lancez docker stop. Le délai d'arrêt par défaut de Docker est de 10 secondes, après quoi Docker envoie SIGKILL.

Pour laisser à OxPHP assez de temps pour drainer, augmentez le délai d'arrêt :

bash
docker stop --time 45 my-oxphp-container

Ou définissez-le dans votre fichier Compose :

compose.yaml
services: oxphp: image: ghcr.io/oxphp/oxphp:0.10.0 stop_grace_period: 45s environment: DRAIN_TIMEOUT_SECONDS: "30"

Messages de journal

Pendant un arrêt gracieux, OxPHP émet des messages de journal structurés que vous pouvez surveiller :

Drainage réussi :

json
{"level":"INFO","message":"Received shutdown signal, draining connections"} {"level":"INFO","message":"Draining in-flight connections","active_connections":3} {"level":"INFO","message":"All connections drained"} {"level":"INFO","message":"Server stopped"}

Délai limite de drainage atteint :

json
{"level":"INFO","message":"Received shutdown signal, draining connections"} {"level":"WARN","message":"Drain timeout reached, cancelling in-flight requests","remaining_connections":1} {"level":"INFO","message":"All connections drained"} {"level":"INFO","message":"Server stopped"}

Si vous voyez régulièrement l'avertissement « Drain timeout reached », augmentez DRAIN_TIMEOUT_SECONDS ou examinez les requêtes de longue durée à l'aide de l'histogramme oxphp_request_duration_us.

Voir aussi