10 min de lecture
Redémarrage gracieux de serveur avec Go
Go a été conçu comme un langage backend et est principalement utilisé dans ce contexte. Les serveurs sont le type de logiciel le plus courant développé avec lui. La question à laquelle je vais répondre ici est la suivante : comment mettre à jour proprement un serveur en cours d’exécution ?

Go a été conçu comme un langage backend et est principalement utilisé dans ce contexte. Les serveurs sont le type de logiciel le plus courant développé avec lui. La question à laquelle je vais répondre ici est la suivante : comment mettre à jour proprement un serveur en cours d’exécution ?
Objectifs :
Ne fermer aucune connexion existante : par exemple, nous ne voulons pas interrompre un déploiement en cours. En revanche, nous voulons pouvoir mettre à jour nos services à tout moment, sans contrainte.
Le socket doit toujours rester disponible pour les utilisateurs : si le socket devient indisponible, certains utilisateurs pourraient recevoir une erreur « connection refused », ce qui n’est pas acceptable.
La nouvelle version du processus doit être lancée et remplacer l’ancienne.
Principe
Dans les systèmes d’exploitation de type UNIX, la manière habituelle d’interagir avec des processus longue durée consiste à utiliser des signaux.
SIGTERM: demande à un processus de s’arrêter proprementSIGHUP: redémarrage / rechargement du processus (exemple : nginx, sshd, apache)
Lorsqu’un signal SIGHUP est reçu, plusieurs étapes permettent de redémarrer le processus proprement :
Le serveur cesse d’accepter de nouvelles connexions, mais le socket reste ouvert.
La nouvelle version du processus est lancée.
Le socket est « transmis » au nouveau processus, qui commencera à accepter les nouvelles connexions.
Une fois que l’ancien processus a terminé de servir ses clients, il doit s’arrêter.
Arrêter d’accepter de nouvelles connexions
Les serveurs ont un point commun : ils contiennent une boucle infinie qui accepte les connexions :
Pour interrompre cette boucle, le plus simple est de définir un timeout sur le listener. Lorsque listener.SetTimeout(time.Now()) est appelé, listener.Accept() retourne immédiatement une erreur de timeout que vous pouvez intercepter et gérer.
Il est important de comprendre la différence entre cette opération et la fermeture du listener. Dans ce cas, le processus écoute toujours sur un port, par exemple, mais les connexions sont mises en file d’attente par la pile réseau du système d’exploitation, en attendant qu’un processus les accepte.
Lancer la nouvelle version du processus
Go fournit une primitive ForkExec pour démarrer un nouveau processus. (Elle ne permet pas de faire uniquement un fork, voir « Is it safe to fork() a Golang process? ».) Vous pouvez partager certaines informations avec ce nouveau processus, comme des descripteurs de fichiers ou votre environnement.
Vous pouvez voir que le processus démarre une nouvelle version de lui-même avec exactement les mêmes arguments os.Args.
Envoyer le socket au processus enfant et le récupérer
Comme vous venez de le voir, il est possible de passer des descripteurs de fichiers au nouveau processus. Avec un peu de magie UNIX (« tout est fichier »), nous pouvons transmettre le socket au nouveau processus, qui pourra alors l’utiliser pour accepter les connexions en attente et les futures connexions.
Mais le processus lancé via fork-exec doit savoir qu’il doit récupérer son socket depuis un fichier, et non en créer un nouveau — qui serait déjà utilisé, puisque nous n’avons pas fermé le listener existant. Vous pouvez le faire comme vous le souhaitez ; le plus courant est d’utiliser une variable d’environnement ou un flag en ligne de commande.
Puis au début du programme :
Le descripteur de fichier 3 n’a pas été choisi au hasard : c’est parce que, dans le slice de uintptr envoyé au fork, le listener se trouve à l’index 3. Attention aux erreurs de shadow declaration.
Dernière étape : attendre que les connexions de l’ancien serveur se terminent
À ce stade, tout est prêt : nous avons passé le relais à un autre processus, qui fonctionne maintenant correctement. La dernière opération pour l’ancien serveur consiste à attendre que les connexions soient fermées. Il existe une manière simple de l’implémenter en Go, grâce à la structure sync.WaitGroup fournie par la bibliothèque standard.
À chaque fois qu’une connexion est acceptée, on ajoute 1 au WaitGroup, puis on décrémente le compteur lorsque le traitement est terminé :
Ainsi, pour attendre la fin des connexions, il suffit d’appeler wg.Wait(). Comme aucune nouvelle connexion n’est acceptée, on attend que wg.Done() ait été appelé pour tous les handlers en cours.
Bonus : ne pas attendre indéfiniment, mais pendant une durée donnée
Avec un time.Timer, c’est très simple à mettre en place :
Conclusion
Utiliser ForkExec avec transmission de socket est une méthode très efficace pour mettre à jour un processus sans perturber les connexions. Au maximum, les nouveaux clients attendront quelques millisecondes, le temps que le nouveau serveur démarre et récupère le socket, mais ce délai reste très court.
Cet article faisait partie de notre série #FridayTechnical. Il n’y aura pas d’article la semaine prochaine, joyeux Noël à tous.
Liens :
Démonstration de déclaration d'ombre des variables Go : http://www.qureet.com/blog/golang-beartrap/
Question bonus : est-il sûr de fork() un processus Golang ? : https://groups.google.com/forum/#!msg/golang-nuts/beKfn7rujNg/zwY6zwl7QtQJ
Notre page d'accueil : https://scalingo.com
— Léo Unbekandt CTO @ Scalingo

Léo Unbekandt
Léo est le fondateur et CTO de Scalingo. Il a étudié en France en tant qu'ingénieur cloud (ENSIIE) et en Angleterre (Cranfield University). Il est responsable du développement technique de Scalingo et il gère notre équipe technique.
Restez informé
Recevez des articles et des mises à jour de la plateforme dans votre boîte de réception.
Prêt à déployer en toute confiance ?
Découvrez des déploiements sans temps d'arrêt, une mise à l'échelle automatique intelligente et une infrastructure entièrement gérée. Commencez à déployer vos applications sur Scalingo dès aujourd'hui.
Aucune carte de crédit requise • Déployez en quelques minutes • Annulez à tout moment






