Freigeben über


Microservices in Containern

Tipp

Diese Inhalte sind ein Auszug aus dem eBook „Enterprise Application Patterns Using .NET MAUI“, verfügbar unter .NET Docs oder als kostenlos herunterladbare PDF-Datei, die offline gelesen werden kann.

Enterprise Application Patterns Using .NET MAUI (Miniaturansicht eBook-Deckblatt).

Die Entwicklung von Client-Server-Anwendungen hat dazu geführt, dass der Schwerpunkt auf dem Erstellen von Anwendungen mit mehreren Ebenen liegt, die bestimmte Technologien in jeder Ebene verwenden. Solche Anwendungen werden häufig als monolithisch bezeichnet und auf Hardware zugeschnitten, die für Spitzenlasten vorskaliert ist. Die Hauptnachteile dieses Entwicklungsansatzes sind die enge Kopplung zwischen den Komponenten innerhalb jeder Ebene, die Tatsache, dass einzelne Komponenten nicht einfach skaliert werden können, und die Kosten für das Testen. Ein einfaches Update kann unvorhergesehene Auswirkungen auf den Rest der Ebene haben, sodass bei einer Änderung einer Anwendungskomponente die gesamte Ebene erneut getestet und bereitgestellt werden muss.

Besonders problematisch ist im Zeitalter der Cloud, dass einzelne Komponenten nicht einfach skaliert werden können. Eine monolithische Anwendung enthält domänenspezifische Funktionen und wird in der Regel durch Funktionsebenen wie Front-End, Geschäftslogik und Datenspeicherung unterteilt. Die folgende Abbildung veranschaulicht, dass eine monolithische Anwendung skaliert wird, indem die gesamte Anwendung auf mehrere Computer geklont wird.

Monolithischer Anwendungsskalierungsansatz.

Microservices

Microservices bieten einen anderen Ansatz für die Anwendungsentwicklung und -bereitstellung, der den Anforderungen an Agilität, Skalierbarkeit und Zuverlässigkeit moderner Cloudanwendungen gerecht wird. Eine Microservicesanwendung ist in unabhängige Komponenten unterteilt, die zusammenarbeiten, um die Gesamtfunktionalität der Anwendung bereitzustellen. Der Begriff „Microservice“ betont, dass Anwendungen aus Diensten bestehen sollten, die klein genug sind, um bestimmte Anliegen widerzuspiegeln, sodass jeder Microservice eine einzelne Funktion implementiert. Darüber hinaus verfügt jeder Microservice über klar definierte Verträge, mit denen andere Microservices kommunizieren und Daten teilen. Typische Beispiele für Microservices sind Warenkörbe, Bestandsverarbeitung, Kaufsubsysteme und Zahlungsverarbeitung.

Microservices können im Vergleich zu riesigen monolithischen Anwendungen, die zusammen skaliert werden, unabhängig voneinander skaliert werden. Dies bedeutet, dass ein bestimmter Funktionsbereich, der mehr Verarbeitungsleistung oder Netzwerkbandbreite benötigt, um die Nachfrage zu unterstützen, skaliert werden kann, anstatt andere Anwendungsbereiche unnötig hochskalieren zu müssen. Die folgende Abbildung veranschaulicht diesen Ansatz, bei dem Microservices unabhängig voneinander bereitgestellt und skaliert werden, sodass Instanzen von Diensten auf verschiedenen Computern erstellt werden.

Microservices-Anwendungsskalierungsansatz.

Die horizontale Skalierung von Microservice kann nahezu sofort erfolgen, sodass sich eine Anwendung an wechselnde Lasten anpassen kann. Beispielsweise kann ein einzelner Microservice in der webseitigen Funktionalität einer Anwendung der einzige Microservice sein, der aufskaliert werden muss, um zusätzlichen eingehenden Datenverkehr zu verarbeiten.

Das klassische Modell für Anwendungsskalierbarkeit besteht darin, eine zustandslose Ebene mit Lastenausgleich mit einem freigegebenen externen Datenspeicher zum Speichern persistenter Daten zu verwenden. Zustandsbehaftete Microservices verwalten ihre eigenen persistenten Daten und speichern sie in der Regel lokal auf den Servern, auf denen sie sich befinden, um den Mehraufwand für Netzwerkzugriff und die Komplexität dienstübergreifender Vorgänge zu vermeiden. Dies ermöglicht die schnellstmögliche Verarbeitung von Daten und kann die Notwendigkeit von Zwischenspeicherungssystemen überflüssig machen. Darüber hinaus partitionieren skalierbare zustandsbehaftete Microservices in der Regel Daten zwischen ihren Instanzen, um die Datengröße und den Übertragungsdurchsatz zu verwalten, über die hinaus ein einzelner Server keine Unterstützung bereitstellen kann.

Microservices unterstützen auch unabhängige Updates. Diese lose Kopplung zwischen Microservices ermöglicht schnelle und zuverlässige Anwendungsentwicklung. Ihr unabhängiger, verteilter Charakter begünstigt parallele Updates, bei denen nur eine Teilmenge der Instanzen eines einzelnen Microservice zu einem bestimmten Zeitpunkt aktualisiert wird. Wenn also ein Problem entdeckt wird, kann ein fehlerhaftes Update zurückgenommen werden, bevor alle Instanzen mit dem fehlerhaften Code oder der fehlerhaften Konfiguration aktualisiert werden. In ähnlicher Weise verwenden Microservices in der Regel eine Schema-Versionierung, so dass Clients bei der Anwendung von Updates eine konsistente Version sehen, unabhängig davon, mit welcher Microserviceinstanz kommuniziert wird.

Daher haben Microserviceanwendungen gegenüber monolithischen Anwendungen viele Vorteile:

  • Jeder Microservice ist relativ klein, einfach zu verwalten und weiterzuentwickeln.
  • Jeder Microservice kann unabhängig von anderen Diensten entwickelt und bereitgestellt werden.
  • Jeder Microservice kann unabhängig von anderen Diensten skaliert werden. Beispielsweise muss ein Katalogdienst oder Warenkorbdienst mehr horizontal skaliert werden als ein Bestelldienst. Aus diesem Grund verbraucht die sich ergebende Infrastruktur Ressourcen effizienter, wenn horizontal hochskaliert wird.
  • Jeder Microservice isoliert eventuelle Probleme. Wenn beispielsweise ein Problem in einem Dienst vorliegt, wirkt sich dies nur auf diesen Dienst aus. Die anderen Dienste können weiterhin Anforderungen verarbeiten.
  • Jeder Microservice kann die neusten Technologien verwenden. Da Microservices autonom sind und parallel ausgeführt werden, können die neuesten Technologien und Frameworks verwendet werden, anstatt gezwungen zu sein, ein älteres Framework zu nutzen, das möglicherweise von einer monolithischen Anwendung verwendet wird.

Eine auf Microservices basierende Lösung hat jedoch auch potenzielle Nachteile:

  • Die Entscheidung, wie eine Anwendung in Microservices aufgeteilt werden soll, kann eine Herausforderung darstellen, da jeder Microservice vollständig autonom sowie End-to-End sein muss, einschließlich der Verantwortung für seine Datenquellen.
  • Entwickler müssen dienstübergreifende Kommunikation implementieren, was der Anwendung Komplexität und Latenz hinzufügt.
  • Atomische Transaktionen zwischen mehreren Microservices sind normalerweise nicht möglich. Die Geschäftsanforderungen müssen daher eventuelle Konsistenz zwischen Microservices umfassen.
  • In der Produktion besteht eine betriebliche Komplexität bei der Bereitstellung und Verwaltung eines Systems, das aus vielen unabhängigen Diensten besteht.
  • Die direkte Kommunikation zwischen Client und Microservice kann das Refactoring der Verträge von Microservices erschweren. Im Laufe der Zeit kann sich zum Beispiel die Aufteilung des Systems in Dienste ändern. Ein einzelner Dienst kann in zwei oder mehr Dienste aufgeteilt werden, oder zwei Dienste können zusammengeführt werden. Wenn Clients direkt mit Microservices kommunizieren, kann diese Umgestaltung die Kompatibilität mit Clientanwendungen beeinträchtigen.

Containerisierung

Containerisierung ist ein Ansatz für Softwareentwicklung, bei dem eine Anwendung und ihr mit Versionsangaben versehener Satz von Abhängigkeiten sowie die Umgebungskonfiguration, die als Bereitstellungsmanifestdateien abstrahiert wird, als Containerimage gepackt, als Einheit getestet und unter einem Hostbetriebssystem bereitgestellt wird.

Ein Container ist eine isolierte, ressourcengesteuerte und portable Betriebsumgebung, in der eine Anwendung ausgeführt werden kann, ohne die Ressourcen anderer Container oder des Hosts anzutasten. Ein Container verhält sich daher wie ein neu installierter physischer oder virtueller Computer.

Es gibt viele Ähnlichkeiten zwischen Containern und VMs, wie unten gezeigt.

Vergleich von VMs und Containern.

Ein Container führt ein Betriebssystem aus, verfügt über ein Dateisystem, und auf ihn kann über ein Netzwerk zugegriffen werden, genau wie bei einem physischen oder virtuellen Computer. Die von Containern verwendeten Technologien und Konzepte unterscheiden sich jedoch in hohem Maße von VMs. VMs enthalten die Anwendungen, die erforderlichen Abhängigkeiten und ein vollständiges Gastbetriebssystem. Container enthalten die Anwendung und ihre Abhängigkeiten, teilen sich aber das Betriebssystem mit anderen Containern, die als isolierte Prozesse unter dem Hostbetriebssystem ausgeführt werden (abgesehen von Hyper-V-Containern, die innerhalb einer speziellen VM pro Container ausgeführt werden). Daher verwenden Container Ressourcen gemeinsam und benötigen in der Regel weniger Ressourcen als VMs.

Der Vorteil eines containerorientierten Entwicklungs- und Bereitstellungsansatzes besteht darin, dass die meisten Probleme beseitigt werden, die sich aus inkonsistenten Umgebungssetups und den damit verbundenen Problemen ergeben. Darüber hinaus ermöglichen Container eine schnelle Anwendungsskalierungsfunktion, indem bei Bedarf neue Containerinstanzen erstellt werden.

Die wichtigsten Konzepte beim Erstellen von und Arbeiten mit Containern sind:

Konzept BESCHREIBUNG
Containerhost Der physische oder virtuelle Computer, der zum Hosten von Containern konfiguriert ist. Auf dem Containerhost wird mindestens ein Container ausgeführt.
Containerimage Ein Image besteht aus einer Union von übereinander gestapelten Dateisystemen und ist die Basis eines Containers. Ein Image hat keinen Zustand und ändert sich nie, wenn es in verschiedenen Umgebungen bereitgestellt wird.
Container Ein Container ist eine Runtimeinstanz eines Images.
Image des Containerbetriebssystems Container werden mithilfe von Images bereitgestellt. Das Betriebssystemimage des Containers ist die erste Ebene von möglicherweise zahlreichen Imageebenen, aus denen ein Container besteht. Ein Containerbetriebssystem ist unveränderlich und kann nicht angepasst werden.
Containerrepository Bei jeder Erstellung eines Containerimages werden das Image und dessen Abhängigkeiten in einem lokalen Repository gespeichert. Diese Images können auf dem Containerhost mehrfach wiederverwendet werden. Die Containerimages können auch in einer öffentlichen oder privaten Registrierung (z. B. Docker Hub) gespeichert werden, damit sie in verschiedenen Containerhosts verwendet werden können.

Unternehmen setzen bei der Implementierung von auf Microservices basierenden Anwendungen zunehmend auf Container, und Docker ist zur Standardcontainerimplementierung geworden, die von den meisten Softwareplattformen und Cloudanbietern übernommen wurde.

Die eShop-Referenzanwendung verwendet Docker zum Hosten von vier containerisierten Back-End-Microservices, wie in der folgenden Abbildung gezeigt.

Back-End-Microservices der eShop-Referenzanwendung

Die Architektur der Back-End-Dienste in der Referenzanwendung wird in mehrere autonome Untersysteme in Form zusammenarbeitender Microservices und Container aufgeteilt. Jeder Microservice bietet einen einzelnen Funktionsbereich: einen Identitätsdienst, einen Katalogdienst, einen Bestelldienst und einen Warenkorbdienst.

Jeder Microservice verfügt über eine eigene Datenbank, sodass er vollständig von den anderen Microservices entkoppelt werden kann. Bei Bedarf wird die Konsistenz zwischen Datenbanken aus verschiedenen Microservices mithilfe von Ereignissen auf Anwendungsebene erreicht. Weitere Informationen finden Sie unter Kommunikation zwischen Microservices.

Kommunikation zwischen Client und Microservices

Die eShop-Multiplattform-App kommuniziert mit den containerisierten Back-End-Microservices über direkte Kommunikation zwischen Client und Microservice, wie unten gezeigt.

Direkte Kommunikation zwischen Client und Microservice.

Bei direkter Kommunikation zwischen Client und Microservice sendet die Multiplattform-App Anforderungen an jeden Microservice direkt über seinen öffentlichen Endpunkt mit einem anderen TCP-Port pro Microservice. In der Produktionsumgebung wäre dieser Endpunkt dem Lastenausgleich des Microservice zugeordnet, der Anforderungen auf die verfügbaren Instanzen verteilt.

Tipp

Erwägen Sie die Verwendung von API-Gatewaykommunikation.

Direkte Kommunikation zwischen Client und Microservice kann Nachteile beim Erstellen einer großen und komplexen auf Microservices basierenden Anwendung haben, ist aber für eine kleine Anwendung mehr als ausreichend. Erwägen Sie die Verwendung von API-Gatewaykommunikation beim Entwerfen einer großen auf Microservice-basierenden Anwendung mit Dutzenden von Microservices.

Communication between microservices (Kommunikation zwischen Microservices)

Eine auf Microservices basierende Anwendung ist ein verteiltes System, das möglicherweise auf mehreren Computern ausgeführt wird. Jede Dienstinstanz ist in der Regel ein Prozess. Daher müssen Dienste bei der Interaktion ein prozessinternes Kommunikationsprotokoll wie HTTP, TCP, AMQP (Advanced Message Queuing Protocol) oder ein Binärprotokoll verwenden. Welches Protokoll verwendet wird, hängt von der Art des jeweiligen Diensts ab.

Die beiden gängigen Ansätze für die Kommunikation zwischen Microservices sind HTTP-basierte REST-Kommunikation beim Abfragen von Daten und einfaches asynchrones Messaging beim Kommunizieren von Updates über mehrere Microservices hinweg.

Asynchrone messagingbasierte und ereignisgesteuerte Kommunikation ist bei der Weitergabe von Änderungen über mehrere Microservices hinweg von entscheidender Bedeutung. Bei diesem Ansatz veröffentlicht ein Microservice ein Ereignis, wenn etwas Erwähnenswertes geschieht, z. B. wenn er eine Geschäftsentität aktualisiert. Andere Microservices abonnieren diese Ereignisse. Wenn ein Microservice dann ein Ereignis empfängt, kann er die eigenen Geschäftsentäten aktualisieren, was dazu führen kann, dass weitere Ereignisse veröffentlicht werden. Diese Veröffentlichungs- und Abonnierungsfunktion wird in der Regel mit einem Ereignisbus erreicht.

Ein Ereignisbus ermöglicht eine auf Veröffentlichen/Abonnieren basierende Kommunikation zwischen Microservices, ohne dass sich die Komponenten ausdrücklich berücksichtigen müssen (siehe Abbildung unten).

Veröffentlichen/Abonnieren mit einem Ereignisbus.

Aus Anwendungssicht ist der Ereignisbus einfach ein Kanal zum Veröffentlichen und Abonnieren, der über eine Schnittstelle verfügbar gemacht wird. Die Art und Weise, wie der Ereignisbus implementiert wird, kann jedoch variieren. Beispielsweise könnte eine Ereignisbusimplementierung RabbitMQ, Azure Service Bus oder andere Servicebusse wie NServiceBus und MassTransit verwenden. Die folgende Abbildung zeigt, wie ein Ereignisbus in der eShop-Referenzanwendung verwendet wird.

Asynchrone ereignisgesteuerte Kommunikation in der Referenzanwendung.

Der mit RabbitMQ implementierte eShop-Ereignisbus bietet asynchrone 1:n-Funktionalität für Veröffentlichen/Abonnieren. Dies bedeutet, dass nach der Veröffentlichung eines Ereignisses mehrere Abonnenten auf dasselbe Ereignis lauschen können. Die folgende Abbildung zeigt diese Beziehung.

1:n-Kommunikation

Bei diesem 1:n-Kommunikationsansatz werden Ereignisse verwendet, um Geschäftstransaktionen zu implementieren, die mehrere Dienste umfassen, um letztendliche Konsistenz zwischen den Diensten sicherzustellen. Eine letztendlich konsistente Transaktion besteht aus einer Reihe von verteilten Schritten. Wenn der Benutzerprofilmicroservice den Befehl UpdateUser empfängt, aktualisiert er daher die Details des Benutzers in seiner Datenbank und veröffentlicht das UserUpdated-Ereignis für den Ereignisbus. Sowohl der Warenkorbmicroservice als auch der Bestellmicroservice haben den Empfang dieses Ereignisses abonniert und aktualisieren als Antwort darauf ihre Käuferinformationen in ihren jeweiligen Datenbanken.

Zusammenfassung

Microservices bieten einen Ansatz für die Anwendungsentwicklung und -bereitstellung, der den Anforderungen an Agilität, Skalierbarkeit und Zuverlässigkeit moderner Cloudanwendungen gerecht wird. Einer der Hauptvorteile von Microservices besteht darin, dass sie unabhängig voneinander horizontal skaliert werden können, was bedeutet, dass ein bestimmter Funktionsbereich skaliert werden kann, der mehr Verarbeitungsleistung oder Netzwerkbandbreite benötigt, um die Nachfrage zu unterstützen, ohne dass Bereiche der Anwendung unnötig skaliert werden, für die keine erhöhte Nachfrage besteht.

Ein Container ist eine isolierte, ressourcengesteuerte und portable Betriebsumgebung, in der eine Anwendung ausgeführt werden kann, ohne die Ressourcen anderer Container oder des Hosts anzutasten. Unternehmen setzen bei der Implementierung von auf Microservices basierenden Anwendungen zunehmend auf Container, und Docker ist zur Standardcontainerimplementierung geworden, die die meisten Softwareplattformen und Cloudanbieter übernommen haben.