Создание демона Node.js для сервера Linux
Linux, VPS | Комментировать запись
Демон – это программа, которая запускается в фоновом режиме и не имеет управляющего терминала. Они часто используются для поддержки фоновых сервисов; к примеру, в качестве демона часто запускают веб-сервер или сервер баз данных.
Данное руководство покажет, как написать стандартный демон в Node.js и развернуть его на виртуальном выделенном сервере при помощи Upstart. Скрипт Upstart используется исключительно для простоты; можно также написать System-V init или использовать любой другой скрипт для запуска демона.
Требования
Для выполнения данного руководства понадобятся:
- Виртуальный выделенный сервер Linux (желательно Ubuntu или CentOS),
- Node.js,
- Upstart.
Установка Node.js
Существует несколько способов установки Node.js. Проще всего использовать инструмент nvm, который также позволяет управлять различными версиями Node.
Примечание: Более подробную информацию по установке Node.js можно найти в этом руководстве.
Установка Upstart
Многие дистрибутивы Linux поставляются с предустановленным Upstart. В противном случае его можно установить из официального репозитория или собрать из исходников.
Примечание: больше информации по установке Upstart можно найти здесь.
Как работают демоны?
В основном демон запускается как обычный процесс, после чего происходит следующее:
- Демон создаёт свою копию в качестве своего дочернего процесса.
- Дочерний процесс отделяется от родительского процесса.
- Дочерний процесс закрывает свои стандартные файловые дескрипторы.
- Родительский процесс завершается.
- Демон продолжает свою работу в фоновом режиме.
Кроме того, не закрывая стандартные файловые дескрипторы, родительский процесс может открыть пустое устройство (/dev/null) и подключить его к стандартным файловым дескрипторам дочернего процесса.
Пример демона
В данном руководстве демонстрируется создание простого HTTP-демона, который может:
- Запускаться в фоновом режиме (для этого используется модуль daemon);
- Порождать рабочие процессы для сервера HTTP;
- Перезапускать рабочие процессы на SIGHUP;
- Завершать рабочие процессы на SIGTERM;
- Сбрасывать привилегии рабочих процессов после запуска HTTP-сервера.
Базовая структура проекта
Изначально проект имеет такой вид:
node-simple-http-daemon/
|
|-- README.md
|-- bin/
| `-- node-simple-http-daemon
`-- lib/
`-- app.js
Создание файла package.json
Теперь нужно создать файл package.json:
$ npm init
Установите модуль daemon:
$ npm --save install daemon
Содержимое файла package.json выглядит так:
{
"name": "node-simple-http-daemon",
"version": "0.0.0",
"description": "Simple HTTP Daemon written in Node.js",
"main": "lib/app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node lib/app.js"
},
"author": "Fardjad Davari",
"license": "MIT",
"dependencies": {
"daemon": "~1.1.0"
}
}
Обратите внимание: файл содержит скрипт start, а это значит, что позже можно будет использовать команду npm start для запуска приложения.
Создание HTTP-сервера
На данном этапе нужно создать простой HTTP-сервер, прослушивающий порт 80 и отвечающий «Hello world» на любой запрос.
В файл lib/app.js поместите следующее:
/**
* lib/app.js
*/
const PORT = 80;
const ADDRESS = '0.0.0.0';
var http = require('http');
var server = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
server.listen(PORT, ADDRESS, function () {
console.log('Server running at http://%s:%d/', ADDRESS, PORT);
console.log('Press CTRL+C to exit');
});
Теперь запустите виртуальный сервер:
sudo npm start
Исполняемый код демона
Далее представлен исполняемый код демона. Поместите его в bin/node-simple-http-daemon:
#!/usr/bin/env node
/**
* bin/node-simple-http-daemon
*/
// Everything above this line will be executed twice
require('daemon')();
var cluster = require('cluster');
// Number of CPUs
var numCPUs = require('os').cpus().length;
/**
* Creates a new worker when running as cluster master.
* Runs the HTTP server otherwise.
*/
function createWorker() {
if (cluster.isMaster) {
// Fork a worker if running as cluster master
var child = cluster.fork();
// Respawn the child process after exit
// (ex. in case of an uncaught exception)
child.on('exit', function (code, signal) {
createWorker();
});
} else {
// Run the HTTP server if running as worker
require('../lib/app');
}
}
/**
* Creates the specified number of workers.
* @param {Number} n Number of workers to create.
*/
function createWorkers(n) {
while (n-- > 0) {
createWorker();
}
}
/**
* Kills all workers with the given signal.
* Also removes all event listeners from workers before sending the signal
* to prevent respawning.
* @param {Number} signal
*/
function killAllWorkers(signal) {
var uniqueID,
worker;
for (uniqueID in cluster.workers) {
if (cluster.workers.hasOwnProperty(uniqueID)) {
worker = cluster.workers[uniqueID];
worker.removeAllListeners();
worker.process.kill(signal);
}
}
}
/**
* Restarts the workers.
*/
process.on('SIGHUP', function () {
killAllWorkers('SIGTERM');
createWorkers(numCPUs * 2);
});
/**
* Gracefully Shuts down the workers.
*/
process.on('SIGTERM', function () {
killAllWorkers('SIGTERM');
});
// Create two children for each CPU
createWorkers(numCPUs * 2);
Теперь можно запускать демон! Но сначала нужно отредактировать app.js для поддержки SIGTERM.
Откройте файл app.js и внесите в него следующее:
process.on('SIGTERM', function () {
if (server === undefined) return;
server.close(function () {
// Disconnect from cluster master
process.disconnect && process.disconnect();
});
});
Сделайте скрипт демона исполняемым:
$ chmod +x bin/node-simple-http-daemon
Теперь запустите демон (предварительно проверив, не занят ли порт 80 чем-то ещё)
$ sudo bin/node-simple-http-daemon
Демон и его рабочие процессы запущены в фоновом режиме. Чтобы убедиться в этом, отправьте запрос HTTP GET с помощью cURL:
$ curl 127.0.0.1
Теперь нужно просмотреть ID процессов:
$ ps -axf | grep [n]ode-simple-http-daemon | \
awk '{ print "pid:"$2", parent-pid:"$3 }'
Команда выведет на экран примерно такой результат:
pid:33811, parent-pid:1
pid:33853, parent-pid:33811
pid:33856, parent-pid:33811
pid:33857, parent-pid:33811
pid:33858, parent-pid:33811
pid:33859, parent-pid:33811
pid:33860, parent-pid:33811
pid:33861, parent-pid:33811
pid:33862, parent-pid:33811
Обратите внимание: демон является процессом с parent-pid:1.
Попробуйте перезапустить рабочие процессы, отправив демону SIGHUP:
$ kill -HUP 33811 # (укажите PID демона вместо 33811)
Если снова просмотреть PID процессов, можно увидеть новый рабочий процесс с новым PID.
Чтобы остановить демон, запустите:
$ kill -TERM 33811 # (укажите PID демона вместо 33811)
Сброс привилегий
Демон почти готов. Осталось только настроить сброс привилегий рабочих процессов после запуска виртуального сервера.
Для этого нужно отредактировать обратный вызов server.listen() в файле app.js:
server.listen(PORT, ADDRESS, function () {
console.log('Server running at http://%s:%d/', ADDRESS, PORT);
console.log('Press CTRL+C to exit');
// Check if we are running as root
if (process.getgid() === 0) {
process.setgid('nobody');
process.setuid('nobody');
}
});
Демон полностью готов! Теперь можно глобально установить его:
$ sudo npm link
Скрипт Upstart
Upstart очень прост в использовании. Создайте файл в /etc/init/node-simple-http-daemon.conf и поместите в него следующий код:
# /etc/init/node-simple-http-daemon.conf
start on started network
stop on stopping network
respawn
expect daemon
exec https-proxy-daemon
Затем используйте:
$ sudo start node-simple-http-daemon # Запуск
$ initctl --system node-simple-http-daemon # Проверка состояния
$ sudo reload node-simple-http-daemon # Отправить SIGHUP (перезапуск рабочих процессов)
$ sudo stop node-simple-http-daemon # Остановка
Заключение
Любому приложению node необходимо иметь надежную авторизацию; для этого можно использовать node-bunyan.
Tags: CentOS, Linux, Node.js, Ubuntu, Upstart