Gunicorn: Gunicorn + Flask + Tensorflow en el contenedor Docker no funciona

Creado en 3 oct. 2019  ·  23Comentarios  ·  Fuente: benoitc/gunicorn

Hola

Tengo un proyecto de TensorFlow 2.0 que tiene una pequeña API de Flask frente a él para poder realizar solicitudes al modelo a través de llamadas HTTP con preprocesamiento de datos ya realizado en la API. Elegí Gunicorn para ejecutar mi aplicación Flask / TensorFlow en un contenedor acoplable. Lamentablemente, el proceso de trabajo que crea Gunicorn se cuelga en el contenedor hasta que Gunicorn lo mata. El servidor nunca aparece y no puedo realizar solicitudes. Además, la misma configuración de Gunicorn funciona perfectamente fuera de la ventana acoplable, en mi máquina host.

Registros de Docker (simplemente se cuelga allí e imprime un error de tiempo de espera después de mucho tiempo)

[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

He buscado una solución a esto durante años y no he podido encontrar nada, cualquier ayuda sería muy apreciada.

¡Gracias!

Feedback Requested FeaturWorker FeaturIPC PlatforDocker

Comentario más útil

Tuve el mismo problema. Por lo que puedo adivinar por mis propios registros, parece que tensorflow está usando gevent , y no puedes usar gevent al mismo tiempo en gunicorn . Las banderas --workers y --threads no hacen ninguna diferencia para mí, pero cambiar de --worker-class=gevent a --worker-class=gthread solucionó el problema. Gracias @javabrett

Todos 23 comentarios

¿Su configuración de Docker limita la memoria máxima disponible para el contenedor?

Experimentando lo mismo. Sin embargo, no creo que Gunicorn tenga la culpa. Recibo el mismo error cuando ejecuto python3 api.py desde un shell bash en el contenedor.

@tlaanemaa ¿puedes confirmar lo que dice @mackdelany ?

Oye. Perdón por desaparecer así.

Mi configuración está limitando un poco la RAM de Docker, pero sucedió lo mismo incluso cuando eliminé la limitación.

Intentaré ejecutar el archivo api sin gunicorn y volver a informar.

¡Gracias!

@tlaanemaa alguna noticia al respecto?

@benoitc Heya
Lo siento, me he dejado llevar por otras cosas y no he tenido tiempo de ir más lejos con esto.
Intentaré pinchar esto hoy y te responderé

Así que intenté ejecutar la aplicación sin gunicorn en el contenedor y funcionó.
A continuación se muestra el bit CMD de mi Dockerfile

Obras:

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

Registros:

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)

No funciona:

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

Registros:

[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

Además, he abierto el repositorio para que puedas hurgar si quieres.
Podría ser de ayuda

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

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

¿Podría el problema ser que gunicorn está escuchando localhost dentro de un contenedor, por lo que no se puede alcanzar desde el exterior?

No lo creo porque la aplicación del matraz estaba haciendo lo mismo y estaba funcionando.
Además, la versión de gunicorn no registra la versión de tensorflow, lo que sugiere que el problema ocurre antes de esa línea de registro en el código. Cuando se ejecuta sin gunicorn, solo frasco, luego registra eso.
TensorFlow version is 2.0.0

¿Qué dice a nivel de depuración?

@tlaanemaa ¿cómo está configurada la red de su demonio Docker? Según el comentario de @CaselIT , parece probable que su cliente no pueda llegar al puerto Gunicorn a través de la red Docker.

¿Puedes intentar iniciar Gunicorn con el argumento -b 0.0.0.0:8000 ?

No creo que el problema esté en la red porque parece, al menos a partir de los registros, que el servidor no se está iniciando en absoluto, ya que nunca llega a las líneas de registro que vienen después de la importación de tensorflow.

Sin embargo probé tu sugerencia pero me da un error.

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

_Tronco_

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

Si desea probarlo usted mismo, la imagen del contenedor está disponible en registry.gitlab.com/tlaanemaa/image-classifier

@tlaanemaa ¿puedes volver a publicar tu Dockerfile actualizado, el comando de construcción de imagen y el comando de ejecución de contenedor?

@javabrett Seguro

  • Dockerfile: https://gitlab.com/tlaanemaa/image-classifier/blob/master/Dockerfile
  • Comando de compilación: docker build -t tlaanemaa/image-classifier .
  • El contenedor se ejecuta a través de Portainer, lamentablemente no estoy seguro de qué comando usa para hacer eso. Aunque no se está haciendo nada loco, cosas estándar, se reenvía el puerto 8000.

_Dockerfile en el momento de la publicación: _

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

¿Cuál es el registro completo de la ventana acoplable? ¿Puede pegar la línea de comando que finalmente está usando?

A menos que esté haciendo algo que no se puede renunciar durante la depuración de este problema, ¿puede ejecutarlo sin Portainer por ahora?

Esto funciona para mí, Docker Desktop para Mac 2.1.0.5:

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

Acepta solicitudes POST .

Ejecute y publique la salida completa y el resultado.

Lo probé y funciona ahora.
¿Podría haber sido la bandera -b que lo arregló?

¡Muchas gracias!

Lo que es interesante ahora es que cuando hago solicitudes POST, las solicitudes son rápidas, pero las solicitudes GET son muy lentas. Después de un tiempo de hacer las solicitudes GET, estas se vuelven rápidas, pero luego POST se vuelve muy lenta y el trabajador agota el tiempo de espera. Una vez que responde a ese POST, los POST vuelven a ser rápidos y los GET lentos. Parece que puede hacer uno rápido y lleva tiempo cambiar: D

Estos son los registros cuando GET es rápido y POST es lento porque el trabajador agota el tiempo de espera:

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

Además, en algunas situaciones, el registro * Server ready! no parece aparecer en los registros de la ventana acoplable. Eso también podría haber sido engañoso. No estoy seguro de qué está causando eso aunque

El servidor actual en su Docker se configurará con un solo subproceso / sincronización, lo que será trivial para ocuparlo / bloquearlo, por lo que es probable que lo esté viendo. Intente agregar algunos argumentos como --workers=2 --threads=4 --worker-class=gthread .

Gracias @javabrett
¡Eso lo arregló!

Tuve el mismo problema. Por lo que puedo adivinar por mis propios registros, parece que tensorflow está usando gevent , y no puedes usar gevent al mismo tiempo en gunicorn . Las banderas --workers y --threads no hacen ninguna diferencia para mí, pero cambiar de --worker-class=gevent a --worker-class=gthread solucionó el problema. Gracias @javabrett

¡Hola! Como mantenedor de gevent y colaborador de este proyecto, puedo afirmar categóricamente que gevent y gunicorn funcionan bien juntos. Varias bibliotecas pueden interferir, pero eso no es culpa de Gunicorn ni de Gevent. Abra una nueva edición si ese no es su caso. ¡Gracias!

¿Fue útil esta página
0 / 5 - 0 calificaciones