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 :
- Cesser de prendre de nouvelles connexions : le serveur cesse d'accepter des connexions sur le port principal, mais le socket d'écoute lui-même reste ouvert jusqu'à la sortie du processus. Un client qui se connecte pendant le drainage termine donc la poignée de main TCP et ne reçoit jamais de réponse : il attend jusqu'à son propre délai d'expiration, ou jusqu'à ce que le processus sorte et réinitialise la connexion. Seules les connexions établies après la sortie sont refusées d'emblée ; retirez donc l'instance de la rotation avant que le signal ne l'atteigne (voir Kubernetes). Les workers PHP continuent de tourner pour traiter les requêtes en cours.
- Fermer progressivement les connexions actives — les clients HTTP/2 reçoivent une trame
GOAWAYet 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. - 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. Le travail qu'une requête continue de faire après
oxphp_finish_request()compte lui aussi comme en cours, même si sa connexion a déjà disparu. - Appliquer le délai limite de drainage : les requêtes qui tournent encore après
DRAIN_TIMEOUT_SECONDSsont annulées (leurs callbacksregister_shutdown_function()s'exécutent tout de même) et disposent d'environ 2 secondes pour se terminer avant que le serveur ne poursuive. Une requête qui se trouve dans un appel bloquant (sleep(), une requête de base de données, une lecture sur un socket) ne peut pas y être interrompue. C'est toujours le cas en mode traditionnel, et en mode worker sauf siRUNTIME_HOOKSa transformé cet appel en suspension de fibre. Sa connexion est fermée à l'heure prévue, mais son thread de worker, et avec lui le processus, ne sort qu'une fois l'appel revenu. Les ~2 secondes ne bornent donc pas le moment où le processus sort. - 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.
- Arrêter le pool async — le pool de tâches async en arrière-plan est stoppé.
- Interrompre le serveur interne — le serveur de santé/métriques est arrêté une fois le drainage terminé.
- Sortir — le processus se termine avec le code de statut 0.
Les étapes 1 et 2 commencent dès l'arrivée du signal ; le délai limite de drainage ne commence à courir qu'une fois la boucle d'acceptation arrêtée. Si toutes les connexions autorisées par MAX_CONNECTIONS sont occupées à l'arrivée du signal, la boucle attend que l'une d'elles se ferme et ne s'arrête qu'à ce moment-là. Tant qu'aucune connexion ne se ferme, aucun délai ne court et rien de ce qui tourne encore n'est annulé à l'étape 4. Lorsque ce sont des connexions keep-alive inactives qui occupent le quota, l'étape 2 les ferme et cette attente passe en quelques instants ; lorsque ce sont des requêtes longues ou des clients lents, elle dure aussi longtemps qu'eux, et peut consommer toute la période de grâce de l'orchestrateur.
Les threads de workers PHP ne sont pas arrêtés au début de la séquence. Ils sont signalés pour sortir et joints tout à la fin, une fois le drainage terminé et le processus en route vers sa sortie. C'est pourquoi le drainage doit tenir compte du travail encore en cours sur un worker même lorsqu'il ne reste aucune connexion cliente.
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 serveur cesse de servir environ 2 secondes après le délai limite, et le processus sort alors, sauf si une requête se trouve dans un appel bloquant (voir l'étape 4 ci-dessus). 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 :
10–15secondes - Applications avec des envois de fichiers ou des requêtes longues :
30–60secondes - 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 :
- Kubernetes envoie
SIGTERMau pod. - Le pod est retiré de la liste des endpoints du Service.
- OxPHP draine les connexions en cours dans le délai de
DRAIN_TIMEOUT_SECONDS, puis annule les retardataires et cesse de servir environ 2 secondes plus tard. Le processus sort alors, ou une fois que le dernier appel bloquant sur un worker est revenu. - Si le pod tourne encore après
terminationGracePeriodSeconds, Kubernetes envoieSIGKILL.
Fixez terminationGracePeriodSeconds au-dessus de DRAIN_TIMEOUT_SECONDS + 2, plus l'appel bloquant le plus long dans lequel une requête peut se trouver au passage du délai limite (la requête de base de données ou le délai d'expiration de socket le plus long que l'application autorise), 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é. Avec seulement + 2, une requête prise dans une longue requête de base de données au moment du délai limite vaut au pod d'être tué par SIGKILL (code de sortie 137) au lieu de sortir avec 0 :
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
terminationGracePeriodSeconds: 45
containers:
- name: oxphp
image: ghcr.io/oxphp/oxphp:0.13.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 :
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. C'est important, car une connexion routée vers le pod après le signal reste sans réponse pendant tout le reste du drainage au lieu d'être refusée (voir l'étape 1 de la séquence d'arrêt).
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 :
docker stop --time 45 my-oxphp-containerOu définissez-le dans votre fichier Compose :
services:
oxphp:
image: ghcr.io/oxphp/oxphp:0.13.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 :
{"level":"INFO","message":"Received shutdown signal, draining connections"}
{"level":"INFO","message":"Draining in-flight connections","active_connections":3,"in_flight_requests":4}
{"level":"INFO","message":"All connections drained"}
{"level":"INFO","message":"Server stopped"}Les deux comptes se recouvrent au lieu de se partitionner : active_connections correspond aux connexions clientes vivantes, in_flight_requests aux requêtes encore en cours d'exécution sur des workers PHP, et une requête qui détient une connexion est comptée dans les deux. Une requête qui a terminé sa réponse tôt avec oxphp_finish_request() n'apparaît que dans in_flight_requests — sa connexion a déjà disparu pendant que son travail d'arrière-plan s'exécute.
Délai limite de drainage atteint :
{"level":"INFO","message":"Received shutdown signal, draining connections"}
{"level":"WARN","message":"Drain timeout reached, cancelling in-flight requests","remaining_connections":1,"in_flight_requests":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.
Prompts pour votre agent
Ces prompts supposent un agent connecté au serveur MCP de la documentation OxPHP — il lira lui-même les pages dont il a besoin.
Fais en sorte que mon déploiement OxPHP s'arrête sans perdre de requêtes. Lis la page `operations/graceful-shutdown` via le serveur MCP de la documentation OxPHP.
Règle la période de grâce de terminaison, le hook preStop et les délais d'arrêt de mes manifestes Kubernetes de façon cohérente entre eux, en tenant compte de l'appel bloquant le plus long (requête de base de données ou délai d'expiration de socket) que mon application autorise, et dis-moi ce qu'il advient d'une requête en cours à chaque étape.Voir aussi
- Contrôles de santé — sondes de disponibilité et interaction avec l'arrêt
- Référence de configuration — toutes les variables d'environnement, dont
DRAIN_TIMEOUT_SECONDS - Métriques —
oxphp_active_connectionssuit les connexions pendant le drainage