10 min de lecture
Redémarrage gracieux du serveur avec Go
Go a été conçu comme un langage backend et est principalement utilisé comme tel. Les serveurs sont le type de logiciel le plus courant produit avec. La question à laquelle je vais répondre ici est : comment mettre à niveau proprement un serveur en cours d'exécution ? Objectifs : Ne pas fermer les connexions existantes : par exemple, nous ne voulons pas interrompre aucune.

Go a été conçu comme un langage backend et est principalement utilisé comme tel. Les serveurs sont le type de logiciel le plus courant produit avec cela. La question à laquelle je vais répondre ici est : comment mettre à jour proprement un serveur en cours d'exécution ?
Objectifs :
Ne pas fermer aucune des connexions existantes : par exemple, nous ne voulons pas interrompre un déploiement en cours. Cependant, nous souhaitons pouvoir mettre à jour nos services quand nous le voulons sans aucune contrainte.
Le socket doit toujours être disponible pour les utilisateurs : si le socket est indisponible à un moment donné, certains utilisateurs peuvent recevoir un message 'connexion refusée', ce qui n'est pas acceptable.
La nouvelle version du processus doit être démarrée et doit remplacer l'ancienne.
Principe
Dans les systèmes d'exploitation basés sur UNIX, la manière courante d'interagir avec des processus de longue durée est les signaux.
SIGTERM : Demander à un processus de s'arrêter proprement
SIGHUP : Redémarrage/rechargement du processus (exemple : nginx, sshd, apache)
Une fois qu'un signal SIGHUP est reçu, il y a plusieurs étapes pour redémarrer le processus proprement :
Le serveur cesse d'accepter de nouvelles connexions, mais le socket reste ouvert.
La nouvelle version du processus est démarrée.
Le socket est 'donné' au nouveau processus qui commencera à accepter de nouvelles connexions.
Une fois que l'ancien processus a fini de servir son client, le processus doit s'arrêter.
Cesser d'accepter des connexions
Les serveurs ont cela en commun : ils contiennent une boucle infinie acceptant des connexions :
Pour rompre cette boucle, le moyen le plus simple est de définir un délai d'attente sur l'écouteur, quand SetTimeout(time.Now()) est appelé, listener.Accept() renverra immédiatement une erreur de délai d'attente que vous pouvez attraper et gérer.
Il est important de comprendre qu'il y a une différence entre cette opération et la fermeture de l'écouteur. 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.
Démarrer la nouvelle version du processus
Go fournit une primitive ForkExec pour créer un nouveau processus. (Elle ne permet pas uniquement de fork, cf Est-il sûr de fork() un processus Golang ?) 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 le même argument os.Args
Envoyer le socket au processus fils et le récupérer
Comme vous l'avez vu juste avant, vous pouvez passer des descripteurs de fichiers à votre nouveau processus, et avec un peu de magie UNIX (tout est un fichier), nous pouvons envoyer le socket au nouveau processus et il pourra l'utiliser et accepter les connexions en attente et futures.
Mais le processus fork-exécuté doit savoir qu'il doit récupérer son socket d'un fichier et non en construire un nouveau (ce qui serait déjà utilisé de toute façon, car nous n'avons pas fermé l'écouteur existant). Vous pouvez le faire comme vous voulez, le plus courant est via l'environnement ou avec un indicateur de ligne de commande.
Puis au début du programme :
Le descripteur de fichier n'a pas été choisi au hasard, le descripteur de fichier 3, c'est parce que dans le tableau de uintptr qui a été envoyé au fork, l'écouteur a obtenu l'index 3. Faites attention aux erreurs de déclaration d'ombre.
Dernière étape, attendre que les connexions de l'ancien serveur s'arrêtent
À ce stade, c'est tout, nous avons passé la responsabilité à un autre processus qui fonctionne maintenant correctement, la dernière opération pour l'ancien serveur est d'attendre que les connexions se ferment. Il existe une manière simple de l'implémenter avec Go, grâce à la structure sync.WaitGroup fournie dans la bibliothèque standard.
Chaque fois qu'une connexion est acceptée, 1 est ajouté au WaitGroup, puis, nous diminuons le compteur lorsqu'il est fait :
En conséquence, pour attendre la fin des connexions, vous devez simplement appeler wg.Wait(), comme il n'y a pas de nouvelle connexion, nous attendons que wg.Done() ait été appelé pour tous les gestionnaires en cours.
Bonus : ne pas attendre indéfiniment mais un temps donné
Avec un time.Timer, il est vraiment simple d'implémenter cela :
Conclusion
Utiliser ForkExec avec le passage de socket est un moyen vraiment efficace de 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 cette durée est vraiment courte.
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





