Мульти-хостинг django приложений с помощью nginx + uwsgi + virtualenv

Содержание

Задача: развернуть несколько django-проектов, использующих разные версии django и разные версии питона на одном сервере.

Инструкция приводится для ОС Ubuntu 12.04.

Подготовка

Для начала ставим интересующие нас версии питона.

Необходимые пакеты для компиляции:

sudo apt-get install zlib1g zlib1g-dev zlibc

Ставим питон, я поставил 2.7.4 и 3.3.1

wget http://python.org/ftp/python/2.7.4/Python-2.7.4.tar.bz2
tar -xf Python-2.7.4.tar.bz2
cd Python-2.7.4
./configure --prefix=/opt/python2.7/ --enable-unicode=ucs4
make && make install
wget http://python.org/ftp/python/3.3.1/Python-3.3.1.tar.bz2
tar -xf Python-3.3.1.tar.bz2
cd Python-3.3.1
./configure --prefix=/opt/python3.3/
make && make install

Создадим директории для конфигов наших проектов.

  • /home/hosting/.nginx/ — здесь будут конфиги nginx
  • /home/hosting/.uwsgi/ — здесь будут конфиги uwsgi
  • /home/hosting/.virtualenvs/ — здесь будут находится виртуальные окружения проектов
  • /home/hosting/project1/ — файлы первого джанго-проекта
  • /home/hosting/project2/ — файлы второго джанго-проекта

Установка nginx

apt-get install nginx-full

Почему nginx-full, а не nginx? В nginx-full уже входит модуль для работы с uwsgi.

Нужно указать nginx’у откуда загружать конфиги виртуальных хостов.
Открываем /etc/nginx/nginx.conf.
После include /etc/nginx/sites-enabled/*; добавить строчку include /home/hosting/.nginx/*.conf;
Теперь нужно создать nginx-конфиги виртуальных хостов.

  • /home/hosting/.nginx/project1.conf
  • /home/hosting/.nginx/project2.conf

Пример конфига:

server {
    server_name project1.com;
    access_log /var/log/project1.access.log;
    error_log /var/log/project1.error.log;

    location / {
        uwsgi_pass unix:/tmp/project1.sock;
        include /etc/nginx/uwsgi_params;
    }

    location /static/ {
        alias /home/hosting/project1/static/;
    }

    location /media/ {
        alias /home/hosting/project1/media/;
    }
}

Нужно дать права на директорию /home/hosting/.nginx пользователю www-data (или тому пользователю, под которым работает nginx).

[ads1]

chown -R www-data:www-data /home/hosting/.nginx/

Запускаем nginx

service nginx start

Установка virtualenvwrapper

virtualenvwrapper — удобная обертка вокруг virtualenv.
Ставим pip если еще не стоит:

sudo apt-get install python-pip

Ставим virtualenvwrapper:

pip install virtualenvwrapper

В ~/.bashrc добавляем:

export WORKON_HOME=/home/hosting/.virtualenvs/
source /usr/local/bin/virtualenvwrapper.sh

Перелогиниваемся в консоль, чтобы .bashrc загрузился. Теперь у нас должна быть доступна команда mkvirtualenv в консоли.

Размещаем файлы проектов в директориях:

  • /home/hosting/project1/
  • /home/hosting/project2/

Для каждого проекта создадим виртуальное окружение. Допустим, project1 будет работать на питоне 2.7, а project2 на 3.3.

mkvirtualenv project1 -p /opt/python2.7/bin/python
deactivate
mkvirtualenv project2 -p /opt/python3.3/bin/python3
deactivate

Для каждого проекта ставим зависимости в виртуальное окружение. (В моем случае зависимости прописаны в файле requirements.txt в корне каждого проекта)

workon project1
cd /home/hosting/project1
pip install -r requirements.txt
workon project2
cd /home/hosting/project2
pip install -r requirements.txt

Настройка uwsgi.

Будем настраивать его в режиме императора (—emperor), т.к. этот режим специально предназначен для мульти-хостинга.

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

По-дефолту uwsgi исполняет код проекта тем питоном, который находится в текущем окружении, поэтому нам нужно будет запускать uwsgi из virtualenv.
Так как у нас несколько разных версий питона, то uwsgi запущенный, например, из-под питона 2.7 не сможет обслуживать джанго-приложение с окружением из питона 3.3.
Значит мы создадим по императору на каждую версию питона, и конфиги приложений будем группировать по версии интерпретатора.

Создаем виртуальные окружения для императоров.

mkvirtualenv python27 -p /opt/python2.7/bin/python
deactivate
mkvirtualenv python33 -p /opt/python3.3/bin/python3
deactivate

Теперь нужно поставить uwsgi в каждый virtualenv и настроить его.

workon python27
pip install uwsgi
workon python33
pip install uwsgi

Создадим директории конфигов для каждого uwsgi-императора.

mkdir /home/hosting/.uwsgi/python27
mkdir /home/hosting/.uwsgi/python33

Создадим uwsgi-конфиги для каждого проекта.
/home/hosting/.uwsgi/python27/project1.ini

[uwsgi]
protocol = wsgi
master = true
processes = 1 # по количеству ядер
socket = /tmp/project1.sock

# Докидываем в pythonpath библиотеки из виртуаленва, т.к uwsgi в динамическом режиме не умеет искать библиотеки в virtualenv
pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/site-packages/setuptools-0.6c11-py2.7.egg
pythonpath = /home/hosting/.virtualenvs/project1/lib/python27.zip
pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7
pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/plat-linux2
pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/lib-tk
pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/lib-old
pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/lib-dynload
pythonpath = /home/hosting/.virtualenvs/project1/lib/python2.7/site-packages
 # почему-то в virtualenv не все нужные файлы есть, поэтому пришлось добавить эту строчку
pythonpath = /opt/python2.7/lib/python2.7

chdir = /home/hosting/project1
virtualenv = /home/hosting/.virtualenvs/project1
env = DJANGO_SETTINGS_MODULE=settings
module = django.core.handlers.wsgi:WSGIHandler()
no-site = true
vhost = true
chmod-socket = 666

Конфиг второго проекта — по аналогии. Имя файла обязательно должно заканчиваться на .ini, иначе uwsgi не подхватит этот конфиг.

Теперь нужно зарегистрировать uwsgi как сервис в системе. Я использовал upstart, он есть в убунте из коробки.
Создадим два конфига:
/etc/init/uwsgi27.conf

description "uWSGI Emperor (python 2.7)"
start on runlevel [2345]
stop on runlevel [06]
exec /home/hosting/.virtualenvs/python27/bin/uwsgi --master --emperor /home/hosting/.uwsgi/python27 --logto /var/log/uwsgi27.emperor.log

/etc/init/uwsgi33.conf

description "uWSGI Emperor (python 3.3)"
start on runlevel [2345]
stop on runlevel [06]
exec /home/hosting/.virtualenvs/python33/bin/uwsgi --master --emperor /home/hosting/.uwsgi/python33/ --logto /var/log/uwsgi33.emperor.log

Пользователи и безопасность

Из-под root у нас будут выполняться только «императорские» процессы, а сами проекты будут под своими пользователями.

Создадим пользователя для каждого из проектов.

adduser --no-create-home --disabled-login --disabled-password www-project1
adduser --no-create-home --disabled-login --disabled-password www-project2

В каждый из uwsgi ini-конфигов добавим параметры uid gid

uid = www-project1 # пользователь
gid = www-project1 # группа

Установим правильные права доступа

chown -R www-data:www-data /home/hosting/.nginx
chmod -R 770 /home/hosting/.nginx

chown -R root:root /home/hosting/.uwsgi
chmod -R 770 /home/hosting/.uwsgi
chown -R root:root /home/hosting/.virtualenvs/python27 /home/hosting/.virtualenvs/python33
chmod -R 775 /home/hosting/.virtualenvs

chown -R www-project1:www-project1 /home/hosting/project1 /home/hosting/.virtualenvs/project1
chown -R www-project2:www-project2 /home/hosting/project2 /home/hosting/.virtualenvs/project2

Запускаем uwsgi

service uwsgi27 start
service uwsgi33 start

Проверяем — все должно работать.

Если что-то не работает, смотрим логи nginx, указанные в конфиге проекта, и логи uwsgi-императора.
Признаком того, что uwsgi удачно развернул приложение является наличие строчки WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x135e280 pid: 21737 (default app) в логе uwsgi.
Чтобы добавить новое приложение, нужно создать конфиг в .nginx и .uwsgi и перезапустить nginx. uwsgi сам подхватит новый конфиг.

Ссылочки

projects.unbit.it/uwsgi/wiki/MultiPython
projects.unbit.it/uwsgi/wiki/DynamicVirtualenv
auphonic.com/blog/2011/06/18/django-deployment-nginx-uwsgi-virtualenv-and-fabric/
eshlox.net/en/2012/09/11/nginx-uwsgi-virtualenv-and-django-ubuntu-1204/
uwsgi-docs.readthedocs.org/en/latest/Emperor.html

P.S.

Описанный в статье способ не очень красивый с точки зрения архитектуры; изначально я надеялся обойтись одним uwsgi-императором и разруливать версию интерпретатора параметром plugin в конфиге приложения. Но у меня не получилось собрать uwsgi-плагин для питона, поэтому пришлось сделать по-другому.