Application NodeJS en tant que service sur GNU/Linux

Bonjour à tous ! Aujourd'hui un article pour présenter une problématique que j'ai rencontrée récemment.

Suite à la mise en production d'une application NodeJS j'avais besoin d'une solution afin de la gérer comme un service. De pouvoir l'arrêter, la lancer, la relancer au démarrage de la machine (avec les reboot automatique pour les mises à jour c'est très pratique).

L'application était lancée de la façon la plus simple qui soit : node /chemin/main.js. Mais cela ne permettait pas de gérer l'application convenablement. N'étant ni développeur, ni expert en NodeJS, je suis parti en quête de réponse...

Rapidement plusieurs solutions étaient proposées :

  • Utiliser pm2
  • Utiliser forever
  • Créer un fichier pour gérer l'application avec systemd

J'ai choisi la 1ère option, qui me semblait être la meilleure pour ma problématique.

PM2 ?

pm2 est un package NodeJS qui permet de gérer les processus et les applications NodeJS pour les applications de production, il fait probablement beaucoup plus que ça, mais je ne l'utilise personnellement que pour gérer mon application et lire les logs.

Toutes les informations ici : https://pm2.keymetrics.io/

Installation et mise en place

Pour information, j'ai utilisé Ubuntu 20.04 pour mon exemple.

Pour l'installer il suffit d'utiliser npm :

  • sudo npm install pm2@latest -g

L’option -g indique à npm d’installer le module pour tout le monde, afin qu’il soit disponible dans tout le système.

Une fois installé, il est déjà possible de lancer votre application avec pm2 :

  • pm2 start /data/http/chemin/appli/dist/app-server/main.js`

Merci de remplacer ce chemin vers le votre.

L'application s'appellera main pour pm2 car le fichier se nomme main.js. Si vous souhaitez la nommer autrement c'est possible avec l'option --name <nom>.

L'application est maintenant lancée. Il suffit maintenant de jouer les commandes suivantes afin d'ajouter l'application au démarrage :

  • pm2 startup

Cette commande va générer tous les éléments afin de permettre de gérer l'application comme un service.

Si cette commande est lancée par un simple utilisateur, le retour de la commande vous demandera d'exécuter afin d'ajouter les droits à l'utilisateur voulu. Si vous exécutez la commande en sudo ou directement depuis root l'application sera lancée en tant que root. Ce qui n'est pas conseillé.

Merci de remplacer chaque balise <user> par le votre.

La commande en question :

  • sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u <user> --hp /home/<user>

Après avoir exécuté la commande il reste à sauvegarder les modifications et à lancer le nouveau service créé :

1pm2 save
2
3sudo systemctl start pm2-<user>
4sudo systemctl enable pm2-<user>
5sudo systemctl status pm2-<user>

A ce moment-là, votre application est capable d'être gérée via pm2 et le service pm2-<user>.

Voici le retour que j'ai de mon côté :

 1user@vm-test:/data/http/chemin/app# systemctl status pm2-user.service
 2 pm2-user.service - PM2 process manager
 3     Loaded: loaded (/etc/systemd/system/pm2-user.service; enabled; vendor preset: enabled)
 4     Active: active (running) since Tue 2022-03-29 11:41:56 CEST; 1s ago
 5       Docs: https://pm2.keymetrics.io/
 6    Process: 49745 ExecStart=/usr/local/lib/node_modules/pm2/bin/pm2 resurrect (code=exited, status=0/SUCCESS)
 7   Main PID: 49773 (PM2 v5.2.0: God)
 8      Tasks: 22 (limit: 9443)
 9     Memory: 203.8M
10     CGroup: /system.slice/pm2-user.service
11             ├─49773 PM2 v5.2.0: God Daemon (/home/user/.pm2)
12             └─49784 node /data/http/chemin/app/dist/app-server/main.js
13
14Mar 29 11:41:56 vm-test pm2[49745]: [PM2] PM2 Successfully daemonized
15Mar 29 11:41:56 vm-test pm2[49745]: [PM2] Resurrecting
16Mar 29 11:41:56 vm-test pm2[49745]: [PM2] Restoring processes located in /home/user/.pm2/dump.pm2
17Mar 29 11:41:56 vm-test pm2[49745]: [PM2] Process /data/http/chemin/app/dist/app-server/main.js restored
18Mar 29 11:41:56 vm-test pm2[49745]: ┌─────┬──────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
19Mar 29 11:41:56 vm-test pm2[49745]:  id   name                  namespace    version  mode     pid       uptime       status     cpu       mem       user      watching 
20Mar 29 11:41:56 vm-test pm2[49745]: ├─────┼──────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
21Mar 29 11:41:56 vm-test pm2[49745]:  0    main                  default      0.0.1    fork     49784     0s      0     online     0%        20.7mb    user      disabled 
22Mar 29 11:41:56 vm-test pm2[49745]: └─────┴──────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
23Mar 29 11:41:56 vm-test systemd[1]: Started PM2 process manager.
24user@vm-test:/data/http/chemin/app# 

A ce moment je pensais avoir terminé, sauf que... En regardant la supervision je vois que l'application retourne une belle : 500 Error.

Error: Failed to lookup view "index" in views directory

Je regarde donc ce que retourne l'application avec la commande pm2 logs main :

 1Error: Failed to lookup view "index" in views directory "/data/http/chemin/www"
 2    at Function.render (/data/http/chemin/app/dist/app-server/main.js:136733:17)
 3    at ServerResponse.render (/data/http/chemin/app/dist/app-server/main.js:252022:7)
 4    at server.get (/data/http/chemin/app/dist/app-server/main.js:400578:13)
 5    at Layer.handle [as handle_request] (/data/http/chemin/app/dist/app-server/main.js:148047:5)
 6    at next (/data/http/chemin/app/dist/app-server/main.js:141081:13)
 7    at Route.dispatch (/data/http/chemin/app/dist/app-server/main.js:141056:3)
 8    at Layer.handle [as handle_request] (/data/http/chemin/app/dist/app-server/main.js:148047:5)
 9    at /data/http/chemin/app/dist/app-server/main.js:422848:22
10    at param (/data/http/chemin/app/dist/app-server/main.js:422921:14)
11    at param (/data/http/chemin/app/dist/app-server/main.js:422932:14)

Cela indique que le working directory n'est pas bon. Quand l'application est lancée depuis un autre dossier que /data/http/chemin/app elle ne fonctionne pas. Il faut donc définir le cwd dans l'application NodeJS.

Après avoir lu la documentation j'ai compris qu'il était possible de faire des fichiers de configuration afind d'entrer ce paramètre.

Pour ça j'ai réalisé les actions suivantes :

1cd /data/http/chemin/app
2pm2 init simple

La commande pm2 init simple va permettre de créer un fichier de configuration afin d'enrichir le main.js ce fichier s'appelle ecosystem.config.js.

Il faut en suite configurer le fichier pour prendre en compte le cwd :

1module.exports = {
2  apps : [{
3    name   : "App Test",
4    script : "/data/http/chemin/app/dist/app-server/main.js",
5    cwd : "/data/http/chemin/app/"
6  }]
7}

Ce fichier permet de dire que :

  • L'app s'appelle App Test
  • Le script NodeJS est ici : /data/http/chemin/app/dist/app-server/main.js
  • Le working directory est ici : /data/http/chemin/app/

Une fois créé, il suffit d'arrêter et de supprimer l'ancienne application avant de la relancer via le nouveau fichier :

1pm2 stop main
2pm2 delete main
3
4pm2 start /data/http/chemin/app/ecosystem.config.js

L'application doit maintenant être visible depuis pm2 et doit fonctionner normalement :

  • pm2 ls
1user@vm-test:/data/http/chemin/app# pm2 ls
2┌─────┬──────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
3│ id  │ name                 │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
4├─────┼──────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
5│ 0   │ App Test             │ default     │ 0.0.1   │ fork    │ 26025    │ 19h    │ 0    │ online    │ 0%       │ 259.4mb  │ user     │ disabled │
6└─────┴──────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

En vérifiant la supervision, l'application ne retourne plus d'erreur 500.

Vous pouvez maintenant redémarrer le serveur par exemple pour vérifier que l'application se lance automatiquement, mais pour moi, le problème était réglé.

Autres commandes

Vérifier les services :

  • pm2 ls

Lancer un service :

  • pm2 start /chemin

Arrêter un service :

  • pm2 stop nom_app ou chemin

Supprimer un service :

  • pm2 delete nom_app ou chemin

Vérifier les logs

  • pm2 logs
  • pm2 logs nom_app

J’espère que cet article vous aura plu, si vous avez des questions ou des remarques sur ce que j’ai pu écrire n’hésitez pas à réagir avec moi par mail ou en commentaire ! N’hésitez pas à me dire également si ce genre d’article vous plaît !

PI : Je suis loin d'être un expert en NodeJS, s'il y a des choses à corriger ou des abérations, n'hésitez pas à revenir vers moi !

Merci pour votre lecture et à bientôt !

Sources