initd-foreverでNode.jsアプリをデーモン化する
ども、大瀧です。
最近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でも同じような話が出てくると思うので、早めに検証/習得しておきたいですね。