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

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

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