Python-Anwendungen für Cloud Run optimieren

In diesem Leitfaden werden Optimierungen für Cloud Run-Dienste beschrieben, die in der Programmiersprache Python geschrieben sind. Außerdem finden Sie hier Hintergrundinformationen, damit Sie die Vor- und Nachteile einiger Optimierungen nachvollziehen können. Die Informationen auf dieser Seite ergänzen die allgemeinen Optimierungstipps, die auch für Python gelten.

Viele der Best Practices und Optimierungen in herkömmlichen webbasierten Python-Anwendungen beziehen sich auf folgende Probleme:

  • Verarbeiten gleichzeitiger Anfragen (sowohl thread-basierter als auch nicht blockierender E/A-Vorgänge)
  • Reduzieren der Antwortlatenz durch Verbindungspooling und Stapelverarbeitung nicht kritischer Funktionen, wie z. B. das Senden von Traces und Messwerten an Hintergrundaufgaben.

Container-Image optimieren

Optimieren Sie das Container-Image, um die Lade- und Startzeiten zu reduzieren. Verwenden Sie dazu die folgenden Methoden:

  • Beim Start geladene Dateien minimieren
  • WSGI-Server optimieren

Beim Start geladene Dateien minimieren

Um die Startzeit zu optimieren, sollten Sie beim Start nur die erforderlichen Dateien laden und ihre Größe reduzieren. Bei großen Dateien haben Sie folgende Möglichkeiten:

  • Speichern Sie große Dateien wie KI-Modelle in Ihrem Container, um schneller darauf zugreifen zu können. Laden Sie diese Dateien nach dem Start oder zur Laufzeit.

  • Erwägen Sie, Cloud Storage-Volume-Bereitstellungen für große Dateien zu konfigurieren, die beim Start nicht kritisch sind, z. B. Medien-Assets.

  • Importieren Sie nur die erforderlichen Untermodule aus umfangreichen Abhängigkeiten oder importieren Sie Module bei Bedarf in Ihrem Code, anstatt sie beim Start der Anwendung zu laden.

WSGI-Server optimieren

Python hat die Art und Weise, wie Anwendungen mit Webservern interagieren können, durch Implementierung des WSGI-Standards PEP-3333 standardisiert. Einer der gängigsten WSGI-Server ist gunicorn, der in einem Großteil der Beispieldokumentation verwendet wird.

gunicorn optimieren

Fügen Sie Dockerfile das folgende CMD hinzu, um den Aufruf von gunicorn zu optimieren:

CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app

Wenn Sie diese Einstellungen ändern möchten, passen Sie die Anzahl der Worker und Threads pro Anwendung an. Versuchen Sie beispielsweise, eine Anzahl von Workern zu verwenden, die den verfügbaren Kernen entspricht, vergewissern Sie sich, dass es eine Leistungsverbesserung gibt, und passen Sie dann die Anzahl der Threads an. Das Festlegen von zu vielen Workern oder Threads kann sich negativ auf die Kaltstartlatenz, den verbrauchten Arbeitsspeicher, die Anfragen pro Sekunde usw. auswirken.

Standardmäßig startet gunicorn beim Starten Worker und überwacht den angegebenen Port, noch bevor Ihr Anwendungscode ausgewertet wird. In diesem Fall sollten Sie benutzerdefinierte Startprüfungen für Ihren Dienst einrichten, da die Standardstartprüfung von Cloud Run eine Containerinstanz sofort als fehlerfrei kennzeichnet, sobald sie $PORT überwacht.

Wenn Sie dieses Verhalten ändern möchten, können Sie gunicorn mit der Einstellung --preload aufrufen, um Ihren Anwendungscode vor der Überwachung zu prüfen. Das kann helfen bei:

  • Schweregrad der Laufzeitfehler bei der Bereitstellung ermitteln
  • Speicherressourcen sparen

Sie sollten überlegen, was Ihre Anwendung vorab lädt, bevor Sie dies hinzufügen.

Andere WSGI-Server

Sie sind nicht auf die Verwendung von gunicorn zum Ausführen von Python in Containern beschränkt. Sie können einen beliebigen WSGI- oder ASGI-Webserver verwenden, solange der Container den HTTP-Port $PORT gemäß dem Containerlaufzeitvertrag überwacht.

Gängige Alternativen sind uwsgi, uvicorn und waitress.

Bei der folgenden Datei namens main.py mit dem app-Objekt würden die folgenden Aufrufe einen WSGI-Server starten:

# uwsgi: pip install pyuwsgi
uwsgi --http :$PORT -s /tmp/app.sock --manage-script-name --mount /app=main:app

# uvicorn: pip install uvicorn
uvicorn --port $PORT --host 0.0.0.0 main:app

# waitress: pip install waitress
waitress-serve --port $PORT main:app

Diese können entweder als CMD exec-Zeile in einem Dockerfile oder als web:-Eintrag in Procfile hinzugefügt werden, wenn Google Cloud Buildpacks verwendet wird.

Anwendungen optimieren

In Ihrem Cloud Run-Dienstcode können Sie auch die Startzeiten und die Arbeitsspeichernutzung optimieren.

Threads reduzieren

Sie können den Arbeitsspeicher optimieren, indem Sie die Anzahl der Threads reduzieren, indem Sie nicht blockierende reaktive Strategien verwenden und Hintergrundaktivitäten vermeiden. Vermeiden Sie außerdem das Schreiben in das Dateisystem, wie auf der Seite Allgemeine Tipps beschrieben.

Wenn Sie Hintergrundaktivitäten in Ihrem Cloud Run-Dienst unterstützen möchten, legen Sie für Ihren Cloud Run-Dienst die instanzbasierte Abrechnung fest. Dadurch können Sie Hintergrundaktivitäten außerhalb von Anfragen ausführen und haben dennoch Zugriff auf die CPU.

Startaufgaben reduzieren

Webbasierte Python-Anwendungen können während des Startvorgangs viele Aufgaben ausführen, z. B. Daten vorab laden, Cache vorbereiten und Verbindungspools einrichten. Wenn diese Aufgaben sequenziell ausgeführt werden, kann das lange dauern. Wenn sie jedoch parallel ausgeführt werden sollen, sollten Sie die Anzahl der CPU-Kerne erhöhen.

Cloud Run sendet eine echte Nutzeranfrage, um eine Kaltstartinstanz auszulösen. Bei Nutzern, denen eine Anfrage einer neu gestarteten Instanz zugewiesen wurde, kann es zu langen Verzögerungen kommen.

Sicherheit mit schlanken Basis-Images verbessern

Um die Sicherheit Ihrer Anwendung zu verbessern, sollten Sie ein schlankes Basis-Image mit weniger Paketen und Bibliotheken verwenden.

Wenn Sie Python nicht aus der Quelle in Ihren Containern installieren möchten, verwenden Sie ein offizielles Python-Basis-Image von Docker Hub. Diese Images basieren auf dem Debian-Betriebssystem.

Wenn Sie das python-Image von Docker Hub verwenden, sollten Sie die slim-Version verwenden. Diese Images sind kleiner, da sie nicht eine Reihe von Paketen enthalten, die zum Erstellen von Wheels verwendet werden, die für Ihre Anwendung vielleicht nicht erforderlich sind. Das python-Image enthält den GNU-C-Compiler, den Präprozessor und Kerndienstprogramme.

Zum Ermitteln der zehn größten Pakete in einem Basis-Image können Sie den folgenden Befehl ausführen:

DOCKER_IMAGE=python # or python:slim
docker run --rm ${DOCKER_IMAGE} dpkg-query -Wf '${Installed-Size}\t${Package}\t${Description}\n' | sort -n | tail -n10 | column -t -s $'\t'

Da weniger dieser untergeordneten Pakete vorhanden sind, bieten die slim-basierten Images auch weniger Angriffsfläche für potenzielle Sicherheitslücken. Einige dieser Images enthalten möglicherweise nicht die Elemente, die zur Erstellung von Wheels aus der Quelle erforderlich sind.

Sie können bestimmte Pakete wieder hinzufügen, indem Sie dem Dockerfile eine Zeile RUN apt install hinzufügen. Weitere Informationen finden Sie unter Systempakete in Cloud Run verwenden.

Es sind auch Optionen für Container verfügbar, die nicht auf Debian basieren. Die Option python:alpine kann zu einem wesentlich kleineren Container führen. Viele Python-Pakete haben jedoch möglicherweise keine vorkompilierten Wheels, die Alpine-basierte Systeme unterstützen. Die Unterstützung wird verbessert (siehe PEP-656), ist aber immer noch unterschiedlich. Sie können auch das distroless base image verwenden, das keine Paketmanager, Shells oder sonstigen Programme enthält.

Umgebungsvariable PYTHONUNBUFFERED für die Protokollierung verwenden

Wenn Sie ungepufferte Logs Ihrer Python-Anwendung sehen möchten, legen Sie die Umgebungsvariable PYTHONUNBUFFERED fest. Wenn Sie diese Variable festlegen, sind stdout- und stderr-Daten sofort in den Containerlogs sichtbar. Andernfalls werden sie in einem Puffer gespeichert, bis eine bestimmte Datenmenge erreicht ist oder der Stream geschlossen wird.

Nächste Schritte

Weitere Tipps finden Sie unter