Deploy Django проекта на VPS

Технологии:

VPS на базе Ubuntu

Nginx - веб-сервер и почтовый прокси-сервер, работающий на Unix-подобных операционных системах.

Gunicorn (‘Green Unicorn’) - WSGI сервер для UNIX написанный на Python. У него нет сторонних зависимостей, легко установить и использовать. это HTTP-сервер интерфейса шлюза веб-сервера Python. 

virtualenv - виртуальня среда, изоляция проектов.

Supervisor — это система клиент/сервер, при помощи которой пользователь (администратор) может контролировать подключенные процессы в системах типа UNIX. Позволяет запускать автоматически и контролировать фоновые процессы.

PostgreSQL - свободная объектно-реляционная система управления базами данных.

Процесс настройки:

Сначала необходимо закрепить за сервером доменное имя. Обычно делается в панели управления dns и доменными именами, вашего сервера.

Авторизуемся под root пользователем через ssh.

Обновление системы
    $ sudo apt-get install update
    $ sudo apt-get install upgrade

Установка PostgreSQL
   $ sudo apt-get install postgresql postgresql-contrib

Создаем пользователя для нашей базы, и саму базу.
    $ sudo su — postgres
    postgres@django: ~$ createuser —interactive -P

Далее вводим имя для нашего юзера, пароль, и на все остальные вопросы отвечаем нет.

Создаем базу данных и указываем нашего юзера её владельцем
    postgres@django: ~$ createdb —owner user_name database_name
    postgres@django:~$ logout

Ремарка
Если возникает в будущем проблема при миграциях, что ваш проект настроен на работу с базой данной в utf-8, а у вас postgresql база данных создалась в кодировке LATIN1, то делаем следующее:

Проверяем наши locale, какая кодировка должна быть:
    $ locale

Если там отсутствует en_US.UTF-8, то обновляем локали:
    $ sudo update-locale LANG=en_US.UTF-8
Перезапускаем панель, и заново проверяем $locale, должно быть всё ОК!

Запустить панель psql:
     $ sudo -u postgres psql
Проверяем список баз, и кодировки этих баз:
    \l

Если у нас база в кодировке, которая не подходит к нам, то делаем следующее:
    update pg_database set encoding = pg_char_to_encoding('UTF8') where datname = 'our_database_name'
Проверяем снова список баз, командой \l, должна кодировка измениться.

Создание пользователя приложения

Наше web-приложение должно запускаться через системного пользователя с ограниченными правами. То есть у него должен быть доступ, только на это приложение, во избежание того, чтобы не нанести вред безопасности сервера.

Создаем пользователя для нашей базы данных user_name и присваиваем его системной группе под названием webapps

    $ sudo groupadd —system webapps

    $ sudo useradd —system —gid webapps —shell /bin/bash —home /webapps/our_project_name user_name

Здесь мы создали пользователя user_name и привязали ему домашней папкой webapps/our_project_name

Установка virtualenv и создание окружения для приложения

Устанавливаем virtualenv
    $ sudo apt-get install python-virtualenv


Создание директории под наш проект:
    $ sudo mkdir -p webapps/our_project_name/

Даем права нашему пользователю, для проведения операций с этой папкой:
    $sudo chown user_name webapps/our_project_name/

Но лучше сделать права и на внутренние папки:
    $sudo chown -R user_name webapps/our_project_name/
    $sudo chmod -R g+w /webapps/our_project_name/

Авторизуемся как созданный нами новый пользователь:
    $ sudo su — user_name


Переходим в папку нашего проекта и создаем в нем venv
    username@django: ~$ cd webapps/our_project_name

    username@django: ~$ virtualenv .

Ремарка:

По умолчанию создается виртуальное окружение с версией python, которая у нас глобально установлена при вызове комманды python. Чтобы быть увереным, что установится именно та версия, python3, мы добавляем параметры следующим образом при создании окружения: virtualenv . -p python3

Активируем наше окружение:
    username@django: ~$ source bin/activate

И теперь устанавливаем внутрь django (если мы хотим чистый проект, или инициализируем git внутри, и копируем проект с github. (смотри ремарку)):


    (username@django)username@django:~$ pip install django

Далее создаем новый проект django: 
    (user_name)user_name@django:~$ django-admin.py startproject our_project_name

Ремарка:

Чтобы потянуть файлы с git в каталог на сервере. Нужно:

1) Инициализировать в папке пустой репозиторий git init .

2) git remote add origin ссылка на репозиторий — устанавливаете соединение.

3) git pull origin master (загружаете все файлы)

Либо мы можем закачать проект, и установить всё из requirements.txt. Для этого используем: 
    pip install requirements.txt

Далее мы    можем проверить наше django приложение, если оно было пустое изначально, то можем запустить:

    python manage.py runserver example.com:8000

Устанавливаем прокладки для работы с Postgres

Сначала нужно установить зависимости, без которых эта прокладка не будет работать. Устанавливаем глобально:
    $sudo aptitude install libpq-dev python-dev

Далее внутри нашего venv устанавливаем адаптер (прокладку) для postgres
    (user_name)user_name@django:~$ pip install psycopg2

Ремарка:

Скорее всего понадобиться binary версия.

pip install psycopg2-binary

Делаем настройки django проекта

settings.py
DATABASES — делаем настройку с postgresql
ALLOWED_HOSTS = [домен или ip адрес, и обязательно localhost]
DEBUG = False

В settings.py удобно добавлять для продакшена следующее (создаём файл settings_prod.py):

Если есть файл settings_prod то он перезаписывает настройки текущего settings.py, если нет. То остается таким же.

try:
    from .settings_prod import *
except:
    pass

Делаем migrate, если это перенесенный проект, то makemigrations, затем migrate, и collectstatic

Gunicorn

Устанавливаем gunicorn в наше виртуальное окружение:
    (user_name)user_name@django:~$ pip install gunicorn

Далее мы можем проверить, запускается ли сайт через gunicorn следующей командой:
    (user_name)user_name@django:~$ gunicorn our_project_dir_name.wsgi:application --bind example.com:8001

Проверяем, открывается ли сайт по этому адресу: example.com:8001.

Далее делаем bash скрипт настроек для gunicorn, в папке bin нашей виртуальной среды, создаем файл gunicorn_start (возможно нужно будет выйти из venv, не помню точно):

    (user_name)user_name@django:~$ nano bin/gunicorn_start

Вписываем в него, следующие настройки:

#!/bin/bash

NAME="our_project_app"     # Name of the application
DJANGODIR=/webapps/our_project_name/our_project_name  # Django project directory
SOCKFILE=/webapps/our_project_name/run/gunicorn.sock  # we will communicte using this unix socket
USER=user_name             # the user to run as
GROUP=webapps              # the group to run as
NUM_WORKERS=3              # how many worker processes should Gunicorn spawn
DJANGO_SETTINGS_MODULE=our_project_name.settings  # which settings file should Django use
DJANGO_WSGI_MODULE=our_project_name.wsgi          # WSGI module name

echo "Starting $NAME as `whoami`"

# Activate the virtual environment
cd $DJANGODIR
source ../bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

# Create the run directory if it doesn't exist
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR

# Start your Django Unicorn
# Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon)
exec ../bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $NUM_WORKERS \
  --user=$USER --group=$GROUP \
  --bind=unix:$SOCKFILE \
  --log-level=debug \
  --log-file=-

Создаем исполняемый файл для этого скрипта:
     $ sudo chmod u+x bin/gunicorn_start

Теперь мы можем проверить, работает ли наш скрипт. Запустим его из под нашего юзера user_name:
    $ sudo su — user_name
    user_name@django: ~$ bin/gunicorn_start

Если всё ОК! Пишем $exit и выходим.

Запуск gunicorn и мониторинг через Supervisor

Мы должны убедиться, что скрипт gunicorn_start должен запускать автоматически, вне зависимости перезагрузиться ли сервер или по другой причине. Должна быть поставлена задача через сервис supervisord. Устанавливаем supervisor из под root:

    $sudo apt-get install supervisor

Когда supervisor установлен, мы можем задать программы для запуска и мониторинга, через создание конфигурационных файлов в директории /etc/supervisor/conf.d. Для нашего приложения, мы создадим файл под названием:

    $ sudo nano /etc/supervisor/conf.d/our_project_name.conf

И запишем в него следующее:

[program:our_project_name]
command = /webapps/project_name/bin/gunicorn_start    ; Command to start app
user = user_name                                      ; User to run as
stdout_logfile = /webapps/project_name/logs/gunicorn_supervisor.log   ; Where to write log messages
redirect_stderr = true           ; Save stderr in the same log
environment=LANG=en_US.UTF-8,LC_ALL=en_US.UTF-8   ; Set UTF-8 as default encoding

Ремарка:
    Мы можем задать в этом файле больше опций, но базовый файл выглядит так,     как     мы написали. Больше опций по ссылке: http://supervisord.org/configuration.html

Теперь мы создадим файлы для хранения ошибок и логов:
    user_name@django:~$ mkdir -p /webapps/our_project_name/logs/
    user_name@django:~$ touch /webapps/our_project_name/logs/gunicorn_supervisor.log
 
После того, как создали конфигурационный файл для нашего приложения, мы можем попросить supervisor перечитать (обновить) конфигурационные файлы, и запустить наше зарегистрированное приложение:
    $ sudo supervisorctl reread
    $ sudo supervisorctl update

Следующими коммандами, мы можем протестировать работоспособность, запущено ли наше приложение или нет (не обязательно):
    $ sudo supervisorctl status project_name                       
    $ sudo supervisorctl stop project_name  
    $ sudo supervisorctl start project_name                        
    $ sudo supervisorctl restart project_name 

Nginx

Теперь пришло время для настройки Nginx в качестве сервера для нашего приложения и его static файлов. Устанавливаем и запускаем:
    $ sudo aptitude install nginx
    $ sudo service nginx start

Если мы перейдем по нашему домену в данный момент, то увидим nginx стартовую страницу.

Создание Nginx виртуального сервера конфигураций для Django

Каждый виртуальный сервер Nginx должен быть описан файлом, который расположен в папке /etc/nginx/sites-available. Мы выбираем которые сайты мы хотим включить сделав символические ссылки на них в директории /etc/nginx/sites-enabled.

Создадим конфигурационный файл сервера nginx для нашего Django приложения:

    $ nano /etc/nginx/sites-available/hello

И вписываем в него следующее:

upstream our_project_name {
  # fail_timeout=0 means we always retry an upstream even if it failed
  # to return a good HTTP response (in case the Unicorn master nukes a
  # single worker for timing out).

  server unix:/webapps/our_project_name/run/gunicorn.sock fail_timeout=0;
}

server {

    listen   80;
    server_name example.com;

    client_max_body_size 4G;

    access_log /webapps/our_project_name/logs/nginx-access.log;
    error_log /webapps/our_project_name/logs/nginx-error.log;
 
    location /static/ {
        alias   /webapps/our_project_name/static/;
    }
    
    location /media/ {
        alias   /webapps/our_project_name/media/;
    }

    location / {
        # an HTTP header important enough to have its own Wikipedia entry:
        #   http://en.wikipedia.org/wiki/X-Forwarded-For
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # enable this if and only if you use HTTPS, this helps Rack
        # set the proper protocol for doing redirects:
        # proxy_set_header X-Forwarded-Proto https;

        # pass the Host: header from the client right along so redirects
        # can be set properly within the Rack application
        proxy_set_header Host $http_host;

        # we don't want nginx trying to do something clever with
        # redirects, we set the Host: header above already.
        proxy_redirect off;

        # set "proxy_buffering off" *only* for Rainbows! when doing
        # Comet/long-poll stuff.  It's also safe to set if you're
        # using only serving fast clients with Unicorn + nginx.
        # Otherwise you _want_ nginx to buffer responses to slow
        # clients, really.
        # proxy_buffering off;

        # Try to serve static files from nginx, no point in making an
        # *application* server like Unicorn/Rainbows! serve static files.
        if (!-f $request_filename) {
            proxy_pass http://our_project_name; #берем с первой строки этого файла
            break;
        }
    }

    # Error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /webapps/our_project_name/static/;
    }
}

Далее создаем символическую ссылку в папку sites-enabled, чтобы включить:
    $ sudo ln -s /etc/nginx/sites-available/ourt_project_name /etc/nginx/sites-enabled/ourt_project_name

Перезагружаем Nginx:
    $ sudo service nginx restart 

Ремарка:
    Если мы перешли, и все равно видим стартовую страницу Nginx. Это может     быть     вызвано, что остался та называемый default конфигурационный файл,     который     устанавливается Nginx и маскирует ваш новый конфигурационный     файл сайта.     Если мы не планируем его использовать, то удаляем     символическую ссылку на     этот файл с именем default из /etc/nginx/sites-enabled.

Если мы перейдем на наш сайт, то теперь мы увидим, что всё работает! ГОТОВО!

Финальная структура нашего проекта должна быть следующей:

/webapps/our_project_name/
├── bin                          <= Directory created by virtualenv
│   ├── activate                 <= Environment activation script
│   ├── django-admin.py
│   ├── gunicorn
│   ├── gunicorn_django
│   ├── gunicorn_start           <= Script to start application with Gunicorn
│   └── python
├── our_project_name             <= Django project directory, add this to PYTHONPATH
│   ├── manage.py
│   ├── project_application_1
│   ├── project_application_2
│   └── our_project_name         <= Project settings directory
│       ├── __init__.py
│       ├── settings.py          <= hello.settings - settings module Gunicorn will use
│       ├── urls.py
│       └── wsgi.py              <= hello.wsgi - WSGI module Gunicorn will use
├── include
│   └── python2.7 -> /usr/include/python2.7
├── lib
│   └── python2.7
├── lib64 -> /webapps/our_project_name/lib
├── logs                         <= Application logs directory
│   ├── gunicorn_supervisor.log
│   ├── nginx-access.log
│   └── nginx-error.log
├── media                        <= User uploaded files folder
├── run
│   └── gunicorn.sock 
└── static                       <= Collect and serve static files from here

Установка LET“S ENCRYPT SSLУстановка LET“S ENCRYPT SSL

Устанавливаем Let“s Encrypt

    sudo add-apt-repository ppa:certbot/certbot
    sudo apt-get update
    sudo apt-get install --upgrade letsencrypt

Ремарка:
Если выдает ошибку, что комманды add-apt-repository не существует «add-    apt-    repository: command not found error». То, устанавливаем следующее: 
    sudo apt-get install software-properties-common
    sudo apt-get update

Создаем файл конфигурации:
    sudo nano /etc/letsencrypt/cli.ini
Записываем в него:

	authenticator = webroot
	webroot-path = /var/www/html
	post-hook = service nginx reload
	text = True

Регистрируемся в letsencrypt
    sudo certbot register --email my@email.com

Пробуем получить сертификат в режиме тестов:
    sudo certbot certonly --dry-run -d my_site_name.ru -d www.my_site_name.ru

Если видим The dry run was successful, то получаем уже настоящий:
    sudo certbot certonly -d my_site_name.ru -d www.my_site_name.ru

После получения, редактируем файл nginx нашего проекта:
    sudo nano /etc/nginx/sites-available/our_project_name_file

Далее редактируем:
    sudo nano etc/nginx/sites-available/my_project_name_file 

Вставляем туда:

server {
    server_name my_site_name.ru www.my_site_name.ru default_server;
    listen 80;

    return 301 https://my_site_name.ru;
}

server {
    server_name www.my_site_name.ru;
    listen 443 ssl http2;

    ssl_certificate /etc/letsencrypt/live/my_site_name.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/my_site_name.ru/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/my_site_name.ru/chain.pem;

    ssl_stapling on;
    ssl_stapling_verify on;

    add_header Strict-Transport-Security "max-age=31536000";

    return 301 https://my_site_name.ru$request_uri;
}

server {

    server_name example.com;
    listen 443 ssl http2;

    ssl_certificate /etc/letsencrypt/live/my_site_name.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/my_site_name.ru/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/my_site_name.ru/chain.pem;

    ssl_stapling on;
    ssl_stapling_verify on;
	
    client_max_body_size 4G;

    access_log /webapps/our_project_name/logs/nginx-access.log;
    error_log /webapps/our_project_name/logs/nginx-error.log;
 
    location /.well-known {
        root /var/www/html; # Понадобится для letsencrypt
    }
	
    location /static/ {
        alias   /webapps/our_project_name/static/;
    }
    
    location /media/ {
        alias   /webapps/our_project_name/media/;
    }

    location / {
        # an HTTP header important enough to have its own Wikipedia entry:
        #   http://en.wikipedia.org/wiki/X-Forwarded-For
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # enable this if and only if you use HTTPS, this helps Rack
        # set the proper protocol for doing redirects:
        # proxy_set_header X-Forwarded-Proto https;

        # pass the Host: header from the client right along so redirects
        # can be set properly within the Rack application
        proxy_set_header Host $http_host;

        # we don't want nginx trying to do something clever with
        # redirects, we set the Host: header above already.
        proxy_redirect off;

        # set "proxy_buffering off" *only* for Rainbows! when doing
        # Comet/long-poll stuff.  It's also safe to set if you're
        # using only serving fast clients with Unicorn + nginx.
        # Otherwise you _want_ nginx to buffer responses to slow
        # clients, really.
        # proxy_buffering off;

        # Try to serve static files from nginx, no point in making an
        # *application* server like Unicorn/Rainbows! serve static files.
        if (!-f $request_filename) {
            proxy_pass http://our_project_name; #берем с первой строки этого файла
            break;
        }
    }

    # Error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /webapps/our_project_name/static/;
    }
} 

Перезапускаем nginx
    sudo systemctl restart nginx

Атопродление сертификатов через cron

    sudo crontab -e
    45 */12 * * * certbot renew --quiet --allow-subset-of-names

 

Часть информации было позаимствовано из данных статей: 

  • http://michal.karzynski.pl/blog/2013/06/09/django-nginx-gunicorn-virtualenv-supervisor/
  • https://pythonworld.ru/web/django-ubuntu1604.html