Gunicorn: Gunicorn + Flask + Tensorflow no contêiner do Docker não funciona

Criado em 3 out. 2019  ·  23Comentários  ·  Fonte: benoitc/gunicorn

Olá

Eu tenho um projeto TensorFlow 2.0 que tem uma pequena API Flask na frente para que eu possa fazer solicitações ao modelo por meio de chamadas HTTP com pré-processamento de dados já feito na API. Escolhi o Gunicorn para executar meu aplicativo Flask / TensorFlow em um contêiner do docker. Infelizmente, o processo de trabalho que Gunicorn cria fica suspenso no contêiner até ser morto por Gunicorn. O servidor nunca é inicializado e não posso fazer solicitações a ele. Além disso, a mesma configuração do Gunicorn funciona perfeitamente fora do docker, em minha máquina host.

Registros do Docker (ele simplesmente fica pendurado e imprime um erro de tempo limite após um longo tempo)

[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

Eu procuro por uma solução para isso há anos e não consegui encontrar nada, qualquer ajuda seria muito apreciada.

Obrigado!

Feedback Requested FeaturWorker FeaturIPC PlatforDocker

Comentários muito úteis

Tive o mesmo problema. Tanto quanto posso adivinhar com meus próprios registros, parece que tensorflow está usando gevent , e você não pode usar gevent ao mesmo tempo em gunicorn . Os sinalizadores --workers e --threads não fazem nenhuma diferença para mim, mas mudar de --worker-class=gevent para --worker-class=gthread corrigiu o problema para mim. Obrigado @javabrett

Todos 23 comentários

A configuração do Docker está limitando a memória máxima disponível para o contêiner?

Experimentando o mesmo. Eu não acho que Gunicorn seja o culpado. Recebo o mesmo erro ao executar python3 api.py de um shell bash no contêiner.

@tlaanemaa você pode confirmar o que @mackdelany diz?

Ei. Desculpe por desaparecer assim.

Minha configuração está limitando um pouco a RAM do Docker, mas a mesma coisa aconteceu mesmo quando removi a limitação.

Vou tentar executar o arquivo api sem gunicorn e relatar de volta.

Obrigado!

@tlaanemaa alguma notícia sobre isso?

@benoitc Heya
Desculpe, fui levado com outras coisas e não tive tempo para ir mais longe com isso.
Vou tentar cutucar isso hoje e retorno para você

Tentei executar o aplicativo sem gunicorn no contêiner e funcionou.
Abaixo está o bit CMD do meu Dockerfile

Trabalho:

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

Histórico:

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)

Não funciona:

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

Histórico:

[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

Além disso, abri o repositório para que você possa pesquisar se quiser.
Pode ser útil

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

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

O problema pode ser que o gunicorn está escutando localhost dentro de um contêiner, de forma que não pode ser alcançado de fora?

Acho que não porque o aplicativo Flask estava fazendo o mesmo e estava funcionando.
Além disso, a versão do gunicorn não registra a versão do tensorflow, o que sugere que o problema ocorre antes dessa linha de log no código. Ao executar sem gunicorn, apenas frasco, então ele registra isso.
TensorFlow version is 2.0.0

o que diz no nível de depuração?

@tlaanemaa, como a rede daemon do Docker está configurada? Por comentário de @CaselIT , parece provável que seu cliente não seja capaz de alcançar a porta Gunicorn pela rede Docker.

Você pode tentar iniciar o Gunicorn com o arg -b 0.0.0.0:8000 ?

Não acho que o problema esteja na rede porque parece, pelo menos a partir dos logs, que o servidor não está iniciando, pois nunca atinge as linhas de log que vêm após a importação de tensorflow

Mesmo assim tentei sua sugestão, mas me deu um erro

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

_Registro_

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

Se você quiser experimentar, a imagem do comtainer está disponível em registry.gitlab.com/tlaanemaa/image-classifier

@tlaanemaa, você pode repassar seu Dockerfile , comando de compilação de imagem e comando de execução de contêiner atualizados?

@javabrett Claro

  • Dockerfile: https://gitlab.com/tlaanemaa/image-classifier/blob/master/Dockerfile
  • Comando de compilação: docker build -t tlaanemaa/image-classifier .
  • O contêiner é executado através do Portainer, infelizmente, não tenho certeza de qual comando ele usa para fazer isso. Nada louco está sendo feito lá embora, material padrão, a porta 8000 seja encaminhada.

_Dockerfile no momento da postagem: _

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" ]

qual é o log completo do docker, você pode colar a linha de comando que ele finalmente está usando?

A menos que ele esteja fazendo algo que não pode ser abandonado durante a depuração desse problema, você pode executá-lo sem o Portainer por enquanto?

Isso funciona para mim, Docker Desktop for Mac 2.1.0.5:

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

Aceita solicitações de POST .

Execute e publique a produção e o resultado completos.

Eu tentei e funciona agora.
Poderia ter sido o sinalizador -b que o corrigiu?

Muito obrigado!

O que é interessante agora é que, quando faço solicitações POST, as solicitações são rápidas, mas as solicitações GET são muito lentas. Depois de fazer solicitações GET, elas ficam mais rápidas, mas o POST fica muito lento e o trabalho expira. Depois de responder a esse POST, os POSTs são rápidos novamente e os GETs são lentos. Parece que ele pode fazer um rápido e leva tempo para mudar: D

estes são os registros quando GET é rápido e POST é lento porque o trabalhador atinge o tempo limite:

[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!

Além disso, em algumas situações, o log * Server ready! não parece passar pelos logs do docker. Isso também poderia ser enganoso. Não tenho certeza do que está causando isso

O servidor atual em seu Docker será configurado single / sync threaded, o que será trivial para tornar ocupado / bloqueado, então é provável que você esteja vendo isso. Tente adicionar alguns argumentos como --workers=2 --threads=4 --worker-class=gthread .

Obrigado @javabrett
Isso resolveu tudo!

Tive o mesmo problema. Tanto quanto posso adivinhar com meus próprios registros, parece que tensorflow está usando gevent , e você não pode usar gevent ao mesmo tempo em gunicorn . Os sinalizadores --workers e --threads não fazem nenhuma diferença para mim, mas mudar de --worker-class=gevent para --worker-class=gthread corrigiu o problema para mim. Obrigado @javabrett

Oi! Como mantenedor do gevent e contribuidor deste projeto, posso afirmar categoricamente que gevent e gunicorn funcionam bem juntos. Várias bibliotecas podem interferir, mas isso não é culpa do gunicorn ou do gevent. Abra um novo problema se esse não for o seu caso. Obrigado!

Esta página foi útil?
0 / 5 - 0 avaliações