Optimierung von Service-Auto-Scaling von Amazon ECS - Amazon Elastic Container Service

Optimierung von Service-Auto-Scaling von Amazon ECS

Ein Amazon-ECS-Service ist eine verwaltete Sammlung von Aufgaben. Jedem Service ist eine Aufgabendefinition, eine gewünschte Anzahl von Aufgaben und eine optionale Platzierungsstrategie zugeordnet.

Service-Auto-Scaling von Amazon ECS funktioniert über den Application-Auto-Scaling-Service. Application Auto Scaling verwendet CloudWatch-Metriken als Quelle für Skalierungsmetriken. Der Service verwendet auch CloudWatch-Alarme, um Schwellenwerte für die Ein- oder Ausskalierung Ihres Services festzulegen.

Sie geben die Schwellenwerte für die Skalierung an. Sie können ein metrisches Ziel festlegen, das als Skalierung der Ziel-Nachverfolgung bezeichnet wird. Sie können auch Schwellenwerte angeben, was als schrittweise Skalierung bezeichnet wird.

Nachdem Sie Application Auto Scaling konfiguriert haben, berechnet es kontinuierlich die entsprechende gewünschte Anzahl an Aufgaben für den Service. Der Service benachrichtigt Amazon ECS auch, wenn sich die gewünschte Anzahl an Aufgaben ändern sollte, entweder durch Auf- oder Abskalieren.

Um Service-Auto-Scaling effektiv nutzen zu können, müssen Sie eine geeignete Skalierungsmetrik auswählen. In den folgenden Abschnitten wird erläutert, wie Sie eine Metrik auswählen.

Charakterisierung Ihrer Anwendung

Um eine Anwendung richtig zu skalieren, müssen Sie die Bedingungen kennen, unter denen Sie Ihre Anwendung skalieren und wann Sie sie skalieren sollten.

Im Wesentlichen sollten Sie Ihre Anwendung skalieren, wenn prognostiziert wird, dass die Nachfrage die Kapazität übersteigt. Umgekehrt können Sie Ihre Anwendung skalieren, um Kosten zu sparen, wenn die Ressourcen den Bedarf übersteigen.

Identifizieren einer Nutzungsmetrik

Für eine effektive Skalierung müssen Sie eine Metrik identifizieren, die auf Auslastung oder Sättigung hinweist. Diese Metrik muss die folgenden Eigenschaften aufweisen, um für die Skalierung nützlich zu sein.

  • Die Metrik muss mit der Nachfrage korreliert sein. Wenn Sie die Ressourcen konstant halten, sich aber der Bedarf ändert, muss sich auch der Metrikwert ändern. Die Metrik sollte steigen oder sinken, wenn die Nachfrage steigt oder sinkt.

  • Der metrische Wert muss proportional zur Kapazität abskaliert werden. Wenn die Nachfrage konstant bleibt, muss das Hinzufügen weiterer Ressourcen zu einer proportionalen Änderung des Metrikwerts führen. Eine Verdoppelung der Anzahl der Aufgaben sollte also zu einer Verringerung der Metrik um 50 % führen.

Der beste Weg, eine Nutzungsmetrik zu ermitteln, sind Belastungstests in einer Vorproduktionsumgebung, wie z. B. einer Staging-Umgebung. Kommerzielle und Open-Source-Lösungen für Lasttests sind weit verbreitet. Diese Lösungen können in der Regel entweder synthetische Last erzeugen oder echten Benutzerverkehr simulieren.

Um mit dem Auslastungstest zu beginnen, sollten Sie zunächst Dashboards für die Nutzungsmetriken Ihrer Anwendung erstellen. Zu diesen Metriken gehören CPU-Auslastung, Speicherauslastung, I/O-Vorgänge, I/O-Warteschlangentiefe und Netzwerkdurchsatz. Sie können diese Metriken mit einem Service wie CloudWatch Container Insights erfassen. Sie können die Metriken auch erfassen, indem Sie Amazon Managed Service für Prometheus zusammen mit Amazon Managed Grafana verwenden. Stellen Sie während dieses Prozesses sicher, dass Sie Kennzahlen zu den Antwortzeiten oder den Abschlussquoten Ihrer Anwendung erfassen und grafisch darstellen.

Beginnen Sie beim Auslastungstest mit einer geringen Anzahl von Anfragen oder Auftrags-Injektionsrate. Halten Sie diese Rate mehrere Minuten lang konstant, damit sich Ihre Anwendung aufwärmen kann. Erhöhen Sie dann langsam die Geschwindigkeit und halten Sie sie einige Minuten lang konstant. Wiederholen Sie diesen Zyklus und erhöhen Sie die Rate jedes Mal, bis die Antwort- oder Abschlusszeiten Ihrer Anwendung zu langsam sind, um Ihre Service Level Objectives (SLOs) zu erreichen.

Untersuchen Sie beim Belastungstest die einzelnen Nutzungsmetriken. Die Metriken, die mit der Auslastung steigen, eignen sich am besten als Ihre besten Nutzungsmetriken.

Identifizieren Sie als Nächstes die Ressource, deren Sättigung erreicht ist. Untersuchen Sie gleichzeitig auch die Nutzungsmetriken, um festzustellen, welche sich auf hoher Ebene zuerst abflachen. Oder untersuchen Sie zuerst, welches System seinen Höhepunkt erreicht und dann Ihre Anwendung zum Absturz bringt. Wenn die CPU-Auslastung beispielsweise von 0 % auf 70–80 % steigt, wenn Sie mehr Last hinzufügen, dann kann man mit Sicherheit sagen, dass die CPU ausgelastet ist. Je nach CPU-Architektur erreicht die Auslastung möglicherweise nie 100 %. Gehen Sie beispielsweise davon aus, dass die Speicherauslastung zunimmt, wenn Sie mehr Last hinzufügen, und Ihre Anwendung dann plötzlich abstürzt, wenn sie das Speicherlimit für Aufgaben oder Amazon-EC2-Instances erreicht. In dieser Situation ist es wahrscheinlich der Fall, dass der Arbeitsspeicher vollständig verbraucht wurde. Möglicherweise werden mehrere Ressourcen von Ihrer Anwendung verwendet. Wählen Sie daher die Metrik aus, die die Ressource darstellt, die zuerst erschöpft ist.

Versuchen Sie abschließend erneut, die Auslastung zu testen, nachdem Sie die Anzahl der Aufgaben oder Amazon-EC2-Instances verdoppelt haben. Gehen Sie davon aus, dass die Schlüsselmetrik halb so schnell wie zuvor steigt oder sinkt. Wenn dies der Fall ist, ist die Metrik proportional zur Kapazität. Dies ist eine gute Nutzungsmetrik für Auto Scaling.

Betrachten Sie nun dieses hypothetische Szenario. Nehmen wir an, Sie führen einen Auslastungstest für eine Anwendung durch und stellen fest, dass die CPU-Auslastung bei 100 Anfragen pro Sekunde irgendwann 80 % erreicht. Wenn Sie mehr Last hinzufügen, erhöht sich die CPU-Auslastung nicht mehr. Dadurch reagiert Ihre Anwendung jedoch langsamer. Dann führen Sie den Auslastungstest erneut durch und verdoppeln dabei die Anzahl der Aufgaben, halten aber die Geschwindigkeit auf dem vorherigen Spitzenwert. Wenn Sie feststellen, dass die durchschnittliche CPU-Auslastung auf etwa 40 % sinkt, ist die durchschnittliche CPU-Auslastung ein guter Kandidat für eine Skalierungsmetrik. Bleibt die CPU-Auslastung dagegen bei 80 %, nachdem die Anzahl der Aufgaben erhöht wurde, ist die durchschnittliche CPU-Auslastung keine gute Skalierungsmetrik. In diesem Fall benötigen Sie mehr Nachforschungen, um eine geeignete Metrik zu finden.

Allgemeine Anwendungsmodelle und Skalierungseigenschaften

Sie können Software aller Art in AWS ausführen. Viele Workloads sind selbst entwickelt, während andere auf beliebter Open-Source-Software basieren. Unabhängig davon, woher sie stammen, haben wir einige gängige Entwurfsmuster für Services beobachtet. Wie Sie effektiv skalieren, hängt zu einem großen Teil vom Muster ab.

Der effiziente CPU-gebundene Server

Der effiziente CPU-gebundene Server nutzt fast keine anderen Ressourcen als den CPU- und Netzwerkdurchsatz. Jede Anfrage kann von der Anwendung alleine bearbeitet werden. Anfragen hängen nicht von anderen Services wie Datenbanken ab. Die Anwendung kann Hunderttausende von gleichzeitigen Anfragen verarbeiten und dafür mehrere CPUs effizient nutzen. Jede Anfrage wird entweder von einem dedizierten Thread mit geringem Arbeitsspeicheraufwand bedient, oder es gibt eine asynchrone Ereignisschleife, die auf jeder CPU läuft, die Anfragen bearbeitet. Jedes Replikat der Anwendung ist gleichermaßen in der Lage, eine Anfrage zu verarbeiten. Die einzige Ressource, die vor der CPU aufgebraucht sein könnte, ist die Netzwerkbandbreite. Bei CPU-gebundenen Services macht die Speicherauslastung selbst bei Spitzendurchsatz nur einen Bruchteil der verfügbaren Ressourcen aus.

Sie können CPU-basiertes Auto Scaling für diese Art von Anwendung verwenden. Die Anwendung bietet maximale Flexibilität in Bezug auf die Skalierung. Sie können es vertikal skalieren, indem Sie der Anwendung größere Amazon-EC2-Instances oder Fargate-vCPUs zur Verfügung stellen. Sie können auch horizontal skalieren, indem Sie weitere Replikate hinzufügen. Durch das Hinzufügen weiterer Replikate oder die Verdoppelung der Instance-Größe kann die durchschnittliche CPU-Auslastung im Verhältnis zur Kapazität um die Hälfte reduziert werden.

Wenn Sie Amazon-EC2-Kapazität für diese Anwendung verwenden, sollten Sie erwägen, sie auf für Datenverarbeitung optimierte Instances wie die c5- oder c6g-Familie zu platzieren.

Der effiziente arbeitsspeichergebundene Server

Der effiziente arbeitsspeichergebundene Server weist pro Anforderung eine beträchtliche Menge an Arbeitsspeicher zu. Bei maximaler Nebenläufigkeit, aber nicht unbedingt bei Durchsatz, wird der Arbeitsspeicher erschöpft, bevor die CPU-Ressourcen aufgebraucht sind. Der einer Anforderung zugeordnete Arbeitsspeicher wird freigegeben, wenn die Anforderung endet. Zusätzliche Anforderungen können akzeptiert werden, solange Arbeitsspeicher verfügbar ist.

Sie können arbeitsspeicherbasiertes Auto Scaling für diese Art von Anwendung verwenden. Die Anwendung bietet maximale Flexibilität in Bezug auf die Skalierung. Sie können es sowohl vertikal skalieren, indem Sie größere Amazon-EC2- oder Fargate-Arbeitsspeicherressourcen zur Verfügung stellen. Sie können auch horizontal skalieren, indem Sie weitere Replikate hinzufügen. Durch das Hinzufügen weiterer Replikate oder die Verdoppelung der Instance-Größe kann die durchschnittliche Speicherauslastung im Verhältnis zur Kapazität um die Hälfte reduziert werden.

Wenn Sie Amazon-EC2-Kapazität für diese Anwendung verwenden, sollten Sie erwägen, sie auf arbeitsspeicheroptimierte Instances wie die r5- oder r6g-Familie zu platzieren.

Einige arbeitsspeichergebundene Anwendungen geben den Arbeitsspeicher, der einer Anforderung zugeordnet ist, nicht frei, wenn diese beendet ist, sodass eine Verringerung der Nebenläufigkeit nicht zu einer Verringerung des verwendeten Arbeitsspeichers führt. Aus diesem Grund wird empfohlen, keine arbeitsspeicherbasierte Skalierung zu verwenden.

Der Worker-basierte Server

Der Worker-basierte Server verarbeitet nacheinander eine Anfrage für jeden einzelnen Worker-Thread. Bei den Worker-Threads kann es sich um leichtgewichtige Threads wie POSIX-Threads handeln. Sie können auch Threads mit höherem Gewicht sein, wie z. B. UNIX-Prozesse. Unabhängig davon, um welchen Thread es sich handelt, gibt es immer eine maximale Nebenläufigkeit, die die Anwendung unterstützen kann. Normalerweise wird das Limit für die Nebenläufigkeit proportional zu den verfügbaren Arbeitsspeicherressourcen festgelegt. Wenn das Limit für die Nebenläufigkeit erreicht ist, platziert die Anwendung zusätzliche Anfragen in einer Backlog-Warteschlange. Wenn die Backlog-Warteschlange überläuft, lehnt die Anwendung sofort weitere eingehende Anfragen ab. Zu den gängigen Anwendungen, die diesem Muster entsprechen, gehören der Apache Webserver und Gunicorn.

Die Nebenläufigkeit von Anfragen ist normalerweise die beste Metrik für die Skalierung dieser Anwendung. Da es für jedes Replikat ein Limit für die Nebenläufigkeit gibt, ist es wichtig, eine Aufskalierung vorzunehmen, bevor das durchschnittliche Limit erreicht wird.

Der beste Weg, Metriken zur Nebenläufigkeit von Anfragen zu erhalten, besteht darin, sie von Ihrer Anwendung an CloudWatch melden zu lassen. Jedes Replikat Ihrer Anwendung kann die Anzahl der gleichzeitigen Anfragen als benutzerdefinierte Metrik mit hoher Frequenz veröffentlichen. Wir empfehlen, die Frequenz auf mindestens einmal pro Minute einzustellen. Nachdem mehrere Berichte gesammelt wurden, können Sie die durchschnittliche Nebenläufigkeit als Skalierungsmetrik verwenden. Sie berechnen diese Metrik, indem Sie die gesamte Nebenläufigkeit durch die Anzahl der Replikate dividieren. Wenn beispielsweise die Gesamtzahl der Nebenläufigkeit 1 000 beträgt und die Anzahl der Replikate 10 beträgt, beträgt die durchschnittliche Nebenläufigkeit 100.

Wenn Ihre Anwendung hinter einem Application Load Balancer steht, können Sie die ActiveConnectionCount-Metrik für den Load Balancer auch als Faktor in der Skalierungsmetrik verwenden. Sie müssen die ActiveConnectionCount-Metrik durch die Anzahl der Replikate dividieren, um einen Durchschnittswert zu erhalten. Sie müssen den Durchschnittswert für die Skalierung verwenden und nicht den reinen Zählwert.

Damit dieses Design optimal funktioniert, sollte die Standardabweichung der Antwortlatenz bei niedrigen Anforderungsraten gering sein. Wir empfehlen, dass in Zeiten geringer Nachfrage die meisten Anfragen innerhalb kurzer Zeit beantwortet werden, und es gibt nicht viele Anfragen, deren Beantwortung deutlich länger als der Durchschnitt dauert. Die durchschnittliche Antwortzeit sollte in der Nähe des 95. Perzentils der Antwortzeit liegen. Andernfalls kann es zu Warteschlangenüberläufen kommen. Dies führt zu Fehlern. Wir empfehlen, dass Sie bei Bedarf zusätzliche Replikate bereitstellen, um das Risiko eines Überlaufs zu minimieren.

Der wartende Server

Der wartende Server verarbeitet jede Anfrage in gewissem Maße, ist jedoch in hohem Maße von einem oder mehreren nachgelagerten Services abhängig, damit er funktioniert. Container-Anwendungen nutzen häufig stark nachgelagerte Services wie Datenbanken und andere API-Services. Es kann einige Zeit dauern, bis diese Services reagieren, insbesondere in Szenarien mit hoher Kapazität oder hoher Nebenläufigkeit. Dies liegt daran, dass diese Anwendungen in der Regel nur wenige CPU-Ressourcen verwenden und gleichzeitig den verfügbaren Arbeitsspeicher maximal ausnutzen.

Der wartende Server eignet sich entweder für das speichergebundene Servermuster oder das Worker-basierte Servermuster, je nachdem, wie die Anwendung konzipiert ist. Wenn die Nebenläufigkeit der Anwendung nur durch den Arbeitsspeicher begrenzt ist, sollte die durchschnittliche Speicherauslastung als Skalierungsmetrik verwendet werden. Wenn die Nebenläufigkeit der Anwendung auf einem Worker-Limit basiert, sollte die durchschnittliche Nebenläufigkeit als Skalierungsmetrik verwendet werden.

Der Java-basierte Server

Wenn Ihr Java-basierter Server CPU-gebunden ist und proportional zu den CPU-Ressourcen skaliert, ist er möglicherweise für das effiziente CPU-gebundene Servermuster geeignet. Wenn das der Fall ist, könnte die durchschnittliche CPU-Auslastung als Skalierungsmetrik geeignet sein. Viele Java-Anwendungen sind jedoch nicht CPU-gebunden, was ihre Skalierung schwierig macht.

Für eine optimale Leistung empfehlen wir, dem Java Virtual Machine (JVM)-Heap so viel Arbeitsspeicher wie möglich zuzuweisen. Neuere Versionen der JVM, einschließlich Java 8 Update 191 oder höher, legen die Heap-Größe automatisch so groß wie möglich fest, damit sie in den Container passt. Das bedeutet, dass in Java die Speicherauslastung selten proportional zur Anwendungsauslastung ist. Mit steigender Anforderungsrate und Nebenläufigkeit bleibt die Speicherauslastung konstant. Aus diesem Grund empfehlen wir nicht, Java-basierte Server auf der Grundlage der Speicherauslastung zu skalieren. Stattdessen empfehlen wir in der Regel, je nach CPU-Auslastung zu skalieren.

In einigen Fällen kommt es bei Java-basierten Servern zu einer Heap-Erschöpfung, bevor die CPU-Auslastung erreicht wird. Wenn Ihre Anwendung bei hoher Nebenläufigkeit zur Heap-Erschöpfung neigt, sind durchschnittliche Verbindungen die beste Skalierungsmetrik. Wenn Ihre Anwendung bei hohem Durchsatz zur Heap-Erschöpfung neigt, sind durchschnittliche Anforderungsraten die beste Skalierungsmetrik.

Server, die andere Garbage-Collection-Laufzeiten verwenden

Viele Serveranwendungen wie .NET und Ruby basieren auf Laufzeiten, die Garbage Collection durchführen. Diese Serveranwendungen passen möglicherweise in eines der zuvor beschriebenen Muster. Wie bei Java empfehlen wir jedoch nicht, diese Anwendungen auf der Grundlage des Arbeitsspeichers zu skalieren, da ihre beobachtete durchschnittliche Speicherauslastung oft nicht mit dem Durchsatz oder der Nebenläufigkeit übereinstimmt.

Für diese Anwendungen empfehlen wir, die CPU-Auslastung entsprechend zu skalieren, wenn die Anwendung CPU-gebunden ist. Andernfalls empfehlen wir, die Skalierung auf der Grundlage Ihrer Lasttestergebnisse auf der Grundlage des durchschnittlichen Durchsatzes oder der durchschnittlichen Nebenläufigkeit durchzuführen.

Auftragsprozessoren

Viele Workloads beinhalten asynchrone Auftragsverarbeitung. Dazu gehören Anwendungen, die Anfragen nicht in Echtzeit empfangen, sondern stattdessen eine Arbeitswarteschlange abonnieren, um Aufträge zu erhalten. Für diese Art von Anwendungen ist die richtige Skalierungsmetrik fast immer die Warteschlangentiefe. Ein Anstieg der Warteschlange ist ein Hinweis darauf, dass die ausstehende Arbeit die Verarbeitungskapazität übersteigt, wohingegen eine leere Warteschlange darauf hindeutet, dass mehr Kapazität als zu erledigende Arbeit vorhanden ist.

AWS-Messaging-Services wie Amazon SQS und Amazon Kinesis Data Streams bieten CloudWatch-Metriken, die für die Skalierung verwendet werden können. Für Amazon SQS ist ApproximateNumberOfMessagesVisible die beste Metrik. Für Kinesis Data Streams sollten Sie die MillisBehindLatest-Metrik verwenden, die von der Kinesis Client Library (KCL) veröffentlicht wurde. Diese Metrik sollte für alle Verbraucher gemittelt werden, bevor sie für die Skalierung verwendet wird.