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

Parfois, je me demande si je sais grand-chose.

Il y a quelques semaines à peine, je parlais à un ami qui a mentionné de manière désinvolte, "vous n'exécuteriez jamais une application directement sur Node en production".

J'ai hoché vigoureusement la tête pour signaler que j'aussi ne courrait jamais contre Node en production parce que… hahaha… tout le monde le sait. Mais je ne le savais pas ! Aurais-je dû 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 plutôt comme ça…

J'aime tellement ce schéma parce que je veux qu'il soit vrai. Je ne veux pas passer le reste de ma vie comme un petit point bleu d'insignifiance qui rétrécit.

SI DRAMATIQUE. Blâmer Pandore. Je ne contrôle pas ce qui se joue ensuite pendant que j'écris cet article et Dashboard Confessional est une sacrée drogue.

Eh bien, en supposant que le diagramme d'Alicia est vrai, je voudrais partager avec vous ce que je maintenant savoir comment exécuter 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 "n'exécutez jamais d'applications directement sur Node en production".

Ne jamais exécuter directement sur Node en production

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

Supposons que nous ayons 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 http://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.json fichier.

"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 de 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 ce problème, nous utilisons généralement une sorte de gestionnaire de processus de nœud comme supervisor ou nodemon . Ces packages surveilleront notre projet et redémarreront notre serveur chaque fois que nous apporterons des modifications. J'ai l'habitude de faire ça comme ça…

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

Ensuite, je lance npm run dev . Notez que j'utilise npx supervisor ici qui me permet d'utiliser le supervisor package sans avoir à l'installer. Je ❤️ 2019. Surtout.

Notre autre problème est que nous courons 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 http://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 exécutons cela directement sur Node avec npm start et accédez au read endpoint, nous obtenons une erreur car ce fichier n'existe pas.

Lequel - pas grave, n'est-ce pas? C'est une erreur. Ça arrive.

NON. Grosse affaire. Si vous revenez à votre terminal, vous verrez que l'application est complètement arrêtée.

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 bloqué 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 supervisor ou nodemon en 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 le reste "JavaScript", vous l'installez (globalement) à partir de npm — ou vous pouvez simplement utiliser npx encore. Je ne veux pas vous dire comment vivre votre vie.

Il existe de nombreuses façons d'exécuter votre application avec pm2. Le moyen le plus simple est d'appeler simplement pm2 start sur votre 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…

C'est notre processus exécuté en arrière-plan surveillé par pm2. Si vous visitez le read endpoint et planter l'application, pm2 la redémarrera automatiquement. Vous ne verrez rien de tout cela dans le terminal car il s'exécute en arrière-plan. Si vous voulez regarder pm2 faire son travail, vous devez exécuter pm2 log 0 . Le 0 est l'ID du processus pour lequel nous voulons voir les journaux.

On y va ! Vous pouvez voir pm2 redémarrer l'application lorsqu'elle tombe en panne à cause de notre erreur non gérée.

Nous pouvons également extraire notre commande dev et avoir des fichiers de surveillance pm2 pour nous et redémarrer en cas de modification.

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

Notez que parce que pm2 exécute des choses en arrière-plan, vous ne pouvez pas simplement ctrl+c votre sortie d'un processus pm2 en cours d'exécution. Il faut l'arrêter en passant l'ID ou le nom.

pm2 stop 0

pm2 stop index

Notez également que pm2 conserve une référence au processus afin que vous puissiez le redémarrer.

Si vous souhaitez supprimer cette référence de processus, vous devez exécuter pm2 delete . Vous pouvez arrêter et supprimer un processus en une seule commande avec delete .

pm2 delete index

Nous pouvons également utiliser pm2 pour exécuter plusieurs processus de notre application. pm2 équilibrera automatiquement la charge sur ces instances.

Plusieurs processus avec le mode fork pm2

pm2 a une tonne d'options de configuration et celles-ci sont contenues dans un fichier "écosystème". Pour en créer un, exécutez pm2 init . Vous obtiendrez quelque chose comme ça…

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"
      }
    }
  ]
};

Je vais ignorer la section "déploiement" de cet article car je n'ai aucune idée de ce qu'elle fait.

La section "applications" est l'endroit où vous définissez les applications que vous souhaitez que pm2 exécute et surveille. Vous pouvez en exécuter plusieurs. Beaucoup de ces paramètres de configuration sont probablement explicites. Celui sur lequel je veux me concentrer ici est les instances réglage.

pm2 peut exécuter plusieurs instances de votre application. Vous pouvez transmettre un certain nombre d'instances que vous souhaitez exécuter et pm2 en créera autant. Donc, si nous voulions exécuter 4 instances, nous pourrions avoir le fichier de configuration suivant.

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"
      }
    }
  ]
};

Ensuite, nous l'exécutons simplement avec pm2 start .

pm2 fonctionne maintenant en mode "cluster". Chacun de ces processus s'exécute sur un processeur différent sur ma machine, en fonction du nombre de cœurs dont je dispose. Si nous voulions exécuter un processus pour chaque cœur sans savoir combien de cœurs nous avons, nous pouvons simplement passer le max paramètre au instances valeur.

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

Voyons combien de cœurs j'ai dans cette machine.

8 CŒURS ! Putain de merde. Je vais installer Subnautica sur ma machine Microsoft. Ne leur dites pas que j'ai dit ça.

La bonne chose à propos de l'exécution de processus sur des processeurs séparés est que si vous avez un processus qui s'exécute de manière incontrôlable et occupe 100% du processeur, les autres fonctionneront toujours. Si vous passez plus d'instances que vous n'avez de cœurs, pm2 doublera les processus sur les CPU si nécessaire.

Vous pouvez faire BEAUCOUP plus avec pm2, y compris la surveillance et la manipulation de ces variables d'environnement embêtantes.

Un autre élément à noter :si pour une raison quelconque vous souhaitez que pm2 exécute votre npm start script, vous pouvez le faire en exécutant npm en tant que processus et en passant le -- start . L'espace avant le "début" est super important ici.

pm2 start npm -- start

Dans Azure AppService, nous incluons pm2 par défaut en arrière-plan. Si vous souhaitez utiliser pm2 dans Azure, vous n'avez pas besoin de l'inclure dans votre package.json dossier. Vous pouvez simplement ajouter un fichier d'écosystème et vous êtes prêt à partir.

D'ACCORD! Maintenant que nous avons tout appris sur pm2, parlons des raisons pour lesquelles vous ne voudrez peut-être pas l'utiliser et il pourrait en effet être acceptable de l'exécuter directement sur Node.

Exécuter directement sur Node en production

J'avais des questions à ce sujet, alors j'ai contacté Tierney Cyren qui fait partie de l'énorme cercle orange de la connaissance, en particulier en ce qui concerne Node.

Tierney a souligné quelques inconvénients à l'utilisation de gestionnaires de processus basés sur des nœuds comme pm2.

La raison principale est que vous ne devriez pas utiliser Node pour surveiller Node. Vous ne voulez pas utiliser la chose que vous surveillez pour surveiller cette chose. C'est un peu comme si vous demandiez à mon fils adolescent de se surveiller un vendredi soir :est-ce que ça finira mal ? C'est possible, et ce n'est peut-être pas le cas. Mais vous êtes sur le point de le découvrir à vos dépens.

Tierney vous recommande de ne pas avoir de gestionnaire de processus de nœud exécutant votre application. Au lieu de cela, ayez quelque chose à un niveau supérieur qui surveille plusieurs instances distinctes de votre application. Par exemple, une configuration idéale serait si vous aviez un cluster Kubernetes avec votre application exécutée sur des conteneurs séparés. Kubernetes peut alors surveiller ces conteneurs et si l'un d'entre eux tombe en panne, il peut les ramener et rendre compte de leur état.

Dans ce cas, vous pouvez exécuter directement sur Node car vous surveillez à un niveau supérieur.

Il s'avère qu'Azure le fait déjà. Si nous ne poussons pas un fichier d'écosystème pm2 vers Azure, il démarrera l'application avec notre package.json script de démarrage de fichier et nous pouvons exécuter directement sur Node.

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

Dans ce cas, nous courons directement contre Node et c'est OK. Si l'application devait planter, vous remarquerez qu'elle revient. En effet, dans Azure, votre application s'exécute dans un conteneur. Azure orchestre le conteneur dans lequel votre application s'exécute et sait quand elle s'exécute.

Mais vous n'avez toujours qu'une seule instance ici. Il faut une seconde au conteneur pour se remettre en ligne après un plantage, ce qui peut entraîner quelques secondes d'indisponibilité pour vos utilisateurs.

Idéalement, vous voudriez plus d'un conteneur en cours d'exécution. La solution consisterait à déployer plusieurs instances de votre application sur plusieurs sites Azure AppService, puis à utiliser Azure Front Door pour équilibrer la charge des applications derrière une seule adresse IP. Front Door saura quand un conteneur est en panne et acheminera le trafic vers d'autres instances saines de votre application.

Service de porte d'entrée Azure | Microsoft Azure
Fournissez, protégez et suivez les performances de vos applications de microservices distribuées dans le monde entier avec Azure Front Door… azure.microsoft.com

systemd

Une autre suggestion de Tierney est d'exécuter Node avec systemd . Je ne comprends pas trop (ou rien du tout) à propos de systemd et j'ai déjà foiré cette formulation une fois déjà, alors je vais laisser Tierney le dire dans ses propres mots…

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 bien placé 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 dans 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 veut également que vous sachiez que les Worker Threads arrivent dans Node. Cela vous permettra de démarrer votre application sur plusieurs "travailleurs" (threads), annulant ainsi le besoin de quelque chose comme pm2. Peut-être. Je ne sais pas. Je n'ai pas vraiment lu l'article.

Documentation Node.js v11.14.0
Le module worker_threads permet l'utilisation de threads qui exécutent JavaScript en parallèle. Pour y accéder :const worker =… nodejs.org

Être un adulte

La dernière suggestion de Tierney était de simplement gérer l'erreur et d'écrire des tests comme un adulte. Mais qui a le temps pour ça ?

Le petit cercle demeure

Maintenant, vous savez la plupart de ce qui se trouve dans le petit cercle bleu. Le reste n'est que faits inutiles sur les groupes emo et la bière.

Pour plus d'informations sur pm2, Node et Azure, consultez les ressources suivantes…

  • http://pm2.keymetrics.io/
  • Déploiement de Node.js sur VS Code
  • Déployer un site Node simple sur Azure