Node.js демон для FreeBSD: forever + rc.d

В процессе работы над проектом с использованием 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. Собственно о чём и решил написать, вдруг будет кому полезно.
Share post

Similar posts

Comments 21

    0
    Я возможно неуловил, но каков результат то?)
      +1
      Результат однозначно положительный. Окончательная версия rc.d скрипта работает уже неделю. Аптайм процессов радует.
        0
        Клево, спасибо, в избранное.
      –1
      Занимался тем же для Ubuntu. Думаю, еще полезней было бы написать подобное, чтобы поднимать пачку скриптов в виде демонов из какого-нибудь конфига в виде node модуля.
        0
        Описанное решение позволяет запускать каждый node-процесс отдельно, вы просто создаёте для каждого процесса свой rc.d скрипт. Плюс в том, что rc.d позволяет вам прописать зависимости и обеспечить старт процессов в нужном порядке. При этом с forever у вас всегда остаётся централизованный инструмент для оценки состояния всех запущенных процессов, а также их группового перезапуска / остановки.
          –1
          Нет, я думаю писать контроллер, с возможность мониторинга состояния запущенных демонов, скажем, через веб-интерфейс, ну или через любую панель управления.
            –1
            Может стоит тогда форкнуть тот же forever и дописать к нему возможность отдачи состояния наблюдаемых процессов через те же сокеты?
              –1
              Ну, в случае если бы мне нужен был только контроль над процессами я бы так и сделал. Но задача несколько сложнее: требуется наладить взаимодействие с процессами, чтобы не только запускать/останавливать процесс, но и выполнять различные операции внутри процессов. Например, запустить апдейт системы или сделать бэкап. Но это гипотетические задачи. А реально требуется переносимость, как минимум Ubuntu — CentOS — FreeBSD, поэтому делать сложную систему управления только для одной ОСи, как минимум невозможно ) вот и хочется все это дело на ноде реализовать.
              0
              Попробуйте Monit.
                0
                Видимо, я не правильно выразился. Мне нужно не столько управление системой на уровне администратора, сколько няшная и максимально простая веб-панелька для девочек-менеджеров, чтобы они не вникая во все тонкости веб-технологий, жали бы «создать сайт», «запустить сайт», «добавить возможностей», ну, и т.п.

                Кстати, не смотрели в сторону ajenti?
                  0
                  А, это действительно немного не то. Ajenti смотрел в демке, понравилось. Вроде как раз то для создания хостов из веб-интерфейса. Но для «добавить возможностей» видимо придётся дописывать модули.
          +1
          Почему не supervisord?
            0
            Я уверен что уже есть и будет ещё написана куча подобных инструментов. Но моя цель была решить конкретную задачу, а не попробовать всё что только можно. Результат работы forever меня более чем устроил.

            Если у вас есть решение на базе supervisord — поделитесь. Напишите в чём плюсы, в чём минусы. Это всегда интересно.
              0
              Я использую его для gunicorn проектов.
              Удобства? Отделение от rc.d (один процесс, запускает остальные)
              Плюсы в логгировании данных, хттп демоне с авторизацией и xmlrpcd.
                0
                внизу конфиг для супервизора notes.sovechkin.com/post/3180299453
                минусы не знаю, работает как часы уже год. То что падает, моментально поднимает
              0
              Use runit, Luke,
              • UFO just landed and posted this here
                  0
                  Промахнулся, ответил ниже.
                  0
                  Установить локально в каталог проекта:

                  npm install forever

                  или глобально:

                  npm -g install forever
                    0
                    forever не плох, но он к сожалению при запуске чего-либо не проверяет запущено ли уже это самое чего-либо, хотя мог бы это делать по имени скрипта :(
                      0
                      не комильфо запускать от рута, кмк.

                      Only users with full accounts can post comments. Log in, please.