Debian, Dojo, Django, Python

Делюсь опытом в описанных технологиях. Блог в первую очередь выполняет роль памяток для меня самого.

Показаны сообщения с ярлыком wsgi. Показать все сообщения
Показаны сообщения с ярлыком wsgi. Показать все сообщения

Django 1.8, Python 3, WSGI и Gunicorn

Введение

В данной статье рассказывается, как можно запустить приложение на Django и Python 3 под WSGI. Способ не претендует на звание самого лучшего, замечания по настройке и прочим нюансам приветствуются.

Структура каталогов

Следует создать в каталоге /var/www подкаталог для нашего проекта. Пусть сегодня он будет называться talos. В нём нужно будет создать каталоги для размещения статичных файлов, файлов, загружаемых пользователями, и логов.

От имени www-data
cd /var/www
mkdir talos
cd talos/
mkdir static media log

Сам проект будет расположен в виртуальном окружении, которое будет создано чуть позже.

Создание файла запуска

Здесь же, в каталоге /var/www/talos, создадим файл run.bash, который будет запускать приложение, передавая ему нужные параметры.

/var/www/talos/run.bash
   #!/bin/bash

NAME="talos"                                # Название приложения
DJANGODIR=/var/www/.virtualenvs/talos/talos # Директорая проекта - путь к виртуальному окружению
                                            # плюс папка с проектом
SOCKFILE=/var/www/sockets/talos.sock        # Тут будет лежать сокет
USER=www-data                               # От чьего имени запускается
GROUP=www-data                              # Группа для запуска
NUM_WORKERS=3                               # Кол-во воркеров, обычно число ядер * 2 + 1
DJANGO_SETTINGS_MODULE=talos.settings       # Откуда брать настройки
DJANGO_WSGI_MODULE=talos.wsgi               # Имя wsgi-файла для запуска

echo "Starting $NAME as `whoami`"

# Активация окружения
cd $DJANGODIR
source ../bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

# Если папки для сокета нет, её надо создать
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR

# Запуск через gunicorn с передачей параметров
exec ../bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $NUM_WORKERS \
  --user=$USER --group=$GROUP \
  --bind=unix:$SOCKFILE \
  --log-level=debug \
  --log-file=/var/www/talos/log/talos.log
  

Создание виртуального окружения

Последние версии Django написаны на Python 3, да и вообще использовать Python 2 в 2015 году - дурной тон. Ставим нужные пакеты, если их ещё нет:

Установка пакетов
apt-get install python3-dev virtualenvwrapper -y

Первый пакет нужен для сборки пакетов, поставляемых в исходных кодах (lxml, psycopg2, pillow), второй - для удобного управления виртуальными окружениями. Пакеты установлены, пользователь www-data в системе. Пришло время создать окружение и поставить нужные пакеты:

Создание окружения, установка пакетов
mkvirtualenv talos --python=/usr/bin/python3
workon talos

В результате в каталоге для виртуальных окружений (у каждого пользователя свой, по умолчанию называется .virtualenvs) будет создан подкаталог talos. В нём будут размещены необходимые для работы с окружением скрипты и несколько других каталогов. Разместим наш проект внутри каталога /var/www/.virtualenvs/talos.

Далее следует поставить в окружение все необходимые пакеты, требуемые для запуска проекта, а так же пакет gunicorn. При необходимости стоит так же обновить pip, его последние версии умеют кэшировать скачанные пакеты.

Не забудьте создать статику и при необходимости подкорректировать файл настроек приложения.

Запуск через supervisor

Если supervisor ещё не установлен, пришло время это сделать:

Установка supervisor
apt-get install supervisor -y

Настройки хранятся в каталоге /etc/supervisor. Главный файл называется supervisor.conf, файлы для запуска приложений следует расположить в каталоге /etc/supervisor/conf.d/, указав расширение .conf. В нашем случае файл будет лежать по пути /etc/supervisor/conf.d/talos.conf

/etc/supervisor/conf.d/talos.conf
[program:talos]
command=/var/www/talos/run.bash
user=www-data
group=www-data

autostart=true
autorestart=true

redirect_stderr=true
stdout_logfile=/var/www/talos/log/supervisor.log

Когда файл будет создан, следует обновить данные Supervisor'а:

supervisorctl update

При необходимости перезапустить то или иное приложение следует вызывать не перезапуск системной службы supervisor, а давать команды supervisorctl:

Управление Supervisor'ом
supervisorctl update        # Перечитать файлы конфигации приложений
supervisorctl start talos   # Запустить приложение talos
supervosorctl stop talos    # Остановить приложение talos
supervisorctl restart talos # Перезапуск приложения talos
supervisorctl status        # Посмотреть статус всех приложений
supervisorctl ДЕЙСТВИЕ all  # Выполнить ДЕЙСТВИЕ со всеми приложениями, например, перезапуск

Если всё сделано правильно, приложение будет запущено, а в каталоге /var/www/sockets/ появится файл сокета talos.sock (как было настроено в файле run.bash).

Подключение к nginx

Всё, что нужно теперь сделать - указать nginx путь к сокету и откуда брать статику для проекта. В общем-то, всё довольно просто. Nginx лучше ставить из официального репозитория проекта, а не из Debian'овского, там пакет не обновляется годами.

apt-get install nginx -y

Все настройки в каталоге /etc/nginx, главный файл - nginx.conf, настройки для сайтов в .conf-файлах, лежащих в каталоге /etc/nginx/conf.d/.

Создадим файл настроек, общих для всех сайтов: /etc/nginx/proxy_params.conf:

/etc/nginx/proxy_params.conf
proxy_redirect                          off;
proxy_set_header Host                   $http_host;
proxy_set_header X-Real-IP              $remote_addr;
proxy_set_header X-Url-Scheme           $scheme;
proxy_set_header X-Forwarded-For        $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto      $scheme;
client_max_body_size                    20m;
client_body_buffer_size                 1m;
proxy_buffering                         off;
proxy_send_timeout                      180;
proxy_read_timeout                      180;
proxy_connect_timeout                   180;
proxy_buffer_size                       4k;
proxy_buffers                           32 32k;
proxy_busy_buffers_size                 64k;
proxy_temp_file_write_size              1m;
add_header X-Frame-Options "SAMEORIGIN";

Возможно, данные настройки придётся доработать исходя из реальной конфигурации сервера. Здесь я на этом останавливаться не буду. Переходим к созданию файла конфигурации для нашего проекта:

/etc/nginx/conf.d/talos.conf
upstream talos {
    server unix:/var/www/sockets/talos.sock fail_timeout=0;
    keepalive 30;
}

server {
    listen 80;
    server_name talos.lo;
    keepalive_timeout 3;
    access_log off;
    error_log /var/www/talos/log/nginx_error.log crit;

    location / {
        proxy_pass http://talos/;
        include /etc/nginx/proxy_params.conf;
    }

    location /static/ {
        alias /var/www/talos/static/;
        expires 3d;
    }

    location /media/ {
        alias /var/www/talos/media/;
           expires 3d;
    }

    location ~* \.(7z|jpg|jpeg|gif|png|ico|css|bmp|swf|js|html|txt|doc|docx|pdf|rar|xls|xlsx|zip)$ {
        root /var/www/talos/;
        expires 3d;
        add_header Cache-Control: public;
        access_log off;
        error_log /var/www/talos/log/nginx_static_error.log;
    }
}

Перезапустите nginx, тобы он подхватил новый конфигурационный файл. На этом всё.

Обо всех ошибках или дополнения прошу писать мне на почту dunmaksim@yandex.ru

Apache2, nginx, virtualenv и Python 2.6 в Debian

Как я уже писал, в моей компании многие продукты писаны на Python, при том некоторые на Python 2.6, что не может не удручать, т.к. в большинстве популярных дистрибутивов давно используется Python 2.7 и выше. В этой статье рассказывается, как настроить Debian 7.1 Wheezy. Как обычно, всё делается под root или через sudo. Явно я об этом больше нигде в статье писать не буду.

Собственно, почему Debian?

В отличие от той же CentOS 6.4 Debian удобнее в администрирование. Говоря это, я имею ввиду, не количество пакетов, а то, что он "из коробки" имеет кучу оптимальных настроек, в то время как в CentOS приходится всё допиливать под себя.

  1. Огромное количество пакетов. Я не знаю, есть ли где-то ещё больше. Если есть - напишите мне.
  2. Python 2.6 в официальном репозитории. Да, Debian 7.1 не требует возни с дохлыми змеями.
  3. Под него есть официальные сборки PostgreSQL и Nginx. Я раньше тоже думал, что MySQL наше всё, а сейчас вообще не понимаю, как можно пользоваться этой поделкой, не соблюдающей 90% мировых SQL-стандартов. Да, если вы пишете скрипты, руководствуясь SQL-92 или SQL-99, можете быть уверены, что в PostgreSQL они будут работать без каких-либо изменений, в то время как в MySQL их придётся переписывать, и весьма основательно (к слову, в разработке PostgreSQL принимают участие несколько профессоров математики и информатики, что как бы говорит о качестве получающегося продукта).
  4. Стабильность. Пакеты старые, но если они есть в репозитории, то можно не сомневаться, что они будут работать так, как то ожидается.

Немного про виртуальные окружения

Пакет python-virtualenv позволяет на одной системе иметь несколько версий не только интерпретатора, но и одних и тех же пакетов. Допустим, нужно, чтобы проект работал с soaplib версии 0.8.0, а при очередном плановом обновлении системы версия пакета повысится до 1.0.0, некоторые функции будут удалены как устаревшие, в итоге приложение перестанет работать вовсе. Чтобы такого не случалось, лучше всего вовремя создать виртуальное окружение. Пакеты в нём будут именно той версии, которая нужна, независимо от того, какая версия пакета установлена в системе.

Также следует пару слов сказать про пакет virtualenvwrapper. Это небольшой скрипт для ленивых питонистов, который позволяет удобно и быстро управлять созданными виртуальными окружениями.

Бывает два вида виртуальных окружений - изолированные и открытые. Отличия таковы: изолированное окружение не видит пакетов, установленных в системе. При создании открытого виртуального окружения можно быть уверенным, что приложение будет видеть те пакеты, что установлены в системе. Если, например, все приложения используют psycopg2, lxml и pytz одной и той же версии, почему бы не установить его на уровне системы, а затем не заставить окружения "видеть" его?

Nginx как фронтэнд Apache2

В этой статье не будет рассказываться, как заставить Nginx работать в связке с Gunicorn и Supervisor. Я расскажу, как научить Nginx раздавать статику, а все остальные запросы переадресовывать Apache. Сразу скажу о недостатках используемого подхода.

  • Первое - модуль mod_wsgi, необходимый для увязки Apache с Python. Ограничение тут такое, что этот модуль может работать только с одной версией Python, т.е. заставить его обрабатывать проекты, работающие на Python2.6 и также проекты на Python2.7 не получится, нужно выбрать что-то одно. При неправильном выборе версии будете получать ошибку No module named django.core.handlers.wsgi. Об этом чуть позже.
  • Второе - прожорливость Apache. Nginx частично решит проблему, тем не менее, без тонкой настройки Apache ест ОЧЕНЬ много памяти и создаёт отдельный процесс на КАЖДОЕ подключение, что не есть хорошо.

Установка пакетов

Первым делом нужно добавить репозиторий nginx и ключ для него в список пакетов apt. Многие рекомендуют отредактировать файл /etc/apt/sources.list. Мне это решение не по душе, т.к. я считаю, что там должны быть только те репозитории, которые относятся непосредственно к системе, поэтому делаем так:

nano /etc/apt/sources.list.d/nginx.list

В этот файл нужно написать:

deb http://nginx.org/packages/debian/ wheezy nginx
deb-src http://nginx.org/packages/debian/ wheezy nginx

Соответственно, если используется другая версия, нужно написать её имя. Для Ubuntu нужно также заменить /debian/ на /ubuntu/.

Нужно скачать публичный ключ репозитория и установить его в систему, делается так:

wget http://nginx.org/keys/nginx_signing.key
apt-key add nginx_signing.key

Если wget откажется скачивать ключ (во всяком случае, такое было у меня при добавлении репозитория PostgreSQL), необходимо также добавить параметр --no-check-certificate.

Делаем обновление списка доступных для установки пакетов, обновление устаревших и установку nginx

apt-get update && apt-get dist-upgrade && apt-get install nginx -y

Когда поставится, можно будет увидеть сообщение об успешной установке.

Теперь установка остальных пакетов и Apache2:

apt-get install apache2 libapache2-mod-wsgi libapache2-mod-rpaf python-dev libxml2-dev libxslt1-dev libpq-dev python-pip python-setuptools  python-virtualenv virtualenvwrapper -y

По шагам:

  • apache2 - сам сервер, обязательно
  • libapache2-mod-wsgi - обязательно, без него не получится увязать Apache с Python'ом
  • libapache2-mod-rpaf - обязательно, иначе при работе в связке с nginx Apache не будет отличать одно подключение от другого
  • python2.6-dev - обязательно, нужен, если расширения для Python будут собираться в виртуальном окружении (а они будут!)
  • libxml2-dev, libxslt1-dev - нужны для сборки и корректной работы библиотек lxml, xmlsec и других в виртуальном окружении, ставить по желанию (если не поставить, собрать, например, пакет lxml в виртуальном окружении не получится)
  • libpq-dev - опционально, если используется модуль psycopg2
  • python-pip - опционально, для установки пакетов Python, распространяемых в исходных кодах. Рекомендую в систему ставить пакет из репозиториев Debian, а в виртуальные окружения через easy_install pip
  • python-setuptools - обязательно, для установки пакетов в формате egg (вообще setuptools может ставить и другие форматы пакетов, но он всё равно нужен для последующей установки pip)
  • python-virtualenv, virtualenvwrapper - рекомендуется, первый для создания виртуальных окружений, второй для удобного управления ими

Помимо этих пакетов apt сам подтянет кучу зависимостей, нужно их все поставить.

Если Вы не стали ставить системный pip, его можно поставить следующей командой:

easy_install pip

По команде pip freeze можно увидеть список установленных пакетов и их версий, либо сообщение об ошибке. Ничего страшного, работе это не помешает. Если установка прошла успешно, можно приступать ко второму этапу.

Настройка виртуальных окружений

Перед тем, как приступать к настройке виртуальных окружений, желательно добавить в начало своего .bashrc следующее:

export WORKON_HOME=/var/www/.virtualenvs
export VIRTUALENVWRAPPER_LOG_DIR=/var/www/.virtualenvs
export VIRTUALENVWRAPPER_HOOK_DIR=/var/www/.virtualenvs
Естественно, папка /var/www/.virtualenvs должна существовать и быть доступной для записи для текущего пользователя. Если определения этих переменных не будет, virtualenv создаст папку .virtualenvs в домашнем каталоге пользователя. Итак, переопределили папку для хранения виртуальных окружений и где их должен искать workon (команда будет доступна после установки virtualenvwrapper, без параметров выдаёт список существующих окружений). Нужно перелогиниться либо перечитать файл настроек .bashrc:
source .bashrc

Примечание: не надо редактировать /etc/bash.bashrc, начнутся конфликты из-за того, что после root никто не сможет писать в указанную папку виртуальных окружений, эти настройки делаются в файле пользователя, который будет с этими окружениями работать.

Создадим виртуальное окружение командой (под обычным пользователем):

mkvirtualenv new_env --python=/usr/bin/python2.6

Этой командой мы создали в каталоге /var/www/.virtualenvs каталог new_env, куда поместили одноимённое окружение, которое будет работать с Python2.6. Если не указывать ключ --python, окружение будет работать с тем интерпретатором, на который указывает мягкая ссылка в каталоге /usr/bin (в Debian 7 по умолчанию это Python2.7). Некоторые пишут, что обязательно нужно также указать --no-system-site-packages. У меня встречный вопрос: зачем, если по умолчанию все окружения и так создаются с этим ключом? Переопределить ссылку на интерпретатор Python можно так (под root):

cd /usr/bin
rm -f python
ln -s python2.6 python

Теперь команда workon выдаст нам список наших окружений, а команда workon %имя_окружения% его активирует.

workon new_env

Слева от интерпретатора появится подсказка, например, такая:

(new_env)xphoenix@debian:/etc/apt/sources.list.d$

Тут команда pip freeze выведет сразу:

argparse==1.2.1
distribute==0.6.24
wsgiref==0.1.2

Не надо удалять эти пакеты! Без них окружение работать НЕ БУДЕТ!

Можно немного допилить окружение, для этого в каталоге bin нужно отредактировать файл activate
nano /var/www/.virtualenv/new_end/bin/activate

Добавим в самое начало строку:

export DJANGO_SETTINGS_MODULE='project.settings'

Те, кто пользуется Django, сразу поняли, что произошло. Если Вы не поняли, объясняю: эта переменная указывает, где искать файл настроек для Django-проекта. После этого нужно выйти из окружения командой

deactivate

и активировать снова.

Настройка Apache 2

Апач поставлен, но т.к. он ставился после Nginx, он не сможет запуститься, т.к. порт 80 уже занят для прослушивания. Делаем так:

nano /etc/apache2/ports

Тут должны присутствовать вот такие строки:

ServerName debian.lo
NameVirtualHost 127.0.0.1:8080
Listen 127.0.0.1:8080

Вместо debian.lo можно написать хоть ololoyakrutoj, ни на что не влияет, но избавляет от сообщения об отсутствии имени при перезапуске Apache. Следующие две строки говорят, что будут использоваться виртуальные хосты, находящиеся на локалхосте на порту 8080, и слушать Apache будет только его. Тем самым убираем конфликт с Nginx.

Включаем модуль rpaf (wsgi включает себя сам):

a2enmod rpaf

Также следует немного подредактировать его настройки:

nano /etc/apache2/mods-available/rpaf.conf
<IfModule rpaf_module>
    RPAFenable On
    RPAFsethostname Off
    RPAFproxy_ips 127.0.0.1 ::1
    RPAFheader X-Real-IP
</IfModule>
Можно включить модуль сжатия данных deflate (если выключен) и настоятельно рекомендуется включить модуль rewrite (по умолчанию выключен):
a2enmod deflate
a2enmod rewrite

Следует подкрутить также загрузчик модуля wsgi:

nano /etc/apache2/mods-avaliable/wsgi.load

Там будет написано нечто подобное:

LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi_2.7.so

Если используется Python2.6, следует изменить 7 на 6, иначе опять будем получать ошибку no module named django.core.handlers.wsgi. Добавим виртуальный хост Apache:

nano /etc/apache2/sites-available/new_site.ru

В этот файл записываем примерно следующее:

<VirtualHost 127.0.0.1:8080>
  ServerName new_site.ru
  ServerAlias www.new_site.ru
  ServerAdmin mymail@domen.ru

  WSGIScriptAlias / /var/www/my_project/my_project.wsgi
  WSGIProcessGroup my_project
  WSGIDaemonProcess my_project user=xphoenix group=xphoenix threads=2 maximum-requests=1000

  DocumentRoot "/var/www/my_project"
  <Directory "/var/www/my_project">
    Order allow,deny
    Allow from all
  </Directory>

  FileETag none

  <Location "/">
    Order deny,allow
    Allow from all
  </Location>

  # Сжатие трафика
  <Location />
    SetOutputFilter DEFLATE
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
    SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
    SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|bz2|sit|rar)$ no-gzip dont-vary
    SetEnvIfNoCase Request_URI \.pdf$ no-gzip dont-vary
  </Location>
</VirtualHost>

Этот виртуальный хост будет обрабатывать запросы к сайтам new_site.ru и www.new_site.ru (что одно и то же), для запуска проекта будет использоваться файл /var/www/my_project/my_project.wsgi

Для обработки запросов будет использоваться два потока (рекомендуется ставить по числу ядер), процесс будет работать в группе my_project, запускаться от имени пользователя xphoenix. Дальнейшие директивы, в принципе, понятны.

Включаем сайт в apache:

a2ensite new_site.ru

Создадим вышеуказанный файл запуска приложения:

nano /var/www/my_project/my_project.wsgi

со следующим содержимым

#!encoding=utf-8
import os

activate_this = '/var/www/.virtualenvs/new_env/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))

os.environ['DJANGO_SETTINGS_MODULE'] = 'project.settings'

import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

Теперь можно перезапустить apache:

service apache2 restart

Настройка Nginx

Nginx будет проксировать запросы, приходящие на 80-й порт с любого интерфейса. Если это будет запрос статики, он сам его обработает, если нет, переадресует Apache. Создадим настройки для нашего сайта:

nano /etc/nginx/conf.d/my_site.conf

со следующим содержимым:

server {
        listen *:80; #слушать порт 80 на всех интерфейсах
        server_name new_site.ru www.new_site.lo; #этот сервер будет обрабатывать эти сайты
        access_log off; #не хочу собирать общую статистику доступа
        error_log /var/www/my_project/log/nginx_error.log; #А вот ошибки - да

        location / {
                proxy_pass http://127.0.0.1:8080/; #Переадресация запросов к Apache
                proxy_set_header Host $http_host;  #Подкорректируем заголовки, чтобы отличать одного клиента от другого
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $remote_addr;
                proxy_connect_timeout 120;
                proxy_send_timeout 120;
                proxy_read_timeout 180;
        }

        location ~* \.(jpg|jpeg|gif|png|ico|css|bmp|swf|js|html|txt|exe|pdf|djvu|doc|docx|xls|xlsx)$ {
                root /var/www/my_project;
                access_log /var/www/my_project/log/nginx_static.log;#Статистика по статике здесь
                expires max; #Срок жизни кеша - максимальный
        }
}

Вот теперь можно перезапустить и nginx

service nginx restart

При подключении nginx будет отдавать все файлы указанных расширений сам, а остальные запросы направлять к Apache, значительно разгружая нагрузку на него. Буду рад любой критике в комментариях.