Найти в Дзене
Коля Бараненко

Full Stack для StartUp: flask webapp run, wgci, docker, nginx, free DNS and free SSL key HTTPS...

Месяца полтора тому назад мне нужно было решить задачу с написанием web приложения, которое бы решало задачу личного кабинета. Для ее решения сначала была попытка заиспользовать Vue JS (frontend) и Flask + Python (backend), т.к. решил, что переиспользовать свой опыт 2017 года: HTML/JS (frontend) + Java (backend) не актуально в 2020, было написано много своих велосипедов, которые уже решались

Месяца полтора тому назад мне нужно было решить задачу с написанием 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.

https://github.com/drno-reg/flask_webapp.git
https://github.com/drno-reg/flask_webapp.git

Сейчас поработаем с app. Для начала нам необходимо добавить виртуальное окружение python. Сделаем это в webapp директорию через оснастку среды разработки.

добавляем виртуальное окружение python
добавляем виртуальное окружение python

venv
venv

после того как в проекте появится интерпретатор языка необходимо к нему подключиться и выполнить установку 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/

http://127.0.0.1:5000/
http://127.0.0.1:5000/

Запускать Flask приложение мы попробовали, можно выставлять 5000 порт хоть в интернет и уже приглашать пользователей для тестирования :-) НО для production такое решение не подойдет - необходимо перейти на WSGI.

Переходим на WSGI

Production архитектура
Production архитектура

Для этих целей в нашем проекте есть файл 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

http://127.0.0.1:8118/
http://127.0.0.1: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/ , должен получиться такой результат

http://127.0.0.1:8118/ - из docker контейнера
http://127.0.0.1:8118/ - из docker контейнера

Готовим 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

проверяем результат

http://127.0.0.1:80
http://127.0.0.1:80

Теперь запросы идут через 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

-10

контейнеры на docker.hub

Free DNS на biz.mail.ru

Представим, что у вас есть хост с белым IP и вам хотелось бы привязать к нему DNS. На просторах Internet есть free сервисы, которые предоставляют такую возможность я предлагаю воспользоваться сервисом от mail. Домен первого уровня будет bizmail.ru, для нашего тестового приложения выберем flask-webapp. Для этого Вам достаточно иметь почту на mail.ru.

регистрируем flask-webapp.bizml.ru
регистрируем flask-webapp.bizml.ru
Добавляем белый IP для связи с DNS
Добавляем белый IP для связи с DNS

итого c привязкой вашего белого IP и домена flask-webapp.bizml.ru

связка white ip и DNS
связка white ip и DNS

Из жирных минусов с ноября 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

http://flask-webapp.bizml.ru/
http://flask-webapp.bizml.ru/

Free SSL сертификат от ZeroSSL

Осталось перевести на HTTPS. Для этого воспользуемся сервисом ZeroSSL . Чтобы получить SSL сертификат необходимо для начала зарегистрироваться и перейти к созданию нового сертификата (по условиям free сервиса на одну УЗ могут быть зарегистрированы 3 ssl сертификата сроком на 3 месяца)

создаем SSL сертификат на ZeroSSL
создаем SSL сертификат на ZeroSSL
90 дней SSL
90 дней SSL
auto CSR и финализируем свой выбор
auto CSR и финализируем свой выбор

далее остался еще шаг - валидировать, есть email валидация, CNAME валидация и c помощью загрузки файла по HTTP. Выбираем именно этот путь валидации. Для этого нам придется скачать файл с zerossl и залить его к нам в flask_webapp - это контейнер справа) и предоставить возможность его скачать по ссылке /.well-known/pki-validation/

валидация по /.well-known/pki-validation/*.key
валидация по /.well-known/pki-validation/*.key

для этих целей в нашем проекте создано download/views.py и папка files

-19

скачиваем файл и размещаем его в files. Локальная проверка должна показать, что файл, который вы разместили в files должен быть доступен для скачивания по HTTP в моем случае ссылка будет выглядеть так

http://0.0.0.0:8118/.well-known/pki-validation/F6EFCF7D4D1C7611B0D9CBA0773734DF.txt

http://0.0.0.0:8118/.well-known/pki-validation/*.txt
http://0.0.0.0:8118/.well-known/pki-validation/*.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 сертификата

проверяем что файл который необходим для валидации скачивается
проверяем что файл который необходим для валидации скачивается
скачиваем ключи для nginx
скачиваем ключи для nginx

здесь еще инструкция от zerossl https://zerossl.com/help/installation/nginx.

Из жирных минусов free тарифного плана этого сервиса:

3 - количество выпущенных сертификатов, если вы выпустили 3 сертификата, то перевыпуск возможен будет только уже под другой УЗ если вы захотите продолжить Free историю.

-23

Скачанные ключи размещаем в server в подпапке ssl

ssl ключи
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

если вдруг ошибка, то нужно зайти и разделить друг от друга сертификаты

-25

вносим изменения в конфигурацию 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

https://flask-webapp.bizml.ru/
https://flask-webapp.bizml.ru/

Итого:

  • мы научились создавать и запускать простейшее WebApp приложение на Flask
  • собирать в docker Flask WebApp
  • научились Production архитектуре - перешли на WCGI
  • перед Flask WebApp организовали docker c nginx
  • научились получать DNS на примере сервиса biz.mail.ru
  • научились получать SSL сертификаты на примере ZeroSSL

Если данный материал оказался кому-то полезен пишите в комментариях. Продолжу дальше :-)