Gunicorn: Gunicorn + Flask + Tensorflow в контейнере Docker не работает

Созданный на 3 окт. 2019  ·  23Комментарии  ·  Источник: benoitc/gunicorn

Привет

У меня есть проект TensorFlow 2.0, перед которым стоит крошечный Flask API, поэтому я могу делать запросы к модели через HTTP-вызовы с предварительной обработкой данных, уже выполненной в API. Я выбрал Gunicorn для запуска моего приложения Flask / TensorFlow в докер-контейнере. К сожалению, рабочий процесс, созданный Gunicorn, зависает в контейнере, пока его не убьет Gunicorn. Сервер никогда не запускается, и я не могу отправлять к нему запросы. Более того, та же самая установка Gunicorn безупречно работает вне докера, на моем хост-компьютере.

Журналы Docker (он просто зависает и через долгое время выдает ошибку тайм-аута)

[2019-10-03 18:03:05 +0000] [1] [INFO] Starting gunicorn 19.9.0
[2019-10-03 18:03:05 +0000] [1] [INFO] Listening at: http://127.0.0.1:8000 (1)
[2019-10-03 18:03:05 +0000] [1] [INFO] Using worker: sync
[2019-10-03 18:03:05 +0000] [8] [INFO] Booting worker with pid: 8
2019-10-03 18:03:08.126584: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2019-10-03 18:03:08.130017: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 3392000000 Hz
2019-10-03 18:03:08.130306: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x55fbb23fb2d0 executing computations on platform Host. Devices:
2019-10-03 18:03:08.130365: I tensorflow/compiler/xla/service/service.cc:175]   StreamExecutor device (0): Host, Default Version

dockerfile:

FROM python

RUN pip install gunicorn

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD [ "gunicorn", "--chdir", "src", "api:app" ]

api.py:

from flask import Flask, request
import inference

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def predict():
    if request.method == 'GET':
        return 'POST a json payload of {"imageBase64": "base64base64base64"} to this address to predict.'
    try:
        result = inference.run(request.json['imageBase64'])
        return result
    except Exception as e:
        return {'error': str(e)}, 500

if __name__ == "__main__":
    app.run()
else:
    print('\n * Server ready!')

inference.py

# Import packages
from __future__ import absolute_import, division, print_function, unicode_literals

import os
import tensorflow as tf
from tensorflow import keras
import PIL
import numpy as np
from io import BytesIO
import base64
import json

print("TensorFlow version is ", tf.__version__)

# Set variables
##########################################################################################
##########################################################################################

model_name = 'catsdogs'

base_dir = os.path.join(os.path.dirname(__file__), '..')
model_dir = os.path.join(base_dir, 'models')

##########################################################################################
##########################################################################################

# Load model
model = keras.models.load_model(os.path.join(model_dir, model_name + '.h5'))

# Load metadata
with open(os.path.join(model_dir, model_name + '_metadata.json')) as metadataFile:
    metadata = json.load(metadataFile)

# Split metadata
labels = metadata['training_labels']
image_size = metadata['image_size']

# Exported function for inference
def run(imgBase64):
    # Decode the base64 string
    image = PIL.Image.open(BytesIO(base64.b64decode(imgBase64)))

    # Pepare image
    image = image.resize((image_size, image_size), resample=PIL.Image.BILINEAR)
    image = image.convert("RGB")

    # Run prediction
    tensor = tf.cast(np.array(image), tf.float32) / 255.
    tensor = tf.expand_dims(tensor, 0, name=None)
    result = model.predict(tensor, steps=1)

    # Combine result with labels
    labeledResult = {}
    for i, label in enumerate(labels):
        labeledResult[label] = float(result[0][labels[label]])

    return labeledResult

Я искал решение этой проблемы целую вечность и не смог ничего придумать, любая помощь была бы очень признательна.

Спасибо!

Feedback Requested FeaturWorker FeaturIPC PlatforDocker

Самый полезный комментарий

Была такая же проблема. Насколько я могу догадаться из моих собственных журналов, похоже, что tensorflow использует gevent , и вы не можете одновременно использовать gevent в gunicorn . Флаги --workers и --threads не имеют для меня никакого значения, но изменение с --worker-class=gevent на --worker-class=gthread устранило проблему для меня. Спасибо @javabrett

Все 23 Комментарий

Ваша установка Docker ограничивает максимальный объем памяти, доступной для контейнера?

Испытывает то же самое. Я не думаю, что виноват Gunicorn. Я получаю ту же ошибку при запуске python3 api.py из оболочки bash в контейнере.

@tlaanemaa, ты можешь подтвердить, что говорит @mackdelany ?

Привет. Извини, что так исчез.

Моя установка немного ограничивает оперативную память Docker, но то же самое произошло, даже когда я снял ограничение.

Я попробую запустить api-файл без пулемета и отправлю отчет.

Спасибо!

@tlaanemaa есть новости об этом?

@benoitc Heya
Извините, я увлекся другими вещами, и у меня не было времени углубляться в это.
Я попробую сегодня ткнуть в это и вернуться к тебе

Поэтому я попытался запустить приложение без пулемета в контейнере, и это сработало.
Ниже приведен бит CMD моего файла Dockerfile.

Работает:

CMD [ "python", "src/api.py" ]

Журналы:

2019-12-02 11:40:45.649503: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2019-12-02 11:40:45.653496: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 2208000000 Hz
2019-12-02 11:40:45.653999: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x55f969cf6a40 executing computations on platform Host. Devices:
2019-12-02 11:40:45.654045: I tensorflow/compiler/xla/service/service.cc:175]   StreamExecutor device (0): Host, Default Version
TensorFlow version is  2.0.0
 * Serving Flask app "api" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Не работает:

CMD [ "gunicorn", "--chdir", "src", "api:app" ]

Журналы:

[2019-12-02 11:39:22 +0000] [1] [INFO] Starting gunicorn 20.0.4
[2019-12-02 11:39:22 +0000] [1] [INFO] Listening at: http://127.0.0.1:8000 (1)
[2019-12-02 11:39:22 +0000] [1] [INFO] Using worker: sync
[2019-12-02 11:39:22 +0000] [9] [INFO] Booting worker with pid: 9
2019-12-02 11:39:24.041188: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2019-12-02 11:39:24.046495: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 2208000000 Hz
2019-12-02 11:39:24.047129: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x5623e18b5200 executing computations on platform Host. Devices:
2019-12-02 11:39:24.047183: I tensorflow/compiler/xla/service/service.cc:175]   StreamExecutor device (0): Host, Default Version

Кроме того, я сделал репозиторий открытым, так что вы можете покопаться, если хотите.
Может быть полезно

https://gitlab.com/tlaanemaa/image-classifier

Listening at: http://127.0.0.1:8000 (1)

Может быть проблема в том, что Gunicorn слушает localhost внутри контейнера, поэтому к нему нельзя добраться извне?

Я так не думаю, потому что приложение flask делало то же самое, и оно работало.
Кроме того, версия gunicorn не регистрирует версию тензорного потока, что как бы предполагает, что проблема возникает в этой строке журнала в коде. Если вы работаете без пулеметчика, просто наберите флакон, и он это зарегистрирует.
TensorFlow version is 2.0.0

что он говорит на уровне отладки?

@tlaanemaa, как @CaselIT, похоже, что ваш клиент не может достичь порта Gunicorn через сеть Docker.

Можете ли вы попробовать запустить Gunicorn с аргументом -b 0.0.0.0:8000 ?

Я не думаю, что проблема кроется в сети, потому что, по крайней мере, из журналов кажется, что сервер вообще не запускается, поскольку он никогда не попадает в строки журнала, которые появляются после импорта тензорного потока.

Тем не менее я попробовал ваше предложение, но это дает мне ошибку

CMD [ "gunicorn", "-b", "0.0.0.0:8000", "--chdir", "src", "api:app" ]

_Бревно_

usage: gunicorn [OPTIONS] [APP_MODULE]
gunicorn: error: unrecognized arguments: -d

Если вы хотите попробовать себя, образ comtainer доступен по адресу registry.gitlab.com/tlaanemaa/image-classifier.

@tlaanemaa, можете ли вы повторно Dockerfile , команду построения образа и команду запуска контейнера?

@javabrett Конечно

  • Dockerfile: https://gitlab.com/tlaanemaa/image-classifier/blob/master/Dockerfile
  • Команда сборки: docker build -t tlaanemaa/image-classifier .
  • Контейнер запускается через Portainer, к сожалению, я не уверен, какую команду он использует. Ничего сумасшедшего там не делается, зато стандартная штука, порт 8000 перенаправлен.

_Dockerfile на момент публикации: _

FROM python:3.7

RUN pip install gunicorn

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD [ "gunicorn", "-b", "0.0.0.0:8000", "--chdir", "src", "api:app" ]

какой полный журнал докера, можете ли вы вставить командную строку, которую он наконец использует?

Если он не делает что-то, от чего нельзя отказаться во время отладки этой проблемы, можете ли вы сейчас запустить его без Portainer?

Это работает для меня, Docker Desktop для Mac 2.1.0.5:

docker build -t tlaanemaa/image-classifier .
docker run -it --rm -p 8000:8000 tlaanemaa/image-classifier

Принимает запросы POST .

Пожалуйста, запустите и опубликуйте полный вывод и результат.

Я попробовал, и теперь он работает.
Может быть, это был исправлен флаг -b ?

Большое спасибо!

Что интересно, когда я делаю запросы POST, запросы выполняются быстро, а запросы GET очень медленные. После некоторого времени выполнения запросов GET они становятся быстрыми, но затем POST становится очень медленным, и рабочий процесс истекает. Как только он отвечает на этот POST, POST снова выполняется быстро, а GET - медленно. Кажется, что он может сделать один быстро, и ему нужно время, чтобы переключиться: D

это журналы, когда GET выполняется быстро, а POST - медленно из-за времени ожидания рабочего:

[2020-01-10 09:34:46 +0000] [1] [CRITICAL] WORKER TIMEOUT (pid:72)
[2020-01-10 09:34:46 +0000] [72] [INFO] Worker exiting (pid: 72)
[2020-01-10 09:34:47 +0000] [131] [INFO] Booting worker with pid: 131
TensorFlow version is  2.0.0
2020-01-10 09:34:48.946351: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2020-01-10 09:34:48.951124: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 2208000000 Hz
2020-01-10 09:34:48.951612: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x56481dbabd80 executing computations on platform Host. Devices:
2020-01-10 09:34:48.951665: I tensorflow/compiler/xla/service/service.cc:175]   StreamExecutor device (0): Host, Default Version

 * Server ready!

Кроме того, в некоторых ситуациях журнал * Server ready! не отображается в журналах докеров. Это тоже могло ввести в заблуждение. Не уверен, что вызывает это

Текущий сервер в вашем Docker будет настроен однопоточным / синхронизирующим, что будет тривиально сделать занятым / блокирующим, поэтому, вероятно, вы это видите. Попробуйте добавить несколько аргументов, например --workers=2 --threads=4 --worker-class=gthread .

Спасибо @javabrett
Это исправило!

Была такая же проблема. Насколько я могу догадаться из моих собственных журналов, похоже, что tensorflow использует gevent , и вы не можете одновременно использовать gevent в gunicorn . Флаги --workers и --threads не имеют для меня никакого значения, но изменение с --worker-class=gevent на --worker-class=gthread устранило проблему для меня. Спасибо @javabrett

Привет! Как сопровождающий gevent и участник этого проекта, я могу категорически заявить, что gevent и gunicorn хорошо работают вместе. Различные библиотеки могут мешать, но это не вина Gunicorn или gevent. Пожалуйста, откройте новый выпуск, если это не ваш случай. Спасибо!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги