Всем доброго времени суток.

Была задача поднять свой 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.
Запускаем установку.
make altinstall

Проверить, что Python установился, можно командой:

python3.8 -V

Она выведет версию 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

Настроим права, для нормальной работы с каталогом /var/www
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 (его мы используем по умолчанию, если планируете использовать другой, то укажите его):

sudo ufw allow 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

Для сбора статики и деактивируем виртуальное окружение:

deactivate

Настройка 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 для выхода (обязательно на английской раскладке).

file /run/gunicorn.sock

Последняя команда должна вывести сообщение о наличии файла.

Если первая команда sudo systemctl status gunicorn.socket выдаёт ошибку, или если вторая команда file /run/gunicorn.sock сообщит, что в каталоге отсутствует файл gunicorn.sock, то сокет Gunicorn не удалось создать. Нужно проверить журналы сокета Gunicorn с помощью следующей команды:

sudo journalctl -u gunicorn.socket

Смотреть в чём ошибка и устранять.

Перейдём к тестированию активации сокета.

sudo systemctl status gunicorn

Скорее всего у вас будет запись:

Active: inactive (dead)

Пока всё отлично идём дальше.

Установим соединение с сокетом через curl.

curl --unix-socket /run/gunicorn.sock localhost


Может выдать ошибку Bad Request (400).

Попробуйте вместо localhost указать IP вашего сервера, если всё нормально, то скорее всего это из-за настроек nginx, которые мы ещё не делали. Просто идём дальше.

И снова запросим вывод статуса:

sudo systemctl status gunicorn

Active: active (running)

Если запись будет такой + ещё масса информации, но не об ошибках, то всё отлично.

Иначе смотрим ошибки в журнале

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 nginx -t

Если ошибок нет, то перезапускаем сервер и даём нашему брандмауэру необходимые права:

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.

Создадим базу данных и пользователя.

sudo -u postgres psql

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 с помощью команды:

\q

Теперь настройка PostgreSQL завершена, теперь настроим Django для корректной работы с PostreSQL.

Для начала скопируем старые данные из SQLite (если это необходимо). Для этого переходим в ваш проект и запускаем виртуальное окружение:

cd /var/www/djangoprojectname
source ./env/bin/activate
cd mainappname/
python3.8 manage.py dumpdata > datadump.json

Поменяем настройки для подключения к базе данных:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'dbforproject',
        'USER': 'projectdbuser',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '',
    }
}

Запустите миграцию для БД:

python3.8 manage.py migrate --run-syncdb

После этого shell:

python3 manage.py shell

И пропишите в нём:

>>> from django.contrib.contenttypes.models import ContentType
>>> ContentType.objects.all().delete()
>>> quit()

И подгрузим ранее выгруженные данные из SQLite:

python3.8 manage.py loaddata datadump.json

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

При написании использовал:

  1. www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-18-04-ru
  2. vexxhost.com/resources/tutorials/how-to-deploy-django-on-nginx-gunicorn-with-postgres
  3. pythonworld.ru/web/django-ubuntu1604.html