В процессе работы над проектом с использованием Node.js в качестве серверсайда, возникла задача запуска JS скрипта в качестве сервиса, со всем плюшками типа start, stop, restart. По этой теме в принципе уже достаточно информации, но она в основном сводится к использованию Monit + Init под линуксом, либо кратких советов типа «use nodemon, Luke».
В моём случае в качестве продакшн-сервера была машина на FreeBSD. В качестве утилиты запускающей JS файл как процесс мне приглянулся forever. Эта штука, в отличии от nodemon, умеет следить за запущенными под ней процессами и поднимать их при падении без ожидания изменения в исходниках скрипта, что очень удобно именно на продакшене. К тому же forever умеет показывать текущий список запущеных процессов, с отображением их аптайма, ну и позволяет останавливать/перезапускать процессы по имени или индексу.
Недолго думая, был написан rc.d скрипт:# cat /usr/local/etc/rc.d/factory
#!/bin/sh
# PROVIDE: factory
# REQUIRE: NETWORKING SERVERS DAEMON
# BEFORE: LOGIN
# KEYWORD: shutdown
. /etc/rc.subr
name="factory"
forever="/usr/local/bin/node /usr/local/bin/forever"
workdir="/usr/home/www/factory"
script="index.js"
rcvar=`set_rcvar`
start_cmd="start"
stop_cmd="stop"
restart_cmd="restart"
load_rc_config $name
eval "${rcvar}=${${rcvar}:-'NO'}"
start()
{
HOME=/root
NODE_ENV=production
${forever} start -a -l /var/log/forever.log -o /dev/null -e ${workdir}/logs/node_err.log --sourceDir ${workdir} ${workdir}/node/${script}
}
stop()
{
${forever} stop ${workdir}/node/${script}
}
restart()
{
${forever} restart ${workdir}/node/${script}
}
run_rc_command "$1"
В /etc/rc.conf была добавлена строка: factory_enable=«YES». После чего произведён пробный запуск: /usr/local/etc/rc.d/factory start и скрипт радостно запустился. Проверив так же работу restart и stop я довольный собой отправился налить себе чаю. При этом, дабы проверить запуск скрипта в боевых условиях, отправил в ребут сервер.
Вернувшись с чаем, я открыл браузер и обнаружил что серверсайд не запустился. Журнал forever содержал следующее:# cat /var/log/forever.log
...
warn: Forever restarting script for 11119 time
warn: Forever detected script exited with code: 127
warn: Forever restarting script for 11120 time
warn: Forever detected script exited with code: 127
warn: Forever restarting script for 11121 time
warn: Forever detected script exited with code: 127
warn: Forever restarting script for 11122 time
warn: Forever detected script exited with code: 127
warn: Forever restarting script for 11123 time
warn: Forever detected script exited with code: 127
warn: Forever restarting script for 11124 time
Таким образом, с момента как сервис «запустился», дочерний JS-процесс успел упасть/подняться уже 11124 раза, пока я топил в кипятке пакетик с чаем.
Всякий раз, когда что-то запускается с консоли но не работает при старте в «чистом» окружении внутренний голос шепчет одно и то же: «Переменные окружения же!». Но как же так? Я же указал HOME о котором везде пишут, и NODE_ENV=production необходимый для работы express. Что ещё надо?
А надо сравнить разницу между пользовательским окружением и окружением при старте системы. В результате этого сравнения в rc.d скрипт, в секцию «start», было добавлено ещё три переменные окружения: USER, PATH и PWD. После этого JS-скрипт начал стартовать корректно. Собственно рабочая версия rc.d скрипта стала выглядеть так:#!/bin/sh
# PROVIDE: factory
# REQUIRE: NETWORKING SERVERS DAEMON
# BEFORE: LOGIN
# KEYWORD: shutdown
. /etc/rc.subr
name="factory"
forever="/usr/local/bin/node /usr/local/bin/forever"
workdir="/usr/home/www/factory"
script="index.js"
rcvar=`set_rcvar`
start_cmd="start"
stop_cmd="stop"
restart_cmd="restart"
load_rc_config $name
eval "${rcvar}=${${rcvar}:-'NO'}"
start()
{
USER=root
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/root/bin
PWD=/root
HOME=/root
NODE_ENV=production
${forever} start -a -l /var/log/forever.log -o /dev/null -e ${workdir}/logs/node_err.log --sourceDir ${workdir} ${workdir}/node/${script}
}
stop()
{
${forever} stop ${workdir}/node/${script}
}
restart()
{
${forever} restart ${workdir}/node/${script}
}
run_rc_command "$1"
Покопавшись в «этих ваших интернетах», был удивлён отсутствию информации об отказоустойчивом запуске node.js сервиса таким способом на машине под FreeBSD. Собственно о чём и решил написать, вдруг будет кому полезно.