Открыть список
Как стать автором
Обновить
70,49
Рейтинг

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

Блог компании МегаФонPythonПрограммированиеFlask
Tutorial

Для чего вообще нужен 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 |

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

Теги:pythonflaskdockerdocker-composegunicornconfigparser
Хабы: Блог компании МегаФон Python Программирование Flask
Всего голосов 9: ↑5 и ↓4 +1
Просмотры3.3K

Комментарии 10

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Похожие публикации

Лучшие публикации за сутки

Информация

Дата основания
Местоположение
Россия
Сайт
job.megafon.ru
Численность
свыше 10 000 человек
Дата регистрации

Блог на Хабре