Выпуск 8. Октябрь 2013

От редактора | Содержание | Консольные приложения на Curses

Разворачивание PSGI/Plack приложения

С появлением Plack у программистов пропала необходимость затачивать свое приложение под каждый из возможных вариантов развертывания приложений. Однако у администраторов никуда не пропала необходимость настроек сервера под это самое приложение. И если для опытного программиста/администратора этот вопрос не вызвает затруднений, то у новичков в вебе трудности вылезают вполне ощутимые.

Итак, вниманию благодарной публики представляется сборник рецептов по развертыванию минимального PSGI/Plack приложения на всех популярных видах хостингов.

В качестве тестового приложения мы будем использовать немножко измененное приложение, автоматически созданное фреймворком с названием, начинающимся на M. Тем, кому не нравится фреймворк на М, могут использовать фреймворк на D. или любой другой по своему вкусу с поддержкой Plack.

Plack предствляет собой стандартизированный интерфейс между сервером и приложением. В обычном случае приложению даже нет необходимости знать, в каком режиме и с каким сервером оно работает, что очень упрощает жизнь простому программисту.

Итак, поехали. Иходник приложения:

#!/usr/bin/env perl
use Mojolicious::Lite;

get '/' => sub {
  my $self = shift;
  $self->render('index');
};

get '/test' => sub {
    my $self = shift;
    $self->render('test');
};

app->start;
__DATA__

@@ index.html.ep
% layout 'default';
% title 'Welcome';
Welcome to the Super-Puper app!

@@ test.html.ep
% layout 'default';
% title 'Test';
Route test page!

@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
  <head><title><%= title %></title></head>
  <body><%= content %></body>
</html>

Начнем от простого к сложному.

Внимание! Все конфигурации для серверов приведены в минимальном варианте. Без ненужных в данном случае настроек типа виртуальных хостов, защит от ботов, ддос, нападения инопланетян и пролития кофе на клавиатуру. В общем, за выкаченные без изменений на боевой сервер конфиги автор ответственности не несет.

Nginx reverse proxy

Здесь все просто, у вас есть собственный сервер/VDS, есть ваше приложение, запущенное любым удобным способом, и nginx просто прокидывает на него запрос пользователя.

$ ./test daemon
[Wed Oct  2 06:16:37 2013] [info] Listening at "http://*:3000".
Server available at http://127.0.0.1:3000.

Лучше, конечно, использовать prefork-сервер типа Starman, но в нашем случае это не принципиально.

Запущенное M.-приложение по умолчанию запускается на 3000-м порту. Его-то мы и будем использовать по умолчанию везде, где нам нужен порт в конфигурации сервера.

Создаем конфиг nginx:

server {
    listen *;

    location / {
        proxy_pass http://127.0.0.1:3000/;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host $http_host;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # Передаем директорию для static файлов на обработку nginx
    location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|pdf|ppt|txt|bmp|rtf|js)$ {
        root /path/to/your/app/public/; # Путь к папке для public файлов
        expires 3d; # кешируем у клиента на 3 дня
    }
}

Способ хорош своей простотой и масштабируемостью. Чтобы увеличить количество обрабатываемых коннектов, достаточно запустить приложение под Starman или любым другим сервером и получить практически линейный рост числа обрабатываемых клиентов (пока не упремся в мощьность сервера, конечно).

Минус этого способа — за приложением придется следить самому и писать дополнительную обвязку на случай перезагрузки сервера/приложения.

Но здесь нам поможет Ubic. Устанавливаем из CPAN:

# cpan -i Ubic
# ubic-admin setup

Создаем минимальный файл конфигурации:

# vim /etc/ubic/service/site.ini

[options]
bin = /path/to/your/app/super_app.pl daemon

Стартуем сервис:

# ubic start site

И получаем работающий за nginx бекэнд. Если приложение внезапно упадет, Ubic запустит его обратно. Естественно, вместо test daemon лучше использовать какой-либо из специализированных серверов, например Starman.

Рассматривать способ деплоя через Nginx/FastCGI не имеет особого смысла т.к. отличия в конфигурации буквально в двух строчках. Но Nginx, в отличии от Apache, не умеет автоматически запускать запрошенное приложение. Так что здесь не обойтись без внешнего менедждера, и пропадает вообще какой-либо смысл использования FastCGI.

Перейдем к устаревшему варианту. Допустим, что выделенного сервера у нас нет, а есть shared-хостинг с Apache и поддержкой Perl.

Apache/CGI

Вариант, отличающийся простотой настройки, работоспособностью везде, где есть Apache и Perl, а так же самой высокой тормознутостью из всех возможных к использованию вариантов.

В Cookbook предлагается для этого случая прописать ScriptAlias

ScriptAlias / /home/sri/myapp/script/myapp/

Проблема этого метода в том, что эта директива не работает в .htaccess, а править файл конфигурации виртуального хоста вам никто не даст. С другой стороны, если у вас есть доступ к редактированию конфигурации виртуальных хостов, зачем настраивать CGI?

В общем мы, как настоящие герои, пойдем в обход и все настройки будем делать через .htaccess. В директории, где лежит сайт, создаем файл .htaccess со следующим содержимым:

Options +ExecCGI
AddHandler cgi-script .pl
DirectoryIndex index.pl

index.pl — это приложение, которое должно обрабатывать запрос. Внимательно проверьте атрибуты файла, по умолчанию M. создает файл с атрибутами 744, так что Apache не сможет его запустить и выдаст ошибку 500. Для нормальной работы надо сменить атрибуты на 755.

Заходим на сайт, проверяем — маршруты не работают. Для этого воспользуемся возможностями модуля mod_rewrite, таким образом Apache обучается делать то, что нам нужно.

Полный .htaccess:

CharsetDisable On

# Принудительно выставляем кодировку UTF-8 ибо 21 век на дворе!
AddDefaultCharset utf-8

# Разрешаем CGI
AddHandler cgi-script .pl
Options +ExecCGI

# Включаем mod_rewrite
RewriteEngine on

# Проверяем что запрошенный ресурс не реальный файл или директория
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_FILENAME} !-d

# и передаем путь нашему обработчику
RewriteRule ^(.*)$ index.pl/$1 [L]

Все, на этом настройку Apache/CGI в целом можно считать законченной.

Apache/mod_perl

Более прогрессивный способ по сравнению с CGI, позволяет запускать приложение автоматически и выдает достаточно неплохую скорость. В минусах — доступен не везде, и есть небольшие заморочки с написанием кода под mod_perl. Так что, возможно, понадобится небольшая доработка приложения, если не повезет. Как минимум, для этого способа понадобится установить Plack из CPAN, т.к. нам понадобится Plack::Handler::Apache2.

Настройка .htaccess:

<Perl>
    $ENV{PLACK_ENV} = 'production';
    $ENV{MOJO_HOME} = '/var/www/index.pl';
    $ENV{MOJO_TEMPLATE_CACHE} = 0;
</Perl>
SetHandler perl-script
PerlHandler Plack::Handler::Apache2
PerlSetVar psgi_app /var/www/index.pl

# Включаем mod_rewrite
RewriteEngine on

# Проверяем, что запрошенный ресурс не реальный файл или директория
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_FILENAME} !-d

# Передаем путь обработчику
RewriteRule ^(.*)$ index.pl/$1 [L]

Разворачиваем наше приложение и радуемся жизни.

Apache/FastCGI

В целом все аналогично предыдущим пунктам, за исключением необходимости небольшой доработки приложения. Строку

app->start;

необходимо заменить на:

app->start('fastcgi');

И все это сопроводить следующим .htaccess файлом:

# Объявляем хэндлер для FastCGI приложения
SetHandler fcgid-script
Options +ExecCGI

# Включаем mod_rewrite
RewriteEngine on

# Проверяем, что запрошенный ресурс не реальный файл или директория
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_FILENAME} !-d

# Передаем путь обработчику
RewriteRule ^(.*)$ index.pl/$1 [L]

# Запрос без указания пути передаем обработчику
# принудительно, иначе получим 403
RewriteRule ^$ index.pl [L]

И напоследок рассмотрим способ развертывания через Apache/mod_proxy.

Apache/mod_proxy

Да, как это ни странно, Apache тоже умеет работать в режиме реверс-прокси. Этот режим примечателен тем, что позволяет использовать всю мощь модулей Apache и при этом обработку непосредственно приложения возложить на отдельный сервер. У меня работает конфигурация, в которой Apache осуществляет SSO-авторизацию пользователей через mod_kerberos и после этого направляет их на приложение, которое крутится под Starman. Позволяет убить двух зайцев сразу — в браузерах с поддержкой SSO пользователю нет необходимости вводить свой доменный логин/пароль, с другой стороны — мне нет необходимости заниматься вопросами аутентификации пользователей, если авторизация через Kerberos не пройдет — то Apache просто не пропустит пользователя до приложения. Сплошная экономия, хотя для сайтов в интернете технология практически не применимая.

Итак, приступим к настройке, .htaccess нам здесь уже не помощник, будем править файл виртуального хоста:

<VirtualHost *:80>
    DocumentRoot /var/www/

    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>

    ProxyVia On

    # Превращаем переменные Apache в переменные %ENV
    ProxyPassInterpolateEnv On
    ProxyRequests Off
    ProxyPreserveHost On
    ProxyPass / http://localhost:3000/ keepalive=On
    ProxyPassReverse / http://localhost:3000/
    RequestHeader set X-Forwarded-HTTPS "0"
</VirtualHost>

Конфиг не отличается затейливостью и есть в Cookbook, за одним исключением:

ProxyPassInterpolateEnv On

Эта опция заставляет Apache прокинуть все свои переменные в переменные %ENV. Сильно помогает, когда вы используете авторизацию через Apache, например.

Но просто так вся эта конструкция не заработает, нужно включить дополнительные модули:

# a2enmod proxy
# a2enmod headers
# a2enmod proxy_http
# service apache2 restart

На этом краткий практикум можно считать законченным. Он покрывает 95% процентов случаев развертывания приложений на всевозможных серверах, с которыми мне только приходилось сталкиваться за всю свою трудовую деятельность. Оставшиеся 5% обычно представляли собой особо экзотичные настройки окружения shared-хостингов и побеждались допиливанием напильником вышепреведенных хостингов или техподдержки хостинг-провайдеров.

Денис Федосеев


От редактора | Содержание | Консольные приложения на Curses
Нас уже 1393. Больше подписчиков — лучше выпуски!

Комментарии к статье