Стритрейсинг на Nginx + Passenger

Опубликовано 07.10.2008

Что роднит веб-девелоперов и стрит-рейсеров? Стремление выжать из своей “лошадки” максимум возможного. И для тех, и для других лишняя миллисекунда - это потенциальное поражение в конкурентной борьбе за славу и уважение миллионов фанатов. Возможно, с миллионами я слегка загнул, по крайней мере для гонщиков :)

Даже когда счет идет на тысячи постоянных посетителей, лишняя миллисекунда может вылиться в многие и многие бесцельно потраченные серверо-часы. Но что самое страшное - лишнее время загрузки страницы увеличивает шансы на то, что особо торопливый пользователь просто не станет дожидаться пока ваш сервер соизволит выдать ему требуемую информацию, плюнет и уйдет в Google искать ваших конкурентов. Менеджер проекта в ярости, инвесторы спрашивают где бабки, у вас стресс. Занавес.

Статика

Если вы занимаетесь веб-разработкой не первый день (а хотя бы второй), то вы наверняка знаете, что в процессе загрузки получение непосредственно самой страницы (HTML-кода) может занимать порядка 10-15%, редко когда больше. Львиную же долю времени отнимает загрузка статичных файлов - картинок, javascript’ов, CSS-файлов, флэшек и прочих прелестей.

Для быстрой отгрузки статичных файлов очень часто используется сервер Nginx. Если ничего не поменялось за последнее время, то он до сих пор считается самым быстрым веб-сервером для раздачи статики. Старикашка Apache за ним угнаться не может точно, другие тоже отстают. Поправьте если ошибаюсь.

Динамика

Однако, старикашка Apache тоже не лыком шит, у него есть свои плюсы. В контексте разработки Rails-проектов - это Passenger (он же mod_rails), который есть только под Apache.

Развертывая проекты под Nginx, для обработки динамики приходится стартовать отдельные rails-процессы (например, с использованием Thin, Mongrel и собратьев), причем чем активнее используется приложение, тем больше надо процессов. До поры до времени такой подход имеет право на жизнь, но однажды наступает один из возможных вариантов:

  • Вы хотите сократить время загрузки на секунду-другую
  • Вам надо запустить полсотни мелких рельсовых проектов с мизерной посещаемостью
  • Вам надо запустить толстый проект на сервере с малым объемом памяти (например, на VPS-хостинге от Agava, Linode и других)
  • У ваших инвесторов кончилось бабло на закупку железа под ваш мега-стартап
  • Вас парит что в памяти сидят и курят бестолковые процессы по сотне метров

Меня настигли одновременно 4 пункта из 5, угадайте какие. Поэтому единственный вариант для меня - это переход на Passenger.

Лед и пламень

Однако, решил я, если уж браться за оптимизацию и минимизацию, то нужно свести воедино преимущества двух серверов - быструю отдачу статики Nginx’ом и экономию памяти за счет Passenger под Apache. Задача не очень сложная и просто требует понимания того, кто за что отвечает. В моем случае получается вот так:

  • Apache - обрабатывает динамические запросы к Rails
  • Nginx - отдает статичные файлы (картинки, css, js, flash), закэшированные страницы и осуществляет поддержку фрагментарного кэширования через SSI+memcached

Входящий запрос попадает на Nginx, который определяет, можно ли отдать статику. Если результат запроса нельзя отдать из статичного файла - Nginx проксирует запрос на Apache. Nginx вешается на порт 80, а Apache например на 3000.

Конфигурация Nginx (размещаем ее в /my_project_folder/nginx.conf):

upstream apache {
  server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
}
server {
  listen 80;

  server_name myproject.com www.myproject.com somealias.com;

  proxy_next_upstream error;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_redirect false;
  proxy_max_temp_file_size 0;

  # Задаем корневую папку из которой будет браться статика
  root /my_project_folder/public;

  location / {
    proxy_set_header Host myproject.com;

    if (-f /my_project_folder/_cache/$uri.html) {
      root /my_project_folder/_cache;
      rewrite (.*) $1.html break;

    }

    if (-f /my_project_folder/_cache/$uri/index.html) {
      root /my_project_folder/_cache;
      rewrite (.*) $1/index.html break;
    }

    if (!-f $request_filename) {
      proxy_pass http://apache;
      break;
    }
  }
}

Обратите внимание, в инструкции server_name мы прописываем все возможные доменные алиасы, по которым мы хотим видеть наш проект, однако в location мы через proxy_set_header задаем вполне конкретный Host. Это нужно для того, чтобы в виртуальных хостах Apache можно было прописать только главный домен. Если вам требуется обрабатывать запросы в зависимости от домена - пропишите так:

proxy_set_header Host $http_host;

Далее настраиваем Apache. Прежде всего нужно установить mod_rails следуя официальной инструкции. Затем прописываем виртуальные хосты (размещаем в /my_project_folder/apache.conf):

# Это лучше вынести в основной конфиг
Listen 3000
NameVirtualHost *
ServerName localhost

# Виртуальный хост, на который проксируются запросы
<VirtualHost *>
  ServerName myproject.com
  DocumentRoot /my_project_folder/public
</VirtualHost>
Собственно, теперь конфиг для виртуального хоста **Nginx** нужно подключить в основной конфиг **Nginx**:
http {
  ...

  include /my_project_folder/nginx.conf;
}

А в основном конфиге Apache подключить виртуальный хост Apache:

Include /my_project_folder/apache.conf

Перезапускаем оба сервера и наслаждаемся :) Статику отдает Nginx, динамику Apache+Passenger.

Ruby on Rails на хостинге Agava VPS (через mod_rails)

Опубликовано 26.08.2008

Один из старых клиентов, для которых я разрабатывал движок, решили переехать к новому хостеру. Путем недолгих изысканий был выбран VPS хостинг от Agava, план VPS Basic. В этой заметке я вкратце опишу процесс развертывания Rails-приложения на сервере Агавы с использованием Passenger (он же mod_rails). Для развертывания мы выбрали сервер под управлением операционной системы CentOS 5.2, контрольная панель ISPManager Lite.

Установка GCC

Сразу после регистрации сервера вам может потребоваться установить GNU C++ Compiler. Когда я первый раз настраивал аккаунт на Агаве, он был уже установлен, однако на новом аккаунте он почему-то отсутствовал. Поэтому его потребовалось установить:

yum install gcc-c++

При установке может возникнуть ошибка Error: Missing Dependency: kernel-headers >= 2.2.1 is needed by package. В этом случае необходимо обновить пакет kernel-headers. Я его ставил отсюда:

wget ftp://ftp.pbone.net/mirror/ftp.centos.org/5.2/os/i386/CentOS/kernel-headers-2.6.18-92.el5.i386.rpm
rpm -i kernel-headers-2.6.18-92.el5.i386.rpm

После этого все должно ставиться нормально.

Установка Ruby

Изначально на сервер не установлен интерпретатор Ruby, поэтому нам необходимо его установить. Чтобы сделать эксперимент максимально интересным, будем ставить не стандартный MRI, а Ruby Enterprise Edition оптимизированный для работы с Passenger.

Качаем архив с официального сайта, распаковываем и запускаем инсталлятор:

./installer

Следуем инструкциям в инсталляторе, там все прозрачно.

Установка MySQL

Следующий этап - установка gem’а для работы с MySQL. Сам сервер MySQL уже установлен, нужно только поставить дополнительные библиотеки:

yum install mysql-devel

Затем ставим сам gem:

gem install mysql

Идем дальше

Установка RMagick

Следующий этап - установка ImageMagick и RMagick. Я при установке следовал инструкциям, предложенным в этой статье, копипастить не буду :)

Установка Passenger (mod_rails)

Затем приступаем к установке Passenger. Для начала нам надо поставить дополнительные библиотеки для Apache:

yum install httpd-devel

Затем, чтобы Passenger нашел все исполняемые файлы, которые идут в составе Ruby, нам надо добавить путь до бинарников в наш .bash_profile:

PATH=$PATH:$HOME/bin:/opt/ruby-enterprise-1.8.6-20080810/bin

Вам необходимо поставить тот путь, куда вы установили Ruby.

Затем ставим gem и модуль для Apache:

gem install passenger
passenger-install-apache2-module

Подключаем модуль в нашем файле httpd.conf (у меня он находится по адресу /etc/httpd/conf/httpd.conf):

LoadModule passenger_module /opt/ruby-enterprise-1.8.6-20080810/lib/ruby/gems/1.8/gems/passenger-2.0.3/ext/apache2/mod_passenger.so
PassengerRoot /opt/ruby-enterprise-1.8.6-20080810/lib/ruby/gems/1.8/gems/passenger-2.0.3
PassengerRuby /opt/ruby-enterprise-1.8.6-20080810/bin/ruby

Через панель управления создаем нового пользователя и под его именем создаем новый WWW-домен. В httpd.conf автоматически добавится VirtualHost. Чтобы по на этом хосте заработали рельсы, нам необходимо поменять DocumentRoot таким образом, чтобы он указывал на папку public в нашем проекте:

<VirtualHost 123.45.67.89:80>
  ServerName mysite.ru
  DocumentRoot /var/www/mysite/data/www/mysite.ru/public
  SuexecUserGroup mysite mysite
  CustomLog /var/www/httpd-logs/mysite.ru.access.log combined
  ErrorLog /var/www/httpd-logs/mysite.ru.error.log
  ServerAlias www.mysite.ru mysite.ru
  ServerAdmin webmaster@mysite.ru
</VirtualHost>

Установка git

Для установки Git из исходников нам потребуются дополнительные библиотеки:

yum install gettext-devel expat-devel curl-devel zlib-devel openssl-devel

Поставив библиотеки, качаем и ставим сам git:

wget http://kernel.org/pub/software/scm/git/git-1.6.0.tar.gz
tar zxvf git-1.6.0.tar.gz
cd git-1.6.0
make all
make install

Как видите, все просто, с настройкой справится даже человек, не искушенный в настройке серверов (типа меня). Цены на хостинг тоже вполне доступные - от 559 рублей

Phusion Passenger - уже интереснее!

Опубликовано 17.06.2008

Первую версию mod_rails я даже смотреть не стал, потому как заранее было понятно что она сырая как носки туриста. А вот на предстоящий релиз Passanger 2.0 уже вполне стоит потестировать, потому как:

  • Добавлена поддержка Rack (читай merb, sinatra и прочие не-рельсы)
  • Существенно уменьшен аппетит к памяти
  • Добавлена поддержка Python WSGI, то есть Django
  • Реализована буферизация загрузок файлов
  • Оптимизирована балансировка запросов

Я, конечно, уже приноровился к связке Thin+Nginx, но попробовать однозначно стоит. Что мне нравится в nginx’е так это пулеметная отдача статики. Но объемы памяти и скорость работы динамики все же важнее.