Задача: развернуть несколько 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).
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-плагин для питона, поэтому пришлось сделать по-другому.
Автор: lightsgoout