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