Django 1.8, Python 3, WSGI и Gunicorn
Введение
В данной статье рассказывается, как можно запустить приложение на Django и Python 3 под WSGI. Способ не претендует на звание самого лучшего, замечания по настройке и прочим нюансам приветствуются.
Структура каталогов
Следует создать в каталоге /var/www подкаталог для нашего проекта. Пусть сегодня он будет называться talos. В нём нужно будет создать каталоги для размещения статичных файлов, файлов, загружаемых пользователями, и логов.
cd /var/www
mkdir talos
cd talos/
mkdir static media log
Сам проект будет расположен в виртуальном окружении, которое будет создано чуть позже.
Создание файла запуска
Здесь же, в каталоге /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 ещё не установлен, пришло время это сделать:
apt-get install supervisor -y
Настройки хранятся в каталоге /etc/supervisor. Главный файл называется supervisor.conf, файлы для запуска приложений следует расположить в каталоге /etc/supervisor/conf.d/, указав расширение .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:
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:
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";Возможно, данные настройки придётся доработать исходя из реальной конфигурации сервера. Здесь я на этом останавливаться не буду. Переходим к созданию файла конфигурации для нашего проекта:
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, тобы он подхватил новый конфигурационный файл. На этом всё.
Gunicorn + NGinx + Virtualenv на Debian 7
В этой заметке я хочу рассмотреть наиболее перспективный в настоящее время способ размещения проектов на базе Django на боевых серверах. Будет использовано следующее ПО:
- Python 2.6. Вы можете использовать вообще любой, какой Вам нравится, просто проекты, которыми пользуется моя организация, используют именно его. По умолчанию в Debian 7 установлен Python 2.7, но без проблем можно установить и более старую версию. Проблем с 3-ей версией также не возникнет, он есть в стандартных репозиториях.
- Virtualenv и virtualenvwrapper. Благодаря этим пакетам можно создать свою песочницу для каждого проекта Django, указав свой собственный интерпретатор Python (2.6, 2.7, 3, 3.1 - и всё на одной машине) и набор пакетов (а также версий пакетов). Данное ПО позволяет изолировать проекты друг от друга, при необходимости используя разные версии библиотек. Допустим, в одном проекте используется lxml версии 2.3.1, а в другом 3.2.1. Как быть? Просто добавляем в одно окружение 2.3.1, а в другое - 3.2.1, и никаких конфликтов библиотек не будет.
- Gunicorn. Этот легковесный сервер позволяет размещать на одном сервере проекты, использующие разные версии Python. В отличие от
apache2-mod-wsgi, он позволяет работать одновременно и с Python2.6, и с Python2.7 (имеется ввиду разделение по проектам). Работа же mod-wsgi глобальна, т.е. весь сервер сразу поддерживает либо одну версию Python, либо другую. - Supervisor. Эта утилита позволяет демонизировать процессы. В рассматриваемом случае она будет запускать gunicorn-серверы проектов. Некоторые скажут, что можно использовать
init. Пожалуйста, я никого не заставляю, но настроек для init'а у меня нет. - PostgreSQL. Наиболее продвинутая из бесплатных и максимально соблюдающая SQL-стандарты из известных мне СУБД. С помощью утилиты
pgtuneможно в автоматическом режиме оптимизировать настройки (содержимое файлаpostgresql.conf) под конкретную машину с учётом размера оперативной памяти, дискового кэша и т.д. - Nginx. В представлении не нуждается. Самый производительный web-сервер в мире на сегодня.
- Пакеты для работы с графикой, XML и сборки драйвера
psycopg2PostgreSQL для Python (ставить желательно, но не обязательно):libfreetype6-dev- позволяет Pillow выводить надписи на изображенияzlib1g-dev- включает поддержку PNG-формата для Pillowlibjpeg8-dev- позволяет Pillow обрабатывать JPEG-изображенияlibwebp-dev- позволяет Pillow работать с форматом webplibtiff-dev- комментарии излишниliblcms2-dev- для поддержки библиотеки управления цветовыми профилями Little Color Management System версии 2.libpq-dev- без этого пакета в виртуальном окружении не соберётся пакетpsycopg2, необходимый для использования PostgreSQLlibxml2-dev, libxslt1-dev- без этих двух пакетов в виртуальном окружении не соберётся пакетlxml
Установка пакетов
Для работы нам понадобится установить некоторые пакеты в систему, но прежде чем приступить к установке, нужно будет добавить репозитории PostgreSQL и Nginx в систему, чтобы всегда использовать последние стабильные версии ПО. Переходим в каталог, содержащий файлы с указанием дополнительных репозиториев apt:
cd /etc/apt/sources.list.d/
Создадим здесь два файла со следующим содержимым:
Первый - postgresql.list
deb http://apt.postgresql.org/pub/repos/apt/ wheezy-pgdg main
Второй - nginx.list
deb http://nginx.org/packages/debian/ wheezy nginx
deb-src http://nginx.org/packages/debian/ wheezy nginx
Конечно, можно добавить эти строки в файл /etc/apt/sources.list, но я предпочитаю не трогать его, а добавлять сторонние репозитории через создание отдельных .list-файлов в каталоге /etc/apt/sources.list.d/.
На следующем этапе следует добавить ключи для проверки пакетов. Здесь для nginx, здесь для PostgeSQL.
Собственно команды:
wget http://nginx.org/keys/nginx_signing.key
wget https://www.postgresql.org/media/keys/ACCC4CF8.asc --no-check-certificate
apt-key add nginx_signing.key
apt-key add ACCC4CF8.asc
Кратко опишу смысл указанных выше команд.
С помощью утилиты wget были скачаны 2 файла. При этом во втором случае была отключена проверка сертификата для шифрованного соединения по протоколу SSL. Сделано это потому, что у сайта PostgreSQL на момент написания статьи были какие-то проблемы с сертификатом и wget не начинал загрузку, выдавая вместо этого сообщение об ошибке.
Далее с помощью команды apt-key add идёт импорт скачанных ключей в системное хранилище. Debian не работает с репозиториями, для которых у него нет ключей проверки цифровой подписи.
Теперь нужно обновить список доступных пакетов с помощью команды apt-get update и установить необходимое ПО с помощью apt-get install:
apt-get update
apt-get install libfreetype6-dev zlib1g-dev libjpeg8-dev libwebp-dev postgresql-9.3 postgresql-server-dev-9.3 postgresql-contrib-9.3 pgtune libpq-dev libxml2-dev nginx python-pip python-setuptools python-virtualenv virtualenvwrapper python-elementtidy supervisor python2.6-dev
Пакет gunicorn следует ставить отдельно, прямо в виртуальное окружение, поэтому здесь его указывать не надо.
Настройка виртуальных окружений и среды
Запуск любых серверных скриптов должен происходить под ограниченной учётной записью. В Debian по умолчанию для этого используется учётная запись www-data. Откройте файл /etc/passwd и убедитесь также, что данному пользователю запрещён вход в систему. При необходимости приведите вот эту строку
www-data:x:33:33:www-data:/var/www:/bin/bash
к вот такому виду:
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
Перейдите в каталог /var и посмотрите, имеется ли там папка www. Если её нет, надо её создать и сделать владельцем пользователя www-data:
cd /var
mkdir www
chown www-data:www-data www/
Теперь нужно будет войти в систему от имени этого пользователя. Так как мы запретили ему вход с использованием шелла, придётся использовать sudo с параметром -s, задающим командную оболочку:
sudo su www-data -s /bin/bash
Без параметра -s система выдаст сообщение, что указанная учётная запись недоступна.
При первом входе в систему в домашнем каталоге пользователя будет создана папка .virtualenvs, о чём будут выведены сообщения.
Создадим виртуальное окружение:
mkvirtualenv project --python=/usr/bin/python2.6
Созданное окружение project будет содержать в себе Python 2.6, не будет видеть пакеты, установленные на уровне всей системы, и может быть активировано командой workon, а деактивировано командой deactivate:
workon project
deactivate
Если вызвать команду workon без параметров, она вернёт список имеющихся виртуальных окружений. Помните, что у каждого пользователя свой набор этих окружений, поэтому результат работы этой команды у каждого пользователя будет свой.
Настройка PostgreSQL
Перейдём в каталог с настройками PostgreSQL:
cd /etc/postgresql/установленная_версия/main/
Настроим доступ к базам по паролю, без наличия учетной записи в системе, для этого изменим в файле pg_hba.conf строку
local all all ident
на
local all all md5
Создадим резервную копию файла настроек, оптимизируем настройки с помощью pgtune и перезапустим службу:
cp postgresql.conf postgresql.conf.bak
pgtune -i postgresql.conf.bak -o postgresql.conf --type=web
service postgresql restart
В версиях PostgreSQL до 9.2 включительно оптимизация настроек с помощью pgtune требует также изменения параметров ядра. Прежде всего надо будет выполнить вот этот скрипт:
#!/bin/bash
# simple shmsetup script
page_size=`getconf PAGE_SIZE`
phys_pages=`getconf _PHYS_PAGES`
shmall=`expr $phys_pages / 2`
shmmax=`expr $shmall \* $page_size`
echo kernel.shmmax = $shmmax
echo kernel.shmall = $shmall
Строки, которые выведет этот скрипт, следует добавить в начало файла /etc/sysctl.conf и перезагрузить систему.
Для создания БД проекта следует запустить интерпретатор PostgreSQL от имени пользователя postgres:
su -c psql postgres
При выполнении SQL-запросов точка с запятой в конце обязательна. Команды, начинающиеся со знака \, вводятся как есть.
Создадим роль project с паролем project и возможностью подключаться к СУБД:
CREATE ROLE project WITH PASSWORD 'project' LOGIN;
Добавим БД project_db, с которой будет работать эта роль:
CREATE DATABASE project_db WITH OWNER project;
Проверим список имеющихся баз данных с помощью команды \l и завершим работу с интерпретатором \q.
Установка пакетов виртуального окружения
Снова залогинимся от имени пользователя www-data. Активируем виртуальное окружение и запустим в нем команду на установку пакетов:
sudo su www-data -s /bin/bash
workon prject
pip install lxml psycopg2 gunicorn Pillow
Если некоторые пакеты должны ставиться из каких-либо внешних репозиториев, следует передать дополнительные параметры:
pip install myPackage -i http://mysite.ru/simple/
Здесь после ключа i идёт указание стороннего репозитория.
Настройка Gunicorn
Для соединения Gunicorn'а с Nginx'ом будем использовать сокеты. В каталоге /var/www/ создадим каталог sockets
cd /var/www
mkdir sockets
chown www-data:www-data sockets/
Теперь в каталоге с настройками проекта /var/www/project/conf создадим файл gunicorn.conf.py со следующим содержимым:
#! coding: utf-8
import os
import multiprocessing
os.environ['DJANGO_SETTINGS_MODULE'] = 'project.settings'
short_name = 'project' # Это имя будет далее использоваться для создания сокета
bind = "unix:/var/www/sockets/%s.sock" % (short_name)#Создаём сокет
proc_name = short_name
workers = multiprocessing.cpu_count() * 2 + 1 #Кол-во воркеров вычисляется по рекомендуемой формуле
print "Socket started"
user = "www-data"
group = "www-data"
errorlog = "/var/www/project/log/gunicorn.log"
loglevel = "warn"
timeout = 30 # 30 секунд на завершение воркера при получении сигнала к завершению
keepalive = 5 #Поддерживать каждое подключение 5 секунд
Указанное содержимое затем будете передано gunicorn'у в качестве параметров. Подробно про переменные, использованные в этом файле, можно почитать здесь. Следует сделать этот файл исполнимым:
chmod u+x gunicorn.conf.py
Настройка supervisor
Установим:
apt-get install supervisor -y
Перейдём в каталог /etc/supervisor/conf.d и создадим файл настроек для нашего проекта (вместо vim новичкам советую использовать nano):
cd /etc/supervisor/conf.d
touch project.conf
vim project.conf
Содержимое файла project.conf
[program:project]
environment=DJANGO_SETTINGS_MODULE="project.settings",PYTHONIOENCODING="UTF-8",LANG="ru_RU.UTF-8",LC_ALL="ru_RU.UTF-8",LC_LANG="ru_RU.UTF-8"
command=/var/www/.virtualenvs/project_env/bin/gunicorn_django -c /var/www/project/conf/gunicorn.conf.py
directory = /var/www/project
user="www-data" #По аналогии с gunciron.conf.py
group="www-data"
stderr_logfile = /var/www/project/log/supervisor_error.log
Пожалуй, самой интересной является здесь строка, описывающая переменные окружения. В частности, она указывает, какие локали использовать. Без указания правильных локалей при загрузке или обработке приложением файлов, содержащих в имени символы, коды которых выходят за границы основной таблицы ASCII, можно будет полюбоваться ошибкой Non-Unicode character at position ...
В принципе, последняя строка необязательна, но лог ошибок позволит быстро разобраться в возникающих проблемах приложения. После сохранения файла следует выполнить обновление данных supervisor'а и посмотрим статус нашего проекта:
supervisorctl update
supervisorctl status project
Если всё хорошо, на экран будет выведено примерно следующее:
project RUNNING pid 2801, uptime 0:02:05
Больше делать ничего не надо, supervisor будет сам стартовать после перезагрузки системы и автоматически запускать все демоны. Для сервисного обслуживания БД или обновления сайта следует остановить демон командой stop, запустить заново командой start:
supervisorctl stop project
...
Какие-то сервисные работы, например, обновление Django, проведение миграций и т.д.
...
supervisorctl start project
Настройка nginx
Возможно, в Вашем случае на одной машине будет несколько сайтов, поэтому дабы не повторяться, некоторые настройки следует вынести в отдельный файл. В каталоге /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-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_connect_timeout 360;
proxy_send_timeout 260;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 1m;
Данная конфигурация успешно работает на машине с 4 Гб RAM и далеко не топовой конфигурацией. Также следует немного модифицировать /etc/nginx/nginx.conf (Вы ведь не забыли сначала сделать его резервную копию, правда?).
user nginx;
worker_processes 2;
worker_priority -5;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
timer_resolution 100ms;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 2;
client_max_body_size 10m;
gzip on;
gzip_comp_level 2;
gzip_proxied any;
gzip_types text/plain text/css application/x-javascript text/xml application/xml+rss text/javascript;
include /etc/nginx/conf.d/*.conf;
}
Единственное, на что здесь следует обратить внимание - это параметры client_max_body_size и gzip. По умолчанию максимальный размер одного пакета, который пропускает nginx, равен 24 КБ. В случае, если пользователь захочет загрузить на сайт фотографию размером 30 КБ (смешно, правда, особенно после 41 МП на некоторых смартфонах?), он получит ошибку 304, поэтому данное значение следует в разумных пределах увеличить. gzip позволяет снизить расход трафика, используя сжатие для указанных типов содержимого (список gzip_types). В остальном данный файл практически не отличается от оригинального.
В каталоге /etc/nginx/conf.d/ создадим файл project.conf
/etc/nginx/conf.d/project.conf
upstream project {
server unix:/var/www/sockets/project.sock fail_timeout=0;
}
server {
listen 80;
server_name project.ru www.project.ru;
access_log off;
error_log /var/www/project/log/nginx_error.log crit;
location / {
proxy_pass http://project;
include /etc/nginx/proxy_params.conf;
}
location /static/ {
root /var/www/project/static;
expires 3d;
}
location ~* \.(jpg|jpeg|gif|png|ico|css|bmp|swf|js|html|txt|xlsx|doc|docx|pdf|rar|zip|7z|exe)$ {
root /var/www/project/;
expires 3d;
add_header Cache-Control: public;
access_log off;
error_log /var/www/project/log/nginx_static_error.log;
}
}
Данный конфиг указывает, что динамические запросы следует передавать на сервер, подключенный к сокету /var/www/sockets/project.sock. Для раздачи статики, т.е. запросов, в которых встречается строка /static/, следует использовать содержимое каталога /var/www/project/static/. После сохранения настроек следует перезапустить nginx:
service nginx restart
Если всё сделано правильно, после ввода адреса в браузере мы попадём на сайт.