Microservices [1][2][3][4] bedeuten Unabhängigkeit. So legen die Teams los und es entstehen viele Microservices. Nach dem ersten Problem in Produktion wird klar: Alle Microservices brauchen Logging, Monitoring und Tracing. Um Angriffen vorzubeugen, muss die Kommunikation außerdem authentisiert und verschlüsselt werden. Eine weitere Herausforderung ist, die Services vor den Effekten von jederzeit möglichen Netzwerk- oder Service-Ausfälle zu schützen.

Keine Microservices-Frameworks bitte!

Was tun? Für Entwickler liegt es nahe, technische Probleme mit einem Framework zu lösen. Ein Microservices-Framework hat jedoch den Nachteil, dass es nur eine bestimmte Programmiersprache unterstützt. Das schränkt die Auswahl der Implementierungstechnologien für die Microservices ein. Damit gibt das Team eine große Stärke der Microservices auf, nämlich die Technologie-Freiheit. Diese Freiheit erlaubt es, für jeden Microservice die beste Technologie zu wählen. Wenn ein anderer Technologie-Stack oder eine Programmiersprache für ein Detail-Probleme in einem System besser geeignet ist, dann kann man diese Technologie einfach in dem Microservice für diese Detail-Probleme einführen. Wenn die Microservices-Bibliothek mit der neuen Programmiersprache oder dem neuen Technologie-Stack inkompatibel ist, dann ist dieser Weg leider unmöglich.

Auch für einen einheitlichen Technologie-Stack ergibt die Technologie-Freiheit Vorteile. Jeder Technologie-Stack veraltet früher oder später. Durch die Technologie-Freiheit kann ein Update auf einen neue Version des Technologie-Stacks oder einer Bibliothek zunächst in einem einzigen Microservice umgesetzt werden, was das Risiko minimiert. So helfen Microservices dabei, ein System risikoarm modern zu halten. Wenn nun das Microservices-Framework mit der neuen Version des Technologie-Stack oder der Bibliothek inkompatibel ist, dann ist das Update entweder erschwert und völlig unmöglich.

Unabhängig davon, ob die technischen Herausforderungen mit Microservices-Frameworks oder anders gelöst werden, hat die Entscheidung erhebliche Konsequenzen. Der Netflix-Stack enthält beispielsweise die Java-Bibliothek Hystrix, mit der die Kommunikation zwischen Microservices bei Ausfällen einzelner Microservices abgesichert werden kann. Leider pflegt Netflix diese Bibliothek aber mittlerweile nicht mehr. So wird Code, der diese Bibliothek nutzt, plötzlich zu veraltetem Code, der so umgeschrieben werden muss, dass er die Bibliothek nicht mehr nutzt.

Was ist ein Service Mesh?

Microservices, die ein Service Mesh nutzen, können auf viele Bibliotheken verzichten. Dazu gehören insbesondere Monitoring-, Tracing- und Circuit-Breaking-Bibliotheken, die bisher von allen Microservices benötigt wurden. Ein Service Mesh hebt diese und weitere Funktionen aus der Anwendungsebene in die Infrastruktur. Es ist bei weitem nicht das erste Konzept mit diesem Ziel, aber das erste, das dem dezentralen Ansatz einer Microservice-Architektur folgt. Während andere Lösungen wie API Gateways auf zentralen Komponenten basieren, stellt ein Service Mesh jeder Microservice-Instanz eine zusätzliche Anwendung zur Seite, in der beispielsweise Monitoring und Circuit Breaking implementiert sind. Die Microservice-Instanz und diese Anwendung können über den localhost miteinander kommunizieren. Dieses Pattern wird “Sidecar” (dt. Beiwagen) genannt. Jegliche eingehende und ausgehende Kommunikation der Microservice-Anwendung verläuft über die als Sidecar bereitgestellte Anwendung, die deshalb auch als Service Proxy bezeichnet wird. Um den Verkehr automatisch an die Service Proxys zu lenken, können Netzwerkkonfigurationen vorgenommen werden, sodass die Microservices völlig unverändert bleiben können

Ein Service Mesh besteht, wie Abbildung 1 zeigt, aus zwei Ebenen, einer Control Plane und einer Data Plane. Die Data Plane ist eine dezentrale Ebene, die aus der Menge aller Service Proxys besteht. Diese liefern flächendeckend Daten zum Netzwerkverkehr und stellen sie der Control Plane zur Verfügung. Die Control Plane besteht aus zentralen Komponenten. Sie verarbeitet die von der Data Plane erfassten Daten und konfiguriert die Service Proxys.

Abb. 1: Service Mesh Architektur und Kommunikation zwischen den Komponenten
Abb. 1: Service Mesh Architektur und Kommunikation zwischen den Komponenten

Durch die verteilte Data Plane aus Service Proxys und die zentrale Control Plane, die diese konfiguriert und Daten verarbeitet, kann ein Service Mesh viele Funktionen implementieren:

Marktüberblick

Die Aufmerksamkeit, die das Thema erhält, hat zu einer Vielzahl von Service Mesh Implementierungen geführt, von denen viele allerdings noch unausgereift sind oder sehr wenige Funktionen umfassen. Bemerkenswert ist, dass alle großen Cloud-Anbieter Service Meshes in Form eigener Produkte (z.B. AWS App Mesh oder Add-ons für existierende Service Meshes anbieten. Die erste Implementierung eines Service Mesh ist Linkerd, dessen zweite Version aus Performance- und Komplexitätsgründen vollständig neu implementiert wurde. Anders als Istio, das zwar für Kubernetes optimiert, aber nicht darauf beschränkt ist, ist Linkerd 2 ausschließlich für Kubernetes verfügbar. In Linkerd 2 ist bisher nur ein Teil der Features implementiert, die Istio umfasst. Linkerd 2 unterstützt weder Tracing noch Möglichkeiten, Regeln für das Routing, Circuit Breaking und Autorisierung zu formulieren. Ähnliche Einschränkungen hat das erst im März produktionsreif verfügbare App Mesh von AWS. Consul, ursprünglich eine Service Discovery Lösung, bezeichnet sich heute als Service Mesh. Allerdings implementiert Consul aktuell lediglich die Sicherheits-Features eines Service Mesh.

Das Istio Service Mesh

Das mit Abstand populärste Service Mesh ist Istio. Es wird beispielsweise von Google Cloud, IBM Cloud und Cloud Foundry als Add-on integriert. Wie auch der Container-Orchestrierer Kubernetes ist es aus einem internen Projekt von Google entstanden. Neben Google tragen auch IBM und der Uber-Konkurrent Lyft von Beginn an zu dem Open Source Projekt bei. Damit haben sich einige der wichtigsten Spieler im IT-Bereich hinter der Technologie versammelt.

Die aktuell primär unterstützte Umgebung für Istio ist Kubernetes. Allerdings ist Istio auch mit Nomad und Consul kompatibel und soll zukünftig noch flexibler werden. Istio basiert zum großen Teil auf bewährten Tools wie dem Monitoring-System Prometheus, dem Dashboard Grafana, dem Tracing-System [Jaeger] (https://www.jaegertracing.io/) und dem Service Proxy Envoy, der für die Sidecars genutzt wird. Die Data Plane in Istio besteht aus adaptierten Envoy Proxys, durch die der gesamte Netzwerkverkehr geleitet wird. Die Control Plane von Istio ist der eigentliche Kern des Istio Service Mesh. Sie ermöglicht die Anbindung von Infrastruktur-Backends wie Prometheus und verteilt die Konfiguration für beispielsweise Routing, Authentifizierung und Circuit Breaking auf die Sidecars.

Das Beispiel

Die Frage ist nun, wie sich Istio auf ein konkretes Projekt auswirkt. Auf Github steht eine beispielhafte Anwendung bereit, die mit Istio implementiert ist. Das Beispiel hat eine ausführliche Anleitung, mit der es recht einfach in einer eigenen Umgebung zum Laufen gebracht werden kann, um Erfahrungen mit den wesentlichen Features von Istio zu sammeln.

Abb 2: Grafana-Dashboard für einen Service, wie es Istio ohne weitere Konfiguration anzeigt.
Abb 2: Grafana-Dashboard für einen Service, wie es Istio ohne weitere Konfiguration anzeigt.

Konkret löst das Beispiel die Microservices-Herausforderungen folgendermaßen:

Abb. 3: Abhängigkeiten zwischen den Services aus dem Tracing von Jaeger
Abb. 3: Abhängigkeiten zwischen den Services aus dem Tracing von Jaeger

Die Bereiche Monitoring, Tracing und Reslience kann Istio also in der Beispielanwendung abdecken. Dabei muss der Microservice nur für das Tracing HTTP Header von den eingehenden Requests in die ausgehenden Requests kopieren. Das ist im Beispiel mit der Bibliothek Spring Cloud Sleuth und einer Zeile Konfiguration implementiert, könnte aber natürlich auch anders implementiert werden. Die Technologie-Unabhängigkeit ist also gewährleistet. So löst Istio diese Probleme mit minimalem Aufwand für die Entwickler. Noch nicht einmal die Kubernetes-Konfigurationen müssen angepasst werden. Und auch die Werkzeuge zur Visualisierung installiert Istio gleich mit. Zumindest für das Beispiel hilft Istio jedoch beim Logging nicht.

Weitere Features von Istio

Der Funktionsumfang von Istio geht aber noch sehr viel weiter. Beispielsweise können die Sidecars automatisch wechselseitig authentifizierte TLS-Verbindungen (mutual TLS / mTLS) auf- und abbauen und somit die Kommunikation zwischen den Microservice-Instanzen authentifizieren und verschlüsseln. Auch in diesem Fall ist keine Änderung am Code der Microservices nötig. Istio implementiert außerdem ein rollenbasiertes Zugriffssystem (Istio RBAC), mit dem Autorisierungsregeln für Endnutzer und Services formuliert und durch die Envoy Proxys angewendet werden können.

Um zwei Versionen einer Anwendung in einem A/B-Test zu vergleichen, können in Istio Routing-Regeln definiert werden, die den Netzwerkverkehr auf mehrere Versionen einer Anwendung verteilen. Routing-Regeln können sogar auf Basis von HTTP-Headern oder der URL formuliert werden. Damit kann Istio auch beim Canary Releasing, das schrittweise, kontrollierte Einführen von neuen Versionen, unterstützen. Benutzer können z.B. nach verwendetem Browser unterschieden werden und sehen dann die neue oder alte Versionen der Website. Ebenso kann ein bestimmter, zufälliger Anteil der Benutzer die neue Version bekommen.

In der Praxis laufen nicht alle Anwendungen in einem Kubernetes Cluster. Eines der neuesten Features von Istio ist deshalb die Kontrolle und Beobachtung des Netzwerkverkehrs über ein Cluster hinaus. Zum einen können Services einbezogen werden, die in virtuellen oder physischen Maschinen laufen. Zum anderen können mit Istio mehrere Kubernetes-Cluster verbunden werden.

Der Haken

Viele Service Mesh Implementierungen wie Istio sind produktionsreif, werden von einem kompetenten Team entwickelt und decken einen bemerkenswerten Funktionsumfang ab. Es scheint fast zu gut um wahr zu sein - wäre da nicht der Lernaufwand, der zusätzliche Ressourcenverbrauch und die erhöhte Latenz.

Ein Service Mesh muss konzeptionell verstanden werden, um entscheiden zu können, ob es sich für eine Anwendung lohnt und welche Technologie am geeignetsten ist. Anschließend steht dem Entwicklungsteam die komplexe Aufgabe der Konfiguration des Service Mesh bevor.Neben dem mentalen gibt es auch technischen Mehraufwand. Die Komponenten der Control Plane und die zusätzlichen Service Proxys, die jedem Container zur Seite gestellt werden, benötigen zusätzliche CPU- und Arbeitsspeicherressourcen, die sich wiederum auf die Kosten für den Betrieb des Clusters auswirken. Der konkrete Mehrbedarf an Ressourcen hängt von der Anzahl der Anfragen und vom Service Mesh Produkt und dessen Konfiguration ab. Unsere Untersuchungen haben gezeigt, dass Istio mehr Ressourcen benötigt als Linkerd 2.

Ein weiterer Nachteil eines Service Mesh sind die Latenzeinbußen, die sich auf das Erlebnis der Endnutzer auswirken können. Die höhere Latenz entsteht durch den zusätzlichen Aufruf der Service Proxys bei jeder Anfrage. Denn statt einem direkten Aufruf zwischen Containern sind in einem Service Mesh nun zwei Proxys - auf Sender- und Empfängerseite - beteiligt. Istio beispielsweise gibt offiziell an, dass 90% der Anfragen eine Verzögerung von 8ms oder weniger aufweisen. Wie unsere Untersuchungen gezeigt haben, ist der Wert ist jedoch stark abhängig von dem konkreten Microservice-System und von der Istio-Konfiguration. Wenn Latenz ein wichtiger Faktor ist, muss die Latenz ohne und mit Istio gegebenenfalls in einem Testcluster verglichen werden, bevor Istio ins Produktionssystem Einzug erhält.

Fazit

Ein Service Mesh scheint den Technologie-Stack für Microservices perfekt zu machen. Es ermöglicht die zentrale Kontrolle von Monitoring, Resilienz, Routing und Sicherheit, die dezentral in den Sidecars umgesetzt werden. Damit fügt sich ein Service Mesh harmonisch in Microservice-Architekturen ein und kann API Gateways und viele Bibliotheken ersetzen. Istio als bekanntester Vertreter eines Service Mesh kann seine Stärken vor allem in Kubernetes-Umgebungen ausspielen, erlaubt aber auch, einzelne virtuelle Maschinen oder Container einzubinden.

Der Preis eines Service Mesh ist der Aufwand für die Einführung neuer Technologien, ein erhöhter Ressourcenverbrauch und eine höhere Latenz. Im konkreten Fall müssen diese Auswirkungen untersucht und Vor- und Nachteile abgewogen werden. Doch da es für Microservice-Systeme keine eleganteren Alternativen gibt, wird wohl kein Weg an einem Service Mesh vorbei führen.

Links & Literatur

  1. Eberhard Wolff: Microservices: Grundlagen flexibler Softwarearchitekturen, 2. Auflage, dpunkt, 2018, ISBN 978–3864905551  ↩

  2. Eberhard Wolff: Das Microservices–Praxisbuch: Grundlagen, Konzepte und Rezepte, dpunkt, 2018, ISBN 978–3864905261  ↩

  3. Eberhard Wolff: Microservices Rezepte – Technologien im Überblick, kostenlos unter https://microservices–praxisbuch.de/rezepte.html  ↩

  4. Eberhard Wolff: Microservices – Ein Überblick, kostenlos unter https://microservices–buch.de/ueberblick.html  ↩