
initd-foreverでNode.jsアプリをデーモン化する
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
ども、大瀧です。
最近Node.jsで作ったアプリをシステム起動時に自動起動させることが良くあるのですが、/etc/rc.localにnode <SCRIPT_NAME>と直書きするのも使い勝手が良くないかなと思い、foreverとSysVinitを連携させるinitd-foreverというツールを試してみたのでレポートします。
initd-foreverとは
Node.jsのプロセスマネージャーとしてはforeverが著名ですが、Linuxの枯れたシステム管理サービスのSysVinitからforeverをキックするスクリプトを生成するのがinitd-foreverです。foreverと同じく、npmでインストールします。
$ npm install -g forever initd-forever npm WARN optional dep failed, continuing fsevents@1.0.6 /home/vagrant/.nvm/versions/node/v0.12.9/bin/initd-forever -> /home/vagrant/.nvm/versions/node/v0.12.9/lib/node_modules/initd-forever/bin/initd-forever.js /home/vagrant/.nvm/versions/node/v0.12.9/bin/forever -> /home/vagrant/.nvm/versions/node/v0.12.9/lib/node_modules/forever/bin/forever initd-forever@0.1.8 /home/vagrant/.nvm/versions/node/v0.12.9/lib/node_modules/initd-forever ├── commander@2.3.0 ├── colors@1.1.2 └── swig@1.4.2 (optimist@0.6.1, uglify-js@2.4.24) forever@0.15.1 /home/vagrant/.nvm/versions/node/v0.12.9/lib/node_modules/forever ├── path-is-absolute@1.0.0 ├── object-assign@3.0.0 ├── colors@0.6.2 ├── clone@1.0.2 ├── timespan@2.3.0 ├── optimist@0.6.1 (wordwrap@0.0.3, minimist@0.0.10) ├── nssocket@0.5.3 (eventemitter2@0.4.14, lazy@1.0.11) ├── shush@1.0.0 (strip-json-comments@0.1.3, caller@0.0.1) ├── cliff@0.1.10 (eyes@0.1.8, colors@1.0.3) ├── utile@0.2.1 (deep-equal@1.0.1, ncp@0.4.2, async@0.2.10, i@0.3.3, mkdirp@0.5.1, rimraf@2.4.4) ├── winston@0.8.3 (cycle@1.0.3, eyes@0.1.8, stack-trace@0.0.9, isstream@0.1.2, async@0.2.10, pkginfo@0.3.1) ├── nconf@0.6.9 (ini@1.3.4, async@0.2.9, optimist@0.6.0) ├── prettyjson@1.1.3 (colors@1.1.2, minimist@1.2.0) ├── flatiron@0.4.3 (optimist@0.6.0, director@1.2.7, prompt@0.2.14, broadway@0.3.6) └── forever-monitor@1.6.0 (minimatch@2.0.10, ps-tree@0.0.3, broadway@0.3.6, chokidar@1.4.1) $
initd-foreverコマンドで実行します。まずは--helpオプションでヘルプを見てみましょう。
$ initd-forever --help
Usage: initd-forever [options]
Options:
-h, --help output usage information
-V, --version output the version number
-a, --app [path] Path to node.js main file
-c, --command [value] Command to execute on main file
-e, --env [value] Export NODE_ENV with value
-l, --logfile [path] Logs the daemon output to LOGFILE
-n, --name [value] Application name
-p, --pidfile [path] The pid file
-m, --monit [value] Generate the monit script file with the listen port number
-f, --forever [value] The location of forever
$
foreverのオプションと同じような項目が並んでいますね。サンプル/home/vagrant/index.jsを用意して、スクリプトを生成してみます。
$ initd-forever -a /home/vagrant/index.js -n samplenode Script daemon file saved to samplenode $ ls index.js samplenode $
カレントディレクトリに、-nで指定したサービス名のスクリプトが生成されました。中身はこんな感じです。
#!/bin/bash
### BEGIN INIT INFO
# If you wish the Daemon to be lauched at boot / stopped at shutdown :
#
# On Debian-based distributions:
# INSTALL : update-rc.d scriptname defaults
# (UNINSTALL : update-rc.d -f scriptname remove)
#
# On RedHat-based distributions (CentOS, OpenSUSE...):
# INSTALL : chkconfig --level 35 scriptname on
# (UNINSTALL : chkconfig --level 35 scriptname off)
#
# chkconfig: 2345 90 60
# Provides: /home/vagrant/index.js
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: forever running /home/vagrant/index.js
# Description: /home/vagrant/index.js
### END INIT INFO
#
# initd a node app
# Based on a script posted by https://gist.github.com/jinze at https://gist.github.com/3748766
#
if [ -e /lib/lsb/init-functions ]; then
# LSB source function library.
. /lib/lsb/init-functions
fi;
pidFile="/var/run/samplenode.pid"
logFile="/var/run/samplenode.log"
command="node"
nodeApp="/home/vagrant/index.js"
foreverApp="forever"
start() {
echo "Starting $nodeApp"
# Notice that we change the PATH because on reboot
# the PATH does not include the path to node.
# Launching forever with a full path
# does not work unless we set the PATH.
PATH=/usr/local/bin:$PATH
export NODE_ENV=production
#PORT=80
$foreverApp start --pidFile $pidFile -l $logFile -a -d -c "$command" $nodeApp
RETVAL=$?
}
restart() {
echo -n "Restarting $nodeApp"
$foreverApp restart $nodeApp
RETVAL=$?
}
stop() {
echo -n "Shutting down $nodeApp"
$foreverApp stop $nodeApp
RETVAL=$?
}
status() {
echo -n "Status $nodeApp"
$foreverApp list
RETVAL=$?
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status
;;
restart)
restart
;;
*)
echo "Usage: {start|stop|status|restart}"
exit 1
;;
esac
exit $RETVAL
5〜11行目にDebianとRedHatそれぞれのコメントがありますので、SysVinitが採用されている多くのディストリビューションで使えそうですね。今回はDebian Wheezyを利用しているので、/etc/init.dにスクリプトを配置し、insservコマンドでサービスの有効化を行います。
$ sudo mv samplenode /etc/init.d/ $ chmod +x /etc/init.d/samplenode $ sudo insserv -d samplenode $ ls /etc/rc?.d/*samplenode /etc/rc0.d/K01samplenode /etc/rc2.d/S17samplenode /etc/rc4.d/S17samplenode /etc/rc6.d/K01samplenode /etc/rc1.d/K01samplenode /etc/rc3.d/S17samplenode /etc/rc5.d/S17samplenode $
ランレベル2〜5でS17samplenodeとなっており、有効になっていることがわかりますね。
いろいろ補足
自動生成されたスクリプトではいくつか不備がありました。フォローしていきます。
nvm環境の場合
nvm環境ではnvmの初期化スクリプトの読み込みが必要ですので、35行目あたりに追加すると良いでしょう。
pidFile="/var/run/samplenode.pid" logFile="/var/run/samplenode.log" # load nvm export NVM_DIR="/home/vagrant/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm command="node" nodeApp="/home/vagrant/index.js"
statusやstopを動くようにする
生成されたスクリプトではstartは正常に動作するのですが、statusやstopが動きません。これは、foreverのプロセス管理ファイルを格納するディレクトリが設定されていないためのようなので、ディレクトリを作成し、環境変数FOREVER_ROOTをスクリプト内に指定します。
$ sudo mkdir /etc/forever
# set FOREVER_ROOT export FOREVER_ROOT="/etc/forever"
依存サービスの調整
実行するアプリケーションによっては他のサービスに依存する場合があると思います。Debian/UbuntuであればRequired-Start/Required-Stopにサービス名を追加しましょう。例えばアプリケーションからBLEデバイスにアクセスするのであればbluetoothサービスに依存するので、以下のように追記します。
# Required-Start: $remote_fs $syslog bluetooth # Required-Stop: $remote_fs $syslog bluetooth
こうすることで、bluetoothサービスが起動した後でアプリケーションが実行されるよう、/etc/rc?.d/以下のファイルの番号が調整されます。
追記前
$ ls /etc/rc2.d/ README S13rpcbind S16rsyslog S17acpid S17cron S17exim4 S17samplenode S18avahi-daemon S19bootlogs S19saned S20rmnologin S01motd S14nfs-common S16sudo S17atd S17dbus S17rsync S17ssh S18bluetooth S19cups S20rc.local $
追記後
$ sudo insserv -r samplenode $ sudo insserv -d samplenode $ ls /etc/rc2.d/ README S13rpcbind S16rsyslog S17acpid S17cron S17exim4 S17ssh S18bluetooth S19cups S19saned S20rmnologin S01motd S14nfs-common S16sudo S17atd S17dbus S17rsync S18avahi-daemon S19bootlogs S19samplenode S20rc.local $
17番から19番(S18bluetoothの後)になっているのがわかりますね。
RedHat系ディストリビューションの場合は、chkconfig行の起動時の番号(以下の90)と終了時の番号(以下の60)を自分で調整しましょう。
# chkconfig: 2345 90 60
まとめ
いろいろ補足事項はありましたが、さくっとSysVinit互換のスクリプトを生成するinitd-foreverは扱いやすい印象を持ちました。systemdでも同じような話が出てくると思うので、早めに検証/習得しておきたいですね。








