Pourquoi devrais-je utiliser un nœud Proxy inverse if.js est prêt pour la production?
div>
L’année était 2012. PHP et Ruby on Rails ont régné en tant que technologies suprêmes côté serveur pour le rendu d’applications Web. Mais, un nouveau concurrent audacieux a pris d’assaut la communauté — celle qui a réussi à gérer 1M de connexions simultanées. Cette technologie n’était autre que Node.js et a régulièrement gagné en popularité depuis.
Contrairement à la plupart des technologies concurrentes de l’époque, Node.js est venu avec un serveur Web intégré. Avoir ce serveur signifiait que les développeurs pouvaient contourner une myriade de fichiers de configuration tels que php.ini
et une collection hiérarchique de fichiers .htaccess
. Avoir un serveur Web intégré offrait également d’autres commodités, comme la possibilité de traiter les fichiers au fur et à mesure qu’ils étaient téléchargés et la facilité de mise en œuvre de WebSockets.
Chaque nœud de jour.les applications Web alimentées par js gèrent joyeusement des milliards de demandes. La plupart des plus grandes entreprises du monde sont alimentées d’une manière ou d’une autre par Node.js. Pour dire ce nœud.js est prêt pour la production est certainement un euphémisme. Cependant, il y a un conseil qui est vrai depuis Node.création de js: il ne faut pas exposer directement un nœud.js traite le web et devrait plutôt le cacher derrière un proxy inverse. Mais, avant d’examiner les raisons pour lesquelles nous voudrions utiliser un proxy inverse, regardons d’abord ce que l’on est.
Un proxy inverse est essentiellement un type spécial de serveur Web qui reçoit des demandes, les transmet à un autre serveur HTTP ailleurs, reçoit une réponse et transmet la réponse au demandeur d’origine.
Un proxy inverse n’envoie généralement pas la demande exacte, cependant. En règle générale, il modifiera la demande d’une manière ou d’une autre. Par exemple, si le proxy inverse se trouve à www.example.org:80
et qu’il va transmettre la requête à ex.example.org:8080
, il réécrira probablement l’en-tête Host
d’origine pour correspondre à celui de la cible. Il peut également modifier la requête d’autres manières, telles que le nettoyage d’une requête mal formée ou la traduction entre protocoles.
Une fois que le proxy inverse reçoit une réponse, il peut alors traduire cette réponse d’une manière ou d’une autre. Encore une fois, une approche courante consiste à modifier l’en-tête Host
pour qu’il corresponde à la demande d’origine. Le corps des demandes peut également être modifié. Une modification courante consiste à effectuer une compression gzip sur la réponse. Une autre modification courante consiste à activer la prise en charge HTTPS lorsque le service sous-jacent ne parle que HTTP.
Les proxys inverses peuvent également envoyer des requêtes entrantes à plusieurs instances backend. Si un service est exposé à api.example.org
, un proxy inverse peut transmettre des requêtes à api1.internal.example.org
api2
, etc.
Il existe de nombreux proxys inverses différents. Deux des plus populaires sont Nginx et HAProxy. Ces deux outils sont capables d’effectuer une compression gzip et d’ajouter une prise en charge HTTPS, et ils se spécialisent également dans d’autres domaines. Nginx est le plus populaire des deux choix, et possède également d’autres fonctionnalités bénéfiques telles que la possibilité de servir des fichiers statiques à partir d’un système de fichiers, nous l’utiliserons donc comme exemple tout au long de cet article.
Maintenant que nous savons ce qu’est un proxy inverse, nous pouvons maintenant examiner pourquoi nous voudrions en utiliser un avec Node.js.
Pourquoi devrais-je utiliser un proxy inverse ?
La terminaison SSL est l’une des raisons les plus populaires pour lesquelles on utilise un proxy inverse. Changer le protocole de l’application de http
en https
prend un peu plus de travail que d’ajouter un s
. Nœud.js lui-même est capable d’effectuer le chiffrement et le déchiffrement nécessaires pour https
, et peut être configuré pour lire les fichiers de certificats nécessaires.
Cependant, la configuration du protocole utilisé pour communiquer avec notre application et la gestion des certificats SSL qui arrivent à échéance ne sont pas vraiment une préoccupation pour notre application. La vérification des certificats dans une base de code serait non seulement fastidieuse, mais aussi un risque pour la sécurité. L’acquisition de certificats à partir d’un emplacement central au démarrage de l’application comporte également des risques.
Pour cette raison, il est préférable d’effectuer une terminaison SSL en dehors de l’application, généralement dans un proxy inverse. Grâce à des technologies telles que certbot
de Let’s Encrypt, la maintenance des certificats avec Nginx est aussi simple que la configuration d’un travail cron. Une telle tâche peut installer automatiquement de nouveaux certificats et reconfigurer dynamiquement le processus Nginx. Il s’agit d’un processus beaucoup moins perturbateur que, disons, le redémarrage de chaque nœud.instance d’application js.
De plus, en permettant à un proxy inverse d’effectuer la terminaison SSL, cela signifie que seul le code écrit par les auteurs du proxy inverse a accès à votre certificat SSL privé. Cependant, si votre nœud.l’application js gère SSL, alors chaque module tiers utilisé par votre application — même les modules potentiellement malveillants – aura accès à votre certificat SSL privé.
compression gzip
la compression gzip est une autre fonctionnalité que vous devez décharger de l’application vers un proxy inverse. les stratégies de compression gzip sont mieux définies au niveau de l’organisation, au lieu de devoir spécifier et configurer pour chaque application.
Il est préférable d’utiliser une certaine logique pour décider quoi gzip. Par exemple, les fichiers très petits, peut-être inférieurs à 1 ko, ne valent probablement pas la peine d’être compressés car la version compressée de gzip peut parfois être plus grande, ou la surcharge du processeur liée à la décompression du fichier par le client pourrait ne pas en valoir la peine. De plus, lorsqu’il s’agit de données binaires, selon le format, il peut ne pas bénéficier de la compression. gzip est également quelque chose qui ne peut pas être simplement activé ou désactivé, il nécessite d’examiner l’en-tête Accept-Encoding
entrant pour des algorithmes de compression compatibles.
Clustering
JavaScript est un langage à thread unique, et par conséquent, Node.js a toujours été une plate-forme de serveur à un seul thread (bien que le support de thread de travail actuellement expérimental soit disponible dans Node.js v10 vise à changer cela). Cela signifie obtenir autant de débit d’un nœud.l’application js que possible nécessite d’exécuter à peu près le même nombre d’instances que les cœurs de PROCESSEUR.
Nœud.js est livré avec un module cluster
intégré qui peut faire exactement cela. Les requêtes HTTP entrantes seront envoyées à un processus maître puis envoyées aux travailleurs du cluster.
Cependant, la mise à l’échelle dynamique des travailleurs de cluster demanderait un certain effort. Il y a également généralement une surcharge supplémentaire lors de l’exécution d’un nœud supplémentaire.processus js en tant que processus maître de répartition. De plus, la mise à l’échelle des processus sur différentes machines est quelque chose que cluster
ne peut pas faire.
Pour ces raisons, il est parfois préférable d’utiliser un proxy inverse pour envoyer des requêtes au nœud en cours d’exécution.processus js. Ces proxys inverses peuvent être configurés dynamiquement pour pointer vers de nouveaux processus d’application à mesure qu’ils arrivent. En réalité, une application devrait simplement se préoccuper de faire son propre travail, elle ne devrait pas se préoccuper de la gestion de plusieurs copies et de l’envoi des demandes.
Routage d’entreprise
Lorsqu’il s’agit d’applications Web massives, telles que celles construites par des entreprises multi-équipes, il est très utile d’avoir un proxy inverse pour déterminer où transférer les requêtes. Par exemple, les requêtes adressées à example.org/search/*
peuvent être acheminées vers l’application de recherche interne tandis que d’autres requêtes adressées à example.org/profile/*
peuvent être envoyées vers l’application de profil interne.
Un tel outillage permet d’autres fonctionnalités puissantes telles que les sessions collantes, les déploiements Bleu/vert, les tests A/ B, etc. J’ai personnellement travaillé dans une base de code où une telle logique était effectuée dans l’application et cette approche rendait l’application assez difficile à maintenir.
Avantages de performance
Nœud.js est très malléable. Il est capable de servir des actifs statiques à partir d’un système de fichiers, d’effectuer une compression gzip avec des réponses HTTP, est livré avec un support intégré pour HTTPS et de nombreuses autres fonctionnalités. Il a même la possibilité d’exécuter plusieurs instances d’une application et d’effectuer sa propre répartition des requêtes, via le module cluster
.
Et pourtant, en fin de compte, il est dans notre intérêt de laisser un proxy inverse gérer ces opérations pour nous, au lieu d’avoir notre nœud.l’application js le fait. Autre que chacune des raisons énumérées ci-dessus, une autre raison de vouloir effectuer ces opérations en dehors du nœud.js est dû à l’efficacité.
Le cryptage SSL et la compression gzip sont deux opérations fortement liées au processeur. Les outils de proxy inverse dédiés, tels que Nginx et HAProxy, effectuent généralement ces opérations plus rapidement que Node.js. Avoir un serveur Web comme Nginx à lire du contenu statique à partir du disque sera plus rapide que Node.js aussi. Même le clustering peut parfois être plus efficace car un proxy inverse comme Nginx utilisera moins de mémoire et de CPU que celui d’un nœud supplémentaire.processus js.
Mais, ne nous croyez pas sur parole. Courons quelques repères!
Les tests de charge suivants ont été effectués à l’aide de siege
. Nous avons exécuté la commande avec une valeur de concurrence de 10 (10 utilisateurs simultanés faisant une requête) et la commande s’exécuterait jusqu’à ce que 20 000 itérations soient effectuées (pour 200 000 requêtes globales).
Pour vérifier la mémoire, nous exécutons la commande pmap <pid> | grep total
plusieurs fois tout au long de la durée de vie du benchmark puis faisons la moyenne des résultats. Lors de l’exécution de Nginx avec un seul thread de travail, deux instances s’exécutent, l’une étant le maître et l’autre le travailleur. Nous additionnons ensuite les deux valeurs. Lors de l’exécution d’un nœud.cluster js de 2 il y aura 3 processus, l’un étant le maître et les deux autres étant des travailleurs. La colonne mémoire approximative du tableau suivant est une somme totale de chaque Nginx et nœud.processus js pour le test donné.
Voici les résultats du benchmark :
Dans le benchmark node-cluster
nous utilisons 2 travailleurs. Cela signifie qu’il y a 3 nœuds.processus js en cours d’exécution: 1 maître et 2 travailleurs. Dans le benchmark nginx-cluster-node
, nous avons 2 nœuds.processus js en cours d’exécution. Chaque test Nginx a un seul maître Nginx et un seul processus de travail Nginx. Les benchmarks impliquent la lecture d’un fichier à partir du disque, et ni Nginx ni Node.js ont été configurés pour mettre en cache le fichier en mémoire.
Utilisation de Nginx pour effectuer la terminaison SSL pour le nœud.js entraîne une augmentation du débit de ~ 16% (749rps à 865rps). L’utilisation de Nginx pour effectuer une compression gzip entraîne une augmentation du débit de ~ 50% (5 047 rps à 7 590rps). L’utilisation de Nginx pour gérer un cluster de processus a entraîné une pénalité de performance de ~-1% (8 006 rps à 7 908 rps), probablement en raison de la surcharge liée au passage d’une requête supplémentaire sur le périphérique réseau de bouclage.
Essentiellement l’utilisation de la mémoire d’un seul nœud.le processus js est de ~ 600 Mo, tandis que l’utilisation de la mémoire d’un processus Nginx est d’environ ~ 50 Mo. Ceux-ci peuvent fluctuer un peu en fonction des fonctionnalités utilisées, par exemple Node.js utilise un ~ 13 Mo supplémentaire lors de la terminaison SSL, et Nginx utilise un ~ 4 Mo supplémentaire lorsqu’il est utilisé comme verset de proxy inverse servant du contenu statique à partir du système de fichiers. Une chose intéressante à noter est que Nginx utilise une quantité constante de mémoire tout au long de sa durée de vie. Cependant, Nœud.js fluctue constamment en raison de la nature de collecte de déchets de JavaScript.
Voici les versions du logiciel utilisées lors de l’exécution de ce benchmark :
- Nginx:
1.14.2
- Node.js:
10.15.3
- Siege:
3.0.8
Les tests ont été effectués sur une machine avec 16 Go de mémoire, un i7-7500U CPU 4x2.70GHz
, et le noyau Linux 4.19.10
div>. Tous les fichiers nécessaires pour recréer les benchmarks ci-dessus sont disponibles ici:
IntrinsecLabs/nodejs-reverse-proxy-benchmarks.
Code d’application simplifié
Les benchmarks sont sympas et tout, mais à mon avis, les plus grands avantages du déchargement du travail à partir d’un nœud.l’application js à un proxy inverse est celle de la simplicité du code. Nous parvenons à réduire le nombre de lignes de code d’application impératif potentiellement bogué et à l’échanger contre une configuration déclarative. Un sentiment commun parmi les développeurs est qu’ils ont plus confiance dans le code écrit par une équipe externe d’ingénieurs — comme Nginx — que dans le code écrit par eux-mêmes.
Au lieu d’installer et de gérer le middleware de compression gzip et de le maintenir à jour sur différents nœuds.projets js nous pouvons à la place le configurer dans un seul emplacement. Au lieu d’expédier ou de télécharger des certificats SSL et de les ré-acquérir ou de redémarrer les processus d’application, nous pouvons à la place utiliser les outils de gestion des certificats existants. Au lieu d’ajouter des conditions à notre application pour vérifier si un processus est un maître ou un travailleur, nous pouvons le décharger vers un autre outil.
Un proxy inverse permet à notre application de se concentrer sur la logique métier et d’oublier les protocoles et la gestion des processus.