Freigeben über


Containermicroservices

Hinweis

Dieses E-Book wurde im Frühjahr 2017 veröffentlicht und seitdem nicht mehr aktualisiert. Es gibt vieles im Buch, das wertvoll bleibt, aber ein Teil des Materials ist veraltet.

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 monolithische Anwendungen bezeichnet und auf vorskalierte Hardware für Spitzenlasten verpackt. 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 eine Änderung an einer Anwendungskomponente erfordert, dass die gesamte Ebene erneut getestet und erneut bereitgestellt wird.

Besonders wichtig im Zeitalter der Cloud ist, dass einzelne Komponenten nicht einfach skaliert werden können. Eine monolithische Anwendung enthält domänenspezifische Funktionen und wird in der Regel durch Funktionale Ebenen wie Front-End, Geschäftslogik und Datenspeicher unterteilt. Eine monolithische Anwendung wird skaliert, indem die gesamte Anwendung auf mehrere Computer geklont wird, wie in Abbildung 8-1 dargestellt.

Monolithischer Anwendungsskalierungsansatz

Abbildung 8-1: 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 wird in unabhängige Komponenten zerlegt, die zusammenarbeiten, um die Gesamtfunktionalität der Anwendung bereitzustellen. Der Begriff Microservice betont, dass Anwendungen aus Diensten bestehen sollten, die klein genug sind, um unabhängige Anliegen widerzuspiegeln, sodass jeder Microservice eine einzelne Funktion implementiert. Darüber hinaus verfügt jeder Microservice über klar definierte Verträge, sodass andere Microservices kommunizieren und Daten mit ihnen teilen können. Typische Beispiele für Microservices sind Warenkörbe, Bestandsverarbeitung, Kaufsubsysteme und Zahlungsverarbeitung.

Microservices können unabhängig skaliert werden, im Vergleich zu riesigen monolithischen Anwendungen, die zusammen skaliert werden. Dies bedeutet, dass ein bestimmter Funktionsbereich, der mehr Verarbeitungsleistung oder Netzwerkbandbreite benötigt, um den Bedarf zu decken, skaliert werden kann, anstatt andere Bereiche der Anwendung unnötig hochzuskalieren. Abbildung 8-2 veranschaulicht diesen Ansatz, bei dem Microservices unabhängig voneinander bereitgestellt und skaliert werden, sodass Instanzen von Diensten auf mehreren Computern erstellt werden.

Das Diagramm zeigt zwei Apps mit Kacheln, die unterschiedliche Funktionsbereiche darstellen, und sechs Rechtecke, in denen verschiedene Funktionsbereiche beider Apps gehostet werden.

Abbildung 8-2: Ansatz für die Anwendungsskalierung von Microservices

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 in der Anwendung sein, der horizontal hochskaliert 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, den ein einzelner Server unterstützen kann.

Microservices unterstützen auch unabhängige Updates. Diese lose Kopplung zwischen Microservices ermöglicht schnelle und zuverlässige Anwendungsentwicklung. Ihre unabhängige, verteilte Art unterstützt parallele Updates, wobei nur eine Teilmenge von 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 microservicebasierte 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 virtuellen Computern, wie in Abbildung 8-3 dargestellt.

Das Diagramm zeigt einen Vergleich zwischen Virtual Machines und Containern, bei denen virtuelle Computer über drei Apps verfügen, die jeweils in einem Gastbetriebssystem mit einem Hypervisor und einem Hostbetriebssystem isoliert sind und die Container über drei Apps verfügen, die in einer Container-Engine auf einem einzelnen Betriebssystem gehostet werden.

Abbildung 8-3: Vergleich von virtuellen Computern 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:

  • 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 Vereinigung von mehrstufigen Dateisystemen, die übereinander gestapelt sind, und ist die Grundlage eines Containers. Ein Image hat keinen Zustand und ändert sich nie, wenn es in verschiedenen Umgebungen bereitgestellt wird.
  • Container: Ein Container ist eine Runtime instance eines Images.
  • Containerbetriebssystem-Image: 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: Jedes Mal, wenn ein Containerimage erstellt wird, werden das Image und seine 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 verwenden zunehmend Container, wenn sie microservicebasierte Anwendungen implementieren, und Docker ist die Standardcontainerimplementierung geworden, die von den meisten Softwareplattformen und Cloudanbietern übernommen wurde.

Die Referenzanwendung eShopOnContainers verwendet Docker, um vier containerisierte Back-End-Microservices zu hosten, wie in Abbildung 8-4 dargestellt.

eShopOnContainers-Referenzanwendungs-Back-End-Microservices

Abbildung 8-4: eShopOnContainers-Referenzanwendungs-Back-End-Microservices

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.

Weitere Informationen zur Referenzanwendung finden Sie unter .NET-Microservices: Architektur für containerisierte .NET-Anwendungen.

Kommunikation zwischen Client und Microservices

Die mobile eShopOnContainers-App kommuniziert mit den containerisierten Back-End-Microservices über die direkte Client-zu-Microservice-Kommunikation , wie in Abbildung 8-5 dargestellt.

Das Diagramm zeigt eine App, die auf einem mobilen Gerät gehostet wird, das mit drei Back-End-Microservices verbunden ist, von denen jeder über einen eigenen Web-AP I-Container verfügt.

Abbildung 8-5: Direkte Client-zu-Microservice-Kommunikation

Bei der direkten Client-zu-Microservice-Kommunikation sendet die mobile App Anforderungen an jeden Microservice direkt über den ö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 Client-zu-Microservice-Kommunikation kann Nachteile beim Erstellen einer großen und komplexen Microservice-basierten Anwendung haben, ist aber für eine kleine Anwendung mehr als ausreichend. Beim Entwerfen einer großen Microservice-basierten Anwendung mit Dutzenden von Microservices sollten Sie die API-Gatewaykommunikation in Erwägung ziehen. Weitere Informationen finden Sie unter .NET-Microservices: Architektur für containerisierte .NET-Anwendungen.

Kommunikation zwischen Microservices

Eine Microservices-basierte 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 Microservice und Microservice sind HTTP-basierte REST-Kommunikation beim Abfragen von Daten und einfaches asynchrones Messaging bei der Kommunikation von Updates über mehrere Microservices hinweg.

Asynchrone messagingbasierte ereignisgesteuerte Kommunikation ist bei der Weitergabe von Änderungen über mehrere Microservices 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, aktualisiert er seine eigenen Geschäftsentitäten, was wiederum 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 die Veröffentlichungs-Abonnieren-Kommunikation zwischen Microservices, ohne dass die Komponenten explizit aufeinander achten müssen, wie in Abbildung 8-6 dargestellt.

Veröffentlichen und Abonnieren mit einem Ereignisbus

Abbildung 8-6: Veröffentlichen und 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. Abbildung 8-7 zeigt, wie ein Ereignisbus in der Referenzanwendung eShopOnContainers verwendet wird.

Asynchrone ereignisgesteuerte Kommunikation in der Referenzanwendung

Abbildung 8-7: Asynchrone ereignisgesteuerte Kommunikation in der Referenzanwendung

Der mit RabbitMQ implementierte eShopOnContainers-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. Abbildung 8-9 veranschaulicht diese Beziehung.

1:n-Kommunikation

Abbildung 8-9: 1:1-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 Warenkorb-Microservice als auch der bestellende Microservice haben dieses Ereignis abonniert und als Antwort ihre Käuferinformationen in ihren jeweiligen Datenbanken aktualisiert.

Hinweis

Der mit RabbitMQ implementierte eShopOnContainers-Ereignisbus soll nur als Proof of Concept verwendet werden. Für Produktionssysteme sollten alternative Ereignisbusimplementierungen in Betracht gezogen werden.

Informationen zur Ereignisbusimplementierung finden Sie unter .NET-Microservices: Architektur für containerisierte .NET-Anwendungen.

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 Standard Vorteile von Microservices besteht darin, dass sie unabhängig 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, die keine erhöhte Nachfrage aufweisen.

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 implementieren zunehmend Container, wenn sie Microservice-basierte Anwendungen implementieren, und Docker hat sich zur Standardcontainerimplementierung entwickelt, die von den meisten Softwareplattformen und Cloudanbietern übernommen wurde.