Diese Anleitung enthält Best Practices zum Entwerfen, Implementieren, Testen und Bereitstellen eines Cloud Run-Dienstes. Weitere Tipps finden Sie unter Vorhandenen Dienst migrieren.
Effektive Dienste schreiben
In diesem Abschnitt werden allgemeine Best Practices für das Erstellen und Implementieren eines Cloud Run-Dienstes beschrieben.
Hintergrundaktivität
Als Hintergrundaktivität werden alle Aktivitäten bezeichnet, die nach Eingang der HTTP-Antwort erfolgen. Prüfen Sie Ihre Logs auf Punkte, die nach dem Eintrag für die HTTP-Anfrage protokolliert wurden, um festzustellen, ob Hintergrundaktivitäten in Ihrem Dienst vorhanden sind.
CPU wir immer zugewiesen für die Zuordnung zu Hintergrundaktivitäten konfigurieren
Wenn Sie Hintergrundaktivitäten in Ihrem Cloud Run-Dienst unterstützen möchten, legen Sie die CPU des Cloud Run-Dienstes so fest, dass sie Immer zugewiesen wird. Dadurch können Sie Hintergrundaktivitäten außerhalb von Anfragen ausführen und dennoch Zugriff auf die CPU haben.
Vermeiden Sie Hintergrundaktivitäten, wenn die CPU nur während der Anfrageverarbeitung zugewiesen wird
Wenn Sie Ihren Dienst so einstellen müssen, dass die CPU nur während der Anfrageverarbeitung zugewiesen wird, wird der Zugriff der Instanz auf die CPU deaktiviert, wenn der Cloud Run-Dienst die Verarbeitung einer Anfrage beendet oder stark eingeschränkt. Sie sollten keine Hintergrundthreads oder Routinen starten, die außerhalb des Bereichs der Anfrage-Handler ausgeführt werden, wenn Sie diese Art von CPU-Zuordnung verwenden.
Überprüfen Sie den Code, um sicherzugehen, dass alle asynchronen Vorgänge abgeschlossen sind, bevor Sie eine Antwort senden.
Das Ausführen von Hintergrundthreads mit dieser Art von CPU-Zuordnung kann zu unerwartetem Verhalten führen, da jede nachfolgende Anfrage an dieselbe Containerinstanz angehaltene Hintergrundaktivitäten fortsetzt.
Temporäre Dateien löschen
In der Cloud Run-Umgebung werden die Daten in einem In-Memory-Dateisystem gespeichert. In das System geschriebene Dateien belegen Speicher, der ansonsten für den Dienst verfügbar ist. Die Dateien können zwischen Aufrufen bestehen bleiben. Werden sie nicht explizit gelöscht, kann es zu einem Fehler aufgrund Speichermangels und zu einem anschließenden langsamen Start des Containers kommen.
Berichtsfehler
Behandeln Sie alle Ausnahmen und lassen Sie den Dienst bei Fehlern nicht abstürzen. Ein Absturz führt zu einem langsamen Containerstart und etwaiger Traffic wird bei einer Ersatzinstanz in die Warteschlange gestellt.
Informationen zur ordnungsgemäßen Erstellung von Fehlerberichten finden Sie im Error Reporting-Leitfaden.
Leistung optimieren
In diesem Abschnitt erfahren Sie mehr über die Best Practices zur Optimierung der Leistung.
Container schnell starten
Da Instanzen nach Bedarf skaliert werden, wirkt sich die Startzeit auf die Latenz Ihres Dienstes aus. Obwohl Cloud Run Instanzstart und Anfrageverarbeitung entkoppelt, kann es passieren, dass zur Verarbeitung einer Anfrage auf den Start einer neuen Instanz gewartet werden muss, was insbesondere bei einer Skalierung von null aus geschieht.
Die Startroutine besteht aus:
- Container-Image herunterladen (mithilfe der Container-Image-Streaming-Technologie von Cloud Run)
- Starten des Containers durch Ausführen des entrypoint-Befehls
- Warten, bis der Container den konfigurierten Port überwacht
Durch die Optimierung der Container-Startgeschwindigkeit wird die Latenz der Anfrageverarbeitung minimiert.
CPU-Boost beim Starten zum Reduzieren der Startlatenz verwenden
Sie können den CPU-Boost beim Start aktivieren, um die CPU-Zuweisung während des Starts einer Instanz vorübergehend zu erhöhen, um die Startlatenz zu reduzieren.
Mindestanzahl von Instanzen zur Verringerung der Containerstartzeiten verwenden
Sie können Mindestinstanzen und Nebenläufigkeit konfigurieren, um die Containerstartzeiten zu minimieren. Wenn Sie beispielsweise mindestens 1 Instanz verwenden, kann Ihr Dienst die für Ihren Dienst konfigurierte Anzahl gleichzeitiger Anfragen empfangen, ohne eine neue Instanz starten zu müssen.
Eine Anfrage, die auf den Start einer Instanz wartet, wird wie folgt in einer Warteschlange aufbewahrt:
- Wenn neue Instanzen gestartet werden, z. B. während einer horizontalen Skalierung, bleiben Anfragen mindestens für die durchschnittliche Startzeit von Containerinstanzen dieses Dienstes ausstehend. Dies gilt auch, wenn die Anfrage ein Hochskalieren initiiert, z. B. bei einer Skalierung von null.
- Wenn die Startzeit weniger als 10 Sekunden beträgt, bleiben Anfragen bis zu 10 Sekunden ausstehend.
- Wenn keine Instanzen beim Start vorhanden sind und die Anfrage keine horizontale Skalierung initiiert, bleiben Anfragen bis zu 10 Sekunden lang ausstehend.
Abhängigkeiten mit Bedacht verwenden
Wenn Sie eine dynamische Sprache mit abhängigen Bibliotheken verwenden, z. B. Module in Node.js importieren, erhöht die Ladezeit für diese Module die Startlatenz.
Reduzieren Sie die Startverzögerung auf folgende Weise:
- Minimieren Sie die Anzahl und Größe der Abhängigkeiten, um einen schlanken Service zu erstellen.
- Laden Sie selten verwendeten Code erst bei Bedarf, sofern Ihre Sprache dies unterstützt.
- Verwenden Sie Codeladeoptimierungen wie die Composer-Autoloader-Optimierung von PHP.
Globale Variablen verwenden
In Cloud Run können Sie nicht davon ausgehen, dass der Dienststatus zwischen den Anfragen beibehalten wird. Tatsache ist aber, dass Cloud Run die einzelnen Instanzen zur Verarbeitung des laufenden Traffics wiederverwendet. Deshalb können Sie eine globale Variable deklarieren, deren Wert in nachfolgenden Aufrufen wiederverwendet wird. Jedoch kann nicht vorhergesagt werden, ob später eine der Anfragen von dieser Wiederverwendung profitiert.
Sie können Objekte auch im Speicher zwischenspeichern, wenn deren Neuerstellung bei jeder Serviceanfrage zu ressourcenintensiv wäre. Wenn Sie diese Funktion aus der Anfragelogik in den globalen Bereich verschieben, wird die Leistung verbessert.
Node.js
Python
Go
Java
Globale Variablen nur bei Bedarf initialisieren
Die Initialisierung globaler Variablen erfolgt immer beim Start, wodurch sich die Containerstartzeit erhöht. Selten verwendete Objekte sollten daher nur bei Bedarf initialisiert werden, um den Zeitaufwand zu verschieben und den Containerstart zu beschleunigen.
Ein Nachteil der verzögerten Initialisierung ist eine erhöhte Latenz bei den ersten Anfragen an neue Instanzen. Dies kann zu einer Überdimensionierung und zu verlorenen Anfragen führen, wenn Sie eine neue Version eines Dienstes bereitstellen, der viele Anfragen verarbeitet.
Node.js
Python
Go
Java
Andere Ausführungsumgebung verwenden
Kürzere Startzeiten ergeben sich eventuell durch Verwendung einer anderen Ausführungsumgebung.
Nebenläufigkeit optimieren
Cloud Run-Instanzen können bis zur konfigurierbaren maximalen Gleichzeitigkeit mehrere Anfragen gleichzeitig verarbeiten.
Dies unterscheidet sich von Cloud Run Functions, das concurrency = 1
verwendet.
Cloud Run passt die Nebenläufigkeit automatisch bis zum konfigurierten Maximum an.
Die standardmäßige maximale Nebenläufigkeit von 80 eignet sich gut für viele Container-Images. Sie sollten jedoch Folgendes tun:
- Verringern Sie den Wert, wenn Ihr Container nicht viele gleichzeitige Anfragen verarbeiten kann.
- Erhöhen Sie ihn, wenn Ihr Container ein großes Volumen an Anfragen bewältigen kann.
Nebenläufigkeit für einen Dienst abstimmen
Wie viele Anfragen eine Instanz gleichzeitig verarbeiten kann, ist abhängig vom Softwarepaket sowie von der Nutzung freigegebener Ressourcen, wie Variablen und Datenbankverbindungen.
So optimieren Sie einen Dienst für maximal stabile Gleichzeitigkeit:
- Optimieren Sie die Leistung des Dienstes.
- Legen Sie bei der Konfiguration auf Codeebene fest, in welchem Umfang Gleichzeitigkeit unterstützt werden soll. Nicht alle Softwarepakete erfordern eine solche Einstellung.
- Stellen Sie den Dienst bereit.
- Legen Sie für Cloud Run dieselbe oder eine geringere Gleichzeitigkeit fest als auf Codeebene. Wenn auf Codeebene nichts konfiguriert ist, verwenden Sie die erwartete Gleichzeitigkeit.
- Verwenden Sie Lasttest-Tools, die eine konfigurierbare Gleichzeitigkeit unterstützen. Wichtig ist, dass der Dienst unter der erwarteten Last und Gleichzeitigkeit stabil bleibt.
- Bei schlechter Leistung kehren Sie zu Schritt 1 zurück, um den Dienst weiter zu überarbeiten, oder zu Schritt 2, um die Gleichzeitigkeit zu reduzieren. Wenn der Dienst eine gute Leistung zeigt, fahren Sie mit Schritt 2 fort und erhöhen die Gleichzeitigkeit.
Wiederholen Sie diese Schritte, bis Sie eine maximal stabile Gleichzeitigkeit erreichen.
Speicher auf Nebenläufigkeit abstimmen
Jede Anfrage, die der Dienst bearbeitet, benötigt etwas zusätzlichen Speicher. Wenn Sie also die Gleichzeitigkeit nach oben oder unten skalieren, sollten Sie gleichzeitig das Speicherlimit anpassen.
Veränderliche globale Zustände vermeiden
Wenn Sie veränderliche globale Zustände zusammen mit Gleichzeitigkeit nutzen möchten, müssen Sie den Code so anpassen, dass dies sicher funktioniert. Konflikte lassen sich minimieren, indem Sie globale Variablen nur einmal initialisieren und ihre Wiederverwendung beschränken, wie oben unter Leistung beschrieben.
Wenn Sie in einem Dienst, der mehrere Anfragen gleichzeitig verarbeitet, veränderliche globale Variablen einsetzen, kommen Sie nicht umhin, Sperren oder Mutexe zu verwenden, um Race-Bedingungen zu verhindern.
Kompromisse zwischen Durchsatz, Latenz und Kosten
Durch die Optimierung der Einstellung für die maximale Anzahl gleichzeitiger Anfragen können Sie den notwendigen Kompromiss zwischen Durchsatz, Latenz und Kosten für Ihren Dienst optimieren.
Im Allgemeinen führt eine niedrigere Einstellung für die maximale Anzahl gleichzeitiger Anfragen zu einer geringeren Latenz und einem geringeren Durchsatz pro Instanz. Bei einer niedrigeren maximalen Anzahl gleichzeitiger Anfragen konkurrieren weniger Anfragen um Ressourcen innerhalb der einzelnen Instanzen und jede Anfrage erzielt eine bessere Leistung. Da jede Instanz jedoch weniger Anfragen gleichzeitig bedienen kann, ist der Durchsatz pro Instanz niedriger und der Dienst benötigt mehr Instanzen, um denselben Traffic zu bedienen.
In umgekehrter Richtung führt eine höhere Einstellung für die maximale Anzahl gleichzeitiger Anfragen in der Regel zu einer höheren Latenz und einem höheren Durchsatz pro Instanz. Anfragen müssen möglicherweise auf den Zugriff auf Ressourcen wie CPU, GPU und Speicherbandbreite innerhalb der Instanz warten, was zu einer erhöhten Latenz führt. Jede Instanz kann jedoch mehr Anfragen gleichzeitig verarbeiten, sodass der Dienst insgesamt weniger Instanzen benötigt, um denselben Traffic zu verarbeiten.
Kostengesichtspunkte
Die Cloud Run-Abrechnung erfolgt pro Instanz und Zeit. Wenn CPU immer zugewiesen ausgewählt ist, entspricht die Instanzzeit der gesamten Lebensdauer der jeweiligen Instanz. Wenn die CPU nicht immer zugewiesen wird, ist die Instanzzeit die Zeit, die jede Instanz für die Verarbeitung mindestens einer Anfrage benötigt.
Wie sich die maximale Anzahl gleichzeitiger Anfragen auf die Abrechnung auswirkt, hängt von Ihrem Traffic-Muster ab. Wenn Sie die maximale Anzahl gleichzeitiger Anfragen senken, kann dies zu einer niedrigeren Rechnung führen, wenn die niedrigere Einstellung zu Folgendem führt:
- Verringerte Latenz
- Instanzen erledigen ihre Arbeit schneller
- Instanzen werden schneller heruntergefahren, auch wenn mehr Instanzen insgesamt erforderlich sind
Das Gegenteil ist aber auch möglich: Wenn die erhöhte Anzahl der Instanzen nicht durch die geringere Laufzeit der einzelnen Instanzen aufgrund der verbesserten Latenz ausgeglichen wird, kann die Abrechnung höher ausfallen.
Die beste Möglichkeit zur Optimierung der Abrechnung ist ein Lasttest mit verschiedenen Einstellungen für die maximale Anzahl gleichzeitiger Anfragen. So lässt sich die Einstellung ermitteln, die die niedrigste abrechenbare Instanzzeit ergibt, wie im Überwachungsmesswert container/billable_instance_time zu sehen ist.
Containersicherheit
Viele Sicherheitskonzepte, die für Standardsoftware gelten, werden auch bei containerisierten Diensten verwendet. Es gibt jedoch einige Praktiken, die entweder nur für Container gelten, oder die sich an der Philosophie und Architektur von Containern orientieren.
So verbessern Sie die Containersicherheit:
Verwenden Sie sichere Basis-Images, die laufend aktualisiert werden, z. B. Basis-Images oder offizielle Images von Docker Hub.
Wenden Sie Sicherheitsupdates auf Ihre Dienste an, indem Sie die Container-Images regelmäßig neu erstellen und die Dienste neu bereitstellen.
Übernehmen Sie in den Container nur das, was zur Dienstausführung wirklich erforderlich ist. Zusätzlicher Code, Pakete und Tools sind generell potenzielle Sicherheitslücken. Informationen zur Auswirkung auf die Leistung finden Sie weiter oben.
Implementieren Sie einen deterministischen Build-Prozess, der bestimmte Software- und Bibliotheksversionen enthält. Dies verhindert, dass nicht verifizierter Code in den Container aufgenommen wird.
Legen Sie den Container mit der Dockerfile
USER
-Anweisung so fest, dass er als anderer Nutzer alsroot
ausgeführt wird. Für einige Container-Images ist möglicherweise bereits ein bestimmter Nutzer konfiguriert.Mit benutzerdefinierten Organisationsrichtlinien können Sie die Verwendung von Vorabversionen verhindern.
Sicherheitsscans automatisieren
Aktivieren Sie das Scannen auf Sicherheitslücken für Sicherheitsscans von Container-Images, die in Artifact Registry gespeichert sind.
Minimale Container-Images erstellen
Große Container-Images können zu größeren Sicherheitslücken führen, da sie mehr enthalten, als für den Code erforderlich ist.
Aufgrund der Container-Image-Streaming-Technologie von Cloud Run wirkt sich die Größe des Container-Images nicht auf die Containerstartzeit oder die Anfrageverarbeitungszeit aus. Die Container-Image-Größe wird auch nicht auf den verfügbaren Arbeitsspeicher Ihres Containers angerechnet.
Orientieren Sie sich bei der Erstellung eines minimalen Containers an einem schlanken Basis-Image wie zum Beispiel:
Ubuntu ist größer, wird aber gerne als Basis-Image verwendet, weil es standardmäßig eine umfangreichere Serverumgebung mitbringt.
Wenn der Build-Prozess für Ihren Dienst viele Tools umfasst, sollten Sie mehrstufige Builds verwenden, um den Ressourcenverbrauch des Containers während der Laufzeit möglichst gering zu halten.
Hier einige Artikel zur Erstellung ressourcensparender Container-Images:
- Best Practices für Kubernetes: Kleine Container-Images – Erstellen und Vorteile
- Sieben Best Practices für das Erstellen von Containern