Перейти к содержимому

Как развернуть Django приложение с помощью Nginx, Gunicorn и DigitalOcean

Наибольшую проблему для большинства программистов, кроме написания приложения, вызывает развёртывание этого приложения, хотя со временем появляется навык делать это с минимальными затратами. Но тем, кто ещё не освоился, в этом эссе я хотел бы показать как развернуть Django приложение с помощью Nignx, Gunicorn и DigitalOcean на Ubuntu Linux 18.04.

схема работы django-приложения
https://simpleisbetterthancomplex.com/series/2017/10/16/a-complete-beginners-guide-to-django-part-7.html#configuring-gunicorn


Итак, у нас есть приложение на Django, т.е. WSGI приложение. Для того, чтобы всё заработало в первую очередь необходимы домен и vps.

Домен

С доменом всё просто: идём к любому регистратору, например, regru или namecheap. Покупаем нужный домен, процедура до боли проста, затем на время забываем про него. Все дополнительные опции отключаем, автопродление на усмотрение. Следующим шагом важно будет купить мощности у DO (DigitalOcean).

VPS

Минимальный тариф начинается с 5$/месяц. Для большинства проектов этого хватает, но если планируете популярное приложение, то можно запастись на сколько позволяет бюджет. Вообще подсчёт необходимых мощностей вопрос непростой. Одним из показателей может служить количество одновременных соединений, много сервер за 5$ не выдержит, но сможет обслуживать 20-30 соединений. Начните с малого, а затем масштабируйте, благо у DO это делается очень просто.
На тарифе за 15$ (3GB RAM, 1CPU, 60GB SSD) простая страница выдаёт в среднем 200req/s (запроса в секунду) при 20 параллельных соединениях на 22 тысячи запросов. Пытайтесь расширять до тех пор, пока вас не устроит результат. Как провести тестирование – здесь. Калькулятор, про время загрузки, ответ на stackoverflow. Тестируйте!

P.S. Не забудьте добавить SSH-ключ, чтобы можно было подключаться к VPS без пароля root.

А теперь необходимо делегировать домен к DO. Для этого у регистратора доменных имён изменяем DNS для домена и меняем их на сервера DO.

dns домена на do

В личном кабинете DO необходимо перейти к доменам и добавить необходимый, затем две записи типа A: одну @, вторую www, а в поле "will direct to" ip-адрес вашего VPS.

добавлние записей типа A

Вот и всё, теперь домен связан с сервером и можно приступать к настройке.

Настройка

Разобрались с покупкой и нагрузкой, настало время "деплоя"!

Входим под учётной записью root и начинаем вводить команды:
Напомню, что работы выполняются под Ubuntu Linux 18.04.

$ sudo apt update && sudo apt upgrade
$ sudo apt install nginx
$ sudo chown -R www-data:www-data /var/log/nginx;
$ sudo chmod -R 755 /var/log/nginx;

Мы обновили систему и установили веб-сервер nginx. Сразу поменяли владельца на пользователя www-data, чтобы nginx мог корректно работать, и прописали права для папки логирования.

Дальше, для обеспечения безопасности, включаем firewall.

$ sudo ufw app list
$ sudo ufw allow 'Nginx Full'
$ sudo ufw allow 'OpenSSH'
$ sudo ufw enable
$ sudo ufw status

Проверяем, что всё корректно работает с помощью curl. Если всё хорошо – придёт ответ 200 ОК и страница nginx.

$ curl <ip_адрес_твоего_сервера>

Опционально. Далее создадим пользователя для базы данных, настроим Postgres.

$ sudo apt install postgresql postgresql-contrib
$ sudo -u postgres psql
// или sudo -i -u postgres
$ createuser --interactive
$ createdb <название_бд>
$ logout
$ sudo adduser <имя_пользователя_который_был_создан_в_бд>

С БД, думаю, дальше вы разберётесь сами, т.к. настройки произведены, а в конфиг Django осталось лишь вбить учётные данные и ORM делает своё дело.


Устанавливаем python, pip, venv, зависимости.

$ sudo apt install -y python3-pip
$ sudo apt install build-essential libssl-dev libffi-dev python3-dev
$ sudo apt install -y python3-venv

Далее даём sudo права <имя_пользователя_который_был_создан_в_бд> (далее <пользователь>) и заходим под ним.

$ usermod -aG sudo <пользователь>
$ login <пользователь>

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

$ mkdir <название_проекта>
$ cd <название_проекта>
$ python3 -m venv venv

Далее используется git и GitHub для загрузки проекта в папку, но вы можете сделать это как вам удобно, например, через FTP (но тогда не забудьте настроить firewall).

$ git clone https://github.com/<имя_пользователя_github>/<название_проекта>.git
$ git config --global credential.helper store // чтобы не вбивать всё время пароль

Не забываем про SSL, поэтому воспользуемся certbot, который раздаёт бесплатные сертификаты от Let's Encrypt.

$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx
$ sudo certbot --nginx certonly
$ sudo chmod 755 /etc/letsencrypt/live/
$ sudo setfacl -R -m u:<пользователь>:rX /etc/letsencrypt/
$ sudo crontab -e // в crontab вводим: 17 7 * * * certbot renew --post-hook "systemctl reload nginx"

Ну вот мы и добрались до настроек Gunicorn и Nginx. Общаться у нас Nginx и Gunicorn будут по unix сокетам, а не по http, потому что так быстрее и сервер приложения (gunicorn) расположен на той же VPS.

Для развёртывания я создал shell-скрипт, который выполняет все действия по обновлению настроек Nginx, Gunicorn или сбору статических файлов. Содержимое файла deploy.sh:

git pull # вытягиваем изменения
source ../venv/bin/activate # активируем окружение python
pip install -r requirements.txt # устанавливаем зависимости
cd <папка_django_проекта> # переходим в папку проекта
python3 manage.py migrate # обновляем записи БД
python3 manage.py collectstatic # собираем статические файлы
#python3 manage.py runserver
cd .. # выходим в каталог <проект>
cd gunicorn # заходим в папку с настройками gunicorn
sudo systemctl disable gunicorn.socket # отключаем gunicorn.socket
sudo systemctl stop gunicorn # останавливаем процесс gunicorn
sudo cp gunicorn.socket /etc/systemd/system/ # обновляем файл сокета
sudo cp gunicorn.service /etc/systemd/system/ # обновляем файл сервиса
sudo systemctl start gunicorn.socket # запускаем gunicorn.socket
sudo systemctl enable gunicorn.socket # активируем gunicorn.socket
sudo systemctl daemon-reload
sudo systemctl restart gunicorn # перезапускаем gunicorn
cd .. # выходим снова в каталог <проект>
cd nginx # идём в папку с настройками nginx
sudo cp nginx-conf /etc/nginx/sites-available/nginx-conf
sudo ln -s /etc/nginx/sites-available/nginx-conf /etc/nginx/sites-enabled
sudo nginx -t # проверяем конфиг на ошибки
sudo systemctl restart nginx # перезапускаем nginx

Конфигурация nginx:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name www.<домен_сайта>;
    return 301 https://<домен_сайта>$request_uri;

    ssl on;
    ssl_certificate /etc/letsencrypt/live/<домен_сайта>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<домен_сайта>/privkey.pem;
}

server {
	listen 80;
	listen [::]:80;
	server_name <домен_сайта> www.<домен_сайта>;
	return 301 https://<домен_сайта>$request_uri;
}

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name <домен_сайта>;
    access_log  /var/log/nginx/default.access.log;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto https;

    ssl on;
    ssl_certificate /etc/letsencrypt/live/<домен_сайта>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<домен_сайта>/privkey.pem;

    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options nosniff;

    gzip on;
    gzip_types application/javascript image/* text/css;
    gunzip on;

    location /static/ {
        autoindex off;
        alias /home/путь/к/django/проекту/static/;
        expires 30d;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }

    rewrite ^/(robots.txt)/$ /$1 permanent;

    location ~ ^/(robots.txt|robots.txt/) {
        autoindex off;
        root /home/путь/к/папке/проекта;
        expires 30d;
    }

    rewrite ^/(sitemap.xml)/$ /$1 permanent;

    location ~ ^/(sitemap.xml|sitemap.xml/) {
        autoindex off;
        root /home/путь/к/папке/проекта/;
        expires 30d;
    }

    rewrite ^/(favicon.ico)/$ /$1 permanent;

    location ~ ^/(favicon.ico|favicon.ico/) {
        autoindex off;
        root /home/путь/к/django/проекту/static/images/icons/current/;
        expires 30d;
    }
}

Ко всему прочему здесь настроено перенаправление с www на non-www и с http на https.


Конфигурационные файлы Gunicorn:

gunicorn.socket

#/etc/systemd/system/gunicorn.socket
[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

gunicorn.service

# /etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=turbolaser
Group=www-data
WorkingDirectory=/home/turbolaser/turbolaser_site/turbolaser/turbolaser_project
ExecStart=/home/turbolaser/turbolaser_site/venv/bin/gunicorn \
        --access-logfile - \
        --workers 3 \
        --bind unix:/run/gunicorn.sock \
        turbolaser_project.wsgi:application

[Install]
WantedBy=multi-user.target

Количество workers выставлено согласно совету Gunicorn количество ядер CPU * 2 + 1.


Для примера, это аналогичная структура проекта turbolaser:

Структура проекта turbolaser
Как бы выглядела папка в домашнем каталоге на VPS.
Полная структура проекта turbolaser

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

292