Fork me on GitHub
5/2/2007

Развертываем WSGI приложение...

Статья, обобщение которой войдет в доклад для RuPyRu2007.

Итак, вопрос развертывание WSGI-приложений. Тема, а особенно на русском, не особенно развита. Краткий обзор решений.

mod_python

Скорость: 5
Удобство: 4

WSGI-app -> wsgi-modpy-handler -> mod_python (apache)

Один из самых быстрых методов развертывания 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

WSGI-app -> flup -> httpd (apache, nginx, lighttpd)

Здесь основная идея в том, что 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

Комментарии

Все статьи