Месяца полтора тому назад мне нужно было решить задачу с написанием web приложения, которое бы решало задачу личного кабинета. Для ее решения сначала была попытка заиспользовать Vue JS (frontend) и Flask + Python (backend), т.к. решил, что переиспользовать свой опыт 2017 года: HTML/JS (frontend) + Java (backend) не актуально в 2020, было написано много своих велосипедов, которые уже решались другими более простыми способами. Недели 2 ушло на погружение в особенности Vue JS. В процессе погружения всплыло так много тонкостей Vue JS, что стали уже посещать мысли, а нужно ли тратить время и на него. Моему вниманию на просторах yutube попалась серия уроков по изучению Flask : там есть от Создание веб сайтов на Python до деплоя вашего приложения через сервис Heroku .
Собираем свой первый проект на Flask
В качестве среды разработки я использовал продукт IntelliJ IDEA c подключенным плагином для python, можете использовать PyCharm. Клонируете себе проект flask_webapp. В проекте два каталога app и server, app для flask web приложения, другой для web server на базе NGinx.
Сейчас поработаем с app. Для начала нам необходимо добавить виртуальное окружение python. Сделаем это в webapp директорию через оснастку среды разработки.
после того как в проекте появится интерпретатор языка необходимо к нему подключиться и выполнить установку flask
~/IdeaProjects/flask_webapp/app$ source webapp/venv/bin/activate
~/IdeaProjects/flask_webapp/app$ pip3 install flask
проверяем установленные пакеты
(venv) :~/IdeaProjects/flask_webapp/app$ pip3 list
Package Version
------------- -------
click 7.1.2
Flask 1.1.2
itsdangerous 1.1.0
Jinja2 2.11.2
MarkupSafe 1.1.1
pip 20.2.2
pkg-resources 0.0.0
setuptools 49.6.0
Werkzeug 1.0.1
задаем переменную окружения FLASK_APP и запускаем наше приложение
(venv) :~/IdeaProjects/flask_webapp/app$ export FLASK_APP=flask_run.py
(venv) :~/IdeaProjects/flask_webapp/app$ flask run
* Serving Flask app "flask_run.py"
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
наше приложение сейчас доступно по адресу http://127.0.0.1:5000/
Запускать Flask приложение мы попробовали, можно выставлять 5000 порт хоть в интернет и уже приглашать пользователей для тестирования :-) НО для production такое решение не подойдет - необходимо перейти на WSGI.
Переходим на WSGI
Для этих целей в нашем проекте есть файл wsgi.py
from webapp import webapp
if __name__ == "__main__":
webapp.run(host='0.0.0.0', port=8118)
чтобы получить возможность запускать наш проект в production режиме необходимо доустановить модуль gunicorn
(venv) :~/IdeaProjects/flask_webapp/app$ pip3 install gunicorn
Collecting gunicorn
Using cached gunicorn-20.0.4-py2.py3-none-any.whl (77 kB)
Requirement already satisfied: setuptools>=3.0 in ./webapp/venv/lib/python3.8/site-packages (from gunicorn) (49.6.0)
Installing collected packages: gunicorn
Successfully installed gunicorn-20.0.4
запускаем
(venv) :~/IdeaProjects/flask_webapp/app$ gunicorn -w 1 -b 0.0.0.0:8118 wsgi:webapp
[2020-08-30 17:32:43 +0300] [289064] [INFO] Starting gunicorn 20.0.4
[2020-08-30 17:32:43 +0300] [289064] [INFO] Listening at: http://0.0.0.0:8118 (289064)
[2020-08-30 17:32:43 +0300] [289064] [INFO] Using worker: sync
[2020-08-30 17:32:43 +0300] [289066] [INFO] Booting worker with pid: 289066
теперь наше приложение доступно по порту 8118
Готовим Docker контейнер для Flask WebApp
Архитектура представлена в виде отдельных docker контейнеров, на мой взгляд это уже современный тренд и необходимо теперь наше приложение поместить в него, для этих целей в проекте лежит Dockefile. Для того, чтобы нам собрать контейнер сначала необходимо сформировать файл с зависимостями requirements.txt, чтобы при сборки эти зависимости попали в контейнер.
(venv) :~/IdeaProjects/flask_webapp/app$ pip3 freeze > requirements.txt
в проекте у нас появится соответствующий файл или он просто обновится. Есть небольшой нюанс при сборке docker ругается, что не может установить pkg-resources и хочет, чтобы был соответственно пакет psycopg2-binary. Соответственно один я закомментирую, а другой добавлю. Итого список пакетов
click==7.1.2
Flask==1.1.2
gunicorn==20.0.4
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
psycopg2-binary==2.8.5
Werkzeug==1.0.1
далее пробуем собрать docker контейнер с обозначением сразу тега, готового для заливки артифакта в DockerHub: отсюда drnoreg, у вас он будет свой. Запускаем билд
(venv) :~/IdeaProjects/flask_webapp/app$ docker build -t drnoreg/flask_webapp_app:0.1 ./
сборка должна пройти без ошибок и в результате в локальном хранилище артифактов вы найдете flask_webapp_app:01
(venv) :~/IdeaProjects/flask_webapp/app$ docker images -a | grep flask
flask_webapp_app 0.1 c749240f841b 51 seconds ago 424MB
можем попробовать запустить контейнер и проверить его работоспособность, НО предварительно создадим подсеть для docker и проверим результат
(venv) :~/IdeaProjects/flask_webapp/app$ docker network create flask_webapp_network
(venv) :~/IdeaProjects/flask_webapp/app$ docker network ls
NETWORK ID NAME DRIVER SCOPE
9047f009a303 bridge bridge local
648ce60b1ec2 flask_webapp_network bridge local
запускаем контейнер и проверяем статус запуска
(venv) :~/IdeaProjects/flask_webapp/app$ docker run --net flask_webapp_network --name=flask_webapp_app -d -p 8118:8118 drnoreg/flask_webapp_app:0.1
c55e9ddca3091d3833c442165b9478e5b6f20662cb199469215762b61bafce45
(venv) :~/IdeaProjects/flask_webapp/app$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c55e9ddca309 drnoreg/flask_webapp_app:0.1 "gunicorn -w 1 -b 0.…" 16 seconds ago Up 14 seconds 0.0.0.0:8118->8118/tcp flask_webapp_app
заходим по ссылке http://127.0.0.1:8118/ , должен получиться такой результат
Готовим Docker контейнер для web сервера NGinx
"Правая" часть схемы готова, теперь нам необходимо собрать "левую": поместить в docker nginx, для этого нам необходимо сменить папку app на сервер и запустить билд контейнера flask_webapp_server
(venv) :~/IdeaProjects/flask_webapp/server$ docker build -t drnoreg/flask_webapp_server:0.1 ./
(venv) :~/IdeaProjects/flask_webapp/server$ docker images -a | grep flask
drnoreg/flask_webapp_server 0.1 14d5a12a7176 55 seconds ago 109MB
drnoreg/flask_webapp_app 0.1 c01467b6e56d 15 minutes ago 424MB
(venv) :~/IdeaProjects/flask_webapp/server$ docker run --net flask_webapp_network --name=flask_webapp_server -d -p 80:80 drnoreg/flask_webapp_server:0.1
проверяем результат
Теперь запросы идут через web сервер к нашему flask приложению. На нашем локальном хосте теперь есть 2 готовых артифакта docker, которые мы теперь можем "выставить" в Internet.
Docker Hub
Отправим их в docker hub
(venv) :~/IdeaProjects/flask_webapp/server$ docker push drnoreg/flask_webapp_app:0.1
The push refers to repository [docker.io/drnoreg/flask_webapp_app]
98bc56f310b6: Pushed
0.1: digest: sha256:f3f7835aaed39f8d6052f21709a2ca34fd3aed4114a67f98d3577c626d50ee9b size: 2208
(venv) :~/IdeaProjects/flask_webapp/server$ docker push drnoreg/flask_webapp_server:0.1
The push refers to repository [docker.io/drnoreg/flask_webapp_server]
6a4e80ffaa87: Pushed
0.1: digest: sha256:2b5dbca9675f3c784d4b4cd13c383f6fbd38e60672ffe6a8375e390ea5037268 size: 1776
после заливки на docker.hub
контейнеры на docker.hub
Free DNS на biz.mail.ru
Представим, что у вас есть хост с белым IP и вам хотелось бы привязать к нему DNS. На просторах Internet есть free сервисы, которые предоставляют такую возможность я предлагаю воспользоваться сервисом от mail. Домен первого уровня будет bizmail.ru, для нашего тестового приложения выберем flask-webapp. Для этого Вам достаточно иметь почту на mail.ru.
итого c привязкой вашего белого IP и домена flask-webapp.bizml.ru
Из жирных минусов с ноября 2020 для free тарифа: теперь на 1 УЗ только 1 проект.
идем на хост и выполняем pull наших артифактов в локальный репозиторий нашего сервера.
# docker pull drnoreg/flask_webapp_app:0.1
0.1: Pulling from drnoreg/flask_webapp_app
Status: Downloaded newer image for drnoreg/flask_webapp_app:0.1
docker.io/drnoreg/flask_webapp_app:0.1
# docker pull drnoreg/flask_webapp_server:0.1
0.1: Pulling from drnoreg/flask_webapp_server
Digest: sha256:2b5dbca9675f3c784d4b4cd13c383f6fbd38e60672ffe6a8375e390ea5037268
Status: Downloaded newer image for drnoreg/flask_webapp_server:0.1
docker.io/drnoreg/flask_webapp_server:0.1
теперь мы готовы к запуску на удаленном хосте, сделаем это предварительно создав подсеть
# docker network create flask_webapp_network
# docker run --net flask_webapp_network --name=flask_webapp_app -d -p 8118:8118 drnoreg/flask_webapp_app:0.1
docker run --net flask_webapp_network --name=flask_webapp_server -d -p 80:80 drnoreg/flask_webapp_server:0.1
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
eee32b374090 drnoreg/flask_webapp_server:0.1 "nginx -g 'daemon of…" 34 seconds ago Up 33 seconds 0.0.0.0:80->80/tcp flask_webapp_server
da431ed74c3c drnoreg/flask_webapp_app:0.1 "gunicorn -w 1 -b 0.…" About a minute ago Up About a minute 0.0.0.0:8118->8118/tcp flask_webapp_app
проверяем по DNS
Free SSL сертификат от ZeroSSL
Осталось перевести на HTTPS. Для этого воспользуемся сервисом ZeroSSL . Чтобы получить SSL сертификат необходимо для начала зарегистрироваться и перейти к созданию нового сертификата (по условиям free сервиса на одну УЗ могут быть зарегистрированы 3 ssl сертификата сроком на 3 месяца)
далее остался еще шаг - валидировать, есть email валидация, CNAME валидация и c помощью загрузки файла по HTTP. Выбираем именно этот путь валидации. Для этого нам придется скачать файл с zerossl и залить его к нам в flask_webapp - это контейнер справа) и предоставить возможность его скачать по ссылке /.well-known/pki-validation/
для этих целей в нашем проекте создано download/views.py и папка files
скачиваем файл и размещаем его в files. Локальная проверка должна показать, что файл, который вы разместили в files должен быть доступен для скачивания по HTTP в моем случае ссылка будет выглядеть так
http://0.0.0.0:8118/.well-known/pki-validation/F6EFCF7D4D1C7611B0D9CBA0773734DF.txt
Плохая практика для размещения файлов внутри контейнера, НО в данном случае мы сделаем так, чтобы пройти путь получения SSL до конца. Пересобираем docker для app и обновляем на сервере на новый артифакт, номер тега оставим тот же.
(venv) :~/IdeaProjects/flask_webapp/app$ docker build -t drnoreg/flask_webapp_app:0.1 ./
(venv) :~/IdeaProjects/flask_webapp/app$ docker push drnoreg/flask_webapp_app:0.1
на сервере
docker pull drnoreg/flask_webapp_app:0.1
docker stop flask_webapp_app && docker rm flask_webapp_app
docker run --net flask_webapp_network --name=flask_webapp_app -d -p 8118:8118 drnoreg/flask_webapp_app:0.1
теперь тот же самый файл с ключом должен быть доступен по ссылке
http://http//flask-webapp.bizml.ru/.well-known/pki-validation/F6EFCF7D4D1C7611B0D9CBA0773734DF.txt
проверяем, что это действительно так и возвращаемся к получению SSL сертификата
здесь еще инструкция от zerossl https://zerossl.com/help/installation/nginx.
Из жирных минусов free тарифного плана этого сервиса:
3 - количество выпущенных сертификатов, если вы выпустили 3 сертификата, то перевыпуск возможен будет только уже под другой УЗ если вы захотите продолжить Free историю.
Скачанные ключи размещаем в server в подпапке ssl
далее необходимо объединить certificate.crt и ca_bundle.crt
$ cat certificate.crt ca_bundle.crt >> certificate.crt
проверить результат объединения можно так, не должно быть ошибок
(venv) :~/IdeaProjects/flask_webapp/server$ openssl x509 -text -noout -in ssl/certificate.crt
если вдруг ошибка, то нужно зайти и разделить друг от друга сертификаты
вносим изменения в конфигурацию nginx, раскомментируем секции с ssl и отдельную секцию для 80 и принудительного редиректа на ssl по 443 порту.
server {
listen 80;
server_name flask-webapp.bizml.ru;
return 301 https://$host$request_uri;
}
server {
# listen 80;
# переходим на https
listen 443 ssl;
ssl on;
ssl_certificate /etc/ssl/certificate.crt;
ssl_certificate_key /etc/ssl/private.key;
server_name flask-webapp.bizml.ru;
и в dockerfile раскомментируем
COPY ssl/certificate.crt /etc/ssl/certificate.crt
COPY ssl/private.key /etc/ssl/private.key
пересобираем docker контейнер и пушим в docker hub
$ docker build -t drnoreg/flask_webapp_server:0.1 ./
$ docker push drnoreg/flask_webapp_server:0.1
на сервере pull, останавливаем server docker и запускаем уже с 2мя портами
docker stop flask_webapp_server
docker rm flask_webapp_server
docker pull drnoreg/flask_webapp_server:0.1
docker run --net flask_webapp_network --name=flask_webapp_server -d -p 80:80 -p 443:443 drnoreg/flask_webapp_server:0.1
Итого:
- мы научились создавать и запускать простейшее WebApp приложение на Flask
- собирать в docker Flask WebApp
- научились Production архитектуре - перешли на WCGI
- перед Flask WebApp организовали docker c nginx
- научились получать DNS на примере сервиса biz.mail.ru
- научились получать SSL сертификаты на примере ZeroSSL
Если данный материал оказался кому-то полезен пишите в комментариях. Продолжу дальше :-)