Краткая история о том, как развернуть веб-сервер Flask в docker контейнере

—

от автора

Для чего вообще нужен docker контейнер? Обычно, во время разработки, для каждого проекта вы настраиваете своё окружение. Но вот произошла такая ситуация: что-то случилось с вашим компьютером и приходится переустанавливать операционную систему(ОС). Соответственно, чтобы запустить ваш проект, необходимо настраивать окружение заново. Бывает ещё гигантское количество ситуаций, которые сводятся к одной проблеме — настройка окружения для разработки. Так вот Docker — коробка, которую достаточно единожды настроить под проект, чтобы в дальнейшем не было проблем с эксплуатацией/расширением сервиса

Для начала необходимо установить Docker Engine по одной из этих инструкций

Следующим этапом устанавливаем Docker Compose — к счастью, эта инструкция меньше =)

Поздравляю! Вы проделали треть работы

Давайте создадим структуру нашего проекта.

- backend/         - main.py         - requirements.txt         - Dockerfile - docker-compose.yml

В файле requirements.txt у нас будут прописаны, скачиваемые для проекта библиотеки.

Flask==1.1.2

В main.py содержится простенький сервер для проверки работоспособности контейнера.

from flask import Flask  app = Flask(__name__)  @app.route('/') def hello_world():     return "<h1>Hello, World!<h1>"  if __name__ == '__main__':    app.run(host='0.0.0.0')

И, переходя к самому сладкому, расскажу про ещё одну важную вещь — DockerHub. Это место, где разработчики размещают свои созданные образы контейнеров. Вы тоже можете разместить свой образ на этой площадке.

Перейдём к следующему шагу — созданию Dockerfile.

# Выкачиваем из dockerhub образ с python версии 3.9 FROM python:3.9 # Устанавливаем рабочую директорию для проекта в контейнере WORKDIR /backend # Скачиваем/обновляем необходимые библиотеки для проекта  COPY requirements.txt /backend RUN pip3 install --upgrade pip -r requirements.txt # |ВАЖНЫЙ МОМЕНТ| копируем содержимое папки, где находится Dockerfile,  # в рабочую директорию контейнера COPY . /backend # Устанавливаем порт, который будет использоваться для сервера EXPOSE 5000

Но Dockerfile — это лишь организация рабочего пространства внутри нашего контейнера. Так как запросы поступают извне, необходимо настроить инфраструктуру. Для этого нам нужен docker-compose.yml.

version: '3' services:   flask:     # Путь до Dockerfile     build: ./backend     # Имя для создаваемого контейнера     container_name: backend-flask     # Создание переменных окружения в контейнере     environment:       # для отладки (при запуске в релиз убрать!)       - FLASK_ENV=development       ## Позволяет отслеживать процесс работы приложения в командной строке       - PYTHONUNBUFFERED=True                          ##     # Перезапускаем сервис в случае падения      restart: on-failure     # Прокладывам путь для файлов. Все файлы, которые хранятся у вас в      # директории ./backend, появятся в директории контейнера /backend     volumes:       - ./backend:/backend     # Открываем порт в контейнер     # Порт, который будет смотреть наружу : порт который используется внутри контейнера     ports:       - "5000:5000"     command: python main.py

Всё готово, осталось только запустить командой docker-compose upиз директории с файлом docker-compose.yml.

Сервер на ПРОКАЧКУ

Круто! Теперь у нас есть веб-сервер, который развёрнут при помощи контейнеров, но этого всё ещё мало. Нам нужна более серьёзная архитектура, как у крутых проггеров. Давайте её прокачаем!

Добавим к нашему проекту gunicorn для будущего распределения нагрузки.

requirements.txt

Flask==1.1.2 gunicorn==20.0.4

И расширим нашу структуру приложения. Теперь приложение выглядит так.

- backend/         - app/                 - __init__.py                 - routes.py                 - config.py         - modules/                 - hello_world.py         - main.py         - requirements.txt         - Dockerfile         - settings.ini - docker-compose.yml

Как вы наверняка заметили, в структуру добавились файл settings.ini, папка для инициализации нашего приложения и страничка modules/hello_world.py.

Обо всём по порядку.

Инициализация приложения

В файл  app/__init__.py импортируем библиотеки. Заранее импортируем наши компоненты, которые опишем чуть ниже.

import os  from flask import Flask  from app.routes import route from app.config import config, init_config

Создадим функцию, которая будет создавать наше приложение.

def create_flask_app():     app = Flask(__name__)          return app

В функции инициализируем пути к модулям(routes), подключим файл с конфигом и обновим конфигурацию приложения.

def create_flask_app():     app = Flask(__name__)          # Подключаем все роуты приложения     route(app)          # Считываем переменную окружения "CONFIG_PATH", если она есть,         # то берём путь из неё, иначе указанный по умолчанию "./settings.ini"     path = os.environ.get('CONFIG_PATH') if os.environ.get(         'CONFIG_PATH') else "./settings.ini"         # Инициализируем конфиг по вышеуказанному пути      init_config(path)         # Обновляем конфигурацию приложения Flask         # Если файл не найден или данные которые используются при обновлении отсутствуют,         # то вылетит Exception - KeyError     try:         app.config.update(dict(             SECRET_KEY=str(config['FLASK_APP']['FLASK_APP_SECRET_KEY'])         ))         print(f"\n\033[32m Сервер запустился с конфигом:\n\033[32m {path}\n")     except KeyError:         print(f"\033[31m Файл {path} не найден или неверный")      return app

В конечном итоге, файл app/__init__.py выглядит так.

import os  from flask import Flask  from app.routes import route from app.config import config, init_config  def create_flask_app():     app = Flask(__name__)      route(app)      path = os.environ.get('CONFIG_PATH') if os.environ.get(         'CONFIG_PATH') else "./settings.ini"     init_config(path)     try:         app.config.update(dict(             SECRET_KEY=str(config['FLASK_APP']['FLASK_APP_SECRET_KEY'])         ))         print(f"\n\033[32m Сервер запустился с конфигом:\n\033[32m {path}\n")     except KeyError:         print(f"\033[31m Файл {path} не найден или неверный")      return app

Теперь можно и переписать app/main.py.

from app import create_flask_app  if __name__ == "__main__":     create_flask_app().run(host='0.0.0.0')

После всех изменений с инициализацией приложения нужно поменять в docker-compose.yml команду запуска приложения.

# Заменить command: python main.py # на command: gunicorn main:"create_flask_app()" -b 0.0.0.0:5000 --reload # gunicorn запускает в файле main.py, функцию create_flask_app по адресу 0.0.0.0:5000

Конфигурационный файл

Во время разработки в приложении частенько, могут встречаться, данные вроде паролей, адресов, портов, токенов и прочих секретных вещей, которые нельзя оставлять в коде. Для этого всё выносится в отдельный файл (который, конечно же, никуда не выкладывается :))

За инициализацию конфига будет отвечать config.py.

import os import configparser  config = configparser.ConfigParser()  def init_config(path):     config.optionxform = str     config.read(path)

Прорубаем путь до вашей странички

Дело в том, что в первой версии нашего приложения адрес для страницы указывали в декораторе app @app.route('/') , но так как app теперь используется только для инициализации, то нам нужно его чем-то заменить. Здесь нас выручит класс Blueprint. Blueprint используется для создания модульных приложений Flask`а.

Для начала, создадим страничку в файле modules/hello_world.py.

from flask import Blueprint  hello_world_bp = Blueprint('hello_world', __name__)  @hello_world_bp.route('/') def hello_world():     return "<h1>Hello, World!<h1>"

После того, как создали новый модуль приложения, необходимо сказать Flask`у, где он располагается (зарегистрировать модуль). Здесь и нужен файл routes.py.

from modules.hello_world import hello_world_bp  def route(app):     app.register_blueprint(hello_world_bp)

Теперь при попытке запустить приложение docker-compose up --build (флаг build нужен для пересборки контейнера, т.к. добавили новые пакеты для скачивания) должна вылететь ошибка о том, что что-то не так с нашим конфигом.

Starting backend-flask ... done Attaching to backend-flask backend-flask | [2020-12-26 09:05:41 +0000] [1] [INFO] Starting gunicorn 20.0.4 backend-flask | [2020-12-26 09:05:41 +0000] [1] [INFO] Listening at: &lt;http://0.0.0.0:5000> (1) backend-flask | [2020-12-26 09:05:41 +0000] [1] [INFO] Using worker: sync backend-flask | [2020-12-26 09:05:41 +0000] [7] [INFO] Booting worker with pid: 7 backend-flask |  Файл ./settings.ini не найден или неверный

Заходим в settings.ini и добавляем значение, которое отсутствовало.

[FLASK_APP] FLASK_APP_SECRET_KEY=Super_slojniy_secret_key 

Запускаем приложение и радуемся

Starting backend-flask ... done  Attaching to backend-flask backend-flask | [2020-12-26 09:12:34 +0000] [1] [INFO] Starting gunicorn 20.0.4 backend-flask | [2020-12-26 09:12:34 +0000] [1] [INFO] Listening at: &lt;http://0.0.0.0:5000> (1) backend-flask | [2020-12-26 09:12:34 +0000] [1] [INFO] Using worker: sync backend-flask | [2020-12-26 09:12:34 +0000] [7] [INFO] Booting worker with pid: 7 backend-flask | backend-flask |  Сервер запустился с конфигом: backend-flask |  ./settings.ini backend-flask |

Поздравляю, вы прекрасны! =)

ссылка на оригинал статьи https://habr.com/ru/company/megafon/blog/541826/