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 bewährten Vorgehensweisen und Optimierungen in gängigen Python-Webanwendungen drehen sich um Folgendes:

  • 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, indem Sie folgende Methoden anwenden:

  • Minimieren Sie die beim Systemstart geladenen Dateien.
  • WSGI-Server optimieren

Minimieren Sie die beim Systemstart geladenen Dateien.

Um die Startzeit zu optimieren, sollten beim Start nur die benötigten Dateien geladen und deren Größe reduziert werden. Für große Dateien sollten Sie folgende Optionen in Betracht ziehen:

  • Speichern Sie große Dateien, wie z. B. KI-Modelle, in Ihrem Container, um einen schnelleren Zugriff zu ermöglichen. Erwägen Sie, diese Dateien nach dem Systemstart oder zur Laufzeit zu laden.

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

  • Importieren Sie nur die benötigten Untermodule aus umfangreichen Abhängigkeiten oder importieren Sie Module erst dann, wenn sie in Ihrem Code benötigt werden, 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 die folgenden CMD zu Dockerfile 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 erzeugt gunicorn beim Start Worker und lauscht auf dem 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

Bei Python-basierten Webanwendungen können während des Startvorgangs viele Aufgaben zu erledigen sein, wie z. B. das Vorladen von Daten, das Aufwärmen des Caches und das Einrichten von Verbindungspools. Wenn diese Aufgaben nacheinander ausgeführt werden, kann dies langsam sein. Wenn Sie jedoch möchten, dass sie parallel ausgeführt werden, erhöhen Sie die Anzahl der CPU-Kerne.

Cloud Run sendet eine echte Benutzeranfrage, um eine Kaltstartinstanz auszulösen. Bei Benutzern, deren 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, verwenden Sie ein schlankes Basis-Image mit weniger Paketen und Bibliotheken.

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

Wenn Sie das python-Image von Docker Hub verwenden, sollten Sie die Version slim in Betracht ziehen. Diese Images sind kleiner, weil sie nicht eine Reihe von Paketen enthalten, die zum Erstellen von Wheels verwendet würden, was Sie für Ihre Anwendung möglicherweise nicht benötigen. Das python-Image enthält den GNU C-Compiler, den Präprozessor und die Kern-Dienstprogramme.

Um die zehn größten Pakete in einem Basis-Image zu ermitteln, führen Sie folgenden Befehl aus:

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 Bilder enthalten möglicherweise nicht die Elemente, die zum Bau von Rädern aus dem Quellcode 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 Verwenden von Systempaketen in Cloud Run.

Es sind auch Optionen für Container verfügbar, die nicht auf Debian basieren. Die Option python:alpine könnte zu einem deutlich kleineren Container führen, aber viele Python-Pakete verfügen möglicherweise nicht über vorkompilierte Wheels, die auf Alpine-basierten Systemen basieren. Die Unterstützung verbessert sich (siehe PEP-656), ist aber weiterhin uneinheitlich. Alternativ können Sie auch distroless base image verwenden, das keine Paketmanager, Shells oder andere Programme enthält.

Verwenden Sie die Umgebungsvariable PYTHONUNBUFFERED für die Protokollierung.

Um ungepufferte Logs Ihrer Python-Anwendung anzuzeigen, setzen Sie die Umgebungsvariable PYTHONUNBUFFERED. Wenn Sie diese Variable setzen, sind die Daten stdout und stderr sofort in den Containerprotokollen sichtbar, anstatt in einem Puffer gespeichert zu werden, bis sich eine bestimmte Datenmenge angesammelt hat oder der Datenstrom geschlossen wird.

Nächste Schritte

Weitere Tipps finden Sie unter