DevGang
Авторизоваться

Python Flask: взаимодействие с контейнерами Docker

А вам приходила в голову мысль о том, что вам нужно реализовать сервер для обслуживания своих функций? Звучит довольно интересно, может стоит попробовать? 

Итак, для этого нужно сделать:

  1. Создать сервер API, который получает файл изображения;
  2. Запустить контейнер docker и передать файл изображения в контейнер, и контейнер создаст текстовый файл, созданный из файла изображения;
  3. Запустить другой контейнер docker и передать текст;
  4. Сервер API знает, что контейнер знает что контейнер выполнил свою работу, и уведомляет другой сервер (backend, который передает данные пользователям).

Для начала

  • pipenv
Pipenv - это инструмент, цель которого - привнести лучшее из мира пакетов (bundler, composer, npm, cargo, yarn и т. д.) в мир Python. Windows - первоклассный гражданин в мире.В самом начале пути знакомства с Python я использовал conda или venv для управления пакетами. Когда я заинтересовался темой управления пакетами, наткнулся на такой инструмент, как npm, и решил попробовать.
  • docker
Docker - платформа, предназначенная для помощи разработчикам в создании, совместном использовании и запуске современных приложений. Мы занимаемся утомительной настройкой, так что вы можете сосредоточится на коде.
  • Flask
Flask - это фреймворк веб-приложения, написанный на Python. Он был разработан Армином Ронахером,, который возглавлял команду международных энтузиастов Python под названием Poocco.Чтобы реализовать простой веб-сервер API, я использовал Flask, существовали и другие библиотеки, такие как Fast API. Так как я привык использовать Flask, я выбрал Flask для сокращения своего рабочего времени.

Это тот процесс, который необходимо реализовать:

  1. Сервер API получает файл от пользователя;
  2. Контейнер A обрабатывает файл, а затем сохраняет его в файл B;
  3. Контейнер B обрабатывает файл B, а затем сохраняет его в файл C;
  4. Пользователи могут видеть результат через API

Приложения Python и образы Docker

Создадим два изображения и загрузим их в свой репозиторий в docker-hub.

Токенизатор и приложение для подсчета слов.

[Tokenizer]

import sys, json, os, requests
from textblob import TextBlob


def extract_nouns(text):
    blob = TextBlob(text)
    filtered_tags = list(filter(lambda tag: tag[1] == "NN", blob.tags))
    nouns = list(map(lambda tag: tag[0], filtered_tags))
    return nouns


def read_file(path):
    with open(path) as f:
        contents = f.read()
    return contents


def save_data(path, data):
    with open(path, "w") as f:
        json.dump(data, f)


def get_filename_from_path(path):
    return os.path.splitext(os.path.basename(path))[0]


def notify_done(url, file_name):
    requests.get(f"{url}/docker/tokenizer_done?file_name={file_name}")


if __name__ == "__main__":
    if len(sys.argv) < 4:
        print("You must pass file path as an argument")
        print("python3 main.py [file path to read] [dir to save] [notification api]")
        print("Example) python3 main.py ./test.txt ./ http://host.docker.internal:20000")
        sys.exit()

    api_url = sys.argv[3]
    file_path = sys.argv[1]
    file_name = get_filename_from_path(file_path)
    target_path = os.path.join(sys.argv[2], file_name + ".json") 

    text = read_file(file_path)
    nouns = extract_nouns(text)

    save_data(target_path, {"nouns": nouns})
    notify_done(api_url, file_name)

    print("Done")

[word-counting]

import sys, json, os, requests


def count_word(nouns_list):
    count_dict = dict()

    for noun in nouns_list:
        if noun in count_dict:
            count_dict[noun] += 1
        else:
            count_dict[noun] = 1

    return count_dict


def load_data(path):
    with open(path) as f:
        json_data = json.load(f)
    return json_data


def save_data(path, data):
    with open(path, "w") as f:
        json.dump(data, f)


def get_filename_from_path(path):
    return os.path.splitext(os.path.basename(path))[0]


def notify_done(url, file_name):
    requests.get(f"{url}/docker/word_count_done?file_name={file_name}")


if __name__ == "__main__":
    if len(sys.argv) < 4:
        print("You must pass file path as an argument")
        print("python3 main.py [file path to read] [dir to save] [notification api]")
        print("Example) python3 main.py ./test.txt ./ http://host.docker.internal:20000")
        sys.exit()

    api_url = sys.argv[3]
    file_path = sys.argv[1]
    file_name = get_filename_from_path(file_path)
    target_path = os.path.join(sys.argv[2], file_name + ".json") 

    json_data = load_data(file_path)
    count_dict = count_word(json_data["nouns"])

    save_data(target_path, {"result": count_dict})
    notify_done(api_url, file_name)
    print("Done")

Для запуска приложений с сервера API создадим оба файла Python с приведенными ниже файлами Docker.

[Tokenizer]

FROM python:3.9

WORKDIR /app
COPY . .

RUN pip install pipenv
RUN pipenv install
RUN pipenv run python3 -m textblob.download_corpora

ENTRYPOINT ["pipenv", "run", "python3", "./main.py"]

[word-counting]

FROM python:3.9

WORKDIR /app
COPY . .

RUN pip install pipenv
RUN pipenv install

ENTRYPOINT ["pipenv", "run", "python3", "./main.py"]

Сервер API

Это основной код, и он довольно прост.

from flask import Flask
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)

import routes

Маршруты (routes)

[routes/docker.py]

import os
from flask import jsonify, request
from server import app
from lib import docker, json


result = []


@app.route('/docker/tokenizer_done')
def get_tokenizer_done():
    file_name = request.args.get("file_name")
    docker.run_word_count_container(file_name)
    return "run a word_count container"


@app.route('/docker/word_count_done')
def get_word_count_done():
    file_name = request.args.get("file_name")

    json_data = json.load_data(
        os.path.join(os.getenv("SHARED_VOLUME_PATH"),
        "word_count_output",
        f"{file_name}.json"
    ))
    result.append(json_data)

    return "all works done"


@app.route('/docker/result')
def get_result():
    file_name = request.args.get("file_name")
    return jsonify({
        "result": result
    })

[routes/upload.py]

import os
from flask import jsonify, request
from werkzeug.utils import secure_filename
from server import app
from lib import docker


@app.route("/upload", methods=["POST"])
def upload_file():
    f = request.files["file"]

    file_name = secure_filename(f.filename)
    f.save(os.path.join(os.getenv("SHARED_VOLUME_PATH"), "input", file_name))

    docker.run_tokenizer_container(file_name)

    return "succeed to upload"

[routes/__init__.py]

from routes import docker, upload

[lib/docker.py]

import os

API_URL = os.getenv("API_URL")
VOLUME_ROOT_PATH = os.getenv("SHARED_VOLUME_PATH")
RUN_TOKENIZER_CONTAINER = 'docker run -it --add-host=host.docker.internal:host-gateway -v "' + VOLUME_ROOT_PATH + ':/shared_volume" hskcoder/tokenizer:0.2 /shared_volume/input/{FILE_NAME_WITH_EXTENSION} /shared_volume/tokenizer_output ' + API_URL
RUN_WORD_COUNT_CONTAINER = 'docker run -it --add-host=host.docker.internal:host-gateway -v "' + VOLUME_ROOT_PATH + ':/shared_volume" hskcoder/word_count:0.2 /shared_volume/tokenizer_output/{FILE_NAME_WITHOUT_EXTENSION}.json /shared_volume/word_count_output ' + API_URL


def run_tokenizer_container(file_name):
    print(RUN_TOKENIZER_CONTAINER.format(
        FILE_NAME_WITH_EXTENSION = file_name
    ))
    os.popen(RUN_TOKENIZER_CONTAINER.format(
        FILE_NAME_WITH_EXTENSION = file_name
    ))



def run_word_count_container(file_name):
    os.popen(RUN_WORD_COUNT_CONTAINER.format(
        FILE_NAME_WITHOUT_EXTENSION = file_name
    ))

[iib/json.py]

import json


def load_data(path):
    with open(path) as f:
        json_data = json.load(f)
    return json_data

Это приложение считывает переменные среды из файла .env, поэтому вам нужно настроить их следующим образом.

Приведенные ниже переменные как раз подходят для моей системы.

API_URL=http://host.docker.internal:20000
ROOT_PATH=C:\Users\hskco\OneDrive\바탕 화면\stuff\docker\api
SHARED_VOLUME_PATH=C:\Users\hskco\OneDrive\바탕 화면\stuff\docker\api\shared_volume

Сервер можно запустить с помощью этого скрипта

python3 -m pipenv run flask run -h 0.0.0.0 --port 20000

Перед выполнением этой команды необходимо установить переменную окружения FLASK_APP.

Поскольку данный пример разрабатывался в Windows, запустим эту команду в директории api.

$env:FLASK_APP = './server.py'

Если вы войдете http://127.0.0.1/docker/result вы увидите эту страницу. 

Отправим файл на сервер API и посмотрим на результат.

[1]

[2]

[3]

Заключение

Этот пример - начальный уровень. Это должно было рассматриваться как:

  • Авторизация и безопасность (в этом примере сервер API предоставляет все маршруты для общего доступа);
  • Связь между контейнерами (использование Restful API для взаимодействия между контейнерами, однако должны быть лучшие способы);
  • Управление контейнерами (когда контейнеры будут выполнены, их необходимо удалить. Кроме того, также должна быть необходима балансировка нагрузки. Для этого есть инструменты, такие как Kubernetes);
  • Развертывание для производства.

Есть много вещей, о которых не упомянулось в данной статье, и вам нужно подумать (Будем уважать бэкенд-разработчиков). Здесь идет просто сосредоточение на внедрении системы, если попытаться сделать ее совершенной, не получилось бы написать эту статью. 

#Python #Docker
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу