Создание демона Node.js для сервера Linux

Published by Leave your thoughts

Демон – это программа, которая запускается в фоновом режиме и не имеет управляющего терминала. Они часто используются для поддержки фоновых сервисов; к примеру, в качестве демона часто запускают веб-сервер или сервер баз данных.

Данное руководство покажет, как написать стандартный демон в 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: , , , ,

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *


*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>