Для чего вообще нужен 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: <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: <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 |
Поздравляю, вы прекрасны! =)