Publié par
Il y a 3 années · 4 minutes · Back

Zero downtime deployment avec Node.js et Express, une première étape …

Lorsqu’on souhaite stopper ou redémarrer un serveur, différentes solutions s’offrent à nous. Parmi elles, la possibilité d’envoyer un signal de type SIGTERM au processus.

Cette solution est couramment utilisée, malheureusement cela entraîne la coupure des connexions en cours sans permettre au serveur d’honorer les requêtes en cours de traitement.

Dans l’objectif de fournir une meilleure qualité de service, il est important d’honorer toute requête entrante. Comment faire, donc, pour permettre le redémarrage d’un serveur en douceur, sans couper brutalement les connexions en cours ?

Le protocole HTTP permet au serveur de répondre aux requêtes entrantes par un status code 503 qui signifie que le service n’est pas disponible (Service Unavailable). L’idée est donc de renvoyer ce status code dès lors que le process a reçu un signal SIGTERM, tout en laissant le temps au serveur de terminer le traitement des requêtes HTTP en cours, puis de stopper le serveur une fois que les requêtes en cours sont traitées. Il est toujours possible de killer le process du serveur si cela met trop longtemps.

Node.js

Avec Node.js, il est possible d’écouter les signaux reçus par le système et d’y réagir. Il est donc possible de mettre en place une mécanique qui instruit le serveur de répondre aux nouvelles requêtes entrantes par un status code 503, puis de couper le serveur une fois les requêtes en cours traitées.

Cela donne le code suivant:

var start = new Date();
var app = express();
var gracefullyClosing = false;

app.configure(function() {
  app.set('port', process.env.PORT || 8000);

  app.use(function(req, res, next) {
    if (!gracefullyClosing) {
      next();
      return;
    }

    res.setHeader("Connection", "close");

    res.send(503, "Le serveur est en cours de redémarrage.");
  });

  app.use(app.router);
});

app.get('/', function(req, res) {
  res.send(200, 'OK');
});

httpServer = app.listen(app.get('port'));

process.on('SIGTERM', function() {
  logger.info("Réception d'un signal SIGTERM. Tentative de fermeture gracieuse des connexions.");

  gracefullyClosing = true;

  httpServer.close(function() {
    logger.info("Fermeture des connexions existantes.");
    process.exit();
  });

  return setTimeout(function() {
    console.error("Fermeture forcée des connexions: Les connexions n'ont pas pu être fermées dans les temps.");
    process.exit(1);
  }, 30 * 1000);
});

L’appel de la fonction close sur l’instance de serveur HTTP renvoyée Express, permet au serveur de terminer le traitement des requêtes en cours avant de s’arrêter.

Si votre application Node.js est correctement redondée avec un reverse proxy (Nginx, HAProxy) devant les différentes instances, les requêtes entrantes seront redirigées vers d’autres instances en état de traiter les requêtes. Cela sera le cas dès lors que votre application répondra aux requêtes entrantes avec des status code 502 ou 503 par exemple.

Nginx

Si votre application est déployée derrière un reverse proxy tel que Nginx, il suffit de configurer celui-ci avec plusieurs flux upstream vers différentes instances de votre application pour qu’il soit capable de passer la main à une autre instance lorsqu’il reçoit un code erreur 502 ou 503 en réponse à une requête transmise à une des instances.

upstream my_app_upstream {
    server 127.0.0.1:7000;
    server 127.0.0.1:8000;
    server 127.0.0.1:9000;
}
Ensuite, il faut déclarer ce que Nginx doit faire lorsqu’il reçoit une réponse 502 ou 503 et le tour est joué. Ici, nous indiquons à Nginx via la directive proxy_next_upstream de faire suivre la requête au prochain flux upstream lorsqu’il reçoit en réponse une erreur, un timeout ou bien un code HTTP 502 ou 503:
location /app {
 ...
 proxy_next_upstream error timeout http_502 http_503;
 ...
 proxy_pass http://my_app_upstream;
}

Limitations

Ce fonctionnement décrit dans cet article répond bien aux besoins d’applications traitant des requêtes HTTP simples, néanmoins il ne répond pas au problème des configurations qui ont activé l’option de keepalive pour les connexions HTTP, ni aux applications utilisant les websockets. Il faudra dès lors trouver une solutions adaptée.

Conclusion

La mise en oeuvre de la notion de Graceful Shutdown lors d’un redémarrage pour raison de déploiement d’une nouvelle version, est une première étape importante pour arriver à faire du Zero Downtime Deployment. Cela permet, non seulement d’honorer les requêtes en cours de traitement, mais également à vos reverse proxy de prendre connaissance de l’absence de service et redispatcher les requêtes sur d’autres serveurs avant que le flux upstream ne soit coupé.

Pour approfondir

Cet article, directement inspiré d’une série d’articles sur la notion de Zero Downtime Deployment (Voir les liens mentionnés suivants), sera suivi d’un prochain article dans la même lignée expliquant la mise en oeuvre de la clusterisation d’une application Node.js.

Si vous souhaitez en apprendre plus sur le sujet, vous pouvez vous référer aux articles suivants très bien écrits:

Alexis Kinsella
Alexis Kinsella est un consultant Xebia passionné aussi bien par les problématiques frontend (web et mobile) que backend. Après de longues années passées sur les technologies Java, Alexis a fait d'iOS, Node.js et du Cloud ( AWS Certified Solutions Architect - Associate ) ses nouveaux terrains d'expérimentation lui permettant d'explorer les architectures web et mobiles ainsi que leurs problématiques associées.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *