Vous ne devriez jamais exécuter directement sur Node.js en production. Peut être.

Parfois, je me demande si je sais beaucoup de choses.

Il y a quelques semaines à peine, je parlais à un ami qui a mentionné à voix basse: «vous ne lanceriez jamais une application directement sur Node en production».

Je hoché la tête vigoureusement pour signaler que je aussi ne jamais jamais couru contre nœud dans la production parce que ... hahaha ... .everyone sait. Mais je ne savais pas ça! Dois-je le savoir?! ?? SUIS-JE TOUJOURS AUTORISÉ À PROGRAMMER?

Si je devais dessiner un diagramme de Venn de ce que je sais par rapport à ce que je ressens comme tout le monde le sait, cela ressemblerait à ceci ...

Au fait, ce petit point devient plus petit à mesure que je vieillis.

Il y a un meilleur diagramme créé par Alicia Liu qui a en quelque sorte changé ma vie. Elle dit que c'est plus comme ça ...

J'aime tellement ce diagramme parce que je veux qu'il soit vrai. Je ne veux pas passer le reste de ma vie comme un minuscule point bleu insignifiant.

SI DRAMATIQUE. Blâmez Pandora. Je ne contrôle pas ce qui est joué ensuite pendant que j'écris cet article et Dashboard Confessional est une drogue infernale.

Eh bien, en supposant que le diagramme d'Alicia est vrai, j'aimerais partager avec vous ce que je sais maintenant sur l'exécution des applications Node en production. Peut-être que nos diagrammes de Venn relatifs ne se chevauchent pas sur ce sujet.

Tout d'abord, abordons la déclaration «ne jamais exécuter d'applications directement sur Node en production».

Ne jamais exécuter directement contre Node en production

Peut être. Mais peut-être pas. Parlons du raisonnement derrière cette déclaration. Voyons d'abord pourquoi.

Disons que nous avons un simple serveur Express. Le serveur Express le plus simple auquel je puisse penser…

const express = require("express"); const app = express(); const port = process.env.PORT || 3000; // viewed at //localhost:3000 app.get("/", function(req, res) { res.send("Again I Go Unnoticed"); }); app.listen(port, () => console.log(`Example app listening on port ${port}!`));

Nous l'exécuterions avec un script de démarrage dans le package.jsonfichier.

"scripts": { "dev": "npx supervisor index.js", "start": "node index.js" }

Il y a en quelque sorte deux problèmes ici. Le premier est un problème de développement et le second est un problème de production.

Le problème du développement est que lorsque nous modifions le code, nous devons arrêter et démarrer l'application pour que nos modifications soient prises en compte.

Pour résoudre cela, nous utilisons généralement une sorte de gestionnaire de processus Node comme supervisorou nodemon. Ces packages surveilleront notre projet et redémarreront notre serveur chaque fois que nous apporterons des modifications. Je fais généralement ça comme ça ...

"scripts": { "dev": "npx supervisor index.js", "start": "node index.js"}

Puis je cours npm run dev. Notez que je cours npx supervisorici ce qui me permet d'utiliser le supervisorpackage sans avoir à l'installer. Je ❤️ 2019. Surtout.

Notre autre problème est que nous fonctionnons toujours directement contre Node et nous avons déjà dit que c'était mauvais et maintenant nous sommes sur le point de découvrir pourquoi.

Je vais ajouter ici une autre route qui tente de lire un fichier à partir d'un disque qui n'existe pas. Il s'agit d'une erreur qui pourrait facilement apparaître dans n'importe quelle application du monde réel.

const express = require("express"); const app = express(); const fs = require("fs"); const port = process.env.PORT || 3000; // viewed at //localhost:3000 app.get("/", function(req, res) { res.send("Again I Go Unnoticed"); }); app.get("/read", function(req, res) { // this does not exist fs.createReadStream("my-self-esteem.txt"); }); app.listen(port, () => console.log(`Example app listening on port ${port}!`));

Si nous l'exécutons directement sur Node avec npm startet naviguons vers le read point de terminaison, nous obtenons une erreur car ce fichier n'existe pas.

Quel - pas grand-chose non? C'est une erreur. Ça arrive.

NON. Grosse affaire. Si vous revenez à votre terminal, vous verrez que l'application est complètement en panne.

Ce qui signifie que si vous revenez au navigateur et essayez d'accéder à l'URL racine du site, vous obtenez la même page d'erreur. Une erreur dans une méthode a supprimé l'application pour tout le monde .

C'est mauvais. Comme vraiment mauvais. C'est l'une des principales raisons pour lesquelles les gens disent «ne jamais courir directement contre Node en production» .

D'ACCORD. Donc, si nous ne pouvons pas exécuter Node en production, quelle est la bonne façon d'exécuter Node en production?

Options pour le nœud de production

Nous avons quelques options.

L'un d'eux serait simplement d'utiliser quelque chose comme supervisorou nodemonen production de la même manière que nous les utilisons en développement. Cela fonctionnerait, mais ces outils sont un peu légers. Une meilleure option est quelque chose appelé pm2.

pm2 le sauvetage

pm2 est un gestionnaire de processus Node qui a beaucoup de cloches et de sifflets. Comme tout autre «JavaScript», vous l'installez (globalement) à partir de npm- ou vous pouvez simplement l'utiliser à npxnouveau. Je ne veux pas te dire comment vivre ta vie.

Il existe de nombreuses façons d'exécuter votre application avec pm2. Le moyen le plus simple consiste simplement à appeler pm2 startvotre point d'entrée.

"scripts": { "start": "pm2 start index.js", "dev": "npx supervisor index.js" },

Et vous verrez quelque chose comme ça dans le terminal…

That’s our process running in the background monitored by pm2. If you visit the read endpoint and crash the application, pm2 will automatically restart it. You won’t see any of that in the terminal because it’s running in the background. If you want to watch pm2 do its thing, you gotta run pm2 log 0. The 0 is the ID of the process we want to see logs for.

There we go! You can see pm2 restart the application when it goes down because of our unhandled error.

We can also pull out our dev command and have pm2 watch files for us and restart on any changes.

"scripts": { "start": "pm2 start index.js --watch", "dev": "npx supervisor index.js" },

Note that because pm2 runs things in the background, you can’t just ctrl+c your way out of a running pm2 process. You have to stop it by passing the ID or the name.

pm2 stop 0

pm2 stop index

Also, note that pm2 retains a reference to the process so you can restart it.

If you want to delete that process reference, you need to run pm2 delete. You can stop and delete a process in one command with delete.

pm2 delete index

We can also use pm2 to run multiple processes of our application. pm2 will automatically balance the load across those instances.

Multiple processes with pm2 fork mode

pm2 has a ton of configuration options and those are contained in an “ecosystem” file. To create one, run pm2 init. You’ll get something like this…

module.exports = { apps: [ { name: "Express App", script: "index.js", instances: 4, autorestart: true, watch: true, max_memory_restart: "1G", env: { NODE_ENV: "development" }, env_production: { NODE_ENV: "production" } } ] };

I’m going to ignore the “deploy” section in this article because I have no idea what it does.

The “apps” section is where you define the apps you want pm2 to run and monitor. You can run more than one. A lot of these configuration settings are probably self-explanatory. The one that I want to focus on here is the instances setting.

pm2 can run multiple instances of your application. You can pass in a number of instances that you want to run and pm2 will spin up that many. So if we wanted to run 4 instances, we could have the following configuration file.

module.exports = { apps: [ { name: "Express App", script: "index.js", instances: 4, autorestart: true, watch: true, max_memory_restart: "1G", env: { NODE_ENV: "development" }, env_production: { NODE_ENV: "production" } } ] };

Then we just run it with pm2 start.

pm2 is now running in “cluster” mode. Each of these processes is running on a different CPU on my machine, depending on how many cores I have. If we wanted to run a process for each core without knowing how many cores we have, we can just pass the max parameter to the instances value.

{ ... instances: "max", ... }

Let’s find out how many cores I’ve got in this machine.

8 CORES! Holy crap. I’m gonna install Subnautica on my Microsoft issued machine. Don’t tell them I said that.

The good thing about running processes on separate CPU’s is that if you have a process that runs amok and takes up 100% of the CPU, the others will still function. If you pass in more instances than you have cores, pm2 will double up processes on CPU’s as necessary.

You can do a WHOLE lot more with pm2, including monitoring and otherwise wrangling those pesky environment variables.

One other item of note: if for some reason you want pm2 to run your npm start script, you can do that by running npm as the process and passing the -- start. The space before the “start” is super important here.

pm2 start npm -- start

In Azure AppService, we include pm2 by default in the background. If you want to use pm2 in Azure, you don’t need to include it in your package.json file. You can just add an ecosystem file and you’re good to go.

OK! Now that we’ve learned all about pm2, let’s talk about why you may not want to use it and it might indeed be ok to run directly against Node.

Running directly against Node in production

I had some questions on this so I reached out to Tierney Cyren who is part of the enormous orange circle of knowledge, especially when it comes to Node.

Tierney pointed out a few drawbacks to using Node based process managers like pm2.

The main reason is that you shouldn’t use Node to monitor Node. You don’t want to use the thing that you are monitoring to monitor that thing. It’s kind of like you asking my teenage son to supervise himself on a Friday night: Will that end badly? It might, and it might not. But you’re about to find out the hard way.

Tierney recommends that you not have a Node process manager running your application at all. Instead, have something at a higher level which watches multiple separate instances of your application. For example, an ideal setup would be if you had a Kubernetes cluster with your app running on separate containers. Kubernetes can then monitor those containers and if any of them go down, it can bring them back and report on their health.

In this case, you can run directly against Node because you are monitoring at a higher level.

As it turns out, Azure is already doing this. If we don’t push a pm2 ecosystem file to Azure, it will start the application with our package.json file start script and we can run directly against Node.

"scripts": { "start": "node index.js" }

In this case, we are running directly against Node and it’s OK. If the application were to crash, you’ll notice that it comes back. That’s because in Azure, your app runs in a container. Azure is orchestrating the container in which your app is running and knows when it faceplants.

But you still only have one instance here. It takes the container a second to come back online after it crashes meaning that there could be a few seconds of downtime for your users.

Ideally, you would want more than one container running. The solution to this would be to deploy multiple instances of your application to multiple Azure AppService sites and then use Azure Front Door to load balance the apps behind a single IP address. Front Door will know when a container is down and will route traffic to other healthy instances of your application.

Azure Front Door Service | Microsoft Azure

Deliver, protect and track the performance of your globally distributed microservice applications with Azure Front Door…azure.microsoft.com

systemd

Another suggestion that Tierney had is to run Node with systemd. I don’t understand too much (or anything at all) about systemd and I’ve already messed this phrasing up once already, so I’ll let Tierney say it in his own words…

Cette option n'est possible que si vous avez accès à Linux dans votre déploiement et que vous contrôlez la façon dont Node est démarré au niveau du service. Si vous exécutez votre processus Node.js dans une machine virtuelle Linux de longue durée, comme les machines virtuelles Azure, vous êtes dans un bon endroit pour exécuter Node.js avec systemd. Si vous déployez simplement vos fichiers sur un service comme Azure AppService ou Heroku ou si vous exécutez à l'intérieur d'un environnement conteneurisé comme Azure Container Instances, vous devriez probablement éviter cette option.

Exécuter votre application Node.js avec Systemd - Partie 1

Vous avez écrit la prochaine grande application, dans Node, et vous êtes prêt à la lancer sur le monde. Ce qui signifie que vous pouvez… nodesource.com

Threads de travail Node.js

Tierney also wants you to know that Worker Threads are coming in Node. This will allow you to start your app on multiple “workers” (threads) thusly negating the need for something like pm2. Maybe. I don’t know. I didn’t really read the article.

Node.js v11.14.0 Documentation

The worker_threads module enables the use of threads that execute JavaScript in parallel. To access it: const worker =…nodejs.org

Be an Adult

Tierney’s last suggestion was to just handle the error and write some tests like an adult. But who has time for that?

The tiny circle abides

Now you know most of what is in the tiny blue circle. The rest is just useless facts about emo bands and beer.

For more information on pm2, Node and Azure, check out the following resources…

  • //pm2.keymetrics.io/
  • Node.js deployment on VS Code
  • Deploy a simple Node site to Azure