Comment maximiser la performance et la résilience de vos applications Node.js avec PM2 ?

  • Date de parution
  • Date de mise à jour
  • AuteurCarl-Stéphan Parent

Dans l'univers du développement logiciel, garantir la disponibilité et la performance des applications en production relève constamment du défi. Comment s'assurer que nos services restent opérationnels, même face à des imprévus, tout en exploitant au mieux les ressources matérielles disponibles ? Comment simplifier la gestion des processus et le déploiement pour optimiser les flux de travail ? Nous avons précédemment abordé deux questions clefs : quel est le framework backend le plus utilisé ? Et pourquoi utiliser Node.js en backend ? Ce guide explore maintenant les atouts majeurs de PM2, un gestionnaire de processus open source spécifiquement conçu pour les applications Node.js. Nous découvrirons ses multiples facettes, du développement à la production, en détaillant ses mécanismes fondamentaux, son rôle crucial dans la gestion des applications, ses stratégies avancées d'auto-redémarrage et de surveillance, l'exploitation du mode cluster pour maximiser les performances, et enfin ses capacités de déploiement intégré.

1. Comprendre et installer PM2 : le socle de la gestion de processus Node.js.

PM2 est un gestionnaire de processus open source conçu spécifiquement pour les applications Node.js. Il agit comme un gardien, assurant le déploiement, la surveillance des journaux, le contrôle des ressources et la minimisation des temps d'arrêt. Sa capacité à maintenir les applications en ligne, même après la déconnexion de l'utilisateur ou en cas de crash, en fait un outil indispensable pour tout développeur ou ingénieur d'exploitation travaillant avec Node.js.

1.1. Installation de PM2 : les premiers pas.

Pour commencer à utiliser PM2, son installation est simple et s'effectue via un gestionnaire de paquets comme npm (Node Package Manager) ou yarn. Il est fortement recommandé de l'installer globalement pour le rendre accessible depuis n'importe quel répertoire.

Par exemple, avec npm : npm install pm2 --save 

ou 

pour une installation globale : npm install -g pm2

Après l'installation, il est possible de vérifier la version de PM2 avec la commande pm2 --version. 

Cette installation fournit également d'autres exécutables utiles, tels que pm2-dev pour le développement (avec un rechargement automatique des modifications), pm2-runtime et pm2-docker pour les environnements conteneurisés.

1.2. Démarrage rapide d'une application avec PM2 : l'exemple concret.

Pour illustrer le fonctionnement de PM2, il est possible d'utiliser une application Node.js simple. Considérons un serveur Express basique qui écoute sur un port défini par une variable d'environnement.

Un exemple de fichier api.js :

const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; // Utilise le port 3000 par défaut si non spécifié

app.get('/', (req, res) => { res.send("Hello World!\n"); });

app.listen(PORT, () => { console.log(Server is running on port ${PORT}); });

Pour lancer cette application avec PM2, la commande la plus simple est : pm2 start api.js 

Par défaut, PM2 nommera le processus d'après le nom du fichier (api dans cet exemple). Il est possible de spécifier un nom personnalisé avec l'option --name : pm2 start --name 'mon-api' api.js

Après le démarrage, PM2 affiche un tableau récapitulatif des processus gérés, indiquant leur statut, temps d'activité, utilisation des ressources (CPU, mémoire) et d'autres métriques pertinentes.

1.3. La gestion programmatique de PM2 : le contrôle par le code.

PM2 offre également une interface de programmation (API) pour interagir avec le gestionnaire de processus directement depuis le code. Cela permet une automatisation plus poussée et une intégration dans des systèmes de gestion personnalisés.

Les méthodes clés de l'API incluent :

  • pm2.connect([no_daemon_mode], fn) : établit une connexion avec l'instance PM2 locale ou en lance une nouvelle. L'option no_daemon_mode permet d'exécuter PM2 sans démon, ce qui le fait se terminer lorsque le script associé se ferme ;
  • pm2.start(process, fn) : démarre un processus. L'argument process peut être un chemin de script ou un objet de configuration détaillé ;
  • pm2.stop(process, fn) : arrête un processus ;
  • pm2.restart(process, [options], fn) : redémarre un processus ;
  • pm2.reload(process, fn) : recharge un processus. Contrairement à restart, reload tente un rechargement sans interruption (zero-downtime) en mode cluster ;
  • pm2.delete(process, fn) : supprime un processus de la liste de PM2 ;
  • pm2.list(fn) : récupère la liste de tous les processus gérés par PM2 ;
  • pm2.describe(process, fn) : obtient les métadonnées détaillées d'un processus cible ;
  • pm2.killDaemon(fn) : arrête le démon PM2 et tous les processus qu'il gère ;

L'utilisation de pm2.disconnect() est indispensable pour libérer la connexion à PM2 et permettre à l'application de se terminer automatiquement.

2. Optimisation et résilience : stratégies avancées avec PM2.

PM2 ne se limite pas au simple démarrage d'applications ; il offre une multitude de fonctionnalités pour les optimiser et assurer leur résilience en production. Ces fonctionnalités vont du monitoring détaillé aux stratégies d'auto-redémarrage et à la gestion des environnements.

2.1. Surveillance et diagnostic : garder un œil sur les performances.

Une fois les applications lancées, PM2 propose plusieurs commandes pour surveiller leurs performances et obtenir des diagnostics.

2.1.1. Visualisation de l'état des applications.

La commande pm2 list fournit un aperçu de toutes les applications actives sur le serveur, incluant leur ID, nom, mode d'exécution (fork ou cluster), PID (Process Identifier), temps d'activité (uptime), nombre de redémarrages (↺), statut, utilisation du CPU (Central Processing Unit) et de la mémoire (mem). Il est possible de trier cette liste selon différents critères, par exemple : pm2 list --sort memory:desc pour trier par consommation de mémoire décroissante. Pour une vue plus détaillée d'une application spécifique, pm2 show <nom_ou_id_application> affiche toutes les métadonnées du processus.

2.1.2. Tableau de bord interactif et journaux en temps réel.

PM2 intègre un tableau de bord en temps réel via la commande pm2 monit. Ce tableau offre une visualisation dynamique des métriques clefs telles que l'utilisation du CPU et de la mémoire, les journaux d'application et d'autres informations vitales, facilitant le diagnostic rapide des problèmes. La gestion des journaux est également cruciale. PM2 archive automatiquement les logs générés par l'application dans le répertoire $HOME/.pm2/logs. Les sorties standard (stdout) sont stockées dans <app_name>-out.log et les erreurs standard (stderr) dans <app_name>-error.log. La commande pm2 logs <nom_ou_id_application> permet d'afficher les entrées de journal en temps réel, un atout précieux en phase de développement.

2.1.3. Rotation des journaux et configurations personnalisées.

Pour éviter que les fichiers journaux ne deviennent excessivement volumineux, PM2 propose le module pm2-logrotate. Ce module, installé via pm2 install pm2-logrotate, permet une rotation automatique des journaux une fois qu'ils atteignent une taille prédéfinie (par exemple, 10 Mo par défaut), tout en conservant un nombre configurable de fichiers (30 par défaut). Il est possible de personnaliser les emplacements des fichiers de sortie et d'erreur dans le fichier de configuration ecosystem.config.js via les options out_file et error_file.

2.2. Stratégies d'auto-redémarrage et d'environnement : garantir la continuité.

La capacité de PM2 à auto-redémarrer les applications est un avantage majeur pour assurer leur disponibilité.

2.2.1. Redémarrages manuels et automatiques.

Un redémarrage manuel d'une application s'effectue avec pm2 restart <nom_ou_id_application>. PM2 redémarre l'application même si elle se termine intentionnellement ou en cas de crash, un comportement par défaut qui assure une haute disponibilité. Toutefois, une limitation actuelle de PM2 est l'incapacité de modifier le comportement de redémarrage en fonction du code de sortie de l'application, bien que l'option --stop-exit-codes soit documentée, elle semble non fonctionnelle dans les versions récentes.

2.2.2. Configuration des stratégies de redémarrage avec ecosystem.config.js.

Le fichier ecosystem.config.js est central. Il permet de consolider les paramètres de configuration des applications. Il peut être généré avec pm2 init simple. Ce fichier permet de définir des stratégies de redémarrage avancées :

  • max_memory_restart : redémarre l'application si son utilisation de la mémoire dépasse une limite configurée (par exemple, '1G', '512M'). Cela prévient les erreurs de type "mémoire insuffisante" ;
  • cron_restart : planifie les redémarrages de l'application selon une expression cron. Par exemple, '0 */24 * * *' pour un redémarrage quotidien ;
  • restart_delay : introduit un délai en millisecondes avant le redémarrage d'une application après son arrêt ;
  • exp_backoff_restart_delay : applique un délai de redémarrage exponentiel, augmentant progressivement le temps entre les tentatives de redémarrage jusqu'à une limite (par exemple, 15 secondes). Ce délai est réinitialisé si l'application reste en ligne pendant plus de 30 secondes ;
  • max_restarts : définit le nombre maximum de redémarrages instables avant que l'application ne soit considérée comme ayant rencontré une erreur irrécupérable et que son statut passe à "errored" ;
  • min_uptime : spécifie le temps minimum en millisecondes pendant lequel une application doit rester en ligne pour être considérée comme "stable" après un démarrage. Un redémarrage avant ce délai est compté comme "instable" ;
  • autorestart : permet de désactiver complètement les redémarrages automatiques de l'application en le réglant à false ;

Ces options permettent une gestion fine du comportement de l'application en cas de défaillance, adaptant les redémarrages aux besoins spécifiques de chaque service.

2.2.3. Gestion des variables d'environnement.

PM2 simplifie l'isolation des variables d'environnement de développement et de production au sein du même fichier ecosystem.config.js. Les sections env_development et env_production (ou tout autre environnement personnalisé) permettent de définir des variables spécifiques. L'activation d'un environnement se fait via la commande pm2 start ecosystem.config.js --env <nom_environnement>.

3. Scalabilité et déploiement : PM2 en environnement de production.

PM2 est particulièrement performant en environnement de production, notamment grâce à son mode cluster et son système de déploiement intégré, qui facilitent la scalabilité et les mises à jour sans interruption.

3.1. Le mode cluster : exploiter la puissance des processeurs.

Node.js, par nature, est mono-threadé, ce qui signifie qu'il n'utilise qu'un seul cœur de CPU par défaut. Le mode cluster de PM2 permet aux applications Node.js de tirer parti de la pleine puissance des CPU multi-cœurs de l'hôte, en lançant plusieurs instances de l'application sur le même port.

3.1.1. Fonctionnement et configuration.

En mode cluster, PM2 utilise le module cluster de Node.js pour générer des processus enfants (child processes) qui partagent le port du serveur. Pour activer ce mode, il faut définir l'option exec_mode à 'cluster' et instances à 'max' (pour utiliser autant d'instances que de cœurs CPU) ou à un nombre spécifique (par exemple, -1 pour utiliser un cœur de moins que le total disponible).

Exemple dans ecosystem.config.js :

module.exports = { apps: [{ // ... instances: 'max', // ou -1, 2, etc. exec_mode: 'cluster', }], };

Le démarrage se fait avec pm2 start ecosystem.config.js -i max. Cela crée des instances de l'application, chacune avec son propre PID, et PM2 distribue automatiquement les requêtes entrantes entre elles, améliorant ainsi les performances et la résilience.

3.1.2. Communication inter-processus et rechargement à chaud.

En mode cluster, la variable d'environnement NODE_APP_INSTANCE permet de distinguer les processus, ce qui est utile pour exécuter des opérations spécifiques à une instance donnée. Un avantage majeur du mode cluster est la possibilité d'effectuer des rechargements sans interruption (zero-downtime reloads) en production via la commande pm2 reload <nom_application>. Contrairement à pm2 restart qui arrête et redémarre toutes les instances simultanément, reload redémarre les processus séquentiellement, garantissant une disponibilité continue du service. PM2 permet également une mise à l'échelle (scaling) dynamique avec la commande pm2 scale <nom_application> +<nombre> (pour ajouter des instances) ou pm2 scale <nom_application> <nombre> (pour définir un nombre spécifique d'instances).

3.2. Le déploiement intégré : automatiser les mises en production.

PM2 propose un système de déploiement intégré pour faciliter la mise en production des applications sur un ou plusieurs serveurs distants.

3.2.1. Configuration du déploiement.

La section deploy du fichier ecosystem.config.js permet de définir les paramètres de déploiement pour différents environnements (par exemple, production). Les propriétés incluent :

  • user : nom d'utilisateur SSH pour l'authentification sur le serveur distant ;
  • host : tableau d'adresses IP des serveurs distants ;
  • ref : branche et dépôt Git à déployer (par exemple, origin/master) ;
  • repo : URL du dépôt Git (HTTPS ou SSH) ;
  • path : chemin du répertoire sur le serveur distant où le dépôt sera cloné ;
  • post-setup : commandes à exécuter après le clonage initial du dépôt ;
  • post-deploy : commandes à exécuter après chaque déploiement (par exemple, npm install && pm2 reload ecosystem.config.js --env production) ;

Avant le déploiement, il est nécessaire de s'assurer que PM2 est bien installé sur chaque serveur distant et que les permissions nécessaires pour cloner le dépôt Git sont configurées (par exemple, clefs SSH).

3.2.2. Processus de déploiement.

Le processus de déploiement se déroule en plusieurs étapes :

  1. Configuration initiale : pm2 deploy <environnement> setup permet de provisionner le serveur (cloner le dépôt, exécuter les commandes post-setup) ;
  2. Déploiement : pm2 deploy <environnement> effectue le déploiement. L'option -force peut être utilisée en cas de modifications locales non poussées ;
  3. Exécution de commandes distantes : pm2 deploy <environnement> exec "<commande>" permet d'exécuter des commandes sur les serveurs distants sans s'y connecter directement (par exemple, pm2 deploy production exec "pm2 reload mon-api") ;

Il est important de noter que bien que PM2 offre une intégration Docker via pm2-runtime, il est généralement recommandé de ne pas utiliser PM2 si l'application Node.js est déployée avec Docker, car Docker et les orchestrateurs de conteneurs comme Kubernetes offrent déjà des fonctions de gestion de processus.

3.3. Lancement au démarrage du système et résilience.

Pour assurer la résilience de l'application face aux redémarrages ou aux pannes du système, PM2 peut être configuré pour lancer automatiquement les applications enregistrées au démarrage. La commande pm2 startup <init_system> (par exemple, pm2 startup systemd pour les systèmes Linux basés sur systemd) génère un script d'initialisation qui démarre le démon PM2 au démarrage du système. Il est ensuite nécessaire de "sauvegarder" la liste des processus PM2 en cours d'exécution avec pm2 save. Cela crée un "instantané" des processus gérés, permettant à PM2 de les restaurer automatiquement après un redémarrage du système. En cas de problème, la commande pm2 resurrect peut être utilisée pour relancer tous les processus sauvegardés. Pour désactiver le script de démarrage, la commande pm2 unstartup est employée.

PM2 est un gestionnaire de processus d'une robustesse et d'une richesse fonctionnelle impressionnantes qui s’impose comme un outil indispensable pour les développeurs et les ingénieurs DevOps travaillant avec Node.js. Sa simplicité d'installation, sa capacité à automatiser les tâches de gestion, et ses fonctionnalités avancées pour la surveillance, la résilience et la scalabilité en font désormais une solution de choix pour garantir la performance et la disponibilité des applications en production. De la gestion des journaux à la mise en œuvre de stratégies de déploiement sans interruption, PM2 simplifie considérablement le cycle de vie des applications Node.js. Cependant, alors que PM2 excelle dans la gestion des processus Node.js, son utilisation peut être réévaluée dans des environnements conteneurisés lourds comme Docker ou Kubernetes, où ces plateformes offrent déjà des capacités d'orchestration et de gestion des processus. Quels sont les avantages et inconvénients de l'utilisation de PM2 en combinaison avec des architectures de microservices ? Comment PM2 peut-il être intégré dans un pipeline d'intégration continue et de déploiement continu (CI/CD) pour automatiser davantage le déploiement ? Quelles sont les meilleures pratiques pour sécuriser les applications Node.js gérées par PM2 en production ?

Sources :

Parth Dangroshiya "Boost Node.js Performance Using the Cluster Module and PM2" in Medium (01/06/25) [02/06/25] [https://javascript.plainenglish.io/boost-node-js-performance-using-the-cluster-module-and-pm2-8cbee8da9573].

Tông Xuân Hoài "Using pm2 to Manage Node.js Applications" in 2coffeedev  [02/06/25] [https://2coffee.dev/en/articles/using-pm2-to-manage-nodejs-applications].

Ayooluwa Isaiah "Running Node.js Apps with PM2 (Complete Guide)" in BetterStack (26/02/25) [02/06/25] [https://betterstack.com/community/guides/scaling-nodejs/pm2-guide/].

Danilo Oliveira "Building a Rest Api Using Node.js, Pm2, and Docker" in owlcation.com (15/12/23) [02/06/25] [https://owlcation.com/stem/nodejs-pm2-docker].

"PM2 API" in pm2.keymetrics.io [02/06/25] [https://pm2.keymetrics.io/docs/usage/pm2-api/].

"PM2: Applications Nodejs prêtes pour la production en quelques minutes" in ichi.pro [02/06/25] [https://ichi.pro/fr/pm2-applications-nodejs-pretes-pour-la-production-en-quelques-minutes-229683631870218].