Gunicorn: Gunicorn + Flask + Tensorflow dans le conteneur Docker ne fonctionnent pas

Créé le 3 oct. 2019  ·  23Commentaires  ·  Source: benoitc/gunicorn

Bonjour

J'ai un projet TensorFlow 2.0 qui a une minuscule API Flask devant lui afin que je puisse faire des requêtes au modèle via des appels HTTP avec un prétraitement des données déjà effectué dans l'API. J'ai choisi Gunicorn pour exécuter mon application Flask/TensorFlow dans un conteneur Docker. Malheureusement, le processus de travail créé par Gunicorn est suspendu dans le conteneur jusqu'à ce qu'il soit tué par Gunicorn. Le serveur ne se lance jamais et je ne peux pas lui faire de requêtes. De plus, la même configuration Gunicorn fonctionne parfaitement en dehors de Docker, sur ma machine hôte.

Journaux Docker (il se bloque et imprime une erreur de délai d'attente après une longue période)

[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

fichier docker :

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!')

inférence.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

J'ai cherché une solution à cela pendant des siècles et je n'ai pas réussi à trouver quoi que ce soit, toute aide serait extrêmement appréciée.

Merci!

Feedback Requested FeaturWorker FeaturIPC PlatforDocker

Commentaire le plus utile

A eu le même problème. Pour autant que je puisse le deviner d'après mes propres journaux, il semble que tensorflow utilise gevent , et vous ne pouvez pas utiliser gevent en même temps dans gunicorn . Les drapeaux --workers et --threads ne font aucune différence pour moi, mais passer de --worker-class=gevent à --worker-class=gthread résolu le problème pour moi. Merci @javabrett

Tous les 23 commentaires

Votre configuration Docker limite-t-elle la mémoire maximale disponible pour le conteneur ?

Vivre la même chose. Je ne pense pas que Gunicorn soit à blâmer cependant. J'obtiens la même erreur lors de l'exécution de python3 api.py partir d'un shell bash dans le conteneur.

@tlaanemaa pouvez-vous confirmer ce que dit

Hey. Désolé d'avoir disparu comme ça.

Ma configuration limite un peu la RAM de Docker, mais la même chose s'est produite même lorsque j'ai supprimé la limitation.

Je vais essayer d'exécuter le fichier api sans gunicorn et faire rapport.

Merci!

@tlaanemaa des nouvelles ?

@benoitc Heya
Désolé, j'ai été emporté par d'autres choses et je n'ai pas eu le temps d'aller plus loin avec ça.
Je vais essayer de faire ça aujourd'hui et je te reviens

J'ai donc essayé d'exécuter l'application sans gunicorn dans le conteneur et cela a fonctionné.
Ci-dessous se trouve le bit CMD de mon Dockerfile

Travaux:

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

Journaux :

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)

Ne fonctionne pas :

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

Journaux :

[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

De plus, j'ai ouvert le référentiel pour que vous puissiez fouiller si vous le souhaitez.
Pourrait être utile

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

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

Le problème pourrait-il être que gunicorn écoute localhost à l'intérieur d'un conteneur, il ne peut donc pas être atteint de l'extérieur?

Je ne pense pas parce que l'application Flask faisait la même chose et cela fonctionnait.
De plus, la version gunicorn n'enregistre pas la version tensorflow, ce qui suggère en quelque sorte que le problème se produit avant cette ligne de journal dans le code. Lors de l'exécution sans gunicorn, il suffit de flacon, puis il enregistre cela.
TensorFlow version is 2.0.0

que dit-il au niveau du débogage ?

@tlaanemaa comment votre réseau de démon Docker est-il configuré ? D'après le commentaire de @CaselIT, il semble probable que votre client ne soit pas en mesure d'atteindre le port Gunicorn via le réseau Docker.

Pouvez-vous essayer de démarrer Gunicorn avec l'argument -b 0.0.0.0:8000 ?

Je ne pense pas que le problème réside dans le réseau car il semble, à partir des journaux au moins, que le serveur ne démarre pas du tout car il n'atteint jamais les lignes de journal qui viennent après l'importation de tensorflow

Néanmoins j'ai essayé votre suggestion mais cela me donne une erreur

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

_Enregistrer_

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

Si vous voulez vous essayez de l'image comtainer est disponible à registry.gitlab.com/tlaanemaa/image-classifier

@tlaanemaa pouvez-vous republier votre Dockerfile , la commande de création d'image et la commande d'exécution de conteneur ?

@javabrett Bien sûr

  • Dockerfile : https://gitlab.com/tlaanemaa/image-classifier/blob/master/Dockerfile
  • Commande de construction : docker build -t tlaanemaa/image-classifier .
  • Le conteneur est exécuté via Portainer, je ne sais pas quelle commande il utilise pour le faire, malheureusement. Rien de fou n'est fait là-bas, les trucs standard, le port 8000 est transféré.

_Dockerfile au moment de la publication :_

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

quel est le journal complet de docker, pouvez-vous coller la ligne de commande qu'il utilise enfin ?

À moins qu'il ne fasse quelque chose qui ne peut pas être évité pendant le débogage de ce problème, pouvez-vous l'exécuter sans Portainer pour le moment ?

Cela fonctionne pour moi, Docker Desktop pour Mac 2.1.0.5 :

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

Accepte les demandes POST .

Veuillez exécuter et publier la sortie et le résultat complets.

Je l'ai essayé et ça marche maintenant.
Serait-ce le drapeau -b qui l'a corrigé ?

Merci beaucoup!

Ce qui est intéressant maintenant, c'est que lorsque je fais des requêtes POST, elles sont rapides mais les requêtes GET sont super lentes. Après un certain temps d'exécution des requêtes GET, celles-ci deviennent rapides, mais POST devient très lent et le temps de travail expire. Une fois qu'il a répondu à ce POST, les POST sont à nouveau rapides et les GET sont lents. On dirait qu'il peut en faire un rapidement et qu'il lui faut du temps pour changer :D

ce sont les journaux lorsque GET est rapide et POST est lent car le délai de travail expire :

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

De plus, dans certaines situations, le journal * Server ready! semble pas apparaître dans les journaux Docker. Cela aurait pu être trompeur aussi. Je ne sais pas ce qui cause ça

Le serveur actuel de votre Docker sera configuré à thread unique/synchrone, ce qui sera trivial pour être occupé/bloquant, il est donc probable que vous le voyiez. Essayez d'ajouter des arguments comme --workers=2 --threads=4 --worker-class=gthread .

Merci @javabrett
Cela a réglé le problème !

A eu le même problème. Pour autant que je puisse le deviner d'après mes propres journaux, il semble que tensorflow utilise gevent , et vous ne pouvez pas utiliser gevent en même temps dans gunicorn . Les drapeaux --workers et --threads ne font aucune différence pour moi, mais passer de --worker-class=gevent à --worker-class=gthread résolu le problème pour moi. Merci @javabrett

Salut! En tant que mainteneur de gevent et contributeur à ce projet, je peux affirmer catégoriquement que gevent et gunicorn fonctionnent bien ensemble. Diverses bibliothèques peuvent interférer, mais ce n'est pas la faute de gunicorn ou de gevent. Veuillez ouvrir un nouveau problème si ce n'est pas votre cas. Merci!

Cette page vous a été utile?
0 / 5 - 0 notes