NodeJS – Mise en production
La mise en production d’une application NodeJS est une étape importante dans la vie de votre application.
Il existe de nombreuses solutions utilisant forever, upstart, monit ou le script shell LSB personnalisé.
Aucune de ces solutions ne m’a convaincu; du coup la solution que je vous propose est la mise en place d’un script LSB fait en Javascript.
Pré-requit
Pour réaliser correctement ce petit tutoriel, il faut :
- Un utilisateur système “node” avec son répertoire personnel: adduser –shell /sbin/nologin node
- NodeJS installé
- Installer globalement le module simple-daemon : npm install -g simple-daemon
Exemple d’application
L’application que nous souhaitons lancer automatiquement au démarrage du serveur est un simple serveur HTTP.
/home/node/funky-server/funky-server.js
1 2 3 4 5 6 7 | var http = require('http'); http.createServer(function (req, res) { console.log('New client'); res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello Funky World\\n'); }).listen(80); console.log('Server running, listen on port 80'); |
Cette application a de particulier qu’elle écoute toujours sur le port 80 et donc qu’une seule instance ne peut-être exécutée.
De plus, pour ouvrir un socket sur un port inférieur à 1024, il faut les privileges root.
Une fois le serveur sur écoute, les privileges root ne sont plus nécessaires.
Déploiement
Vous remarquerez qu’au sein de l’application, il n’y a aucune gestion de l’utilisateur gérant le processus ou de vérification de l’unicité de l’execution.
Cela sera géré directement par notre Wrapper LSB dont voici le contenu.
/etc/init.d/funky-server.js
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/usr/bin/env node var daemon = require('/usr/local/lib/node_modules/simple-daemon'); //En fonction de votre système, ce chemin peut-être different daemon.simple({ pidfile : '/var/run/funky-server.pid', logfile : '/var/log/funky-server.log', command : process.argv[3], runSync : function () { require ("/home/node/funky-server/funky-server.js"); process.setuid("node"); } }); |
Comme vous le remarquez, une fois le require fait, nous changeons l’utilisateur du processus.
Rendons le fichier executable.
1 | chmod +x /etc/init.d/funky-server.js |
Vous pouvez tester
1 2 3 4 5 6 7 8 | /etc/init.d/funky-server.js start /etc/init.d/funky-server.js status curl -o /dev/null http://localhost/ tail /var/log/funky-server.log /etc/init.d/funky-server.js stop /etc/init.d/funky-server.js status |
Pour ajouter le Wrapper ci-dessus aux scripts de demarrage système, il est préférable de le rendre compatible à votre distribution.
Sous Linux, il faut rajouter des headers pour être conforme SysVInitScript / LSBinitScript Debian
Voici le code complet du script de démarrage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #!/usr/bin/env node ### BEGIN INIT INFO # Provides: funky-server # Required-Start: $local_fs $network $syslog # Required-Stop: $local_fs $network $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start daemon at boot time # Description: Enable service provided by funky-server. ### END INIT INFO var daemon = require('/usr/local/lib/node_modules/simple-daemon'); //En fonction de votre système, ce chemin peut-être different daemon.simple({ pidfile : '/var/run/funky-server.pid', logfile : '/var/log/funky-server.log', command : process.argv[3], runSync : function () { require ("/home/node/funky-server/funky-server.js"); process.setuid("node"); } }); |
Sous Debian & Ubuntu, rajouter les headers suivant pour etre conforme :
1 2 3 4 5 6 7 8 9 10 | #!/usr/bin/env node ### BEGIN INIT INFO # Provides: funky-server # Required-Start: $local_fs $network $syslog # Required-Stop: $local_fs $network $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start daemon at boot time # Description: Enable service provided by funky-server. ### END INIT INFO |
On ajoute notre module au système de démarrage.
Sous RedHat & CentOS
1 | chkconfig --add funky-server.js |
Sous Debian & Ubuntu
1 | updaterc.d default funky-server.js |
Pour aller plus loin
Simple-daemon est un module NodeJS qui est le fork de node-init. A ce module il manquait une fonction synchrone pour lancer le processus et être parfaitement fonctionnel.
En effet, basé sur daemon.node qui est un add-on C avec une restriction importante : “all daemonization should happen on the first tick and not as part of an asynchronous action”.
Vous pouvez coupler cette solution à forever pour que l’application survive à un crash impromptu.
Aussi,
L’ouverture du serveur HTTP est une opération asynchrone, le changement d’utilisateur apparait après la demande d’écoute du serveur mais pas forcement après que le serveur soit sur écoute.
Après lecture du code NodeJS et de la libuv concernant l’écoute d’un serveur HTTP, il me semble que la reservation du socket soit fait dans le même tick que la fonction JS http.Server::listen(). Du moins, pour les systèmes Unix.
Si quelqu’un est apte à me le confirmer, les commentaires sont là pour.