Настройка Debian, Nginx и Gunicorn для Django проекта
Была задача поднять свой Debian сервер на Nginx для проектов Django 3.х. Перерыв кучу информации в интернете, удалось это сделать соединив рекомендации с нескольких разных сайтов. Если вам интересно почитать, как настроить свой первый сервер для Django-проекта, то — добро пожаловать.
Немного расскажу о себе, чтобы вы понимали, кто я. Я не разработчик, не программист, не системный администратор и даже не имею IT-образования. Я учитель информатики. Но по работе мне приходится объяснять ученикам некоторые моменты, очень далёкие от школьного курса информатики и один из них это разработка проектов на Django.
Основные установки
Начнём с того, что у нас уже есть сервер с установленной Debian 10, с установкой Debian проблем возникнуть не должно. У меня была чистая установка, без дополнительных настроек, поэтому я зашёл на свой сервер через root и приступил к установке некоторых основных для меня компонентов.
apt-get update apt-get install -y sudo htop git curl wget unzip zip gcc build-essential make apt-get install -y tree redis-server nginx libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python3-dev python-pil ython3-pil apt-get install -y python3-lxml libxslt-dev python-libxml2 python-libxslt1 python-dev gnumeric libpq-dev libxml2-dev libxslt1-dev libjpeg-dev libfreetype6-dev libcurl4-openssl-dev supervisor libgdbm-dev libnss3-dev ufw
Можно всё это засунуть в одну установку, но у меня выдал ошибку, а при таком порядке всё прошло успешно.
Далее создаём нового пользователя и добавляем его в группу sudo, чтобы он мог запускать процессы от имени суперпользователя.
adduser username usermod -aG sudo username
где username — это имя пользователя, которое вы будете использовать в дальнейшем.
Установим последнюю версию Python из исходного кода. На момент написания статьи это была 3.8.2.
cd /home/username curl -O https://www.python.org/ftp/python/3.8.2/Python-3.8.2.tar.xz tar -xf Python-3.8.2.tar.xz cd Python-3.8.2
cd — это команда для изменения рабочего каталога, сокращение от change directory;
curl — позволяет взаимодействовать с различными сервисами по URL. Мы её используем для скачивания файла;
tar — распаковка скаченного архива.
После скачивания последней версии Python и перехода в каталог с исходным кодом мы запускаем:
./configure --enable-optimizations make -j 2
Это позволит нам подготовить всё необходимое для установки Python. Здесь число 2, это количество ядер процессора. Можно узнать командой nproc.
Запускаем установку.
Проверить, что Python установился, можно командой:
Она выведет версию Python.
После этого обновляем pip и устанавливаем наиболее часто используемые пакеты для Python.
python3.8 -m pip install -U pip python3.8 -m pip install -U setuptools python3.8 -m pip install pillow python3.8 -m pip install virtualenv
Настройка Django
Перезапускаем систему и заходим под созданным вами пользователем. Перейдём в каталог /var/www и скачаем в него наш проект, который загружен на GitHub.
cd /var/www sudo git clone LINK_TO_PROJECT
sudo chown -R www-data:www-data /var/www sudo usermod -aG www-data username sudo chmod go-rwx /var/www sudo chmod go+x /var/www sudo chgrp -R www-data /var/www sudo chmod -R go-rwx /var/www sudo chmod -R g+rwx /var/www
Может показаться, что мы делаем одни действия, а потом отменяем их, но суть в том, что мы сначала убираем все разрешения для всех, а потом их назначаем конкретному пользователю или группе.
Переходим в наш проект, создаём виртуальное окружение и запускаем его:
cd djangoprojectname/ virtualenv env source ./env/bin/activate
Если всё активировалось, то будет строка вида: (env) username@server.
Поставим/обновим основные пакеты, которые нужны нам для нашего проекта.
pip install -U pip pip install -U setuptools pip install -U pillow
Ставим Django, Gunicorn и адаптер для PostgreSQL.
pip install django gunicorn psycopg2-binary
Можно ещё поставить необходимые для вашего проекта пакеты, я использую summernote для текста:
pip install django-summernote
Мы ставили брандмауэр, поэтому нужно создать для него исключение на порт 8000 (его мы используем по умолчанию, если планируете использовать другой, то укажите его):
Обязательный шаг — это проверка работоспособности сервера:
python3.8 manage.py runserver 0.0.0.0:8000
И перейдите на ваш сайт, DOMAIN_NAME:8000, чтобы убедится в том, что всё работает! Но, на данный момент у нас ещё не настроено очень многое, это только базовые настройки, нам нужно настроить нормальную работу сервера, подключить статику и т.д.
Если у Вас не запустился сайт, то нужно копаться в настройках (возможно прав доступа) и смотреть какой из пакетов не установился, тут всё очень индивидуально.
Завершить выполнение можно нажатием клавиш: CTRL+C.
Далее переходим, только если ваш проект запустился, крайне не рекомендую переходить, если что-то не получилось. Лучше устранить проблему на начальном этапе, чем потом сносить сервер под корень (я сносил 3 раза, а потом начал писать эту инструкцию, фиксируя каждое действие).
Проверяем работу Gunicorn:
gunicorn --bind 0.0.0.0:8000 MAINAPPNAME.wsgi
MAINAPPNAME — это имя основного приложения, в котором лежит settings.py.
Если сервер работает, то идём дальше.
Завершить выполнение можно нажатием клавиш: CTRL+C.
Настройка settings.py
При развёртывании на продакшн сервере нужно отключить дебаг у проекта и поменять несколько настроек, я это сделал следующим образом в settings.py моего проекта.
DEBUG = False if DEBUG: ALLOWED_HOSTS = ['*'] else: ALLOWED_HOSTS = ['HOST IP', 'DOMAIN NAIM', 'localhost'] . STATIC_URL = '/static/' if DEBUG: STATIC_DIR = os.path.join(BASE_DIR, 'static') STATICFILES_DIRS = [ STATIC_DIR, '/var/www/static/', ] else: STATIC_ROOT = os.path.join(BASE_DIR, 'static/') STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ) MEDIA_URL = '/media/'
В urls.py я изменил настройки для статики и медиа.
if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Я не знаю, насколько это правильное решение и возможно есть более корректные изменения этих файлов. Но в данном случае мне достаточно менять значение DEBUG для того, чтобы переключаться между разработкой и публикацией.
python3.8 manage.py collectstatic
Для сбора статики и деактивируем виртуальное окружение:
Настройка Gunicorn
sudo nano /etc/systemd/system/gunicorn.socket
Пропишем в файле несколько настроек:
[Unit] Description=gunicorn socket [Socket] ListenStream=/run/gunicorn.sock [Install] WantedBy=sockets.target
Мы создали раздел [Unit] для описания сокета, в разделе [Socket] мы определили расположение сокета и в разделе [Install] нужен для установки сокета в нужное время.
Откроем служебный файл systemd ля настройки работы сервиса:
sudo nano /etc/systemd/system/gunicorn.service
[Unit] Description=gunicorn daemon Requires=gunicorn.socket After=network.target [Service] User=username Group=www-data WorkingDirectory=/var/www/djangoprojectname ExecStart=/var/www/djangoprojectname/env/bin/gunicorn \ --access-logfile - \ --workers 5 \ --bind unix:/run/gunicorn.sock \ myproject.wsgi:application [Install] WantedBy=multi-user.target
Не забудьте указать вашего пользователя, ваше название проекта и ваше виртуальное окружение.
Как мне пояснили workers вычисляется как количество ядер процессора * 2 + 1.
Теперь мы запускаем и активируем сокет Gunicorn.
sudo systemctl start gunicorn.socket sudo systemctl enable gunicorn.socket
И обязательно тестируем, что всё работает!
sudo systemctl status gunicorn.socket
Должен вывести данные о запущенном сервисе.
Нажмите клавишу Q для выхода (обязательно на английской раскладке).
Последняя команда должна вывести сообщение о наличии файла.
Если первая команда sudo systemctl status gunicorn.socket выдаёт ошибку, или если вторая команда file /run/gunicorn.sock сообщит, что в каталоге отсутствует файл gunicorn.sock, то сокет Gunicorn не удалось создать. Нужно проверить журналы сокета Gunicorn с помощью следующей команды:
sudo journalctl -u gunicorn.socket
Смотреть в чём ошибка и устранять.
Перейдём к тестированию активации сокета.
sudo systemctl status gunicorn
Скорее всего у вас будет запись:
Пока всё отлично идём дальше.
Установим соединение с сокетом через curl.
curl --unix-socket /run/gunicorn.sock localhost
Может выдать ошибку Bad Request (400).
Попробуйте вместо localhost указать IP вашего сервера, если всё нормально, то скорее всего это из-за настроек nginx, которые мы ещё не делали. Просто идём дальше.
И снова запросим вывод статуса:
sudo systemctl status gunicorn
Если запись будет такой + ещё масса информации, но не об ошибках, то всё отлично.
Иначе смотрим ошибки в журнале
sudo journalctl -u gunicorn
И перезапускаем процессы Gunicorn
sudo systemctl daemon-reload sudo systemctl restart gunicorn
Настройка NGINX
Создадим новый серверный блок для нашего сайта и настроим его, чтобы при обращении к нашему сайту в адресной строке, сервер понимал что и откуда брать.
sudo nano /etc/nginx/sites-available/djangoprojectname
server < listen 80; server_name server_domain_or_IP; location = /favicon.ico < access_log off; log_not_found off; >location /static/ < alias /var/www/djangoprojectname/static/; >location /media/ < alias /var/www/djangoprojectname/media/; >location / < include proxy_params; proxy_pass http://unix:/run/gunicorn.sock; >>
В разделе sever_name можно указать несколько адресов через пробел.
Протестируем на наличие ошибок в синтаксисе:
Если ошибок нет, то перезапускаем сервер и даём нашему брандмауэру необходимые права:
sudo systemctl restart nginx sudo ufw allow 'Nginx Full'
Можно также удалить доступ к порту 8000
sudo ufw delete allow 8000
На этом этапе всё должно работать, но у меня не работало всё нормально, пока я не настроил https. К сожалению, не могу объяснить с чем это связано, но статика не подгружалась, хотя медиа файлы грузились нормально.
Настройка HTTPS
Для настройки будем использовать Cerbot
sudo apt-get install certbot python-certbot-nginx sudo certbot –nginx
И следуем подсказкам на экране. Если появились ошибки, то устраните и повторите. Например, ошибка может возникнуть если вы указали доменное имя, не относящееся к данному серверу.
Для автоматического обновления сертификата введите команду.
sudo certbot renew --dry-run
Теперь тестим сайт и всё должно работать!
Если статика так и не отобразилась, то попробуйте открыть какой-нибудь css файл указав полный адрес до него, если выдал ошибку 403, то всё отлично, но проблема в правах доступа нужно экспериментировать с ними, попробуйте сбросить все настройки прав для www-data на каталог /var/www. Если ошибка 404, то нужно смотреть в сторону настроек location в настройках NGINX.
Настройка PostgreSQL
Настроим PostgreSQL и сделаем импорт данных из SQLite. Если Вам это не нужно, то пропустите этот шаг, либо возьмите только настройки PostgreSQL.
Создадим базу данных и пользователя.
CREATE DATABASE dbforproject; CREATE USER projectdbuser WITH PASSWORD 'password';
Мы будем следовать рекомендациям по проекту Django.
Зададим кодировку по умолчанию UTF-8, схему изоляции транзакций по умолчанию поставим в «read committed», для блокировки чтение со стороны неподтвержденных транзакций. Часовой пояс поставим в значения времени по Гринвичу (UTC).
ALTER ROLE projectdbuser SET client_encoding TO 'utf8'; ALTER ROLE projectdbuser SET default_transaction_isolation TO 'read committed'; ALTER ROLE projectdbuser SET timezone TO 'UTC';
После этого нужно предоставить созданному нами пользователю доступ для администрирования новой базы данных:
GRANT ALL PRIVILEGES ON DATABASE dbforproject TO projectdbuser;
После выполнения всех команд можно закрыть диалог PostgreSQL с помощью команды:
Теперь настройка PostgreSQL завершена, теперь настроим Django для корректной работы с PostreSQL.
Для начала скопируем старые данные из SQLite (если это необходимо). Для этого переходим в ваш проект и запускаем виртуальное окружение:
cd /var/www/djangoprojectname source ./env/bin/activate cd mainappname/ python3.8 manage.py dumpdata > datadump.json
Поменяем настройки для подключения к базе данных:
Запустите миграцию для БД:
python3.8 manage.py migrate --run-syncdb
>>> from django.contrib.contenttypes.models import ContentType >>> ContentType.objects.all().delete() >>> quit()
И подгрузим ранее выгруженные данные из SQLite:
python3.8 manage.py loaddata datadump.json
Вот на этом всё, у меня всё запустилось и работает, но в случае ошибок это очень индивидуально, поэтому рекомендую не пропускать шаги с тестированием работоспособности, чтобы можно было понять, на каком этапе возникла ошибка.
При написании использовал: