Существует некоторое количество статей и инструкций по развертыванию проекта Django с Nginx, Gunicorn\uwsgi. Я столкнулся с проблемой, что не во всех статьях подробно описаны все шаги и не все статьи объясняют смысл шагов при деплое проекта. В данной статье я постараюсь агрегировать полученные теоретические и практические знания по деплою Django проекта. В статье не будет описано создание Django-приложения, подразумевается, что оно уже у вас есть и вам необходимо его разместить на стороннем сервере, например, на арендованной VPS.
Перед началом написания пошаговой подробной инструкции по деплою проекта я бы хотел рассказать про протокол WSGI, серверы uWsgi\Gunicorn, Nginx и немного про Docker\docker-compose.
WSGI
WSGI — стандарт (протокол) взаимодействия между Python-программой, выполняющейся на стороне сервера, и самим веб-сервером, например Apache или Nginx. WSGI-серверы появились в силу того, что веб-серверы в то время не умели взаимодействовать с приложениями, написанными на языке Python. В отличие, например, от приложений, написанных на PHP, где достаточно иметь index.php и сконфигурировать Apache и Nginx. Иными словами, WSGI-приложения исполняют код Python.
Существует некоторое количество WSGI-серверов. В данной статье я рассмотрю настройку наиболее популярных - uWSGI и Gunicorn.
Здесь я сделаю небольшое лирическое отступление. Было бы ошибочно не упомянуть о существовании протокола ASGI. Это более современный асинхронный протокол для фреймворков, которые поддерживают более современные технологии веб-разработки, а именно асинхронность, например, FastAPI. Конечно, Django тоже может поддерживать асинхронщину, но все же в первую очередь Django – тяжеловесный веб-фреймворк, с мощной админкой, который разрабатывался для создания блогов, форумов, интернет-магазинов и так далее, когда еще асинхроннасть не была во главе всего.
Nginx
Итак, с протоколом WSGI разобрались, поняли, для чего нам uWsgi или Gunicorn сервер, но зачем же для деплоя Django проекта еще исппользовать Nginx, если уже есть веб-сервер?
Nginx — это HTTP-сервер, обратный прокси-сервер, почтовый прокси-сервер, а также TCP/UDP прокси-сервер общего назначения.
В контексте нашей задачи будем использовать Nginx как обратный прокси-сервер (тип прокси-сервера, который ретранслирует запросы клиентов из внешней сети на один или несколько серверов, логически расположенных во внутренней сети.) То есть, Nginx будет принимать HTTP запросы от клиента (браузера) и направлять их на сервер uWsgi или Gunicorn, смотря что выбираем. Сервер WSGI создает динамический контент, выполняя Python код, который написал разработчик. Далее сервер WSGI возвращает ответ, который часто имеет формат HTML, JSON или XML, а обратный прокси-сервер (Nginx) отправляет его клиенту.

Также Nginx еще будет работать в качестве веб-сервера для предоставления статических ресурсов, таких как изображения, CSS-стили и JavaScript-файлов.
Deploy
Подготовка сервера
Итак, начнем подробное описание всех шагов деплоя.
В статье рассмотрен пример деплоя на сервер под управлением ОС "Ubuntu 20.x"
Начнем с загрузки и установки всех необходимых элементов из репозиториев Ubuntu. Обновим индекс пакетного менеджера apt.
sudo apt updateУстановим все необходимые пакеты.
sudo apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx curlТакже необходимо создать пользователя, под которым вы будете запускать ваш проект. Все необходимые поля заполняете произвольными данными, также можете оставить пустыми.
adduser forum_userusermod -aG sudo forum_usersu - forum_userКак уже было сказано выше, в данной статье мы рассматриваем ситуацию, в которой у нас уже есть готовое Django-приложение где-то в недрах нашего ssd диска или на github, например.
Представим, что у нас есть некий проект Django, который называется “forum” и представляет из cебя тематический форум с подобным расположением файлов
└── my_super_forum
├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── forum
│ ├── accounts
│ │ ├── models.py
│ │ ├── urls.py
│ │ ├── views.py
│ │ └── templates
│ ├── answers
│ │ ├── models.py
│ │ ├── urls.py
│ │ ├── views.py
│ │ └── templates
│ ├── questions
│ │ ├── models.py
│ │ ├── urls.py
│ │ ├── views.py
│ │ └── templates
│ └── api
│ │ ├── serializer.py
│ │ ├── urls.py
│ │ └── views.py
├── static
├── media
├── manage.py
├── forum_env
└── requirements.txtПроект может быть и другого вида, чуть проще, например, как в базовом представлении проекта
└── app
├── my_django_app
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── requirements.txt
Проект заливается на сервер через scp, либо клонируется из github (могут быть конечно и другие варианты)
scp -r my_super_forum username@remote-server-ip:/home/your_pathcd /home/forum_user
mkdir forum
git clone https://github.com/your-username/your-project-nameНастройка хранилища
В этой части статьи поговорим о данных на страницах форума, с которыми взаимодействуют пользователи. Эти данные должны храниться где-то в недрах жесткого диска. Django из коробки предлагает хранить их в SQLite.
SQLite — это быстрая и легкая встраиваемая однофайловая СУБД на языке C, которая не имеет сервера и позволяет хранить всю баз�� локально на одном устройстве. Для работы SQLite не нужны сторонние библиотеки или службы.
НО!! Использовать SQLite в “боевом” проекте Django – моветон, поэтому ниже я опишу, как установить, настроить и “подружить” базу данных PostgreSQL с Django.
Почему PostgreSQL? Эта СУБД сейчас достаточно популярна, она более мощная, более гибкая, разрабатывать на ней приятнее, чем на MySQL. Ну и практически во всех вакансиях требуются знания postgres, есть смысл потренироваться)
По умолчанию Postgres использует для локальных соединений одноранговую аутентификацию. То есть, имя пользователя операционной системы совпадает с именем пользователя Postgres, соответственно этот пользователь может войти без аутентификации.
Во время установки Postgres можно создать пользователя операционной системы, например, postgres, который будет являться пользователем-администратором в СУБД. Этого пользователя необходимо использовать для выполнения административных задач. Чтобы войти в сеанс Postgres, используйте sudo и передайте имя пользователя с ключем -u
sudo -u postgres psqlНам открывается командная строка базы данных для настройки
Создаем базу данных для нашего проекта
CREATE DATABASE forum;Создаем пользователя базы данных для проекта и выбираем безопасный пароль
CREATE USER forumuser WITH PASSWORD 'password';Меняем несколько параметров подключения для ускорения операций с базой данных, также настроим часовой пояс
ALTER ROLE forumuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE forumuser SET timezone 'Europe/Moscow';Предоставляем новому пользователю доступ для администрирования новой БД
GRANT ALL PRIVILEGES ON DATABASE forum TO forumuser;Выходим из командной строки
\qНастройка проекта
Перейдем к проверке настроек проекта.
Запускаем виртуальное окружение (если его нет, то создаем командой ниже)
# Создание виртуального окружения
sudo apt install python3.10-venv
python3 venv -v forum_envsource forum_env/bin/activateУстановим необходимые пакеты
pip3 install django gunicorn uwsgi psycopg2-binary
# или
pip3 install -r requirements.txt
deactivateВыходим из виртуального окружения
deactivateУстанавливаем и uwsgi и gunicorn, так как далее я буду показывать, как деплоить проект с обеими веб-серверами. Вы уже выберете, какой вам больше подходит. Если вы уже твердо решили, какой вам подходит, тот и оставляйте в команде выше.
Следующим шагом прописываем необходимые настройки проекта в файле settings.py, в моем случае он находится в папке config в корне проекта.
В строчку ALLOWED_HOSTS добавим следующее
ALLOWED_HOSTS = ['your_server_domain_or_IP', 'second_domain_or_IP', . . .,
'localhost']В настройках базы данных укажем Postgres
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'forum',
'USER': 'forum tuser',
'PASSWORD': 'password',
'HOST': 'localhost',
'PORT': '',
}
}Необходимо указать имя базы данных, имя только что созданного пользователя базы данных, пароль пользователя базы данных и указать, что базу данных можно найти на локальном компьютере (так как бд находится на том же сервере, что и проект). Вы можете оставить настройку PORT пустой.
Ну и в конце пропишем настройки статических файлов. Это необходимо для того, чтобы Nginx мог обрабатывать запросы на получение статики.
STATIC_URL = '/static/’
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles/')
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static"),]Немного расскажу, за что отвечает каждая переменная.
STATIC_ROOT: Абсолютный путь к каталогу, в котором будут собираться статические файлы уже во время деплоя проекта. Файлы собираются командой python3 manage.py collectstatic. Все статические файлы Django скопирует в STATIC_ROOT. В дальнейшем мы будем обслуживать статические файлы с помощью Nginx, то есть в настройках nginx пропишем путь до папки, указанной в STATIC_ROOT. То есть при обращении по адресу http://yourdomain/static/css/base.css Nginx будет искать “css/base.css” в директории, указанной в STATIC_ROOT .
STATIC_URL – это переменная, в которой хранится адрес до статических файлов, в основном используется методом static в шаблонах Django.
Пример:
<link rel="stylesheet" href="{{ STATIC_URL }}css/base.css" type="text/css" />в нашем случае будет обрабатываться как
<link rel="stylesheet" href="/static/css/base.css" type="text/css" />И, так как этот href начинается с /, он добавит ваш домен для доступа к статическим файлам: http://yourdomain/static/css/base.css
STATICFILES_DIRS используется для включения каталогов для поиска статики при выполнении python3 manage.py collectstatic
Итого: кладем статику в выбранную директорию(ии), указываем ее\их в STATICFILES_DIRS (сюда будет смотреть django при отладочном режиме), придумываем по какому адресу будут идти обращения на статику — указываем его в STATIC_URL и не забываем указать в дальнейшем в настройках nginx, и наконец указываем директорию в STATIC_ROOT, в которой будут храниться статические файлы (в режиме деплоя) после применения python3 manage.py collectstatic , также не забываем ее указать в настройках nginx.
В целом STATICFILES_DIRS и STATIC_ROOT могут быть одинаковыми. Но чтобы вас не запутать, в данной статье я разделил их.
Применяем миграции.
python3 manage.py makemigrationspython3 manage.py migrateСоздаем администратора проекта.
python3 manage.py createsuperuserНужно будет выбрать имя пользователя, указать адрес электронной почты, а затем выбрать и подтвердить пароль.
Собираем весь статический контент в настроенном вами каталоге, выполнив следующую команду:
python3 manage.py collectstaticЧтобы протестировать сервер разработки, вам необходимо разрешить доступ к порту, который вы будете использовать. В этом случае создайте исключение для порта 8000:
sudo ufw allow 8000Наконец, протестируйте свой проект, запустив сервер разработки Django с помощью следующей команды:
python3 manage.py runserver 0.0.0.0:8000 Откройте браузер на компьютере и проверьте доступность вашего проекта
http://server_domain_or_IP:8000
Настройка Gunicorn.
На удаленном сервере все отлично работает, но приходится указывать порт 8000, на котором развернуто приложение в режиме debug.
Для того, чтобы все работало на 443 или хотя бы на 80 порту, нам необходим Nginx и gunicorn (их необходимость описана выше)
В первую очередь настроим системные сокеты и служебные файлы для Gunicorn.
Идем в каталог /etc/systemd/system/ и создаем два файла: gunicorn.service и gunicorn.socket (вместо имени "gunicorn", можно использовать любое другое):
cd /etc/systemd/system/
sudo nano /etc/systemd/system/gunicorn.socketВставляем следующие строки в файл gunicorn.socket
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target[Unit] для описания сокета,
[Socket] для определения местоположения сокета
[Install] чтобы убедиться, что сокет создан в нужное время
sudo nano /etc/systemd/system/gunicorn.serviceВставляем следующие строки в файл gunicorn.service
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
# имя вашего пользователя
User=forum_user
#путь до каталога с файлом manage.py
WorkingDirectory=/home/forum_user/forum
ExecStart=/home/forum_user/forum/forum_env/bin/gunicorn --workers 5 --bind unix:/run/gunicorn.sock config.wsgi:application
#путь до файла gunicorn в виртуальном окружении (также не забудьте указать верный путь до файла wsgi (обычно лежит рядом с settings.py))
[Install]
WantedBy=multi-user.target[Unit] используется для указания метаданных и зависимостей. Добавляем сюда описание службы и указываем системе инициализации, чтобы она запускала эту службу только после достижения сетевой цели. Поскольку служба использует сокет из файла сокета, необходимо включить директиву Requires, чтобы указать эту связь:
[Service] указываем пользователя и группу, под которыми будет запускаться процесс. Указываем, что обычная учетная запись пользователя является владельцем процесса, поскольку ему принадлежат все соответствующие файлы. Затем пределяем рабочий каталог и указываем команду, которой будет запускаться служба. В этом случае указываем полный путь к исполняемому файлу Gunicorn, который установлен в виртуальной среде. Далее привязываем процесс к сокету Unix, который создался в каталоге /run, чтобы процесс мог взаимодействовать с Nginx. Записываем все данные в стандартный вывод, чтобы процесс Journald мог собирать журналы Gunicorn. Здесь также можем указать любые дополнительные настройки Gunicorn. Для нашего примера мы указали 3 рабочих процесса
[Install] сообщает systemd, с чем связать эту службу, если включен ее запуск при загрузке. Укажем, чтобы эта служба запускалась при запуске и работе обычной многопользовательской системы.
Запускаем Gunicorn
sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket
sudo systemctl status gunicorn.socketНаблюдаем следующий вывод
● gunicorn.socket - gunicorn socket
Loaded: loaded (/etc/systemd/system/gunicorn.socket; enabled; vendor prese>
Active: active (listening) since Thu 2021-12-02 19:58:48 UTC; 14s ago
Triggers: ● gunicorn.service
Listen: /run/gunicorn.sock (Stream)
Tasks: 0 (limit: 1136)
Memory: 0B
CGroup: /system.slice/gunicorn.socket
Dec 02 19:58:48 gunicorn systemd[1]: Listening on gunicorn socket.Проверяем создание сокета.
file /run/gunicorn.sockПри успешном создании наблюдаем следующее:
/run/gunicorn.sock: socketЕсли что-то пошло не так - в первую очередь проверяем журнал
sudo journalctl -u gunicorn.socketДалее проверяем работу gunicorn
sudo systemctl status gunicornДолжно появиться следующее
Если обнаружены ошибки - смотри журнал и логи.
sudo journalctl -u gunicorn
nano var/log/syslogЕсли меняете в файлах настройки Gunicorn конфигурации, то обязательно рестартим Gunicorn
sudo systemctl daemon-reload
sudo systemctl restart gunicornНастройка uWSGI.
Запустить uWSGI можно в командной строке для тестирования конфигурации, но для деплоя будем запускать uWSGI в режиме "императора" (Emperor), который позволяет главному процессу автоматически управлять отдельными приложениями с учетом набора файлов конфигурации.
Создаем каталог, в котором будут храниться файлы конфигурации.
mkdir /etc/uwsgi/sitesСоздаем файл настройки конкретно для нашего проекта forum
nano /etc/uwsgi/sites/forum.iniДобавим следующие настройки в файл forum.ini.
[uwsgi]
project = forum
uid = forum_user
base = /home/%(uid)
chdir = %(base)/%(project)
home = %(base)/%(project)/forum_env
module = %(project)/cinfig.wsgi:application
master = true
processes = 5
socket = /run/uwsgi/%(project).sock
chown-socket = %(uid):www-data
chmod-socket = 660
vacuum = trueНеобходимо создать systemd файл для управления процессом uWSGI Emperor и автоматическим запуском uWSGI при загрузке.
sudo nano /etc/systemd/system/uwsgi.serviceВставляем следующие строки в файл:
[Unit]
Description=uWSGI Emperor service
[Service]
ExecStartPre=/bin/bash -c 'mkdir -p /run/uwsgi; chown forum_user:www-data /run/uwsgi'
ExecStart=/usr/local/bin/uwsgi --emperor /etc/uwsgi/sites
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
[Install]
WantedBy=multi-user.targetНа этом этапе мы не сможем успешно запустить службу, поскольку она зависит от доступности пользователя www-data. Нам придется подождать, чтобы запустить службу uWSGI, пока не будет установлен и настроен Nginx.
Настройка Nginx.
В нашем случае Nginx настраивается как обратный прокси-сервер, для перенаправления запросов на Gunicorn, также nginx будет отдавать статику по запросам.
В первую очередь создадим файл настройки.
sudo nano /etc/nginx/sites-available/forumВнутри откроем новый серверный блок. Начнем с указания, что этот блок должен прослушивать обычный порт 80 и отвечать на доменное имя или IP-адрес вашего сервера:
server {
listen 80;
server_name server_domain_or_IP;
} Далее пропишем, где хранятся статические ресурсы, которые собираются в папку staticfiles
server {
listen 80;
server_name server_domain_or_IP;
location /static/ {
root /home/forum/staticfiles;
}
}Создаем самый основной блок для соответствия всем остальным запросам. Внутри этого блока включаем стандартный файл proxy_params из установки Nginx, а затем передаем трафик непосредственно в сокет Gunicorn
server {
listen 80;
server_name server_domain_or_IP;
location /static/ {
root /home/forum/staticfiles;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}Создаем симлинк в sites-enabled
sudo ln -s /etc/nginx/sites-available/forum /etc/nginx/sites-enabledНемного теории насчет файлов, которые отвечают за конфиги nginx.
Обычно правила настройки конфигов выглядят следующим образом
nginx.conf - общие конфиги всего сервера и всех обслуживаемых сайтов под ним, там подключается всё, что лежит в site-enabled
site-available - конфиги отдельных приложений, туда, например, можно закинуть один для вебдева, другой для вебсокетов, третий для сайта, четвёртый для другого сайта, а пятый вообще для php-fpm и т.д.
sites-enabled - просто включённые сайты - сюда складываются симлинки из site-available, для того, чтобы быстро включить, переключить или отключить какой-то конфиг. Что-то вроде горячей замены.
Проводим проверку синтаксиса
sudo nginx -tПерезапускаем nginx
sudo systemctl restart nginxЗакрываем ранее открытый 8000 порт
sudo ufw delete allow 8000Применяем для фаервола настройки nginx
sudo ufw allow 'Nginx Full'Итого, если ошибок никаких нет, то по указанному в настройках nginx доменному имени можно наблюдать задеплоенный сайт)
P.S. Все ошибки смотрим в логах системы:
sudo nano /var/log/syslogИтог
В этой статье я попытался расписать, как деплоить ваш Django-проект, а также для чего необходимо выполнять указанные в статье шаги. Эта статья будет полезной для вас, если вы собираетесь приступить к деплою проекта на Django. Каждый шаг деплоя протестирован: если выполнять все шаги друг за другом - ошибки возникать не будут. Всем удачного деплоя!
P.S.: Настройки для https, а также деплой в докере будут описаны в следующей статье.
