Всем доброго времени суток.
Была задача поднять свой 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
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
Вот на этом всё, у меня всё запустилось и работает, но в случае ошибок это очень индивидуально, поэтому рекомендую не пропускать шаги с тестированием работоспособности, чтобы можно было понять, на каком этапе возникла ошибка.
При написании использовал:
Exesium
Здорово!
Недавно также настраивал flask проект на VPS, использую gunicorn и nginx. Теперь вот думаю, писать ли тоже об этом :)
Кстати, в дебиан 10 не работал usermod по умолчанию. Можно указать, что если команда не найдена, то использовать /usr/sbin/usermod