Introduction
Node.js alimente une grande partie des services web modernes, mais la fiabilité en production dépend moins du choix du framework que d’opérations disciplinées. Cet article fournit une checklist des opérations de production Node.js actionnable, avec des exemples pratiques. Il s’adresse aux développeurs, aux ingénieurs DevOps et aux équipes techniques de startups qui doivent passer d’un problème observé à une correction vérifiée sans tâtonnements.
Chaque élément de cette checklist suit le même schéma : identifier la version installée et la topologie, exécuter une observation en lecture seule, définir le résultat attendu et le signal d’échec, apporter la plus petite modification justifiée, vérifier le résultat et conserver un chemin de reprise testé. Tout au long, nous utilisons des espaces réservés explicites et ne montrons jamais de véritables identifiants ni d’identifiants de production sensibles.
L’objectif est la sécurité opérationnelle. Observer avant de modifier, limiter le rayon d’impact, vérifier les résultats et documenter comment récupérer si l’état attendu n’est pas atteint.
Inventaire des versions et de l’environnement
Avant de toucher à quoi que ce soit, sachez exactement ce que vous exécutez et où. Un inventaire des versions prévient l’erreur classique consistant à appliquer une documentation ou des correctifs destinés à une version différente de Node.js. Il vous donne également une base de référence à comparer après un changement ou un incident.
Commandes d’observation en lecture seule
Exécutez ces commandes depuis un shell de production ou via votre outil de gestion de configuration. Elles sont en lecture seule et sûres.
# Version de Node.js
node --version
# Sortie attendue : v20.11.0 (ou votre version prise en charge)
# Détails complets de la plateforme
node -p "process.platform + ' ' + process.arch"
# Sortie attendue : linux x64
# Détails du processus en cours (adapter à votre gestionnaire de processus)
ps aux | grep node
# Sortie attendue : un ou plusieurs processus node avec leurs commandes de démarrage et temps de fonctionnement
# Liste des paquets npm installés globalement (révèle souvent des outils non suivis)
npm ls -g --depth=0
# Sortie attendue : une arborescence de paquets globaux avec leurs versions
Documenter la topologie
Enregistrez les informations suivantes dans un runbook d’opérations partagé :
- Version de Node.js et indicateurs de compilation (si une compilation personnalisée est utilisée)
- Système d’exploitation et architecture
- Gestionnaire de processus (systemd, PM2, Kubernetes, Docker, etc.)
- Nombre d’instances et leur répartition sur les hôtes
- Configuration du reverse proxy ou de l’équilibreur de charge (nginx, HAProxy, équilibreur cloud)
- Dépendances en amont (bases de données, caches, files de messages) et leurs paramètres de connexion
- Variables d’environnement qui affectent le comportement à l’exécution (mais ne stockez jamais de secrets en texte clair)
Un exemple concret : pour une API Express typique derrière nginx sur Ubuntu 22.04, la topologie pourrait ressembler à ceci :
Hôtes : 3 instances EC2 t3.medium
OS : Ubuntu 22.04 LTS
Node.js : v20.11.0
Gestionnaire de processus : PM2 en mode cluster (2 instances par hôte)
Équilibreur de charge : Application Load Balancer AWS -> nginx -> PM2
Base de données : MongoDB 6.0 (primaire + deux réplicas)
Cache : Redis 7.2
File de messages : RabbitMQ 3.12
Plus petite modification justifiée
Si vous trouvez une version non prise en charge (par exemple Node.js 12 qui est en fin de vie), ne la mettez pas à niveau en place pendant le trafic de pointe. Au lieu de cela :
- Épinglez la version actuelle fonctionnelle dans votre manifeste de déploiement.
- Testez la mise à niveau dans un environnement de préproduction qui reflète la production.
- Planifiez une fenêtre de maintenance.
- Effectuez la mise à niveau une instance à la fois derrière l’équilibreur de charge.
- Surveillez les taux d’erreur, la latence et l’utilisation de la mémoire après chaque mise à niveau d’instance.
Chemin de reprise : si la nouvelle version provoque un comportement inattendu, revenez à l’image ou à la version du paquet précédente sur l’instance et documentez l’échec.
Chemin de configuration sécuritaire
Les modifications de configuration sont l’une des principales causes d’incidents de production. Le chemin sécuritaire suit une séquence stricte : observer la configuration actuelle, effectuer une sauvegarde, appliquer la modification à une instance, vérifier, puis déployer progressivement.
Observer la configuration actuelle
Pour les applications Node.js, la configuration se trouve souvent dans des variables d’environnement, des fichiers .env, des fichiers de configuration JSON/YAML ou des arguments de ligne de commande. Ne supposez jamais ; inspectez.
# Afficher les variables d’environnement (masquer les secrets avant de partager)
printenv | sort
# Sortie attendue : une liste triée de variables ; recherchez NODE_ENV, PORT, LOG_LEVEL, etc.
# Si vous utilisez PM2, afficher l’environnement d’un processus spécifique
pm2 env 0
# Sortie attendue : environnement du processus id 0
# Si vous utilisez Docker, inspecter l’environnement du conteneur
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' <container_id>
# Sortie attendue : liste des variables d’environnement dans le conteneur
Sauvegarder la configuration
Avant de modifier, créez une sauvegarde horodatée. Pour une configuration basée sur un fichier :
cp /etc/myapp/config.json /etc/myapp/config.json.backup.$(date +%Y%m%d%H%M%S)
# Vérifiez que la sauvegarde existe
ls -la /etc/myapp/
Si la configuration est stockée dans un gestionnaire de secrets ou un système externe, exportez une copie vers un emplacement sécurisé approprié pour votre organisation.
Appliquer une modification avec vérification
Exemple : changer le niveau de journalisation de info à debug sur une instance pour résoudre un problème.
- Modifiez la variable d’environnement dans votre gestionnaire de processus ou votre fichier
.env. - Redémarrez uniquement cette instance.
- Vérifiez que la nouvelle valeur est active :
# Si vous utilisez systemd
systemctl show myapp --property=Environment
# Sortie attendue : Environment=LOG_LEVEL=debug ...
# Si vous utilisez PM2
pm2 env 0 | grep LOG_LEVEL
# Sortie attendue : LOG_LEVEL=debug
- Vérifiez que l’application se comporte correctement : consultez les journaux, exécutez un endpoint de santé et surveillez les taux d’erreur.
Chemin de reprise : si la modification cause des problèmes, rétablissez la configuration de sauvegarde et redémarrez l’instance. Documentez quelle instance a été modifiée et quand.
Vérification et diagnostics
Après toute modification, vérifiez que le système se comporte comme prévu. Les diagnostics aident à confirmer la santé et à isoler les problèmes.
Contrôles de santé et métriques
Chaque service Node.js en production devrait exposer un endpoint de santé. Un exemple simple avec Express :
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok', uptime: process.uptime() });
});
Vérifiez qu’il répond :
curl -s http://localhost:3000/health
# Sortie attendue : {"status":"ok","uptime":123.45}
Si le contrôle de santé échoue, capturez des données de diagnostic :
# Vérifier l’utilisation CPU et mémoire du processus node
ps -p <pid> -o %cpu,%mem,cmd
# Sortie attendue : pourcentages CPU et mémoire pour le processus
# Prendre un instantané du tas si la mémoire est élevée (nécessite l’inspecteur)
node --heapsnapshot-signal=SIGUSR2 --inspect app.js
# Ensuite envoyez SIGUSR2 au processus pour écrire un instantané
kill -USR2 <pid>
Analyse des journaux
Recherchez dans les journaux des erreurs ou des anomalies :
# Si les journaux sont écrits dans un fichier
tail -n 100 /var/log/myapp/app.log | grep -i error
# Sortie attendue : lignes d’erreur récentes, le cas échéant
# Si vous utilisez journald
journalctl -u myapp --since "1 hour ago" | grep -i error
# Sortie attendue : erreurs de la dernière heure
Comparez le taux d’erreur avant et après la modification. Utilisez un outil de surveillance comme un endpoint de métriques Prometheus, Datadog ou New Relic pour suivre le nombre d’erreurs, les percentiles de latence et la saturation.
Commandes de diagnostic courantes
node --trace-warnings app.jspour obtenir des traces de pile pour les avertissementsnode --abort-on-uncaught-exception app.jspour générer des core dumps sur les erreurs fatales (utile pour le débogage post-mortem)NODE_DEBUG=module,http node app.jspour voir la sortie de débogage interne de modules spécifiésstrace -p <pid>pour voir les appels système (à utiliser avec prudence en production en raison de la surcharge)
Modes de défaillance et reprise
Même avec des opérations soignées, des défaillances surviennent. Planifiez-les.
Modes de défaillance courants
- Promesses rejetées non gérées font planter le processus. Dans Node.js 15+, les rejets non gérés lèvent et quittent par défaut. Atténuez ce risque en ajoutant un gestionnaire global qui journalise et éventuellement sort proprement :
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// Optionnel : process.exit(1);
});
- Fuites de mémoire font croître le processus jusqu’à ce que le tueur OOM le termine. Utilisez
--max-old-space-sizepour définir une limite et surveillez le RSS. Si une fuite est suspectée, prenez des instantanés du tas au fil du temps et comparez.
- Blocage de la boucle d’événements rend le service inerte. Diagnostiquez avec des outils comme
clinic doctorou le profileur intégré--profpour profiler l’utilisation du CPU.
- Descripteurs de fichiers épuisés peuvent survenir avec trop de sockets ou de fichiers ouverts. Vérifiez les limites :
ulimit -n
# Sortie attendue : limite actuelle des descripteurs de fichiers
# Augmentez dans /etc/security/limits.conf si nécessaire
- Épuisement du pool de connexions de base de données dû à une taille de pool mal configurée ou à des connexions qui fuient. Surveillez les métriques d’utilisation du pool de votre pilote de base de données (par exemple
pg.Pooldanspga des événements pour acquire et release).
Procédures de reprise
Pour chaque mode de défaillance, définissez un runbook de reprise. Exemple pour un processus planté :
- Détecter : le processus est arrêté (le contrôle de santé échoue, une alerte de surveillance se déclenche).
- Diagnostiquer : vérifiez le code de sortie et les journaux.
systemctl status myappoupm2 logs --err. - Redémarrer : si sûr, redémarrez via votre gestionnaire de processus. Assurez-vous que le redémarrage automatique est configuré (par exemple
restart: alwaysdans Docker,Restart=alwaysdans systemd). - Vérifier : assurez-vous que l’endpoint de santé répond et que le trafic circule.
- Cause racine : une fois le service rétabli, analysez les journaux et les métriques pour trouver pourquoi il a planté.
Pour une fuite de mémoire, la reprise immédiate consiste à redémarrer le processus. La correction à long terme : trouver et corriger la fuite, puis déployer.
Documentez toujours les étapes de reprise dans le runbook et testez-les lors de journées de jeu ou d’exercices planifiés.
Checklist des opérations
Voici une checklist consolidée pour les opérations de routine et la réponse aux incidents. Chaque élément nomme un responsable unique et une cadence de révision.
| # | Élément de la checklist | Responsable | Fréquence | Commande de vérification / Signal |
|---|---|---|---|---|
| 1 | Vérifier que la version de Node.js est prise en charge | Ingénieur DevOps (Anna Singh) | Mensuelle | node --version correspond à la plage prise en charge |
| 2 | Vérifier le temps de fonctionnement et les redémarrages du processus | SRE d’astreinte (Mark Lee) | Quotidienne | pm2 ls ou systemctl status myapp montre un uptime stable |
| 3 | Examiner les métriques de taux d’erreur et de latence | Responsable backend (Priya Shah) | Hebdomadaire | Les seuils du tableau de bord ne sont pas dépassés ; aucune nouvelle alerte |
| 4 | Faire tourner les secrets et les clés API | Ingénieur sécurité (David Kim) | Trimestrielle | Confirmer que les anciennes clés sont invalides, les nouvelles fonctionnent |
| 5 | Tester la procédure de sauvegarde et de restauration | Administrateur de base de données (Chris Chen) | Bimensuelle | Restaurer une sauvegarde en préproduction et vérifier l’intégrité des données |
| 6 | Mettre à jour les dépendances avec les correctifs de sécurité | Ingénieur DevOps (Anna Singh) | Hebdomadaire | npm audit renvoie zéro vulnérabilité élevée/critique |
| 7 | Examiner l’historique des modifications de configuration | Responsable technique (Miguel Lopez) | Mensuelle | Le journal Git ne montre que les modifications approuvées |
| 8 | Exécuter un test de charge en préproduction | Ingénieur QA (Sarah Jones) | Avant les versions majeures | Temps de réponse < 200 ms à 1000 RPS, taux d’erreur < 0,1 % |
| 9 | Valider les alertes de surveillance et les canaux de notification | SRE d’astreinte (Mark Lee) | Mensuelle | Tester le déclenchement d’une alerte et la réception du message |
| 10 | Vérifier l’espace disque et la rotation des journaux | Administrateur système (Tom Wilson) | Hebdomadaire | Utilisation du disque < 80 %, journaux correctement renouvelés |
Utiliser la checklist
- Responsable : une personne est redevable de chaque élément, pas toute une équipe. Si un élément ne peut être réalisé, ce responsable fait remonter.
- Fréquence : certains éléments sont quotidiens, d’autres mensuels ou trimestriels. Ajustez en fonction de votre profil de risque.
- Vérification : chaque élément a une commande ou un signal concret pour vérifier l’achèvement. Pas de « vérifier la santé » vague sans résultat mesurable.
Pièges courants et comment les éviter
Même les équipes expérimentées tombent dans ces pièges. Voici les plus fréquents et comment les éviter ou s’en remettre.
1. Utiliser une version de Node.js non prise en charge
Pourquoi cela arrive : les équipes reportent les mises à niveau parce que l’application fonctionne, et personne ne suit les dates de fin de vie.
Évitement : abonnez-vous aux annonces de versions de Node.js, définissez des rappels de calendrier et planifiez les mises à niveau bien avant la fin de vie.
Reprise : planifiez une fenêtre de maintenance, testez la mise à niveau en préproduction et déployez progressivement. Ne sautez pas plusieurs versions majeures à la fois ; mettez à niveau par étapes.
2. Journaliser des secrets dans les messages d’erreur ou les journaux
Pourquoi cela arrive : les développeurs ajoutent des journaux de débogage qui incluent des en-têtes de requête ou des variables d’environnement, et ils se glissent dans les journaux de production.
Évitement : utilisez une bibliothèque de journalisation qui prend en charge la rédaction (par exemple pino avec l’option redact). Examinez les journaux en préproduction pour détecter les données sensibles.
Reprise : faites immédiatement tourner les secrets exposés, purgez les journaux si possible et mettez à jour le code de journalisation pour masquer.
3. Ne pas définir de limites mémoire dans les conteneurs
Pourquoi cela arrive : Node.js voit la mémoire totale de l’hôte, pas la limite du conteneur, et peut allouer trop de tas, provoquant des tuages OOM.
Évitement : définissez --max-old-space-size en fonction de la limite mémoire du conteneur, ou utilisez NODE_OPTIONS=--max-old-space-size=.... Surveillez l’utilisation de la mémoire.
Reprise : redémarrez avec des limites ajustées. Réglez les paramètres de GC si nécessaire.
4. Ignorer les avertissements de dépréciation
Pourquoi cela arrive : les avertissements sont faciles à ignorer, mais les API dépréciées peuvent être supprimées dans la prochaine version majeure, cassant votre application.
Évitement : exécutez avec --throw-deprecation en CI pour faire échouer les tests sur les avertissements de dépréciation. Corrigez-les avant la mise à niveau.
Reprise : si vous rencontrez une suppression, consultez le guide de migration de Node.js pour la version spécifique et mettez à jour le code en conséquence.
5. Absence de gestion d’arrêt gracieux
Pourquoi cela arrive : de nombreuses applications quittent simplement sur SIGTERM, abandonnant les requêtes en cours.
Évitement : implémentez un arrêt gracieux : interceptez SIGTERM/SIGINT, cessez d’accepter de nouvelles connexions, terminez les requêtes en attente, puis quittez.
const server = app.listen(3000);
process.on('SIGTERM', () => {
console.log('SIGTERM reçu, arrêt gracieux en cours');
server.close(() => {
console.log('Serveur HTTP fermé');
// fermer les connexions à la base de données, etc.
process.exit(0);
});
// Forcer l’arrêt après 30 s
setTimeout(() => {
console.error('Impossible de fermer les connexions à temps, arrêt forcé');
process.exit(1);
}, 30000);
});
Reprise : lors du déploiement, assurez-vous que votre orchestrateur envoie SIGTERM et attend la sortie avant SIGKILL. Si des requêtes sont encore abandonnées, ajustez le délai d’attente.
6. Négliger les limites de descripteurs de fichiers
Pourquoi cela arrive : les limites par défaut peuvent être trop basses pour les services à haute concurrence, provoquant des erreurs EMFILE.
Évitement : définissez ulimit -n 65535 dans votre gestionnaire de processus ou la spécification du conteneur. Surveillez les descripteurs de fichiers ouverts.
Reprise : augmentez la limite et redémarrez le processus. Recherchez si des descripteurs de fichiers fuient (par exemple des connexions non fermées).
Conclusion
Une checklist des opérations de production Node.js n’est utile que si chaque recommandation est limitée à une version, observable et réversible lorsque la technologie le permet. Copier une commande sans vérifier les prérequis et la sortie attendue n’est pas une procédure d’opérations.
Comme prochaine étape, choisissez une vérification à faible risque de cet article, enregistrez l’état actuel, exécutez la vérification documentée, comparez le résultat avec le signal attendu et examinez les dépendances telles que l’API Express, MongoDB et Redis. Ensuite, étendez à la checklist complète, attribuez des responsables et définissez des cadences de révision.
Un flux de travail technique fiable rend les défaillances visibles, protège les valeurs sensibles, limite les modifications à la ressource prévue et définit la vérification de reprise avant qu’un incident ne force la décision. Avec ces pratiques, vous pouvez exploiter Node.js en production avec confiance.