Использование Node.js с Init и Monit
Итак, я написал несколько забавных штучек на Node.js и хочу их запустить, к примеру, в локальной сети. Проблема здесь может быть в том, что Node, во первых, просто вылетает при ошибках (если 2001-й пользователь уронил скрипт, 2002-й уже не увидит сайта) и, во вторых, перезапускать её приходится руками — вводя что нибудь вроде node app.js 2> /var/log/node.log. Сегодня я этот процесс попробую автоматизировать.
Вообще к этой задаче можно подойти разными способами. Можно, например, ловить все исключения и показывать 500 страницу вместо вылета, как это делает Express. Но такой подход не спасёт нас от ошибок, переполнений и утечек памяти как в самой Node.js, так и в используемых библиотеках. Поэтому нам нужен способ отслеживать, жив ли наш сайт, и при необходимости его перезапускать.
Ещё одна проблема — Node.js обычно не запускают в виде демона. Можно конечно каждый раз дописывать & в конце строки вызова, но это опять же не очень удобно.
Init
Сперва нам надо сделать из Node демона. В Debian это удобнее всего реализовать с помощью init. Это, во первых, позволит запускать node.js фоновым процессом и, во вторых, даст нам удобные команды для запуска/завершения/управления демоном. Если Вы пользуетесь Debian Lenny, как я, init уже установлен. Если у Вас Убунту, Вам лучше посмотреть в сторону связки Upstart + Monit.
В Debian у нас уже есть шаблон файла настройки init — он лежит в /etc/init.d/skeleton. Копируем его, переименовываем в nodejs и редактируем:
cp /etc/init.d/skeleton /etc/init.d/nodejs vim /etc/init.d/nodejs
В нём меняем название и описание сервиса, запускаемое приложение и опции, путь к node:
DESC="Node.js-based service" NAME=node DAEMON=/usr/local/bin/$NAME
Кстати, узнать откуда именно запускается у Вас node.js можно командой which node. Здесь же в переменную DAEMON_ARGS можно прописать JS-файл, который Node будет запускать по умолчанию.
Ниже будет функция do_start(), которая собственно и отвечает за запуск демона. Там можно изменить команду, которая запускает сам демон. Я, например, добавил перенаправление ошибок в лог:
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE -b -m --exec $DAEMON -- \ $DAEMON_ARGS 2> /var/log/$NAME.log \ || return 2
Для Node.js надо добавить опции -b и -m, т.к. самостоятельно Node не станет демоном. Здесь же можно добавить дополнительные команды при запуске сайта — например, удаление временных файлов или запуск сервера баз данных.
Ниже есть функция do_stop. Код, останавливающий сервер, в принципе и без изменений сработает неплохо, но если при остановке демона надо выполнить какие-либо действия, они идут как раз сюда.
Теперь сохраняем получившийся файл. Надо дать ему разрешение на выполнение:
chmod +x /etc/init.d/nodejs
Попробуем запустить демон (я использую в качестве параметра запуск поискового сайта):
/etc/init.d/nodejs start ps ax | grep node /etc/init.d/nodejs stop
Если всё прошло нормально, после команды start наш демон должен появиться в списке процессов, а после stop — исчезнуть.
Monit
Итак, демон у нас готов. Теперь нам нужен Monit. Ставим его:
apt-get install monit
По сути, Monit штука довольно простая. Он периодически проверяет жив ли определённый сервис, и выполняет действия в зависимости от ответа.
Прежде чем его запускать, надо его настроить. Редактируем /etc/monit/monitrc. Там есть много примеров, наш код будет выглядеть примерно так:
set logfile /var/log/monit.log
check host nodejs with address 192.168.172.128
start program = "/etc/init.d/nodejs start"
stop program = "/etc/init.d/nodejs stop"
if failed port 8000 protocol HTTP
request /
with timeout 10 seconds
then restart
Если вы знаете английский, конфиг читается довольно легко. Нам надо только указать правильные адрес и порт, которые будет проверять Monit (именно те, на которых висит наш сайт), и добавить что в случае недоступности в течении 10 секунд его надо перезапустить.
Здесь есть одна особенность. Monit запускает указанные команды в “чистом” окружении — т.е. из переменных окружения там доступна только PATH. Для работы Node нам нужна ещё переменная HOME, поэтому добавляем её в /etc/init.d/nodejs:
DESC="Node.js-based service" NAME=node DAEMON=/usr/local/bin/$NAME export HOME=/root
Вам надо сначала проверить, чему равна HOME в вашем случае с помощью команды set. Теперь запускаем сам Monit:
monit -d 60 -c /etc/monit/monitrc
Флаг -d 60 говорит о том что наше приложение надо проверять каждые 60 секунд, -c указывает какой файл настроек использовать. Теперь, если наш сайт на node.js внезапно упадёт, Monit+init оперативно вернут его к жизни. Параллельно Monit может наблюдать и другие приложения: CouchDB, MongoDB, поисковый сервер Sphinx — всё, что может понадобиться для сайта.
>> Node, во первых, просто вылетает при ошибках
Событие uncaughtException же
Ну да ) Не во всех фреймворках оно обрабатывается по умолчанию.
Можно самому обрабатывать.
Ну да, можно отлавливать самому и показывать например 500 ошибку, и в лог писать по желанию.
Вам стоит прочесть http://habrahabr.ru/blogs/sysadm/83775/
Интересная вещь, спасибо, не знал )
Наверно я неправильно понимаю архитектуру самого NodeJS, а также принципы построения решения на нем, и поэтому возник вопрос: тут принцип другой, нежели, например у nginx/apache/mysql, у которых есть хост-процесс, создающий дочерние потоки (процессы?) для параллельной обработки поступающих запросов?
Просто в данной статье намека на это дело не увидел, вот и хотелось бы уточнить))
Вкратце: старт виртуальной машины V8 — дело затратное. Если делать это для каждого запроса, будет медленнее. Кстати, nginx запускает несколько воркеров, каждый из которых обрабатывает много поступающих запросов. В ноде тоже применяется похожий паттерн — в частности, с общим файловым дескриптором.
Суть статьи в том что если скрипт в Node.js падает, его должен кто то поднять. Даже если у нас есть мастер-поток и дочерние, кто то всё равно должен подстраховывать мастер-поток от неожиданностей. И для непрерывной работы и Nginx, и Apache, и MySQL в Debian обычно запускаются через описанный механизм — Init + Monit.