Сообщить об ошибке.

Разворачиваем Nginx + Gunicorn + Gevent + Flask на VDS

Данное руководство HOWTO было проверено при создании реального сервера. Все инструкции и файлы конфигурации рабочие. Ошибки могут быть только в путях, если пользователь будет менять оригинальные пути на свои (например, где-то создал/вставил не ту папку, где-то забыл что-то поменять и т.д.). Будьте внимательны!

Требования к приложению Flask.

Материал будет похож на HOWTO с примерами конфигов. Предварительные требования для создание связки Flask + Nginx + Gunicorn + Gevent - это работающее приложение Flask, построенное в виде пакета на локальном компьютере, сервер VDS с установленной OS Ubuntu или Debian с доступом по ssh от пользователя root (или пользователя, имеющего привилегии sudo). Для тестирования подойдет виртуальная машина развернутая на QEMU-KVM или virtualbox.

Немного статистики. Крупный популярный блог, исходный код которого правильно написан на Flask при работе в вышеуказанной связке и при параметрах VDS: 1 CPU, 1 Гб RAM и HDD на NVMe без особой напруги может выдержать до 25 тыс. уникальных посетителей в сутки (при интенсивности кликов до 45 тыс. сутки) + к этому суточная обработка контента сайта поисковыми роботами около 10 - 15 тыс. заходов в сутки. При этом загрузка сервера не будет превышать 65 - 70% (и все это без использования приложением кэша, дополнительной оптимизации Nginx и использовании БД MariaDB на настройках по умолчанию). Дополнительно можно посмотреть последний раздел материала "Асинхронность в веб-приложении на Flask в Python.".

Допустим, что локальное приложение Flask имеет следующую структуру и лежит в каталоге flask-app:

/flask-app (папка с проектом)
    ... (вспомогательные файлы и папки проекта)
    /www (само приложение)
        __init__.py 
        /static
        /views
        /templates 
        ...

Подготовка к настройке связки Flask+Nginx+Gunicorn+Gevent.

Подключаемся к VDS по ssh и сначала обновим локальный индекс пакетов и установленную ОС, затем установим пакеты, которые позволят создать виртуальную среду Python. Так же установим несколько пакетов и средств разработки, необходимые для создания некоторых компонентов gunicorn. Некоторые пакеты могут быть уже установлены, это зависит от провайдера, у которого арендуется VDS.

Откроем терминал подключенный к VDS (в данном случае от пользователя root):

# обновляем индекс пакетов
$ apt update
# обновляем ОС
$ apt upgrade
# ставим необходимые пакеты
$ apt install build-essential libssl-dev libffi-dev rsync python3-dev
# следующие 2 пакета нужны для создания виртуальной 
# среды из Python, поставляемого с системой, если
# будете компилировать Python, то их можно не ставить
$ apt install python3-pip python3-venv
# rsync - для синхронизации проекта
# supervisor - для запуска проекта и поддержки в рабочем состоянии 
# nginx - легкий прокси сервер 
$ apt install rsync supervisor nginx

Внимание! Если приложение использует базу данных, то на этом этапе нужно установить необходимые пакеты, залить и настроить БД к использованию. Настройка какой-то конкретной БД здесь рассматриваться не будет.

Настроим виртуальное окружение, чтобы изолировать приложение Flask от других файлов Python в системе. Операционные системы Ubuntu и Debian уже поставляются с определенной версией Python3, и если приложению Flask требуется другая версия, то нужную версию Python3 можно скомпилировать из исходников. Для простоты, в виртуальную среду установим системную версию Python3, т.е. версию, установленную по умолчанию в системе.

# создаем каталоги
$ mkdir -p /var/www/venv
# устанавливаем системный python3 в виртуальное окружение
$ python3 -m venv /var/www/venv --prompt VENV
# активируем виртуальную среду
$ source /var/www/venv/bin/activate
(VENV) :~$

Первым делом нужно обновить менеджер пакетов pip в только что установленном виртуальном окружении, а затем установить зависимости приложения Flask, предварительно скопировав файл зависимостей requirements.txt на VDS, например, командой Linux scp в директорию /var/www. Файл зависимостей можно получить при помощи менеджера пакетов pip, например запустив команду python3 -m pip freeze > requirements.txt в активированной локальной виртуальной среде разработки.

# обновляем pip
(VENV) :~$ python3 -m pip install -U pip
# теперь ставим зависимости приложения 
(VENV) :~$ python3 -m pip install -r /var/www/requirements.txt

Далее ставим пакет gunicorn с поддержкой gevent.

(VENV) :~$ python3 -m pip install -U gunicorn[gevent]

Скопируем приложение Flask с локального компьютера на VDS в папку /var/www/flask-app, которая будет располагаться рядом с виртуальной средой Python3. Почему рядом с виртуальным окружением, а не в него. Это делается для удобства. Например, приложение Flask перешло на новую версию Python3, который обеспечивает бОльшую производительность или в процессе рефакторинга внедрены новые функции из более новой версии Python3. В этом случае на VDS компилируем нужную версию Python3 из исходников, ставим опять рядом новое виртуальное окружение, в реальном времени (без остановки сервера) в настройках supervisor переключаем приложение на новое окружение. Затем синхронизируем новую версию приложения с VDS и перезапускаем supervisor. Поле проверки работоспособности удаляем старое окружение.

Приступим к синхронизации файлов приложения. Открываем терминал на локальной машине, где разрабатывалось приложение Flask. Копировать файлы на VDS будем утилитой rsync (она должна быть установлена). При синхронизации файлов приложения можно дополнительно исключить такие файлы как .git и т.д., добавив опцию --exclude 'file-or-dir-name'

# копируем файлы приложения на VDS по сети утилитой `rsync`
$ rsync -r /local/path/to/flask-app root@10.10.10.10:/var/www/

Для справки: адрес удаленного сервера VDS записывается в формате:

имя_пользователя@ip_vds:/папка/на/vds

После синхронизации файлов приложения Flask, переключаем терминал на VDS и внутри папки с проектом создадим пустой файл настроек gunicorn.py и папку для логов gunicorn и supervisor:

# пустой файл настроек `gunicorn`
$ touch /var/www/flask-app/gunicorn.py
# папка для логов, если нет
$ mkdir /var/www/flask-app/logs

В итоге, на VDS должна получится следующая структура каталогов.

/www
    /venv (виртуальное окружение)
    /flask-app (папка с проектом)
        gunicorn.py (пустой файл настроек)
        /logs (папка для логов)
        /www (само приложение)
            __init__.py 
            /static
            /views
            /templates 
            ...

Здесь самое главное структура, чтобы gunicorn без проблем подхватывал приложение Flask (имена папок могут быть любыми).

Настройка gunicorn + gevent.

Открываем файл /var/www/flask-app/gunicorn.py и вставляем конфигурацию gunicorn (все настройки прокомментированы).

# файл `/var/www/flask-app/gunicorn.py`

import os.path
import multiprocessing

# определяем папку в которой лежит файл `gunicorn.py`
BASEDIR = os.path.abspath(os.path.dirname(__file__))
# для связи с nginx будем использовать сокет
# сокеты быстрее чем порты
bind = f"unix://{BASEDIR}/logs/app.sock"

# для обеспечения безопасности, установим 
# пользователя и группу под которыми 
# будет работать приложение Flask
user = "www-data"
group = "www-data"

# выставляем уровень логирования `gunicorn`
# логи бывают "debug", "info", "warning", "error", "critical"
loglevel = "warning"
# папка с ошибками запуска `gunicor`, сюда же
# будут валиться ошибки в коде приложения Flask
errorlog = f"{BASEDIR}/logs/gunicor.log"

######### WORKERS #########
# Кол-во воркеров вычисляется по формуле
workers = multiprocessing.cpu_count() * 2 + 1
# для воркеров используем класс 'gevent'
worker_class = 'gevent'
# на каждого воркера закладываем по 2-5 асинхронных потоков, 
# в зависимости от "тяжести" приложения (подбирается опытным путем)
worker_connections = workers * 3
# перезапускаем воркера, который не отвечает более 30 сек.
timeout = 30
# ждем окончания запросов на соединении 15 секунд
keepalive = 15

Меняем владельца приложения на www-data (пользователь и группа):

# меняем владельца приложения
$ chown -R www-data:www-data /var/www/flask-app
# меняем права доступа к приложению
$ chmod -R 0754 /var/www/flask-app

Настройка supervisor.

Создадим пустой файл конфигурации supervisor для запуска приложения flask-app.

$ touch /etc/supervisor/conf.d/flask-app.conf

Открываем файл /etc/supervisor/conf.d/flask-app.conf и добавляем конфигурацию (все настройки детально прокомментированы).

# файл `/etc/supervisor/conf.d/flask-app.conf`

# указываем имя приложения
[program:flask-app] 
# папка с виртуальным окружением
environment=PYTHONPATH=/var/www/venv/bin/
# команда запуска приложения 
command=/var/www/venv/bin/gunicorn -c /var/www/flask-app/gunicorn.py www:app
# папка в которой лежит приложение
directory = /var/www/flask-app/
# пользователь и группа от которых 
# будет работать приложение
user=www-data
group=www-data
# логи запуска приложения
stderr_logfile = /var/www/flask-app/logs/supervisor.log
# автоматический старт приложения при сбое
autostart=true
autorestart=true

Обновляем конфигурацию supervisor и пробуем запустить приложение командами (если приложение использует базу данных, то она должна быть настроена):

# обновляем конфигурацию
$ supervisorctl update
# заходим в CLI супервизора
$ supervisorctl 
# должно появиться имя приложения
flask-app
# запускаем приложение
supervisor> start flask-app
flask-app: started
# выходим из CLI
supervisor> quit

Если приложение НЕ запустилось, то смотрим логи /var/www/flask-app/logs/supervisor.log и /var/www/flask-app/logs/gunicorn.log. Скорее всего, причина может быть в правах доступа или что-то напутано с путями. Если приложение запустилось, то появится файл сокета /var/www/flask-app/logs/app.sock.

Настройка nginx.

Здесь будут рассмотрены только те настройки nginx, которые отвечают за обслуживание приложения Flask. Рассмотрение других настроек оптимизации работы прокси-сервера nginx не входят в тему данного материала (и вообще - это отдельная песня).

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

# пустой файл конфигурации в папке `sites-available`
touch /etc/nginx/sites-available/flask-app.ru
# ссылка в папке `sites-enabled` на пустой файл конфигурации
ln -s /etc/nginx/sites-available/flask-app.ru /etc/nginx/sites-enabled/flask-app.ru

Открываем пустой файл /etc/nginx/sites-available/flask-app.ru и добавляем следующую конфигурацию nginx.

Внимание! Nginx не поддерживает комментарии в конфигурационных файлах на кириллице, следовательно их необходимо убрать, иначе будут сыпаться ошибки.

# файл  /etc/nginx/sites-available/flask-app.ru

server {
    listen 80;
    server_name www.flask-app.ru;
    # перенаправление на домен без www
    # полезно для SEO оптимизации 
    return 301 $scheme://flask-app.ru$request_uri;
}

server {
    listen 80;
    # настройка домена без www
    server_name flask-app.ru;

    # отдаем обслуживание статики NGINX
    # тем самым облегчаем работу Python 
    location ^~ /static/ {
        root /var/www/flask-app/www;
        # кэшируем статические файлы на клиенте
        expires 7d;
        add_header Cache-Control "public";
        # не пишем логи доступа к статике
        # тем самым облегчая работу HDD
        access_log off;
        log_not_found off;
    }

    # обслуживание самого приложения Flask
    location / {
        # подключаем параметры прокси-сервера, они находятся в файле
        # `/etc/nginx/proxy_params`, если нужно что-то оптимизировать
        include proxy_params;
        # проксируем запросы к приложению через сокет
        proxy_pass http://unix:/var/www/flask-app/logs/app.sock;
    }
}

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

$ nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

Если ошибок не обнаружено, то перезапустим процесс nginx для чтения новой конфигурации:

$ systemctl restart nginx

Так как домен flask-app.ru не делегирован, а проверить работу приложение в браузере по протоколу http хочется, необходимо в файле hosts локального компьютера прописать соответствие ip-адреса сервера VDS и домена flask-app.ru. Другими словами, нужна следующая запись в конце файла hosts:

...
# ip_адрес_VDS domen www.domen
10.10.10.10 flask-app.ru www.flask-app.ru

После этого приложение Flask должно открыться в браузере по ссылке http://flask-app.ru.

Если будут обнаружены любые ошибки, проверяем следующие файлы, расположенные на VDS:

  • файл /var/log/nginx/error.log - журнал ошибок Nginx;
  • файл /var/log/nginx/error.log - журнал доступа Nginx;
  • команда $ journalctl -u nginx - журнал процессов Nginx;
  • файл /var/www/flask-app/logs/supervisor.log - журнал ошибок Supervisor;
  • файл /var/www/flask-app/logs/gunicorn.log - журнал ошибок Gunicorn, а также сюда попадают ошибки в коде приложения Flask.

Примечание: Данное руководство было опробовано при разворачивании реального сервера. Все инструкции и файлы конфигурации рабочие и не зависят от версии операционной системы. Ошибки могут быть только в путях, если пользователь будет менять оригинальные пути на свои (например, где-то неправильно папку создал/вставил, где-то забыл что-то поменять и т.д.)

Что можно сделать еще?

Защита трафика приложения, протокол https.

Чтобы обеспечить защиту трафика сервера, необходимо получить сертификат SSL для домена (перед этим домен должен быть делегирован). Этого можно добиться несколькими способами, в том числе получить бесплатный сертификат от Let’s Encrypt (в связи с санкциями, в России данная опция может быть не актуальна).

Добавим хранилище Certbot на VDS (для Ubuntu и Debian работает одинаково):

# нужно будет нажать ENTER для подтверждения
$ add-apt-repository ppa:certbot/certbot
# установим пакет Certbot Nginx
$ apt install python-certbot-nginx

Пакет Certbot предоставляет широкий выбор способов получения сертификатов SSL с помощью плагинов. Плагин Nginx изменит конфигурацию Nginx и перезагрузит ее, когда это потребуется. Для использования этого плагина используем следующую команду:

# необходимо подставить свои доменные имена
$ certbot --nginx -d flask-app.ru -d www.flask-app.ru

Эта команда запускает certbot с плагином --nginx, опция -d служит для указания доменных имен, для которых получаем сертификат.

Если это первый запуск certbot, то будет предложено указать адрес эл. почты и принять условия обслуживания. После этого certbot свяжется с сервером Let’s Encrypt и отправит запрос с целью подтвердить, что вы контролируете домен, для которого запрашивается сертификат. Если контроль доменного имени будет подтвержден, то certbot запросит предпочитаемый вариант настройки HTTPS.

После выбора желаемых настроек нужно нажать ENTER. Конфигурация будет обновлена, а Nginx перезапустится с новыми настройками. Затем certbot завершит работу и выведет сообщение, указывающее место хранения сертификатов на VDS-сервере.

Все, теперь приложение будет работать по протоколу https.