Развертываем WSGI приложение... ∞
Статья, обобщение которой войдет в доклад для RuPyRu2007.
Итак, вопрос развертывание WSGI-приложений. Тема, а особенно на русском, не особенно развита. Краткий обзор решений.
mod_python
Скорость: 5
Удобство: 4

Один из самых быстрых методов развертывания WSGI-приложений - apache+mod_python. В этом случае все приложение и все зависимые модули загружаются в память, чем и достигается максимальное быстродействие. Существует ряд модулей (~1, ~2, ~3, ~4), преобразовывающие WSGI-протокол в формат обработчика mod_python. Конфигурационный файл для всех модулей примерно одинаков и выглядит так:
<VirtualHost *>
<Location /wsgi>
SetHandler mod_python
# конвертер wsgi <-> mod_python
PythonHandler modpython_gateway::handler
# в зависимости от конвертера, либо application, либо wsgi.application
# т.е. запускаемое wsgi-приложение
PythonOption wsgi.application testapp::app
</Location>
</VirtualHost>
Конвертер указывается опцией PythonHandler
, а запускаемое приложение - PythonOption wsgi.application
(зависит от модуля-конвертера).
Из недостатков можно отметить большой объем потребляемой памяти, как следствие, статику желательно отдавать либо "легким" apache, либо nginx/lighttpd. Т.е. сложность настроек растет, что не радует.
FastCGI

Здесь основная идея в том, что FastCGI сервер (flup) загружает интерпретатор и все зависимые модули приложения и принимает запросы от фронтенд-сервера (apache, lighttp, nginix), не запуская интерпретатор заново.
Иными словами, запущено два сервиса: FastCGI-сервер и http-сервер. Http-сервер отдает статику и перенаправляет запросы на динамику к FastCGI-серверу, который их и обрабатывает.
Прежде настройки фронтенд-сервера нужно обеспечить поддержку FastCGI со стороны приложения. Как сказано выше, это достигается использованием flup - сервера FastCGI на Python. Он может обрабатывать запросы как в threaded режиме, так и в preforked. Скрипт для запуска FastCGI сервера с WSGI-приложением выглядит примерно так:
from testapp import app
from flup.server.fcgi import WSGIServer # threaded
WSGIServer(app, bindAddress=('/tmp/fcgi_wsgi.socket')).run()
FastCGI+Apache
Скорость: 2
Удобство: 2
Чем неприятно удивил mod_fcgi, так это:
- необходимостью прописывать для fcgi-сервера абсолютный путь вне определения VirtualHost
- скоростью - аналогичные решения с nginx/lighttpd дают бОльшую скорость
В целом, я не вижу целевой аудитории apache+mod_fastcgi: если уже стоит apache, то есть смысл использовать mod_python, а если уж использовать FastCGI, то в качестве фронтенда резонно брать более легкий сервер - или nginx, или lighttpd.
В любом случае, конфиг таков:
NameVirtualHost *
FastCGIExternalServer /var/www/wsgi.fcgi -socket /tmp/fcgi_wsgi.socket
<VirtualHost *>
RewriteEngine On
RewriteRule ^/wsgi/(.*)$ /wsgi.fcgi/$1 [QSA,L]
</VirtualHost>
Еще раз отмечу, что FastCGI-сервер определяется вне виртуального хоста и для него указывается абсолютный путь, а для rewrite-правил используется относительный путь к fcgi.
FastCGI может "общаться" с фронтенд-севером либо через unix socket, либо через tcp. В первом случае чуть быстрее, во втором - более гибко (в общем случае. FastCGI может быть на другой машине), подробности см. у Ковырина. Конфиги особо не отличаются, но для единообразия я указываю настройки для unix socket.
FastCGI+lighttpd
Скорость: 4
Удобство: 4
Помимо того, что с lighttpd FastCGI работает быстрей, с ним еще и удобней работать - есть возможность создавать отдельные конфиги для конкретных WSGI-приложений. Например, общий файл настроек lighttpd может не включать настроек для модуля mod_fastcgi, но отдельный конфиг может изменить это, что-то вроде такого:
server.modules += ( "mod_fastcgi", "mod_rewrite" )
fastcgi.server = ( "/wsgi.fcgi" =>
(
"wsgi" => (
"socket" => "/tmp/fcgi_wsgi.socket",
"check-local" => "disable"
)
)
)
url.rewrite = ( "^/wsgi/(.*)" => "/wsgi.fcgi/$1")
FastCGI+nginx
Скорость: 4
Удобство: 4
Что касается nginx vs. lighttpd, то здесь, IMHO, уже играют личные предпочтения да нюансы использования. Что касается моего мнения, то мне nginx чуть больше нравится.
Для секции server я использую (у меня стоит сборка Павла Аммосова для Debian) примерно такие параметры для запуска WSGI-приложения через FastCGI:
location /wsgi {
fastcgi_pass unix:/tmp/fcgi_wsgi.socket;
fastcgi_intercept_errors off;
include fastcgi_params;
}
По скорости FastCGI nginx примерно равен lighttpd. Да и конфиги похожи. Так что вопрос выбора между nginx и lighttpd - дело вашего вкуса.
Python-решения
Существует несколько pure Python WSGI-серверов. У каждого из них свой способ запуска. Для того, чтобы унифицировать и упростить развертывание, Ян Бикинг создал Paste. Использование Paste выходит за рамки данного обзора (тем не менее, в будущем я обязательно коснусь этой темы). Так что я приведу "низкоуровневый" способ запуска WSGI-приложений для наиболее интересных pure Python решений.
Есть один нюанс, который стоит помнить - помимо отдачи динамического контента, нужно еще отдавать и статику, так что если выбранное решение этого не умеет , вам понадобится отдельный http-сервер. Если нужен Python powered httpd - возьмите apricot. Он, конечно же, проигрывает по скорости таким серверам как apache/lighttpd/nginx, однако компенсирует гибкостью и простотой.
Twisted
Скорость: 2
Удобство: 5
Twisted Web2 показывает весьма приличную скорость - даже чуть выше чем у Apache+FastCGI. Twisted Web2 выглядит вполне достойным кандидатом для использования для средненагруженных серверах. Скорость отдачи статики, конечно же, не может сравниться с apache/lighttpd/nginx, но зато вам не нужно настраивать отдельный сервер (особенно, если Twisted уже используется как платформа для сетевых сервисов). И еще - это Python, так что вы можете отдавать статику так как вам нужно, например, с хитрой авторизацией.
Итак, пример tac (tac - twisted app config) для запуска WSGI-приложения testapp.app
:
from twisted.application import service, strports
from twisted.web2 import wsgi, channel, server
from testapp import app as wsgi_app
application = service.Application('Twisted WSGI/Nevow test')
wsgi_site = server.Site(wsgi.WSGIResource(wsgi_app))
wsgi_httpd = strports.service('tcp:8080', channel.HTTPFactory(wsgi_site))
wsgi_httpd.setServiceParent(application)
CherryPy
Скорость: 5+
Удобство: 4
CherryPyWSGIServer - феноменально быстрый WSGI-сервер. Быстрее, чем mod_python! Что интересно, cherrypy.wsgiserver
фактически не зависит от остальных компонент CherryPy и доступен в виде отдельного модуля. Запуск производится следующим способом:
from cherrypy import wsgiserver
from testapp import app
wsgi_apps = [('/wsgi', app)]
wsgiserver.CherryPyWSGIServer(('', 8080), wsgi_apps).start()
CherryPy как и Twisted умеет отдавать статику (Twisted даже чуть быстрей), но лучше это делать при помощи отдельного сервера. Да, я не оговорился, CherryPy обрабатывает WSGI быстрей, чем отдает статику, причем разница - в два-три раза.
Итоги
Для интранета я бы рекомендовал Twisted Web2 - качественное, "всё включено", решение. Вам не нужно настраивать отдельный сервер для отдачи статики, да и производительность для pure Python весьма достойна.
Для интернета четкой рекомендации дать сложно, вот от чего можно смело отказываться, так это от apache+mod_fastcgi: ни скорости, ни гибкости настройки. Интересно выглядит новичок CherryPy+nginx (т.е. nginx для отдачи статики и как reverse proxy для CherryPy), но требуются дополнительные исследования этого вопроса - более полное тестирование производительности, решение вопросов массового развертывания: инфраструктура запуска/останова, запускать ли приложения в одном экземпляре CherryPyWSGIServer, или "разводить" на разные порты. Для тех кто хочет "прям сейчас и без дополнительной возни", но может мириться с более серьезным потреблением памяти, я советую apache+mod_python. Если же приходится действовать в режиме экономии памяти (например, на VDS), то lighttpd/nginx+FastCGI выглядит оптимальным вариантом. Запускать FastCGI-сервер в threaded или preforked, использовать unix socket или tcp - зависит от конкретной ситуации.
Резюме:
- интранет - Twisted Web2
-
интернет
- прям сейчас и без дополнительной возни - apache+mod_python
- оптимально - lighttpd/nginx+FastCGI
- многообещающе - nginx+CherryPy