Запуск нескольких node.js сайтов на одном сервере

Когда на одном сервере находиться несколько одновременно работающих node.js сайтов, для них необходим общий механизм запуска. Наиболее простой вариант, а также наиболее производительный, это запуск одного приложения, которое подключает все нужные сайты. Механизм подключения должен быть без ограничений для создаваемых сайтов, и максимально простым. Он должен быть стабилен, при критической ошибке на одном из сайтов, другие все равно должны продолжать свою работу.
Все подключаемые веб-приложения должны быть легко переносимы на отдельный хостинг. Необходима поддержка как отдельных доменных имен, так и поддоменов для определенного доменного имени.

Допустим, есть две директории, в которых находятся веб-приложения:

/var/www/domains/, с каталогами site.ru, othersite2.ru и тд.
/var/www/subdomains/, с каталогами site3, othersite4 и тд.

Чтобы сайты были доступны по соответсвующим адресам (site.ru, othersite2.ru, site3.example.com, othersite4.example.com), потребуется запустить данную команду:

$ vhoster -n example.com -s /var/www/subdomains/ -d /var/www/domains/ --port 80 --host 0.0.0.0
vhost: site.ru
vhost: othersite2.ru
vhost: site3.example.com
vhost: othersite4.example.com

Для тех, кому нужен vhoster и интересна его реализация, прошу под кат.

Программа просматривает директории на наличие каталогов, и в каждом найденном подключает файл .index.js, если он есть. Корневой файл .index.js может содержать роутинг путей для сайта, обычно именно с него начинается вся серверная логика. При подключении он должен экспортировать объект app, это незапущенный http-сервер приложения, который потом передается библиотеке connect.vhost. С помощью connect.vhost осуществляется роутинг для доменных имен.

Стандартное шаблонное содержание файла .index.js может быть таким:

var http = require('http');
exports.app = http.createServer(function (req, res) {
	res.writeHead(200, {'Content-Type': 'text/plain'});
	res.end('Hello World\n');
});

Аналогично это будет работать с express или connect:

var connect = require('connect');
exports.app = connect().use(function(req, res) {
	res.end('hello world\n');
});

Если от какого-либо сайта придет не перехваченная ошибка, процесс для всех не завершиться, благодаря универсальной конструкции:

process.on('uncaughtException', function (err) {
	console.log('Caught exception: ' + err);
});

Таким образом, мы имеем возможность запускать набор веб-приложений написанных на node.js. Каждый сайт сам делает себе роутинг путей и максимально независим. К тому же, нет никаких проблем запустить сайт на отдельном хостинге без помощи vhoster, добавив в код .index.js следующие условие:

if (require.main === module) { // если веб-приложение запускается как отдельная программа
	exports.app.listen(80); // запустить сервер
}

Прежде чем использовать данную технологию, нужно понимать ее главный недостаток. У всех сайтов будет общая глобальная область и одинаковые права. Если это недопустимо для подключаемых веб-приложений, то их нужно запускать как отдельные новые программы от имени разных пользователей.

Исходный код и небольшой тест программы можно найти на https://github.com/dkiyatkin/node-vhoster. Или ее можно установить с помощью npm:

$ npm install vhoster
$ vhoster --help

Любые предложения и пожеланию по коду приветствуются.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 19

    +9
    Это решение совершенно не подходит для production-использования.

    Конкретнее:
    — Рано или поздно потребуется создавать более сложные правила поведения (например ограничение частоты запросов для одного ip-адреса, кеширование ответов целиком). Нагружать всеми этими условиями event loop одного приложения — плохая идея.
    — Отдача статических файлов нужна 99.9% сайтов. Делать это при помощи node.js далеко не лучший способ.
    — Перезагрузить 1 сайт в вашем случае не получится. На время перезапуска будут остановлены все сайты.
    — Все сайты запускаются в одном процессе и имеют общий event loop. Тормоз в одном из них приведёт к тормозам во всех.
    — Переполнение памяти в одном из сайтов уронит все сайты.

    А самое главное, это всё очень просто реализуется при помощи nginx без костылей, велосипедов и нагрузки приложения лишним функционалом к нему не относящимся.
      0
      Конечно, я не утверждаю что это надо использовать на production, для серьезных проектов такая простая схема не подходит, это очевидно, и в конце статьи перечислены ограничения.
      Запущенный node.js сервер без проблем можно проксировать через nginx с его правилами.
      Все только для удобства разработки, ничего лишнего.
        +4
        Разработка в середе отличной от той, в которой будет использоваться приложение чревата последствиями. На сервере для разработки nginx установлен 1 раз. Файл конфигурации для каждого сайта отличается только путём к www-root, портом для проксирования и кастомными настройками. Копипастом это делается за пару минут и забывается об этом на весь цикл разработки.

        По поводу удобства разработки с таким решением тоже не согласен. Кроме озвученного выше несоответствия production-окружения и окружения разработки, есть ещё такие моменты как существенное затруднение отладки кода с помощью дебагера, т.к. трейсы будут содержать кучу мусора совершенно не относящегося к логике текущего приложения. Можно очень долго гоняться за утечкой памяти, которая происходит в другом приложении. Время перезапуска нескольких сайтов больше времени перезпуска одного сайта, что увеличивает время ожидания.
          –1
          Этот метод делает ровно то, что он делает, и не претендует на большее. Мне он пригодился и в определенных ситуациях был удобен.
          Ваше право с ним не согласиться и не использовать.
        0
        Здравствуйте, BVadim! А можно попросить в двух словах, как сделать аналог (того, что написал автор поста) при помощи nginx? Структуру опишите.
        +1
        Так делать не надо. Как уже выше сказали, нужно использовать nginx, или HA Proxy или любой иной аналогичный продкут.
          +1
          Хотите изоляции более высокого уровня — наймите админа для настройки виртуальных серверов или jails.
            0
            А можно попросить в двух словах, как сделать аналог (того, что написал автор поста) при помощи nginx? Структуру опишите.
              0
              Все тривиально, запускаете nginx и указываеме ему в качестве upstream (директива rtfm) все ваши серверы Node.
                0
                Пока не понятно.

                Во-первых что за еще «ваши серверы Node»? У меня один сервер на котором допустим 5 приложений, которые заняли 5 портов.

                Во-вторых я правильно вас понял, что вы предлагаете одновременный запуск 5-ти копий node? То есть чтобы они заняли 5 портов, а nginx перенаправлял запросы на них по своим правилам (допустим с пяти доменов на каждую копию).
                  0
                  я предполагаю одну копию node на одно процессорное ядро. Это стандартно для Unix.
                    0
                    А можно спросить, каким образом это делается? Я так думал что если второй раз запущу node, то в ОЗУ появится его вторая копия. Собственно по этой причине заинтересовал скрипт автора статьи. И к сожалению не знал, что как-то можно этого избежать другим способом.
                      0
                      Лень уже обьяснть. попробуйте хотя бы node cluster.

                      var cluster = require('cluster');
                      var http = require('http');
                      var numCPUs = require('os').cpus().length;
                      
                      if (cluster.isMaster) {
                        // Fork workers.
                      for (var i = 0; i < numCPUs; i++) {
                      cluster.fork();
                      }
                      
                      cluster.on('exit', function(worker, code, signal) {
                          console.log('worker ' + worker.process.pid + ' died');
                       });
                      } else { 
                      // Workers can share any TCP connection
                      // In this case its a HTTP server
                      http.createServer(function(req, res) {
                        res.writeHead(200);
                        res.end("hello world\n");
                      }).listen(8000);
                      }
                      
                        0
                        Не надо разжевывать, хотя бы направление поисков задайте) Спасибо за ответ. Попробую node cluster.
                          0
                          Кластеризация не подходит для решения проблемы. Там так называемые воркеры вызываются случайным образом. А мне нужно, как и автору статьи, чтобы воркер был привязан к домену. Если запрос приходит с домена № 1, то должен запускаться воркер № 1, но никак №2 и прочие… Кластеризация для масштабирования веб-приложения, но никак не для создания виртуального хостинга…
                            0
                            тогда rtfm nginx upstream.
                              0
                              Тоже рассматривал этот вариант. Но когда будет 100 сайтов — в ОЗУ будут 100 копий node.js. Сейчас одно приложение занимает 60-80Мб, это нужно будет 8Гб ОЗУ… У меня нет столько ОЗУ…
              0
              1) Для uncaughtException у express.js есть специальный плагин.
              2) Forever + Cluster API (Node) + upstart/rc.d/init.d/w.e.
                0
                Каким образом, кластеризация может решить проблему с запуском разных воркеров для разных доменов??? Кластеризация ведь нужна для масштабирования, а не для создания виртуальных хостов… Или я чего-то не догоняю?

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