Gunicorn: Gunicorn + Flask + Tensorflow im Docker-Container funktioniert nicht

Erstellt am 3. Okt. 2019  ·  23Kommentare  ·  Quelle: benoitc/gunicorn

Hallo

Ich habe ein TensorFlow 2.0-Projekt mit einer winzigen Flask-API davor, damit ich über HTTP-Aufrufe Anfragen an das Modell stellen kann, wobei die Datenvorverarbeitung bereits in der API erfolgt ist. Ich habe Gunicorn gewählt, um meine Flask/TensorFlow-Anwendung in einem Docker-Container auszuführen. Leider hängt der von Gunicorn erstellte Worker-Prozess im Container, bis er von Gunicorn getötet wird. Der Server kommt nie hoch und ich kann keine Anfragen an ihn stellen. Darüber hinaus funktioniert das gleiche Gunicorn-Setup auch außerhalb des Dockers auf meinem Host-Rechner einwandfrei.

Docker-Logs (es hängt einfach dort und druckt nach langer Zeit einen Timeout-Fehler)

[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

Dockerdatei:

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

schlussfolgerung.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

Ich habe seit Ewigkeiten nach einer Lösung für dieses Problem gesucht und habe es nicht geschafft, etwas zu finden, jede Hilfe wäre sehr dankbar.

Vielen Dank!

Feedback Requested FeaturWorker FeaturIPC PlatforDocker

Hilfreichster Kommentar

Hatte das gleiche Problem. Soweit ich aus meinen eigenen Protokollen erraten kann, sieht es so aus, als würde tensorflow gevent und Sie können gevent gleichzeitig in gunicorn . Die Flags --workers und --threads machen für mich keinen Unterschied, aber die Änderung von --worker-class=gevent zu --worker-class=gthread das Problem für mich behoben. Danke @javabrett

Alle 23 Kommentare

Beschränkt Ihr Docker-Setup den maximalen Speicher, der dem Container zur Verfügung steht?

Das gleiche erleben. Ich glaube aber nicht, dass Gunicorn schuld ist. Ich erhalte den gleichen Fehler, wenn ich python3 api.py aus einer Bash-Shell im Container ausführe.

@tlaanemaa kannst du bestätigen, was @mackdelany sagt?

Hey. Tut mir leid, dass ich so verschwunden bin.

Mein Setup schränkt den RAM von Docker ein wenig ein, aber das gleiche passierte auch, als ich die Einschränkung entfernte.

Ich werde versuchen, die API-Datei ohne Gunicorn auszuführen und mich zurückmelden.

Vielen Dank!

@tlaanemaa irgendwelche Neuigkeiten dazu?

@benoitc Heya
Entschuldigung, ich habe mich von anderen Dingen mitreißen lassen und hatte keine Zeit, damit weiterzumachen.
Ich versuche das heute mal reinzustecken und melde mich bei dir

Also habe ich versucht, die App ohne Gunicorn im Container auszuführen, und das hat funktioniert.
Unten ist das CMD-Bit meines Dockerfiles

Funktioniert:

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

Protokolle:

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)

Funktioniert nicht:

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

Protokolle:

[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

Außerdem habe ich das Repository geöffnet, damit Sie herumstöbern können, wenn Sie möchten.
Könnte hilfreich sein

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

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

Könnte das Problem sein, dass gunicorn in einem Container auf localhost hört, sodass es von außen nicht erreicht werden kann?

Ich glaube nicht, weil die Flaschen-App dasselbe tat und das funktionierte.
Außerdem protokolliert die Gunicorn-Version keine Tensorflow-Version, was darauf hindeutet, dass das Problem vor dieser Protokollzeile im Code auftritt. Beim Laufen ohne Gunicorn einfach Flasche, dann protokolliert es das.
TensorFlow version is 2.0.0

Was sagt es auf Debug-Ebene?

@tlaanemaa wie ist Ihr Docker-Daemon-Netzwerk konfiguriert? Laut Kommentar von

Können Sie versuchen, Gunicorn mit dem Argument -b 0.0.0.0:8000 starten?

Ich glaube nicht, dass das Problem im Netzwerk liegt, da es zumindest aus den Protokollen so aussieht, dass der Server überhaupt nicht startet, da er nie auf Protokollzeilen trifft, die nach dem Tensorflow-Import kommen

Trotzdem habe ich deinen Vorschlag ausprobiert, aber er gibt mir einen Fehler

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

_Protokoll_

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

Wenn Sie es selbst versuchen möchten, steht das Comtainer-Image unter Registry.gitlab.com/tlaanemaa/image-classifier zur Verfügung

@tlaanemaa können Sie Ihr aktualisiertes Dockerfile , den Image-Build-Befehl und den Container-Run-Befehl erneut veröffentlichen?

@javabrett Klar

_Dockerfile zum Zeitpunkt der Veröffentlichung:_

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

Was ist das vollständige Protokoll von Docker, können Sie die Befehlszeile einfügen, die es schließlich verwendet?

Können Sie es vorerst ohne Portainer ausführen, es sei denn, es tut etwas, auf das beim Debuggen dieses Problems nicht verzichtet werden kann?

Das funktioniert bei mir, Docker Desktop für Mac 2.1.0.5:

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

Akzeptiert POST Anfragen.

Bitte führen Sie die vollständige Ausgabe und das Ergebnis aus und posten Sie sie.

Ich habe es probiert und jetzt funktioniert es.
Könnte es das -b Flag gewesen sein, das das Problem behoben hat?

Danke vielmals!

Was jetzt interessant ist, ist, dass wenn ich POST-Anfragen mache, diese schnell sind, aber GET-Anfragen sind super langsam. Nach einer Weile der Ausführung von GET-Anfragen werden diese schnell, aber dann wird POST sehr langsam und der Worker läuft ab. Sobald es auf diesen POST reagiert, sind POSTs wieder schnell und GETs langsam. Es scheint, als ob es schnell gehen kann und es dauert, bis es umschaltet :D

Dies sind die Protokolle, wenn GET schnell und POST langsam ist, weil Worker eine Zeitüberschreitung hat:

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

Außerdem scheint in manchen Situationen das * Server ready! Protokoll in den Docker-Protokollen nicht durchzukommen. Das hätte auch irreführend sein können. Ich bin mir nicht sicher, was das verursacht

Der aktuelle Server in Ihrem Docker wird als Single/Sync-Thread konfiguriert, was für ein Besetzt/Blockieren trivial ist, daher ist es wahrscheinlich, dass Sie dies sehen. Versuchen Sie, einige Argumente wie --workers=2 --threads=4 --worker-class=gthread hinzuzufügen.

Danke @javabrett
Das hat es behoben!

Hatte das gleiche Problem. Soweit ich aus meinen eigenen Protokollen erraten kann, sieht es so aus, als würde tensorflow gevent und Sie können gevent gleichzeitig in gunicorn . Die Flags --workers und --threads machen für mich keinen Unterschied, aber die Änderung von --worker-class=gevent zu --worker-class=gthread das Problem für mich behoben. Danke @javabrett

Hi! Als Betreuer von gevent und als Mitwirkender an diesem Projekt kann ich kategorisch sagen, dass gevent und gunicorn gut zusammenarbeiten. Verschiedene Bibliotheken können stören, aber das liegt weder an gunicorn noch an gevent. Eröffnen Sie bitte eine neue Ausgabe, wenn dies bei Ihnen nicht der Fall ist. Vielen Dank!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen