Skip to content

Latest commit

 

History

History
513 lines (334 loc) · 21.9 KB

File metadata and controls

513 lines (334 loc) · 21.9 KB

Урок 34. Настройка Django, Gunicorn и Nginx

В прошлом уроке мы подготовили инфраструктуру: сервер, домен, SSH, код проекта на сервере через Git, установленные зависимости. Теперь настроим саму связку Django → Gunicorn → Nginx, благодаря которой страницы filmsite станут доступны по доменному имени в браузере.

Работаем по-прежнему в SSH-сессии на сервере, в каталоге /var/www/filmsite, с активированным виртуальным окружением (source venv/bin/activate).

Напоминание про развилку из прошлого урока: везде, где действия отличаются в зависимости от базы данных, стоит пометка [Только PostgreSQL] или [Только SQLite].


Шаг 1. Подготовка Django-проекта к работе на сервере

Все изменения в этом шаге вносим в файл:

filmsite/settings.py

(путь указывайте относительно корня проекта, где лежит manage.py).

Перед изменением settings.py проверьте, не использует ли ваш проект переменные окружения.

Если в репозитории есть файл .env.example, а рабочего .env нет — создайте .env на основе .env.example:

cp .env.example .env

После этого все значения, которые отличаются для вашего сервера (например, пароль базы данных, SECRET_KEY, настройки базы данных и другие переменные), изменяйте в .env, а не в .env.example.

Файл .env не должен попадать в Git. .env.example, наоборот, обычно хранится в репозитории как шаблон с названиями необходимых переменных, но без настоящих секретных значений.

Если ваш проект уже использует .env и загрузку переменных окружения в settings.py, не нужно создавать эту настройку заново. Используйте существующий механизм и меняйте необходимые значения в .env.


Подключение модуля os

В самом начале файла добавьте импорт, если его ещё нет:

import os

Он понадобится для путей к статике и медиафайлам.

Настройка ALLOWED_HOSTS

По умолчанию Django блокирует все внешние запросы — это защита от случайного запуска в продакшене без явного разрешения. Найдите переменную ALLOWED_HOSTS и укажите домен и IP сервера:

ALLOWED_HOSTS = [
    'filmsite.ru',  # ваш домен
    'www.filmsite.ru',
    '123.123.123.123',   # IP вашего сервера — пригодится при диагностике до подключения SSL
]

Забегая вперёд: IP сервера в ALLOWED_HOSTS пригодится в уроке 35 при получении SSL-сертификата — Certbot иногда обращается к серверу напрямую по IP на этапе проверки. Если этой строки не будет — столкнётесь с DisallowedHost в самый неподходящий момент. Добавим сразу, чтобы не возвращаться к этому файлу лишний раз.

Если не сделать этот шаг — при открытии сайта вы получите ошибку DisallowedHost.

INTERNAL_IPS

Если в файле есть строка:

INTERNAL_IPS = ["127.0.0.1"]

— закомментируйте её, она используется только для отладки (Django Debug Toolbar) и не нужна в продакшене.


Шаг 2. Настройка базы данных

[Только PostgreSQL]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'filmsite_db',
        'USER': 'filmsite_user',
        'PASSWORD': 'strong_password_here',
        'HOST': 'localhost',
        'PORT': '',
    }
}

Здесь используются имя базы и пользователь, которых мы создали в уроке 33.

Про пароль прямо в settings.py — это временное упрощение для первого запуска. Вынесем его в переменные окружения в конце этого урока.

[Только SQLite]

Если вы работали только с SQLite — ничего менять не нужно, оставляем стандартную конфигурацию:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

База данных — это файл db.sqlite3 внутри проекта, который приехал вместе с кодом при git clone (если вы закоммитили его) или будет создан заново при первой миграции.


Шаг 3. Настройка статических файлов и медиафайлов

В нашем проекте есть и то, и другое: статика (CSS, JS) и медиафайлы, которые загружают пользователи (постеры фильмов, фото актёров и режиссёров, аватары в UserProfile). Это две разные сущности, и путать их — частая ошибка.

Добавьте в settings.py:

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

Если у вас есть переменная STATICFILES_DIRS — закомментируйте её:

# STATICFILES_DIRS = [...]

Почему так:

  • STATIC_ROOT — это папка, куда Django соберёт все статические файлы проекта в одно место командой collectstatic. В продакшене статику отдаёт Nginx напрямую, без участия Django.
  • MEDIA_ROOT — папка, куда сохраняются файлы, загруженные через ImageField/FileField (постеры, аватары). Это не то же самое, что статика: медиафайлы создаются пользователями во время работы сайта, а не собираются заранее.
  • STATICFILES_DIRS в продакшене не нужен — эта настройка используется для локальной разработки, когда статика лежит в нескольких местах проекта. collectstatic соберёт всё содержимое приложений в STATIC_ROOT самостоятельно.

Шаг 4. Миграции и сбор статических файлов

Из корня проекта, с активированным окружением, выполняем:

python manage.py makemigrations
python manage.py migrate

[Только PostgreSQL] — на этом шаге Django создаст все таблицы проекта заново в пустой базе filmsite_db. Если вы переносили данные из SQLite в прошлом курсе через dumpdata/loaddata — процедура ниже, в шаге 6.

Собираем статику:

python manage.py collectstatic

Django спросит подтверждение — вводим yes.

Проверка

Убедитесь, что в проекте появилась папка static/, а внутри неё — CSS, JS и другие файлы, собранные из всех приложений (включая django.contrib.admin, чьи стили тоже должны подгрузиться для админки).


Шаг 5. Временный запуск Django-сервера

Прежде чем настраивать Gunicorn и Nginx, убедимся, что сама конфигурация Django корректна.

Откроем порт 8000 временно:

sudo ufw allow 8000

Запускаем:

python manage.py runserver 0.0.0.0:8000

В браузере открываем:

http://ваш_домен:8000

Проверка

  • сайт открывается;
  • каталог фильмов может быть пустым, если данные ещё не перенесены — это нормально;
  • если видите DisallowedHost — вернитесь к ALLOWED_HOSTS в шаге 1.

⚠️ Это тестовый режим — он не предназначен для постоянной работы. Останавливаем: Ctrl + C.


Шаг 6. Перенос данных (опционально)

Если хотите перенести реальные данные каталога (фильмы, режиссёров, пользователей) с локальной машины на сервер:

Локально:

python manage.py dumpdata --indent=2 -o db.json

Загрузите db.json на сервер любым способом (можно через git add/git push, если файл небольшой и не содержит чувствительных данных, либо через scp).

На сервере:

python manage.py loaddata db.json

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

Если вы тестировали регистрацию/пароли локально — учтите, что хэши паролей перенесутся вместе с пользователями, и локальные пароли будут работать и на сервере.


Шаг 7. Переключение Django в боевой режим

Создание нового SECRET_KEY

Для production-сервера рекомендуется использовать отдельный SECRET_KEY, отличный от ключа локальной разработки.

Создать новый ключ можно прямо в терминале:

python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

Скопируйте получившееся значение.

Если проект использует .env, сохраните новый ключ в .env:

SECRET_KEY=сюда_вставьте_новый_ключ

Если переменные окружения в проекте пока не используются — временно замените значение SECRET_KEY в settings.py. В конце урока в бонусе показано, как вынести секретные данные в .env.


В settings.py:

DEBUG = False

⚠️ Это обязательный шаг перед продакшеном. При DEBUG = True Django показывает подробные страницы ошибок с кусками кода и путями на сервере — в открытом доступе это серьёзная угроза безопасности.

Шаг 8. Тестирование Gunicorn

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

gunicorn --bind 0.0.0.0:8000 filmsite.wsgi

Если ваш основной модуль настроек называется иначе (например, core, если вы называли проект по-другому при startproject) — команда будет core.wsgi.

В браузере снова открываем http://ваш_домен:8000.

Проверка

  • сайт работает;
  • стили не загружаются — это ожидаемо: Gunicorn отдаёт только Python-логику, статику пока некому раздавать (этим займётся Nginx);
  • если вместо страницы — ошибка, проверьте вывод в терминале: чаще всего это опечатка в имени модуля wsgi или незавершённые миграции.

Останавливаем сервер (Ctrl + C) и выходим из окружения:

deactivate

Почему мы остановили Gunicorn

Такой процесс работает только пока открыт терминал и запущен сам процесс Gunicorn. Поэтому после проверки мы остановили его с помощью Ctrl + C.

Дальше запускать Gunicorn вручную больше не нужно. В следующем шаге мы настроим его как systemd-сервис.

После этого Gunicorn будет запускаться системой автоматически, работать в фоне и перезапускаться при сбое. Управлять им можно будет через systemctl, например:

sudo systemctl start gunicorn
sudo systemctl stop gunicorn
sudo systemctl restart gunicorn
sudo systemctl status gunicorn

Шаг 9. Gunicorn как systemd-сервис

Постоянно держать открытый SSH-терминал с запущенным Gunicorn неудобно и ненадёжно — при обрыве соединения процесс завершится. Настроим Gunicorn как системный сервис, который запускается автоматически и перезапускается при сбое.

Создаём файл сервиса:

sudo nano /etc/systemd/system/gunicorn.service

Содержимое:

[Unit]
Description=gunicorn daemon for filmsite
After=network.target

[Service]
User=root
Group=www-data
WorkingDirectory=/var/www/filmsite
ExecStart=/var/www/filmsite/venv/bin/gunicorn \
    --workers 3 \
    --bind unix:/var/www/filmsite/filmsite.sock \
    filmsite.wsgi:application
Restart=on-failure

[Install]
WantedBy=multi-user.target

Если ваш модуль настроек называется не filmsite, а иначе — замените filmsite.wsgi:application на своё значение, например core.wsgi:application.

Сохраняем файл (Ctrl+O, Enter, Ctrl+X в nano).

Запускаем сервис:

sudo systemctl enable --now gunicorn
sudo systemctl status gunicorn

Проверка

  • статус active (running);
  • в каталоге проекта появился файл filmsite.sock.

Если статус failed — смотрите подробности:

sudo journalctl -u gunicorn -n 50

Чаще всего причина — опечатка в пути WorkingDirectory или в имени модуля wsgi.


Шаг 10. Настройка Nginx

Создаём конфигурацию:

sudo nano /etc/nginx/sites-available/filmsite

Содержимое:

server {
    listen 80;
    server_name filmsite.ru www.filmsite.ru;

    location = /favicon.ico { access_log off; log_not_found off; }

    location /static/ {
        root /var/www/filmsite;
    }

    location /media/ {
        root /var/www/filmsite;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/var/www/filmsite/filmsite.sock;
    }
}

Обратите внимание: root /var/www/filmsite; в блоках /static/ и /media/ должен соответствовать реальному расположению папок static/ и media/, которые мы создали в шагах 3–4. Если у вас, например, проект лежит не прямо в /var/www/filmsite, а на уровень глубже — этот путь тоже нужно поправить, иначе Nginx будет искать файлы не там и вернёт 404 на все картинки и стили.

filmsite.ru и www.filmsite.ru нужно заменить на ваш домен.

Активируем конфигурацию:

sudo ln -s /etc/nginx/sites-available/filmsite /etc/nginx/sites-enabled

Обязательно проверяем синтаксис перед перезапуском:

sudo nginx -t

Если видите syntax is ok и test is successful — перезапускаем:

sudo systemctl restart nginx
sudo systemctl restart gunicorn

Почему так важно nginx -t перед restart/reload: если в конфиге ошибка, а вы сразу перезапустите Nginx — он может не подняться вообще, и сайт станет недоступен полностью, а не только с ошибкой на одной странице. Проверка синтаксиса — это 2 секунды, которые экономят от простоя сайта.


Шаг 11. Firewall и финальная проверка

Закрываем временный порт 8000, которым пользовались для тестов, и открываем стандартные порты для Nginx:

sudo ufw delete allow 8000
sudo ufw allow 'Nginx Full'

Открываем в браузере:

http://filmsite.ru

Проверка

  • сайт открывается по обычному домену, без порта в адресе;
  • стили и изображения (постеры, аватары) подгружаются корректно;
  • каталог фильмов, авторизация и формы работают так же, как локально.

Если не загружаются стили или медиафайлы

Проверьте права доступа:

sudo chmod 755 /var/www/filmsite/static
sudo chmod 755 /var/www/filmsite/media

Если это не помогло — сверьте путь в location /static/ конфига Nginx с реальным STATIC_ROOT из settings.py. Несовпадение путей — самая частая причина 404 на статику, а не права доступа. Подробный разбор этой и других частых ошибок — в следующем уроке.


Бонус: переменные окружения для чувствительных данных

Прежде чем переходить к HTTPS, стоит убрать пароль от базы данных (и в будущем — секретный ключ Django) из settings.py в открытом виде, особенно если репозиторий публичный на GitHub.

Устанавливаем библиотеку:

pip install python-dotenv

Создаём файл .env в корне проекта на сервере (не коммитим его в Git):

# .env
SECRET_KEY=ваш_секретный_ключ
DB_PASSWORD=strong_password_here

В settings.py:

from dotenv import load_dotenv
load_dotenv()

SECRET_KEY = os.environ.get('SECRET_KEY')

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'filmsite_db',
        'USER': 'filmsite_user',
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'HOST': 'localhost',
        'PORT': '',
    }
}

Добавьте .env в .gitignore локально, чтобы файл случайно не попал в репозиторий:

# .gitignore
.env

После изменения settings.py не забудьте перезапустить Gunicorn:

sudo systemctl restart gunicorn

Проверка результата урока

К этому моменту у вас должно быть:

  • ALLOWED_HOSTS, DATABASES, STATIC_ROOT, MEDIA_ROOT настроены под сервер;
  • миграции применены, статика собрана через collectstatic;
  • DEBUG = False;
  • Gunicorn работает как systemd-сервис (active (running));
  • Nginx настроен, отдаёт сайт по домену на порту 80, включая статику и медиафайлы;
  • чувствительные данные (пароль от БД, SECRET_KEY) вынесены в .env.

Сайт уже полностью работает по http://filmsite.ru — в следующем уроке подключим SSL-сертификат и переведём сайт на https://, а также разберём расширенный чек-лист типичных ошибок деплоя.


Предыдущий урок | Следующий урок