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

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

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

Подготовка


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

Необходимые пакеты для компиляции:
sudo apt-get install zlib1g zlib1g-dev zlibc libssl-dev

Ставим питон, я поставил 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-плагин для питона, поэтому пришлось сделать по-другому.
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 11

    +1
    Зря вы все из исходников компилите, как потом обновлять планируете? ручками?
    Но у меня не получилось собрать uwsgi-плагин для питона, поэтому пришлось сделать по-другому.
    apt-get install uwsgi-plugin-python3 uwsgi-plugin-python
    И подумайте о структуре проектов, если вы захотите перенести хотя бы один из них на другой сервер вам придется вытаскивать конфиги и окружение сразу с нескольких папок. У себя использую такой подход:
    www
    --example_proj1
    ----conf(конфиги nginx,uwsgi)
    ----logs(логи nginx,uwsgi,django)
    ----pids(сокет и пидфайл)
    ----project(файлы джанго проекта)
    ----static(статика проекта собираемая через collectstatic)
    ----venv(переносимое виртуальное окружение)
    --example_proj2

    после переноса папки проекта на другой сервер обычно достаточно создать симлинки на конфиги nginx и uwsgi, ну и если нужно то добавить пользователя.
      0
      зачем тащить за собой venv, если есть pip и requirements.txt?
        0
        Можно( и нужно) так конечно:) У меня просто руки еще не дошли добавить скрипт который при запуске будет создавать симлинки для конфигов, создавать бд и импортить в нее данные с бекапа, и настраивать venv.
          0
          Я тоже не вижу смысла в переносном виртуальном окружении, но бывает такое что на pypi какие-то глюки или еще где-то поэтому для особо приоритетных проектов делаю pybundle(на всякий случай), а некоторые вообще свои собственные pypi поднимают))
          0
          Зря вы все из исходников компилите, как потом обновлять планируете? ручками?

          Ну, допустим мне надо сразу версию 2.6, 2.7 и 3.3 на одном сервере, насколько я понял проще это собрать из исходников чем поставить из репозиториев. Может я не прав.

          > apt-get install uwsgi-plugin-python3 uwsgi-plugin-python
          А если нужен плагин для 2.6, 2.7, и 3.3, как их поставить? Похоже, тут не обойтись двумя плагинами: projects.unbit.it/uwsgi/wiki/MultiPython

          Структура проектов годная, спасибо.
            0
            Для 2,7 и 3,3 ставить прям так:
            apt-get install uwsgi-plugin-python3 uwsgi-plugin-python
            для 2,6 придется или компилить или распаковать из uwsgi-plugin-python только старой версии(до 12.04 если не ошибаюсь)

            Ну а сам питон 2,6 в систему можно добавить из ппа
            sudo add-apt-repository ppa:fkrull/deadsnakes
            sudo apt-get update
            sudo apt-get install python2.6 python2.6-dev
          +1
          apt-get install nginx-full
          
          Почему nginx-full, а не nginx? В nginx-full уже входит модуль для работы с uwsgi.
          Лучше сразу ставить версию из официального репозитория: nginx.org/en/linux_packages.html — uwsgi модуль там есть.
            +1
            Как-то странно режим Emperor используется))
            Можно было иметь всего 1 экземпляр uwsgi а для python2/python3 просто разные виртуальные окружения и у приложений в конфигах прописать например «plugin = python3» а вообще какой питон в вирт окружении стоит такой и подхватится, так что не надо делать разные uwsgi.
            А еще у uwsgi конфиг получился слишком длинный и не DRY. Можно было упрощать как-то так:
            [uwsgi]
            protocol = uwsgi  # (protocol = wsgi кстати, а почему не uwsgi?)
            socket = /tmp/%n.sock  # где %n - имя файла без расширения
            
            chdir = /home/hosting/%n
            venv = /home/hosting/.virtualenvs/%n
            
            # не замечал чтобы что-то не подхватывало, ну ладно
            pythonpath = %(venv)/lib/python2.7/site-packages/setuptools-0.6c11-py2.7.egg
            #  далее по аналогии раз уж такое понадобилось, понятно что %(venv) короче и удобнее чем прописывать сто раз путь.
            
            # с django 1.4 не надо env = DJANGO_SETTINGS_MODULE=settings
            
            module = %n.wsgi  # начиная с джанго 1.4 можно так а не django.core.handlers.wsgi:WSGIHandler()
            

            Насчет сокращений в конфиге итд читайте тут

            Я деплою тоже через uWSGI и подключаю еще мониторинг newrelic, и что-то еще…
            чтобы работало с uWSGI нужно добавить:
            enable-threads = true
            single-interpreter = true
            lazy-apps = true
            memory-report = true
            


            А еще uWSGI умеет брать конфиги не только из .ini файлов а и из json, xml, yaml и других а так-же из базы данных, например:
            uwsgi --plugin emperor_mongodb --emperor "mongodb://127.0.0.1:27107,emperor.vassals,{enabled:1}"
            или
            uwsgi --plugin emperor_pg --emperor "pg://host=127.0.0.1 user=foobar dbname=emperor;SELECT name,config,ts FROM vassals"
            

            Тут доки по «считывальщикам конфигов вассалов»
            так что раз уж речь зашла о хостинге, то такой вариант с конфигами в бд одним из лучших будет, причем uwsgi следит за изменениями, как только в бд данные вассала меняются он перегружает этого вассала, как из бд вассал удаляется — его процесс тоже убивается ну и как новая запись в бд появляется — стартует новый вассал.
            Ну и странно что не воспользовались режимом Tyrant(emperor-tyrant) — который как-раз для таких случаев и предусмотрен, разные пользователи итд)
              +1
              Не забываем что через uWSGI можно запускать и Ruby и PHP итд, и он по производительности очень хороший.
              Умеет мониторить файлы и директории, система сигналов имеется(мне uwsgi самостоятельно без всяких скриптов шлет в джаббер всякие тревоги), так-же умеет запускать сам различные программы «attach-daemon», может выступать в роли обработчика аналога celery и cron итд итп)) куча функционала.
              uwsgi даже может скомпилиться с джангой и вашим приложением внутри в бинарнике))) Очень много всего покрывает.
              Для хостинга uwsgi очень неплохой вариант))
                0
                Можно было иметь всего 1 экземпляр uwsgi а для python2/python3 просто разные виртуальные окружения и у приложений в конфигах прописать например «plugin = python3» а вообще какой питон в вирт окружении стоит такой и подхватится, так что не надо делать разные uwsgi.

                Согласен. Тут я просто смалодушничал, не получилось у меня собрать плагины для uwsgi (ошибки компиляции были), поэтому сделал как сделал. Собственно, в P.S. про это написал.
                Вот что получаю при попытке собрать плагин:
                Скрытый текст
                root@192756:~/uwsgi-1.9.10# /opt/python2.7/bin/python uwsgiconfig.py --plugin plugins/python/ core python27
                using profile: buildconf/core.ini
                detected include path: ['/usr/lib/gcc/x86_64-linux-gnu/4.6/include', '/usr/local/include', '/usr/lib/gcc/x86_64-linux-gnu/4.6/include-fixed', '/usr/include/x86_64-linux-gnu', '/usr/include']
                *** uWSGI building and linking plugin plugins/python/ ***
                [gcc -pthread] /usr/lib/uwsgi/python27_plugin.so
                /usr/bin/ld: /opt/python2.7/lib/python2.7/config/libpython2.7.a(abstract.o): relocation R_X86_64_32S against `_Py_NotImplementedStruct' can not be used when making a shared object; recompile with -fPIC
                /opt/python2.7/lib/python2.7/config/libpython2.7.a: could not read symbols: Bad value
                collect2: ld returned 1 exit status
                *** unable to build python27 plugin ***
                

                Если знаете что с этим делать, буду признателен, мое гугл-фу меня подвело в этой ситуации.

                Вариант с конфигами в БД я рассматривал, но отказался: лишняя точка отказа получается: если какая-то беда с постгресом, то и хостинг не поднимется. (Представим что лежит pg и перезапускаем uwsgi). Файлики с конфигами надежнее, на мой взгляд + бекапировать проще, да и поправить если что конфиг руками проще, чем залезть в БД.

                Ну и странно что не воспользовались режимом Tyrant(emperor-tyrant)

                На сколько я понял, единственное отличие тиран-режима, что он сам проставляет приложению uid и gid, причем uid и gid будут владелец и группа конфигурационного файла. Если пользователи не имеют доступ к редактированию uwsgi-конфигов, то такой режим не обязателен.
                0
                > Перелогиниваемся в консоль, чтобы .bashrc загрузился. Теперь у нас должна быть доступна команда mkvirtualenv в консоли.

                Не надо никуда перелогиниваться, достаточно выполнить

                source /usr/local/bin/virtualenvwrapper.sh

                Only users with full accounts can post comments. Log in, please.