Решил сменить связку Apache (mod_wsgi) + nginx на что-то более легковесное и проворное. Хоть выбор и велик решил пока ограничится двумя кандидатами - Gunicorn и uWSGI.
Начнем с установки nginx, он понадобиться как буфер перед gunicorn и для отдачи статики. До версии 0.8.40 нужно было собирать nginx с поддержкой модуля uwsgi (про uwsgi ниже), в последующих версиях модуль уже добавлен.
Под Ubuntu последнюю версию nginx можно установить из любого из двух PPA:
Stable version (на данный момент 1.0.5)
sudo apt-add-repository ppa:nginx/stable sudo apt-get update sudo apt-get install nginx
Development version (на данный момент 1.1.0)
sudo apt-add-repository ppa:nginx/development sudo apt-get update sudo apt-get install nginx
Под CentOS последнюю версию nginx можно установить из CentALT репозитария
rpm -ihv http://centos.alt.ru/repository/centos/5/i386/centalt-release-5-3.noarch.rpm yum install nginx
Проверим версию nginx и поддержку uwsgi
nginx -V
Со слов разработчиков gunicorn'a это WSGI HTTP сервер, написанный на питоне и легкий в настройке. Среди заявленных возможностей:
Установим gunicorn через pip (если pip не работает то вам сюда). Я устанавливаю gunicorn сразу в виртуальное окружение (virtualenv) проекта.
pip install gunicorn setproctitle
В директории с проектом разместим файл с настройками - gunicorn.conf.py.
bind = "127.0.0.1:8888" # или через сокет # bind = "unix:/home/proft/projects/blog/run/blog.socket" workers = 5 user = "www" group = "www" logfile = "/home/proft/projects/blog/log/gunicorn.log" loglevel = "info" proc_name = "blog" #pidfile = "/home/proft/projects/blog/gunicorn.pid"
Число worker'ов расчитываеться по такой формуле 2xCPUs + 1.
Тестовый запуск для django проекта
gunicorn_django -c gunicorn.conf.py
Перезагрузка gunicorn (этот способ использовать в случаи, если не настроен supervisor и есть параметр pidfile в конфигурационном файле).
kill -HUP /home/proft/projects/blog/gunicorn.pid
Рецепт перезапуска gunicorn при работе из vim.
Для управления gunicorn'ом с консоли с curses-интерфейсом есть пакет gunicorn-console.
Настройки proxy_params для nginx остался таким же.
server { listen 127.0.0.1:80; server_name blog.local; server_tokens off; access_log /home/proft/projects/blog/log/nginx_access.log; error_log /home/proft/projects/blog/log/nginx_error.log; location / { proxy_pass http://127.0.0.1:8888; # или через сокет http://unix:/home/proft/projects/blog/run/blog.socket # proxy_pass include proxy_params; } location /static { root /home/proft/projects/blog/apps; expires 24h; } }
Для запуска нескольких проектов на том же сервере - просто меняем порт в настройках gunicorn и nginx.
Осталось настроить supervisor для удобного управления gunicorn-сервером с консоли и автоматического перезапуска в случаи аварии.
Установим supervisor и зависимый модуль elementtree под Ubuntu
pip install elementtree sudo apt-get install supervisor
Установим supervisor и зависимый модуль elementtree под CentOS
pip install elementtree supervisor
Все ниже описанные действия касательно supervisor относятся только к CentOS. Добавим скрипт для управления supervisord
wget http://proft.me/static/linux/supervisord.txt mv supervisord.txt /etc/init.d/supervisord chmod +x /etc/init.d/supervisord
В /etc/init.d/supervisord нужно подправить путь к python.
Добавим supervisord в автозагрузку
chkconfig supervisord on
Создадим директорию для файлов с настройками для проектов
mkdir -p /etc/supervisor/conf.d/
Добавим в /etc/supervisord.conf
[include] files = /etc/supervisor/conf.d/*.conf
Настроем supervisor для тестового проекта на django, для этого сохраним в файл /etc/supervisor/conf.d/blog.conf следующие строки (для Ubuntu и CentOS)
[program:blog] command=/home/proft/.virtualenvs/blog/bin/gunicorn_django -c /home/proft/projects/blog/gunicorn.conf.py directory=/home/proft/projects/blog/apps user=www group=www autostart=true autorestart=true redirect_stderr=True daemon = False debug = False logfile = /home/proft/projects/blog/log/supervisor.log loglevel = "info"
Если gunicorn.conf.py не лежит рядом с settings.py, то в command, через пробел, нужно дописать полный путь к settings.py.
Настроем supervisor для тестового проекта на flask, для этого сохраним в файл /etc/supervisor/conf.d/blog.conf следующие строки (для Ubuntu и CentOS)
[program:blog] command=/home/proft/.virtualenvs/blog/bin/gunicorn -c /home/proft/projects/blog/gunicorn.conf.py main_pyfile:app directory=/home/proft/projects/blog/apps user=www group=www autostart=true autorestart=true redirect_stderr=True daemon = False debug = False logfile = /home/proft/projects/blog/log/supervisor.log loglevel = "info" environment = PYTHON_EGG_CACHE=/home/proft/projects/blog/.python-eggs,PYTHONPATH=/home/proft/projects/blog:$PYTHONPATH
Изменилась команда вызова gunicorn в параметре command и добавился параметр environment.
Запустим supervisord
service supervisord start
Для управления созданными настройками проектов для supervisor'а можно воспользоваться supervisorctl
sudo supervisorctl {start,status,stop} blog
uWSGI — быстрый, легко конфигурируемый и самовосстановливаемый сервер, написанный на C. Про скорость еще ниже поговрим, а самовосстановливаемый означает, что если запрос длится больше заданого времени, то главный процесс прибьет текущего worker'a, обрабатующего текущий запрос и запустит нового. Разработчики называют это - Harakiri mode.
Еще из приятных моментов
Установим последнюю версию uWSGI
pip install http://projects.unbit.it/downloads/uwsgi-latest.tar.gz
Конфигурационный файл с настройками для django-проекта
[uwsgi] djangoproject = /home/proft/projects/blog/apps/ env = DJANGO_SETTINGS_MODULE=settings virtualenv = /home/proft/.virtualenvs/blog/ chdir = %(djangoproject) module = django.core.handlers.wsgi:WSGIHandler() socket = /home/proft/projects/blog/blog.sock master = true processes = 5 idle = 3600 uid = www gid = www harakiri = 30 max-requests = 3000 logto = /home/proft/projects/blog/log/uwsgi.log buffer-size = 32768 post-buffering = 8192
Настройки supervisor для проекта сохраним в файл /etc/supervisor/conf.d/blog_uwsgi.conf
[program:blog_uwsgi] command=/usr/local/bin/uwsgi -ini /home/proft/projects/blog/uwsgi.ini directory=/home/proft/projects/blog/apps user=www group=www autostart=true autorestart=true redirect_stderr=True daemon = False debug = False logfile = "/home/proft/projects/blog/log/supervisor_uwsgi.log" loglevel = "info"
Настройки для nginx
server { listen 127.0.0.1:80; server_name blog.local; server_tokens off; access_log /home/proft/projects/blog/log/nginx_access.log; error_log /home/proft/projects/blog/log/nginx_error.log; location / { uwsgi_pass unix:///home/proft/projects/blog/blog.sock; include uwsgi_params; } location /static { root /home/proft/projects/blog/apps; expires 24h; } }
А теперь самое интресное - кто быстрее всех из трех заявленных конкурсантов :). Тесты проводились на двух ядерном ноуте, каждое ядро по 1.66Hz, 2 GB RAM, Ubuntu 11.04.
Замеры оценок проводились так:
Команда для тестирования
ab -c10 -n100 http://blog.local/
Замер используемой памяти (RSS (resident set size)) (в МБайт) проводилась с помощью команды
# для gunicorn ps -eo fname,rss | grep gunicorn | awk '{sum +=$2} END {print sum/1024}' # для uwsgi ps -eo fname,rss | grep uwsgi | awk '{sum +=$2} END {print sum/1024}' # для apache2 + mod_wsgi ps -eo fname,rss | grep apache2 | awk '{sum +=$2} END {print sum/1024}'
после запуска ab.
Таким образом gunicorn оказался самым проворным и менее прожордивый по ресурсам памяти.