3 points par GN⁺ 2025-05-06 | 1 commentaires | Partager sur WhatsApp
  • Le graceful shutdown est une procédure par laquelle, après avoir reçu un signal d’arrêt, l’application bloque les nouvelles requêtes, termine celles en cours, puis libère les ressources
  • En Go, le package os/signal permet de gérer directement des signaux d’arrêt comme SIGINT et SIGTERM, et signal.NotifyContext rend aussi possible un contrôle de l’arrêt basé sur context
  • Lors de l’arrêt d’un serveur HTTP, il est plus sûr de bloquer le trafic via l’échec de la readiness probe avant d’appeler Server.Shutdown(), puis d’attendre quelques secondes avant d’exécuter le shutdown
  • Tous les handlers doivent pouvoir détecter le signal d’arrêt du context et s’interrompre, ce qui peut être géré de façon unifiée via BaseContext ou un middleware
  • Après la réception d’un signal d’arrêt, les ressources externes comme la base de données, le message broker ou le cache doivent être libérées volontairement, et leur enregistrement avec defer facilite la gestion de l’ordre d’arrêt

Qu’est-ce que le graceful shutdown ?

  • Le graceful shutdown est le processus par lequel une application, au moment de s’arrêter, bloque les nouvelles requêtes, attend la fin des requêtes en cours, puis libère ses ressources
  • Cet article traite principalement des serveurs HTTP et des environnements conteneurisés, mais il s’agit d’un concept applicable à toutes les applications

1. Gestion des signaux d’arrêt

  • Sur les systèmes de type Unix, SIGTERM, SIGINT et SIGHUP sont utilisés comme signaux d’arrêt
  • Le runtime Go arrête par défaut l’application à la réception de SIGTERM ou SIGINT, mais on peut les gérer directement avec os/signal.Notify
  • L’utilisation d’un canal bufferisé (capacité 1) permet d’éviter la perte de signaux pendant l’initialisation
  • Depuis Go 1.16, signal.NotifyContext simplifie le contrôle des signaux via context

2. Prendre en compte le temps d’arrêt

  • Dans Kubernetes, une période de grâce de 30 secondes est accordée par défaut pour l’arrêt (terminationGracePeriodSeconds)
  • Pour un arrêt sûr, il est recommandé de prévoir une marge de 20 % et de terminer les opérations d’arrêt en moins de 25 secondes

3. Arrêter d’accepter de nouvelles requêtes

  • http.Server.Shutdown() bloque les nouvelles connexions et attend la fin des requêtes existantes
  • Dans un environnement Kubernetes, on commence par faire échouer la readiness probe afin de bloquer l’arrivée du trafic, puis on attend un peu avant d’exécuter le shutdown
  • Le handler de readiness peut s’appuyer sur une variable globale indiquant l’état d’arrêt afin de retourner un HTTP 503

4. Finaliser le traitement des requêtes

  • Il faut définir un timeout approprié sur le context dédié à l’arrêt (context.WithTimeout)
  • Si le context de shutdown expire, les connexions restantes sont forcées à se fermer
  • Tous les handlers doivent être conçus pour détecter le signal d’arrêt et pouvoir s’interrompre à l’aide de context.Context
  • Pour cela, on peut injecter le context d’arrêt dans toutes les requêtes via un middleware ou BaseContext

5. Libération des ressources

  • Si l’on ferme les ressources immédiatement après réception du signal d’arrêt, cela peut poser problème aux handlers encore en cours d’exécution
  • Une fois le shutdown terminé, il faut libérer les connexions à la base de données, le message broker, le cache, etc.
  • En utilisant defer en Go, on peut exécuter les routines d’arrêt dans l’ordre inverse de l’initialisation, ce qui facilite la gestion des dépendances
  • Au-delà des ressources que l’OS nettoie automatiquement, comme la mémoire ou les descripteurs de fichiers, certaines nécessitent un arrêt explicite, comme le flush de données ou le rollback de transactions

Résumé de l’exemple complet

  • Réception du signal d’arrêt avec signal.NotifyContext
  • Implémentation de l’endpoint de readiness /healthz
  • Injection du context d’arrêt dans toutes les requêtes avec BaseContext
  • Exécution du shutdown après 5 secondes d’attente de la readiness
  • Inclusion d’un fallback de fermeture forcée en cas d’échec de l’appel à server.Shutdown

Références et ressources associées

1 commentaires

 
GN⁺ 2025-05-06
Avis Hacker News
  • Dans Kubernetes, la mise à jour des IP cibles du load balancer peut parfois prendre du temps. Dans 90 % des cas, le vrai problème est de vérifier que le trafic est effectivement drainé

    • L’ajout d’un délai d’attente de 15 secondes au hook preStop global a nettement amélioré le taux de HTTP 503
    • Cela crée un délai entre la désinscription du load balancer et l’envoi de SIGTERM, ce qui simplifie le traitement côté application
  • Lorsqu’on utilise log.Fatal, le contenu des defer n’est pas exécuté

    • log.Fatal appelle os.Exit et termine immédiatement le processus
    • Avec panic, le contenu des defer est bien exécuté
  • Quand l’endpoint Prometheus /metrics est scrapé périodiquement, les métriques enregistrées entre le dernier scrape et l’arrêt du processus peuvent ne pas être propagées

    • On peut perdre les dernières secondes de logs lors de l’arrêt d’un service
    • Des conditions de concurrence peuvent apparaître quand un fichier de logs est surveillé par un processus sidecar
  • Si un système distribué dépend de l’arrêt propre du client, il peut tomber en panne de manière grave

  • Il manque des explications sur la façon de redémarrer une application sans couper les connexions, quand une nouvelle instance de service reçoit les sockets de l’instance précédente

    • Avec systemd, c’est relativement simple à mettre en œuvre
    • nginx prend cela en charge depuis plus de 20 ans
    • Kubernetes et Docker ne le prennent pas en charge
  • La discussion sur la liveness est insuffisante

    • On voit souvent des applications qui utilisent le même endpoint pour la liveness et la readiness
  • Un programme qui ne sait pas gérer proprement des commandes comme ctrl c est mal conçu

  • Elixir conçoit les processus comme de petits processus de VM, ce qui évite d’avoir à créer délibérément des routines d’arrêt propre

  • Quelqu’un a créé dans son projet une petite bibliothèque pour gérer l’arrêt propre

    • Elle fournit une API pour unifier des services ayant des mécanismes de démarrage et d’arrêt variés
  • Après la mise à jour de la readiness probe, il faut attendre quelques secondes afin que le système n’envoie plus de nouvelles requêtes

    • Un pod en cours d’arrêt n’est pas en état prêt
    • Le service marque les endpoints comme étant en cours d’arrêt
    • Une petite fenêtre peut encore subsister après SIGTERM, mais ce n’est pas un gros problème
    • L’important est de ne plus accepter de nouvelles connexions et de terminer proprement les connexions existantes