Запуск Django сайта на nginx + Gunicorn + SSL

Предисловие

Для написания этой статьи ушло очень много сил и времени. Я натыкался на множество инструкций, как на английском, так и на русском языках, но как я понял, - они все были клонами оригинальной статьи на Digital Ocean. Спросите вы, почему я так считаю, а все потому, что все ошибки и неточности передаются с одного ресурса на другой без всяких изменений.

Подготовка

У нас есть обычный VPS c ОС Ubuntu, и мы уже написали в PyCharm или в блокноте свой сайт на Django и его осталось всего лишь опубликовать, привязать домен, установить сертификат и в путь.

Первым делом необходимо обновить список репозиториев и установить сразу же пакет nginx:

apt-get update
apt-get install nginx

Я решил хранить файлы сайта в каталоге: /var/www/. В данном случае перемещаемся в каталог cd /var/www/ и создаем новый каталог mkdir geekhero и получаем такой путь: /var/www/geekhero/

Переходим в новый каталог geekhero: cd geekhero и создаем виртуальное окружение: python3 -m venv geekhero_env

Активируем виртуальное окружение: source geekhero_env/bin/activate и устанавливаем в него Django: pip install Django и сразу же ставим pip install gunicorn

Создаем проект: django-admin startproject ghproj

Далее нужно произвести все первичные миграции; для этого прописываем: python manage.py makemigrations  , затем python manage.py migrate  .

После этого создаем административную учетную запись: python manage.py createsuperuser и следуем инструкции.

Далее уже создаем applications, но так как вы читаете данную статью, то вы уже умеете все это делать.

Заходим в Settings.py и прописываем, если отсутствует:
import os – в заголовке, остальное в самом низу текстового файла:

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

Настройка Gunicorn

Идем в каталог /etc/systemd/system/ и создаем два файла: gunicorn.service и gunicon.socket:

Содержимое файла gunicorn.service:

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=root
WorkingDirectory=/var/www/geekhero #путь до каталога с файлом manage.py
ExecStart=/var/www/geekhero/geekhero_env/bin/gunicorn --workers 5 --bind unix:/run/gunicorn.sock ghproj.wsgi:application
#путь до файла gunicorn в виртуальном окружении

[Install]
WantedBy=multi-user.target

Содержимое файла gunicorn.socket:

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Для проверки файла gunicorn.service на наличие ошибок:

systemd-analyze verify gunicorn.service

Настройка NGINX

Далее идем в каталог: /etc/nginx/sites-available/ и создаем файл geekhero (название вашего сайта) без расширения:

server {
    listen 80;
    server_name example.com;
    
    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /var/www/geekhero;           #путь до static каталога
    }
    
    location /media/ {
        root /var/www/geekhero;           #путь до media каталога
    }
    
    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

Для того, чтобы создать символическую ссылку на файл в каталоге /etc/nginx/site-enabled/ необходимо ввести следующую команду:

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

При любых изменениях оригинального файла, ярлык из sites-enabled нужно удалять и пересоздавать заново командой выше или выполнять команду: sudo systemctl restart nginx

Финальный этап

Для проверки конфигурации nginx нужно ввести команду:

sudo nginx -t

sudo nginx -tДалее запускаем службу gunicorn и создаем socket:

sudo systemctl enable gunicorn
sudo systemctl start gunicorn

Для отключения выполняем команды:

sudo systemctl disable gunicorn
sudo systemctl stop gunicorn

Также, эти обе команды пригодятся, если вы будете вносить какие-либо изменения в HTML или python файлы, чтобы обновить свой сайт, но помните, что, если вносите изменения в модели, то обязательно нужно сделать python manage.py makemigrations <app> и migrate <app> из каталога с проектом.

Для первичного запуска / полной остановки сервиса Gunicorn: 
service gunicorn start / service gunicorn stop

Чтобы посмотреть статус запущенного сервиса нужно ввести:

sudo systemctl status gunicorn
или
sudo journalctl -u gunicorn.socket
(с последней командой правильный вывод такой: мар 05 16:40:19 byfe systemd[1]: Listening on gunicorn socket. )

Для проверки создания сокета, необходимо ввести команду:

file /run/gunicorn.sock

Такой вывод считается правильным: /run/gunicorn.sock: socket

Если внес какие-либо изменения в файл gunicorn.service или .socket, необходимо выполнить команду:

systemctl daemon-reload

Если все нормально сработало, то можем запустить nginx:

sudo service nginx start

Получаем сертификат SSL для домена

Установим certbot от Let's Encrypt: sudo apt-get install certbot python-certbot-nginx

Произведем первичную настройку certbot: sudo certbot certonly --nginx

И наконец-то автоматически поправим конфигурацию nginx: sudo certbot install --nginx

Осталось только перезапустить сервис nginx: sudo systemctl restart nginx

Итог

В рамках этой статьи мы разобрали как вывести наш сайт в production, установив Django, Gunicorn, nginx и даже certbot от Let's Encrypt.

Средняя зарплата в IT

120 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 6 212 анкет, за 1-ое пол. 2021 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 40

    +1

    Только один вопрос — почему без контейнеров?

      +4
      Контейнеры не нужны.
        +1
        Это шутка такая, да?

        Когда у вас VPS даже с парой-тройкой сервисов, изоляция решает. Сегодня у вас джанга с SQLite, завтра сервис с MariaDB, а послезавтра — условный телеграм бот на PHP с Postgre. И что, все это на хост будете ставить? А когда обновите версию джанги в одном из проектов, руками пересоздавать venv на хосте будете? Или будете писать чудесный деплой-скрипт, который ходит по файловой системе, обновляет репо, создает виртуальные окружения, пересоздает все что можно? *картинка с троллейбусом*

        Тот же certbot — в чем смысл ставить лишний софт на хост, который вам нужен раз в 3 месяца, запускается в одну строчку из контейнера и абсолютно независим от всего остального, что вы там наставили?
          +1

          Ну да, я делаю всё именно так. В качестве "скрипта" использую Ansible-плейбук, в нём уже есть модуль для управления venv и прочие радости жизни. В чём проблема?

            0
            Да нет, если вам норм, кто ж вам запретит, нет никакой проблемы. Но мне кажется, что условные git pull && docker-compose up -d --build достигают той же цели, но куда меньшими затратами и намного проще и надежнее. Ну и docker/docker-compose нынче куда более распространены, нежели Ansible.
              0

              А затраты на написание докерфайлов и организацию взаимодействия между контейнерами вы решили тактично умолчать :) Серьёзно, я как-то пробовал автоматизировать развёртывание одного реального PHP-проекта (хоть я и питонист, ну да ладно) — Ansible-плейбук написался без проблем, а довести докер-контейнеры до пригодного для продашкена состояния у меня так и не получилось

                0
                Я говорю о проекте уровня описанного в статье — простой веб-сервис с условным nginx+uwsgi/gunicorn/php+mysql/mariadb/postgre. Бложик или пет-прожект на джанге или обычный монолит на PHP. Какая еще «организация взаимодействия»? environment: {DB_HOST: db} и depends_on: db для зависимых контейнеров? Вот и все «взаимодействие». Ну да, простите великодушно, «тактично умолчал», потому что это три строчки, которые после первых пары раз пишутся на автомате и уже не думаешь даже. Секундное дело. Но зато после этого вы получаете docker-compose файл, который кто угодно может на своей машине запустить, имея из зависимостей лишь, собственно, докер и компоуз. Один клик — и весь сервис поднят. Да, естественно, если вам захочется больше плюшек, health-чеков, определенного порядка запуска контейнеров, дальше будет сложнее, но для описанного в посте уровня простенького проекта ничего этого не нужно.

                Не говоря уж про то, что написание Dockerfile это как минимум «правило хорошего тона», по которому можно понять, какие у вашего проекта зависимости. Если мы говорим про питон, то каждая вторая нетривильная зависимость из requirements.txt будет требовать какой-нибудь нативной библиотеки, про которую, естественно, автор не упомянет в ридми. А если вы напишете в докерфайле «pip install -r requirements.txt», то это банальный тест — если оно билдится, значит побилдится и у всех остальных. Не раз и не два такое было, что на хосте вроде работает, а пишешь в докерфайл — опа, оказывается, еще десяток нативных зависимостей надо доставлять (которые на хосте были поставлены неизвестно когда и уж все забыли про них).

                Кэп подсказывает, что Ansible playbook у вас написался без проблем, потому что у вас есть опыт в написании плэйбуков. А у меня наоборот — я подобные простые 2-3 сервиса быстро и без проблем оберну в Dockerfile+docker-compose, а вот если надо будет Ansible юзать, то я уйду гуглить на неопределенное время.
                  +1
                  Я говорю о проекте уровня описанного в статье

                  Окей, начнём с банального — раздача статических файлов. В какой контейнер их класть? В каком контейнере запустить "manage.py collectstatic" и как при этом не наплодить стопицот лишних докер-слоёв?


                  Чуть менее банальное — почти любой Django-проект однажды дорастёт до необходимости использовать Celery, где и как запускать его воркеры? Если придётся запускать несколько контейнеров — как им всем обеспечить беспроблемный доступ на запись в media-каталог — возможно, делать chmod 777 и umask 000, или есть что-то более умное?


                  Ну ладно, если запустить два контейнера из одного образа, у них будут одинаковые uid и проблем с доступом возможно не будет. А что если однажды придётся собрать ещё один образ (из моей личной практики — syncthing, например) и дать ему доступ на запись к тому же общему media-каталогу — как это организовать, чтобы было достаточно безопасно и чтобы при этом не было проблем?


                  про которую, естественно, автор не упомянет в ридми.

                  Ага, а контейнеры тут при чём? Пусть автор просто прогонит установку на чистой системе (да хоть в том же контейнере, лол) и в ридми допишет зависимости, да и всё. В плейбуке, кстати, зависимости тоже прописаны.


                  написание Dockerfile это как минимум «правило хорошего тона», по которому можно понять, какие у вашего проекта зависимости.

                  То же самое можно сделать через CI/CD — в скрипт установки для условного Travis CI или Github Actions тоже придётся прописать все зависимости.


                  потому что у вас есть опыт в написании плэйбуков.

                  Так изначально его не было, у меня знания тоже не из воздуха появились.


                  а вот если надо будет Ansible юзать, то я уйду гуглить на неопределенное время.

                  А я до сих пор продолжаю гуглить инфу про докер, но так и не могу собрать что-то пригодное для продакшена ¯\_(ツ)_/¯

                    0
                    Окей, начнём с банального — раздача статических файлов. В какой контейнер их класть? В каком контейнере запустить «manage.py collectstatic» и как при этом не наплодить стопицот лишних докер-слоёв?


                    Вы намекаете, что мне тоже статью стоит написать? Честно говоря, я понимаю, потому что если гуглить «django docker», то во всех этих хипстерских seo-friendly бложиках ни один чел не идет дальше хелловорлда и тему статических файлов не затрагивает. Я сам довольно долго гуглил и спрашивал в чатиках насчет «правильного» способа.

                    На сегодняшний день я использую один из следующих вариантов:
                    — Shared volume лишь между контейнерами (но не с хостом, см. SO). Тогда collectstatic пишется в контейнере с джангой и nginx контейнер просто видит папку со статиками
                    — Если же volume не вариант (например, в Docker Swarm), то можно сделать multistage билд для nginx контейнера. Ну т.е. что-то подобное:
                    FROM python:3.9 as builder
                    RUN pip install Django
                    COPY . /app
                    WORKDIR /app
                    RUN manage.py collectstatic --noinput

                    FROM nginx
                    COPY config/nginx.conf /etc/nginx/config
                    COPY --from=builder /static /static

                    — Ну и «плохой» вариант, но в общем-то вполне рабочий для небольших проектиков без сотен реквестов в секунду — используйте manage.py runserver. Тогда вам даже второй контейнер с nginx не нужен. Есть несколько библиотек типа whitenoise, которые в теории несколько улучшают ситуацию с «runserver не для продакшна».

                    Чуть менее банальное — почти любой Django-проект однажды дорастёт до необходимости использовать Celery, где и как запускать его воркеры?

                    Не могу ответить, поскольку ни в одном из проектов Celery не использовал, не было нужды. Но есть подозрение, что это может выглядеть как один контейнер, в котором воркеры сами скейлятся как надо (собственно, как uwsgi для того же джанго сам делает).

                    Ага, а контейнеры тут при чём? Пусть автор просто прогонит установку на чистой системе (да хоть в том же контейнере, лол) и в ридми допишет зависимости, да и всё. В плейбуке, кстати, зависимости тоже прописаны.


                    Понятно, что хороший автор опишет все, но Dockerfile (как и ваш плейбук, видимо) — это не просто текст, это реальные инструкции, которые должны работать, чтобы проект поднялся. И само средство вынуждает автора описать environment. Лишь один коммент — в каком проценте open source проектов вы видели плейбук (или, например, Vagrantfile?) в репозитории? Сравните с процентом репозиториев, имеющих Dockerfile.

                    Так изначально его не было, у меня знания тоже не из воздуха появились.

                    А я до сих пор продолжаю гуглить инфу про докер, но так и не могу собрать что-то пригодное для продакшена ¯\_(ツ)_/¯


                    Ну видите, мы в одинаковой ситуации, только с разными тулами :) Только в самом начале я пошел гуглить про довер и теперь знаю о докере и применяю его, а вы пошли гуглить про Ansible и применяете его. 1-1 :)
            0
            Нет, это не шутка, я просто недостаточно развил свою мысль. Она действительно не для всех очевидна, так что исправляюсь.

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

            Но сегодня стало очевидно, что этот универсальный механизм себя не оправдал, не реализовал обещаний. Для разработчиков затраты на поддержание той же системы контейнеров, что и на серверах, оказались неподъёмны: сложно, требует изучения и поддержки, нужно мощное железо, намного усложнились задачи, которые раньше были простыми, типа отладки. Девопсы так и не смогли обойтись без изучения целевых платформ, просто вместо прямого подхода (прочитать немного про CPython, pip и virtualenv) им приходится начинать погружение в чудесный мир Python с отлаживания необъяснимых багов (почему cryptography не собирается в контейнере с Alpine).

            В результате поддержка простых и понятных деплой-скриптов на Fabric оказывается намного практичней, чем монструозная система изоляции всего и вся. Особенно если основа продакшена − Python, который уже имел специфические, весьма развитые и взрослые средства изоляции с отличным уровнем переносимости, когда докера ещё не было в проекте.
          +1
          Контейнеры — ладно, но хоть virtualenv-то надо, там же не один проект будет крутиться наверняка.
            0
            Вы правы, virtualenv и pyenv − обязательно. Даже если проект будет один, зависимости надо фиксировать.
          +1

          Прошу прощения — Вы правда считаете что установкой certbot начинается и заканчивается настройка ssl в nginx ?

            0
            Здравствуйте, в данной статье я рассмотрел полный порядок от начала и до конца, как быстро и просто, в том числе заполучить SSL-сертификат силами let's encrypt — файлы сертификаты автоматически создаются для домена и последующей командой в файл конфигурации nginx вносятся изменения для работы домена с https:

                listen 443 ssl; # managed by Certbot
                ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
                ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
                include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
                ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
            
            }server {
                if ($host = example.com) {
                    return 301 https://$host$request_uri;
                } # managed by Certbot
            
            
                listen 80;
                server_name example.com;
                return 404; # managed by Certbot
              0
              Наверно, был намёк на то, что certbot может автоматически обновлять сертификат.
                0

                Не нужно использовать if для ретрая на https. Отдельный блок с 80 портом.

              +3
              Плохо в смысле безопасности.

              Сейчас в systemd есть пользовательские юниты. Использовать их очень просто: нужно вместо /etc/systemd/system использовать ~/.config/systemd/user, а к вызову systemctl добавлять ключ --user.

              Когда пользовательских юнитов не было, принято было подменять пользователя для запуска gunicorn/uwsgi/et c. вот таким образом:

              [Service]
              User=www
              Group=www


              Пускать Django из-под рута − это варварство.
                0
                Все понял, буду пробовать и так делать.
                +1
                Слабовато. Не безопасно. Устаревше. Пора к ASGI привыкать.
                  0
                  я конечно не спец в 3й джанге (давно сменил стек), но небольшой эксперимент все же проводил. и производительность джанго под asgi у меня проседала на 30% относительно wsgi варианта. а где обещанные бонусы ?))
                    0
                    На ASGI придётся переходить, если понадобится вебсокет.
                      0
                      мой опыт показывает, что вебсокеты делаются на чем угодно aio* за 5 минут без джанги. да и зачем джанго для пуш уведомлений? или вы апи фигачите через сокеты…?
                        0
                        1. «Что угодно aio*» − это плюс один фреймворк, то есть для собственного резюме − здорово, а для предприятия − не то чтобы.
                        2. Лично я, скажу честно, не горю желанием перейти на асинхронное программирование, и не встречал ещё таких питонистов, которые бы мечтали об этом. Я видел, как самые простые вещи с async/await превращаются в инкубатор неотлаживаемых багов, и не хочу быть крайним в разгребании этих авгиевых конюшень.
                        3. Что плохого в API через вебсокеты?
                          0
                          Я видел, как самые простые вещи с async/await превращаются в инкубатор неотлаживаемых багов

                          Вот все вокруг почему-то об этом говорят, а я сколько ни программировал асинхронщину — почему-то ни разу не сталкивался с какими-то специфическими проблемами

                            0
                            А что вы делали с неспецифическими проблемами, когда отладчики в 3.4 и 3.5 ещё не умели останавливаться в соседнем треде?
                              –1

                              А я отладчиками не пользуюсь) Как-то ни разу не возникало необходимости

                            0
                            не горю желанием перейти на асинхронное программирование, и не встречал ещё таких питонистов, которые бы мечтали об этом

                            Так я первый буду, который мечтает. Хотя, верится с трудом.
                        0
                        я конечно не спец в 3й джанге (давно сменил стек), но небольшой эксперимент все же проводил. и производительность джанго под asgi у меня проседала на 30% относительно wsgi варианта. а где обещанные бонусы ?))


                        А вы уверены, что ваши тесты верны и в них использовалась реальная работа веб приложения, а не простое измерение пропускной способности? Бонусы от асинхронности вы получите в любом современном веб приложении, за исключением простых лендингов и отдельных статичных страницах или даже динамичных но простых. Нужно понимать где применять асинхронность, а где нет. Я уже не буду говорить что при длительном синхронном запросе вы его можете просто потерять и при высокой нагрузке на сервер он у вас просто упадет при синхронных запросах. Плюс к этому, чем вам не бонус еще и сокеты. При этом ASGI обратно совместим с синхронными запросами.
                      –2
                      я что-то не понял. а что вам дает gunicorn в systemd с таким конфигом, кроме автостарта при рестарте виртуалки? supervizord не осилили ?)
                        +3

                        А зачем ставить лишнюю сущность, если systemd и так есть по умолчанию почти в любом линуксе? А ещё supervisord всё равно будет запускаться через тот же самый systemd)

                          –3
                          вы все верно написали, но не по делу.
                          супервизор решает одну оч простую задачу — рестарт приложения при краше. именно про это и был мой вопрос тк конфиг автора для системд этого не делает. и если приложение ляжет — само не поднимется
                            +1
                            А systemd тоже умеет в рестарт при краше.
                            Достаточно добавить что-то вроде
                            StartLimitIntervalSec=500
                            StartLimitBurst=5

                            [Service]
                            Restart=on-failure
                            RestartSec=5s
                              –1
                              а кто говорит, что он этого не умеет? я написал, что конфиг автора этого не делает
                                +2

                                Не делает.
                                Неясно, правда, зачем нужно ставить аж целый supervisord, если можно добавить одну строчку в конфиг systemd.

                        +1

                        Символическую ссылку не нужно удалять при изменениях оригинального файла. Текстовым редактором можно открывать сразу ссылку, редактироваться будет оригинальный файл. Ведь на то она и ссылка.
                        https://ru.m.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B0

                          0
                          Ну и restart делать тоже необязательно. Как правило, достаточно reload.
                          0
                          Так и это по сути перепечатка статьи с DigitalOcean + немного вредных советов, по типу:
                          [Service]
                          User=root

                          Ну или я что-то не понял.
                            0
                            Как я указал в Предисловии, инструкция с Digital Ocean содержит множество ошибок и является недосказанной, потому что, если следовать ей, то у вас ничего не получится, если вы делаете это в первый раз, как я (возможно профессионал на ту статью посмотрит иначе — просто как на подсказку к действию, но не будет читать каждое ее предложение, чтобы получить результат).
                            Я написал данную статью для новичков, для тех, кто впервые сталкивается с подобной задачей, когда они осилили написать django-проект и умеют только в runserver. Данная статья описывают процесс от самого начала и до конца, чтобы сайт можно было вывести в прод. Также, я собирал информацию со множества источников, форумов и даже личных переписок со специалистами в соц. сетях.
                            0
                            apt get update
                            apt get-install nginx

                            Исправьте, пожалуйста:
                            apt-get update или apt update
                            apt-get install nginx или apt install nginx
                              0
                              Поправил, большое спасибо за внимательность!

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

                            Самое читаемое