Nginx + uWSGI + virtualenv + Django на Debian Squeeze

    Некоторое время назад озадачился поиском способа развертывания проектов Django, к которому предъявлялись два требования:
    1. Удобное управление запуском/остановкой/перезапуском нескольких проектов на одном хосте
    2. Поддержка разных виртуальных сред для разных проектов

    По второму пункту мой выбор склонился в пользу Nginx + uWSGI. По первому же, из рассмотренных мною вариантов больше всего понравились обвязки для uWSGI в Debian.

    Установка ПО


    Поддержка uWSGI появилась в Nginx с версии 0.8.40, но ни первого, ни второго в стабильной ветке Debian нет. Поэтому Nginx будем брать из бэкпортов, а uWSGI – из нестабильной ветки. Для этого добавим следующие строчки в /etc/apt/sources.list:
    deb http://backports.debian.org/debian-backports squeeze-backports main contrib non-free
    deb http://ftp.ru.debian.org/debian testing main non-free contrib
    deb http://ftp.ru.debian.org/debian unstable main non-free contrib
    

    И создадим примерно такой /etc/apt/preferences:
    Package: *
    Pin: release a=stable
    Pin-Priority: 700
    
    Package: *
    Pin: release a=squeeze-backports
    Pin-Priority: 675
    
    Package: *
    Pin: release a=testing
    Pin-Priority: 650
    
    Package: *
    Pin: release a=unstable
    Pin-Priority: 600 
    

    Синхронизируемся с помощью aptitude update и устанавливаем необходимые пакеты:
    aptitude -t squeeze-backports install nginx
    aptitude -t unstable install uwsgi
    aptitude -t unstable install uwsgi-plugin-python
    aptitude -t unstable install python-virtualenv
    

    Структура каталогов для проектов


    Рассмотрим развертывание для проекта example. Для определенности, предположим, что все проекты размещаются в подкаталоге proj домашнего каталога пользователя web. Так же в proj имеется подкаталог static, где будут размещаться статические файлы проектов, за раздачу которых будет отвечать Nginx. Для проекта example получим следующие пути:
    /home/web/proj/example
    /home/web/proj/static/example
    

    В settings.py проекта example имеем настройки:
    STATIC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '../static/example'))
    STATIC_URL = '/static/' 
    

    Таким образом менеджмент-команда collectstatic будет собирать всю статику проекта в /home/web/proj/static/example.

    Виртуальное окружение для проекта example будет находиться в /home/web/envs/example.

    Настройка uWSGI и Nginx


    Конфигурационный файл uWSGI для проекта example будет называться /etc/uwsgi/apps-available/example.ini и примет следующий вид:
    [uwsgi]
    	plugins = python27
    	virtualenv = /home/web/envs/example/
    	chdir = /home/web/proj/example/
    	pythonpath = ..
    	env = DJANGO_SETTINGS_MODULE=example.settings
    	module = django.core.handlers.wsgi:WSGIHandler()
    	touch-reload = /home/web/proj/example/touchme 
    

    Также создаем символическую ссылку на него в /etc/uwsgi/apps-enabled:
    cd /etc/uwsgi/apps-enabled
    ln -s ../apps-available/example.ini .
    

    После чего запускаем демон uWSGI: /etc/init.d/uwsgi start example

    Конфигурационный файл Nginx назовем /etc/nginx/sites-available/example. Его минимальный вариант может быть таким:
    server {
    	listen	80;
    
    	server_name	example;
    
    	access_log	/var/log/nginx/example.access.log;
    	error_log	/var/log/nginx/example.error.log;
    
    	location /static/ {
    		alias	/home/web/proj/static/example/;
    	}
    
    	location / {
    		include	uwsgi_params;
    		uwsgi_pass	unix:///var/run/uwsgi/app/example/socket;
    	}
    }
    

    Точно так же создаем символическую ссылку в /etc/nginx/sites-enabled:
    cd /etc/nginx/sites-enabled/
    ln -s ../sites-available/example .
    

    И перезапускаем Nginx: /etc/init.d/nginx restart

    Под капотом


    Сначала вкратце остановлюсь на пераметрах в конфигурационном файле uWSGI. Параметр plugins явно задает версию Python. Параметры chdir и pythonpath добавляют каталог проекта и его родительский каталог в пути поиска Python. Параметры env и module позволяют обойтись без создания специального скрипта для запуска проекта демоном uWSGI. Параметр touch-reload позволяет делать перезагрузку проекта с помощью команды touch /home/web/proj/example/touchme.

    Такой лаконичный файл конфигурации для uWSGI получился потому, что стартовый скрипт /etc/init.d/uwsgi использует еще один конфигурационный файл /usr/share/uwsgi/conf/default.ini, в котором для всех остальных необходимых параметров заданы достаточно разумные значения по умолчанию. При необходимости их можно переопределить в /etc/uwsgi/apps-available/example.ini. За удалением некоторых комментариев, содержимое /usr/share/uwsgi/conf/default.ini будет следующим:
    [uwsgi]
    # try to autoload appropriate plugin if "unknown" option has been specified
    autoload = true
    
    # enable master process manager
    master = true
    
    # spawn 2 uWSGI worker processes
    workers = 2
    
    # automatically kill workers on master's death
    no-orphans = true
    
    # write master's pid in file /run/uwsgi/<confnamespace>/<confname>/pid
    pidfile = /run/uwsgi/%(deb-confnamespace)/%n/pid
    
    # bind to UNIX socket at /run/uwsgi/<confnamespace>/<confname>/socket
    socket = /run/uwsgi/%(deb-confnamespace)/%n/socket
    
    # set mode of created UNIX socket
    chmod-socket = 660
    
    # place timestamps into log
    log-date = true
    
    # user identifier of uWSGI processes
    uid = www-data
    
    # group identifier of uWSGI processes
    gid = www-data
    

    В обвязках к uWSGI в Debian введено понятие конфигурационного пространства имен. В default.ini оно представлено с помощью переменной %(deb-confnamespace) со значением по умолчанию app. Имя конфигурационного пространства имен определяется следующим образом: стартовый скрипт /etc/init.d/uwsgi ищет в каталоге /etc/uwsgi все подкаталоги, заканчивающиеся на s-enabled, и часть имени подкаталога, предшествующая s-enabled становится именем конфигурационного пространства для файлов в этом подкаталоге. В нашем случае можно было бы создать в /etc/uwsgi более привычные подкаталоги sites-available и sites-enabled, в которые поместить файл конфигурации example.ini и символическую ссылку на него соответственно; pidfile и socket для этой конфигурации находились бы в /run/uwsgi/site/example/pid и /run/uwsgi/site/example/socket, а управление демоном uWSGI для этой конфигурации осуществлялось бы с помощью команд /etc/init.d/uwsgi start|stop|restart site/example. При отсутствии префикса подразумевается пространство имен app.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 45
    • 0
      Расскажите пожалуйста, вы в чём-то выигрываете, вынося директорию проекта из директории виртуального окружения, или просто так карты легли?
      • +2
        Да как-то вообще мысли не приходило одно в другом держать
        • 0
          Тоже зачем-то раньше держал вложенный проект.
          На нынешнем проекте, на dev-сервере приходится часто ставить в виртуальное окружение пакеты из trunk-ов, в такой конфигурации (с отдельными каталогами) гораздо проще тестировать.
          • 0
            А есть разница между «держать зачем-то» и «держать понятно зачем» %)
            Замечу, в статье речь про готовый проект под конкретное окружение (читай «веб-сервис»), а не о приложении, на которое распространяется требование поддержки разных окружений, экземпляры которого уйдут клиентам. Вот последнее попадёт под описанную вами задачу, а для первого разделение излишне. В таком вот разрезе я об этом думаю.
        • 0
          Почему uWSGI а не fastcgi?
          Я последний использую, может у первого есть какие-то скрытые преимущества? )
          • 0
            Ну, например, удобный релоад.
            • 0
              • 0
                а у меня через supervisor
                • 0
                  Релоад не только удобный, но и «graceful» (мягкий? вежливый?) — процессы не просто прибиваются и запускаются снова, старым процессам дается какое-то время чтобы обработать текущие запросы. В противном случае, если пользователю не повезло и его запрос обрабатывался как раз в момент перезапуска — он получит что-нибудь вроде 502 bad gateway.
                  Как реализовать graceful reload в случае fastcgi обсуждают, например, тут: stackoverflow.com/questions/361855/how-to-gracefully-restart-django-running-fcgi-behind-nginx
                  • 0
                    В смысле, у uwsgi — grceful reload. А у баш-скрипта на который вы ссылку дали — не очень :-)
            • 0
              Использую такую же связку, но uwsgi.ini лежит в каждом проекте в папке deploy/ и логи пишутся в deploy/var/log/uwsgi.ini — все файлы к текущему проекту находятся в проекте.
              • 0
                * deploy/var/log/uwsgi.log

                И ещё у меня deploy/uwsgi.ini сам является целью для touch-reload.
              • 0
                > 1. Удобное управление запуском/остановкой/перезапуском нескольких проектов на одном хосте
                Удобней работать с нескольикими проектами на одном хосте, когда:

                1. каждый из их работает от отдельного пользователя
                2. можно перезапускать один не трогая второй
                3. каждый проект полностью изолирован (нельзя пользователем одного проекта, читать данные другого проекта).

                Мне кажется, данная задача с вашим подходом вызовет некоторые трудности.

                Я бы рекомендовал заменить uwsgi на (apache)mod_wsgi — значительно более стабильная и гибкая связка получится.
                • –1
                  Держать монстра ввиде апача даже за nginx как-то совесть не позволяет. В чём проблема запускать uwsgi от разных пользователей? (у меня именно так и сделано) Все проекты изолированы и uwsgi конфиг лежит непосредственно в папке проекта и запускается от разных пользователей.
                  На счёт стабильности можно подробнее что-нибудь почитать кроме субъективного мнения? (у меня, например, ещё ниразу uwsgi не воткнул за полгода). И какая гибкость необходима, которую нельзя получить на uwsgi?
                  • +1
                    >Монстра ввиде апача.
                    apache? не знаю монтстр он или нет, но точно уверен: что mod_wsgi процесс apache-а очень лёгкий.

                    >В чём проблема запускать uwsgi от разных пользователей?
                    Раскажите как вы организовали:
                    1. автоматически старт при перезагрузке системы
                    2. логирование в /var/log (с logrotate-ом)

                    У нас на проекте, раньше использовался в различных местах и mod_wsgi и uwsgi, теперь переводится всё на mod_wsgi потому что стабильнее (uwsgi, supervisor у нас переодически отказывали).

                    >гибкость.
                    Всё что угодно, от ldap аутентификации, до webdav интеграции. Apache умеет очень много чего, а согласитесь, иметь и uwsgi и apache в production не логично.
                    • 0
                      1. Тут всё не очень хорошо — обычно просто дописыванием одной строки в /etc/rc.local, но всё хочу написать init.d скрипт, который будет стартовать все проекты…
                      2. logrotate — дописыванием инклуда в конфиг logrotate из deploy папки проекта.

                      Я согласен, что использовать и mod_wsgi и uwsgi неудобно и приходится выбирать. На счёт приведенных примеров гибкости — тут для меня всё просто, у меня подобных требований не было.
                      • +1
                        1. А если python процесс падает (а они иногда падают), как вы его перезапускаете?
                        2. А как вы сообщите процессу, что его log от rotate-ился? Rotate не такая простая операция. И я надеюсь вы таки сделаете запись log-ов в /var/log/, ибо backup-инг логов станная операция.

                        По мере реализации «cusmtom-ных» решений вокруг uwsgi я готов вам перечислять какие проблемы в вашей системе есть, которые уже давно решены в mod_wsgi apache-а.
                        • 0
                          1. Uwsgi супервизор ещё ниразу не падал, но на всякий случай стоит крон проверяющий жив ли супервизор.
                          2. postrotate
                          /usr/bin/kill -HUP `cat deploy/var/run/uwsgi.pid`

                          Я бы с удовольствием выслушал ещё предложения, вдруг я не всё учёл. Спасибо, за конструктивный диалог.
                          Собственно, причина, по которой я выбрал uwsgi — по тестам производительности в интернетах он выше mod_wsgi, плюс я не хотел тащить apache (просто субъективное мнение о нём сложилось). Сначала пользовался tornado как прокси, но вот ему точно не хватало стабильности и я начал искать что-нибудь с супервизором.
                          • +2
                            По всем тестам которые я видел, у uwsgi и mod_wsgi одинаковая производительность, пока тест не упрощается до return 200. — только тогда у uwsgi выше производительность. Локальное же тестирование показало, что абсолютно одинаковая производительность. Всё равно упирается именно в Python часть.

                            Что меня беспокоит в uwsgi-django-community, так это то, что все пользователи сами пишут пачечьку костылей (init.d скрипты/cron таски), для тех задач, которые весь мир давно считает решёнными.

                            Возвращаясь к 'init.d' скрипту — хотелось бы ещё увидеть, как вы в нём мульти-host-овость сделаете. Точнее так: считаю, что если в вашей системе для добавление очередного vhost-а, надо будет редактировать rc.local это будет не красиво.
                            • 0
                              'init.d' скрипт, скорее всего, я бы сделал какой-нибудь конфиг, где были бы перечислены просто директориии проектов, которые необходимо запустить, так как у меня все django-проекты имеют одинаковую структуру каталогов.

                              Про тестирование — тут я согласен, обычно тесты в интернетах сильно синетические и несовместимые с жизнью и нужно самому проверять уже на живом проекте.

                              «пользователи сами пишут пачечьку костылей» — gentoo-way =)

                              Кстати, на счёт падений супервизора uwsgi, а как вы следите за тем, что апач не падает?
                              • 0
                                «Корневой» apache, тот который с uid=0, gid=0 — он не падает (вообще за ним админы следят какимии-то самыми обычными средствами). На нём же ничего не крутится никаких python-ов и т.д — вообще он очень стабильный и выверенный годами.

                                Падения свойственны уже python процессам, а у их ppid — apache. Собственно apache отлично следит за всеми своими детьми — опять же стабильный и выверенный годами подход.
                                • 0
                                  > «пользователи сами пишут пачечьку костылей» — gentoo-way =)
                                  Как человек который помнит установку gentoo ещё со stage1 — нет, это совсем не gentoo way.
                                • 0
                                  Мир не без добрых людей, напомнили о Emperor режиме uWSGI.

                                  Вкратце — создаю /etc/uwsgi/sites-enabled/ (просто ради единообразия с nginx) в эту папку делаем символьные ссылки всех конфигов проектов, которые нужно стартовать. После этого в rc.local или нормальным init.d скриптом вызываем одну команду:
                                  uwsgi --emperor /etc/uwsgi/sites-enabled


                                  PS Это всего лишь альтернативное, прозрачное решение относительно описанного метода автором статьи.
                              • 0
                                Делаю вывод, что статью вы не читали. Если бы прочитали, то увидели бы, что init-скрипт уже есть, что он при запуске поднимает все проекты, для которых найдет конфигурации в /etc/uwsgi/apps-enabled (на самом деле там симлинки на файлы конфигурации в /etc/uwsgi/apps-available), что любой проект можно запустить/остановить/перезапустить с помощью /etc/init.d/uwsgi start|stop|restart project_conf_name. И логи там ротейтятся, я вас уверяю. Как-то так вобщем
                                • –1
                                  Статью читал, и даже запускал сам подобные конфигурации. Просто вы упустили очень важный (имхо) момент:

                                  один instance uwsgi не позволяет запускать 2 проекта под различнными пользователями (разными uid и gid) — попробуйте настроить так 2 проекта. и вы увидите что uid и gid второго, переопеределяют 1й.

                                  получается, всё равно надо запускать uwsgi отдельно для каждого проекта, а это уже идёт в разрез с вашей статьей.

                                  • 0
                                    Так для каждого проекта и запускается свой instance uwsgi. Где вы увидели противоречие эту в статье, я не очень понимаю
                                    • –1
                                      Покажите пожалуйста 2 проекта, запущенные от различный пользователей.
                                      Пожалуйста! Молю.
                                      • +1
                                        example1.ini:
                                        [uwsgi]
                                        plugins = python27
                                        virtualenv = /home/web/envs/example1/
                                        chdir = /home/web/proj/example1/
                                        pythonpath =…
                                        env = DJANGO_SETTINGS_MODULE=example1.settings
                                        module = django.core.handlers.wsgi:WSGIHandler()
                                        uid = user1
                                        gid = user1

                                        example2.ini:
                                        [uwsgi]
                                        plugins = python27
                                        virtualenv = /home/web/envs/example2/
                                        chdir = /home/web/proj/example2/
                                        pythonpath =…
                                        env = DJANGO_SETTINGS_MODULE=example2.settings
                                        module = django.core.handlers.wsgi:WSGIHandler()
                                        uid = user2
                                        gid = user2
                                        • –1
                                          Спасибо, а можно ещё init.d скрипт, чтобы debian не ставить?
                                          Хотелось бы соотнести скрипт с конфигом.

                                          И ещё один помент, как бы вы добавили в автозагрузку оба example-а.
                        • 0
                          Структура каталогов как в статье только для примера. Можно и по-другому организовать.
                          По пунктам:
                          1. Переопределяем uid и gid
                          2. Статья как раз об этом
                          3. А это решается правами на уровне файловой системы

                          А как в вашем подходе обеспечивается поддержка разных виртуальных сред для разных проектов?
                          • 0
                            Попробуйте запустить 2 проекта, с разными uid и gid, это возможно тока если uwsgi сам запускатеся от пользователя.
                            • 0
                              <VirtualHost *:8080>
                              ServerAdmin admin@example.com
                              ServerName site1.example.com

                              ErrorLog /var/log/httpd/site1_error.log
                              CustomLog /var/log/httpd/site1_access.log common

                              WSGIScriptAlias / /home/httpd/site1/app/wsgi/django.wsgi
                              WSGIDaemonProcess user=site1 group=site1 processes=2 threads=1
                              WSGIProcessGroup site1
                              WSGIApplicationGroup site1
                              WSGIPassAuthorization On

                              • 0
                                А вы не путаете виртуальные хосты с виртуальным окружением python (virtualenv)? Как мне помнится, virtualenv на весь апач может быть только один
                                • 0
                                  Не путаю. virtualenv к apache-у вообще отношение не имеет, там несколько другая система взаимодействия.

                                  1. В apache можно завести сколько угодно virtualhost-ов
                                  2. На эти virtualhost-ы мы назначаем обработчиком mod_wsgi
                                  3. Каждый mod_wsgi-virtualhost мы демоинизируем (при этом root-процесс apache-а остаётся «за главного»)
                                  4. У mod_wsgi есть опция — wsgi скрипт запуска.
                                  5. Ну а в wsgi скрипте вы уже можете извращаться как захотите, например настроить virtualenv
                                  • 0
                                    Мне не нравится в связке apache-mod_wsgi то, что используется интерпретатор питона из mod_wsgi, нельзя использовать версии питона из virtualenv. Если конечно, не ошибаюсь.
                          • –9
                            В этом наборе все хорошо кроме Debian. Увы эта ос уже не приоритет.
                            • 0
                              Что в альтернативе? просто уже около года использую связку nginx+uwsgi+supervisor+debian — проблем не было ни разу.
                              • 0
                                На вкус и цвет все фломастеры разные. У меня сервера и на CentOS, и на Ubuntu Server, и на Debian живут, я не сисадмин и мне привычнее debian-подобные системы, но всё-таки что сейчас «приоритет»?
                                • –2
                                  Я использую centos. Сидел несколько лет на debian и freebsd. Позакрывал все эти сервера, перевел все на centos.
                                  У меня еще вируталки на некоторых серверах, а для них centos то что доктор прописал.
                                  А минусоидам — мне вас жаль ).
                                • +2
                                  Попробуйте gunicorn
                                  • +1
                                    Пробовал) но люблю деплоить именно nginx + uwsgi ибо у nginx есть модуль для uwsgi, ну и uwsgi уже привычнее и больше с ним знаком — знаю почти от и до все что нужно и есть разные джанго пакеты для него(например django-uwsgi-admin) ну и как бонус — запускать rails через uwsgi и многое другое. Да и по производительности uwsgi оч хорош.
                                  • 0
                                    Как-то я делал набор скриптов, чтобы разворачивать хостинг для wsgi-приложений, написанных на pylons http://tinycode.ru/ubuntu-server-for-wsgi-with-pylons-nginx-uwsgi Только я сразу настраивал монит, чтобы он смотрел на всё это. Ещё на тот момент я не использовал virtualenv (сейчас использую, чтобы запускать на одном сервере pylons и pyramid)
                                    • 0
                                      Я тоже деплою через nginx + uwsgi но у меня чуть иначе:
                                      есть свой стартовый джанговский проект в котором переделанный под меня django-config-gen он сразу генерит нужные мне конфиги согласно настройкам и кладет куда надо:
                                      /etc/init/project_name.conf примерно такого содержания:
                                      description "uWSGI server for Project project_name"
                                      start on runlevel [2345]
                                      stop on runlevel [!2345]
                                      respawn
                                      exec /server/env/bin/uwsgi -p 2 --uid mechanism --home /server/env/ --socket /server/sock/project_name.uwsgi.sock --chmod-socket --module runsite --pythonpath /server/SITES/project_name --daemonize /server/log/project_name.uwsgi.log

                                      упомянутый runsite.py выглядит примерно так:
                                      import os, sys
                                      current_directory = os.path.dirname(__file__)
                                      parent_directory = os.path.dirname(current_directory)
                                      module_name = os.path.basename(current_directory)
                                      sys.path.append(parent_directory)
                                      sys.path.append(current_directory)
                                      os.environ["CELERY_LOADER"] = "django"
                                      os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % module_name
                                      import django.core.handlers.wsgi
                                      application = django.core.handlers.wsgi.WSGIHandler()

                                      «Почему uWSGI а не fastcgi?
                                      Я последний использую, может у первого есть какие-то скрытые преимущества? )»

                                      в зависимости от требований проекта могут добавляться: --enable-threads --loop gevent --async 100 и уже в джангу можно добавлять например gevent

                                      ну еще генерится конфиг для nginx и еще разные нужные штуки.
                                    • 0
                                      Еще на заметку github.com/dpetzold/django-runuwsgi

                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.