13 сентября 2012

Gentoo, Django, uWSGI (emperor mode), nginx или хостим много Django-проектов

Возможно кому-то пригодится мой опыт настройки хостинга множества Django-приложений. Всё нижеизложенное я проводил на операционной системе Gentoo.


Итак, для начала необходимо установить следующие пакеты:

  • www-servers/uwsgi (нужно добавить в /etc/portage/package.keywords, т.к. сообщество Gentoo считает его нестабильным =)
  • www-servers/nginx (с включённым USE-флагом nginx_modules_http_uwsgi)
  • dev-lang/python (нужной вам версии, можно иметь несколько версий одновременно, переключая их с помощью eselect python)
  • dev-python/virtualenv
  • dev-python/virtualenvwrapper

1. Создаём пользователя под которым будут работать django-процессы (uwsgi и файлы на диске):

useradd -m django

1.1. Заходим под ним:

su django


2. Создадим в его домашней директории /home/django 3 папки:

mkdir ~/env ~/projects ~/vassals

  • env - для виртуальных окружений, чтобы у каждого проекта были свои версии пакетов не мешающие другим проектам (например, для одного можно использовать python 2.7, для другого python 3.2 и т.п.);
  • projects - для проектов;
  • vassals - для uWSGI с настройками запуска в режиме emperor (это такой интересный режим, когда запускается 1 монитор-сервис uWSGI и по процессу на каждый найденный конфиг в этой папке. Что самое замечательное - uWSGI может динамически запускать новый процесс при обнаружении в этой папке нового конфига без перезапуска монитор-сервиса).


3. Разворачиваем исходники проектов в папку ~/projects. Мы используем bitbucket.org для хранения и разработки наших проектов. Если вы используете иную схему - разворачиваете каждый проект в отдельной подпапке и переходите к п. 4. Далее я описываю как это делать с bitbucket.org.

3.1. Сгенерируем id_rsa.pub ключ для доступа по ssh к bitbucket:

ssh-keygen

3.2. Добавим содержимое ~/.ssh/id_rsa.pub в настройки профиля юзвера на bitbucket (ssh keys)

3.3. Клонируем пакеты к себе

cd ~/projects
hg clone ssh://hg@bitbucket.org/my_super_nickname/project1
hg clone ssh://hg@bitbucket.org/my_super_nickname/project2


4. Создадим виртуальные окружения для наших проектов в папке ~/env. Будьте внимательны на этом этапе, т.к. нужно чётко выбрать для каждого окружения правильную версию Python (т.е. версию на которой это разрабатывалось и тестировалось). В принципе, ничего страшного если ошиблись - просто удалите потом подпапку ~/env/<название проекта> и создайте по-новой.

4.1. Инициализируем виртуальные окружения:

export WORKON_HOME=~/env
mkdir -p $WORKON_HOME
source /usr/bin/virtualenvwrapper.sh

4.2. Добавим 2 строки инициализации в профиль пользователя django:

echo 'export WORKON_HOME=~/env' >> ~/.bashrc
echo 'source /usr/bin/virtualenvwrapper.sh' >> ~/.bashrc

Это нужно для того чтобы при логине под юзвером django у нас восстанавливался "скелет" для виртуальных окружений. Актуально потом, когда ваши проекты будут меняться и, соответственно, могут меняться их требования (новые или иных версий python-пакеты, смена версии Python).

4.3. Собственно, создаём окружения для каждого проекта:

mkvirtualenv project1
deactivate
mkvirtualenv project2
deactivate

4.4. TIP: Для того чтобы перейти в конкретное окружение (например, для manage.py collectstatic или pip install) - надо выполнить комманду:

workon <имя проекта>

где <имя проекта> - название нужного окружения.


5. Переходим в папку с клонированным проектом:

cd ~/projects/project1

5.1. Активируем окружение согласно п. 4.4.


6. Устанавливаем требуемые для проекта пакеты питона:
pip install -r requirements.txt

Возможно, вы ставите пакеты для Django-проекта иначе (например, через pip install project1.bundle).

6.1. Повторяем это для каждого проекта, начиная с п. 4.4.


7. Для сервера uWSGI, в папке ~/vassals создадим для каждого проекта по файл:

cd ~/vassals
touch project1.ini
touch project2.ini

7.1. Содержимое каждого из файлов - одинаковое (благодаря макро-подстановке %n равной basename файла):
[uwsgi]
virtualenv = /home/django/env/%n
chdir = /home/django/projects/%n
plugins = python27
pythonpath = ..
socket = /var/run/uwsgi/%n.sock
logto = /var/log/uwsgi/%n.log
env = DJANGO_SETTINGS_MODULE=%n.settings
module = django.core.handlers.wsgi:WSGIHandler()
buffer-size = 32768

Внимание! Папки /var/run/uwsgi и /var/log/uwsgi должны быть доступны на запись пользователю django. В первой будут лежать сокеты для связи uWSGI и nginx, во второй - логи uWSGI по каждому проекту. Также обратите внимание на строчку plugins = python27 - здесь должна быть указана версия питона используемая в проекте.


8. Выходим обратно под пользователя root, чтобы отредактировать настройки uWSGI (для Gentoo, они расположены в /etc/conf.d/uwsgi). Приводим к следующему виду:

UWSGI_SOCKET=
UWSGI_THREADS=1
UWSGI_PROGRAM=
UWSGI_XML_CONFIG=
UWSGI_PROCESSES=1
UWSGI_LOG_FILE=
UWSGI_DIR=
UWSGI_USER=django
UWSGI_EMPEROR_PATH=/home/django/vassals
UWSGI_EXTRA_OPTIONS=


9. Необходимо создать папки /var/run/uwsgi, /var/log/uwsgi и сделать их владельцами пользователя django:

mkdir /var/run/uwsgi
mkdir /var/log/uwsgi
chown -R django:django /var/run/uwsgi /var/log/uwsgi


10. Добавляем uwsgi в автозагрузку и запускаем:

rc-update add uwsgi default
/etc/init.d/uwsgi start


11. И самое простое - поставим и настроим nginx. Тут самое главное - указать, что имя пользователя под которым запускается nginx. Обычно это системный пользователь www-data или nginx. Этот пользователь должен быть в одной группе с django-пользователем чтобы иметь доступ к статическим файлам (возможно даже на запись). Конфиг /etc/nginx/nginx.conf должен быть примерно такого содержания:

user nginx;
worker_processes 1;

error_log /var/log/nginx/error_log info;

events {
        worker_connections 1024;
        use epoll;
}

http {
        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        log_format main
                '$remote_addr - $remote_user [$time_local] '
                '"$request" $status $bytes_sent '
                '"$http_referer" "$http_user_agent" '
                '"$gzip_ratio"';

        client_header_timeout 10m;
        client_body_timeout 10m;
        send_timeout 10m;

        connection_pool_size 256;
        client_header_buffer_size 1k;
        large_client_header_buffers 4 16k;
        request_pool_size 4k;

        gzip on;
        gzip_min_length 1100;
        gzip_buffers 4 8k;
        gzip_types text/plain;

        output_buffers 1 32k;
        postpone_output 1460;

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;

        keepalive_timeout 75 20;

        ignore_invalid_headers on;

        index index.html;

        include /etc/nginx/vhosts.d/*.conf;
}


12.1 Создадим подпапку /etc/nginx/vhosts.d и в ней по файлу для каждого проекта:

mkdir /etc/nginx/vhosts.d
cd /etc/nginx/vhosts.d
touch project1.conf
touch project2.conf

Вот содержимое project1.conf (для project2.conf он аналогичен):

upstream wsgi_project1 {
   server unix:/var/run/uwsgi/project1.sock;
}

server {
    listen      80;
    server_name <доменное имя нашего сайта> default; << default означает, что если будут обращаться по IP к нашему серверу - то именно этот сайт будет там
    charset     utf8;
    autoindex   off;
    access_log  /var/log/nginx/project1_access.log;
    error_log   /var/log/nginx/project1_error.log error;

    set         $project_home /home/django/projects/project1;

    location /images {
        root           $project_home/static;
        expires       1d;
    }

    location /css {
        root           $project_home/static;
        expires       1d;
    }

    location /js {
        root           $project_home/static;
        expires       1d;
    }

    location /static {
        root            $project_home;
        expires         1d;
    }

    location / {
        try_files       $uri    @django;
    }

    location @django {
        uwsgi_pass      wsgi_project1;
        include         uwsgi_params;
    }
}


12.2 Добавим nginx в автозагрузку и запустим его:

rc-update add nginx default
/etc/init.d/nginx start

Это пока всё. Если у кого-то будут замечания и дополнения - оставляйте их в комментариях. Вместе мы победим =)

16 комментариев:

  1. В nginx.conf следует прописать
    large_client_header_buffers 4 16k;

    а в project.ini
    buffer-size = 32768

    Иначе будет 502 ошибка выскакивать при добавления больших объектов (новости, галереи)

    ОтветитьУдалить
  2. Ну автор и садист. У меня чуть кровь из клаз не потекла. Выкоючил бекграунд-полегчалою Ща буду читать

    ОтветитьУдалить
  3. При использовании virtualenvwrapper`a можно вместо source писать `workon project1` . И deactivate для выхода.

    ОтветитьУдалить
    Ответы
    1. Ага. Сейчас поправлю. Насчёт фона не согласен, видать не игрались на Спектруме - это полосочки при загрузке программ в турбо-режиме и стандартным загрузчиком =)

      Удалить
  4. Выполнил все как написано, за исключением того, что проект создавал только один и называл project. Запуск nginx закончился ошибкой

    nginx: [emerg] no port in upstream "wsgi_mtcrm" in /etc/nginx/vhosts.d/project.conf:40

    В чем может быть дело? Спасибо.

    ОтветитьУдалить
    Ответы
    1. упс =) нашли багу в конфиге =)
      вместо
      location @django {
      uwsgi_pass wsgi_mtcrm;
      include uwsgi_params;
      }
      надо написать
      location @django {
      uwsgi_pass wsgi_project1;
      include uwsgi_params;
      }

      Удалить
    2. Ok. Теперь приходит страница

      "uWSGI Error
      Python application not found"

      В логах ничего интересного, никаких ошибок. Если делать из виртуальной среды ./manage.py runserver 8080 — успешно работает, показывает нужную страницу. Куда копать?

      Удалить
    3. не может быть, что ничего интересного =)
      Очень похоже на ошибку создания сокета - либо порт занят, либо прав на создание сокета нет в указанном каталоге. По-возможности приведите лог тут, тогда ещё помогу.
      ЗЫ: интересует часть лога где примерно такие строки:
      *** Starting uWSGI 1.4.9 (32bit) on [Sun Jun 23 18:18:34 2013] ***
      compiled with version: 4.6.3 on 26 April 2013 21:50:16
      os: Linux-3.8.6-hardened #1 SMP Sun May 5 07:54:15 MSK 2013
      nodename: web
      machine: i686
      clock source: unix
      pcre jit disabled
      detected number of CPU cores: 1
      current working directory: /home/django/vassals
      detected binary path: /usr/bin/uwsgi
      your processes number limit is 16166
      your memory page size is 4096 bytes
      detected max file descriptor number: 1024
      lock engine: pthread robust mutexes
      uwsgi socket 0 bound to UNIX address /var/run/uwsgi/testproj.sock fd 3
      Python version: 2.7.3 (default, Apr 23 2013, 11:51:59) [GCC 4.6.3]
      Set PythonHome to /home/django/env/testproj
      *** Python threads support is disabled. You can enable it with --enable-threads ***
      Python main interpreter initialized at 0xb77d3a60
      your server socket listen backlog is limited to 100 connections
      mapped 184960 bytes (180 KB) for 1 cores
      *** Operational MODE: single process ***
      added ../ to pythonpath.

      Удалить
    4. О. Теперь все получилось: после очередного перезапуска uwsgi и nginx сработало. Возможно какая-то особенность кэша nginx'а: выдавал страницу с ошибкой, но в логах никаких ошибок. Спасибо.

      Удалить
    5. ну да, походу завис один из вассалов и не давал создавать сокет =)
      там же есть 2 лога - один nginx а другой uwsgi, и в первый обычно не попадают ошибки уровня питона

      Удалить
    6. Естественно я смотрел оба лога и перед последней проблемой это даже помогло мне решать одну проблему с питоном — путь до библиотек django.

      Удалить
  5. Если > django-1.6, то нужно в ini файле заменить
    module = django.core.handlers.wsgi:WSGIHandler()
    на
    module = django.core.wsgi:get_wsgi_application()

    ОтветитьУдалить
  6. Этот комментарий был удален автором.

    ОтветитьУдалить
  7. Здравствуй автор. Помоги разобраться настроил по мануалу один в один: Ошибка 502 Bad Gateway, в лог сыпется *11 connect() to unix:/var/run/uwsgi/blog.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.0.5, server: example.com, request: "GET / HTTP/1.1", upstream: "uwsgi://unix:/var/run/uwsgi/blog.sock:", host: "example.com"

    ОтветитьУдалить
    Ответы
    1. Ну очевидно, что проблема с правами доступа. Если поможет - тут обсуждают возможные пути решения этой проблемы - http://stackoverflow.com/questions/22071681/permission-denied-nginx-and-uwsgi-socket

      Удалить
    2. Разобрался. Данный трабл был из-за module = django.core.handlers.wsgi:WSGIHandler() поставил себе module = my_project.wsgi:application
      и все работает. Спасибо за статью.

      Удалить