Während monolithische Applikationen die Tanker der Software­welt sind, gleichen modulare Anwendungen eher einer Armada schneller, wendiger Motorboote. Kein Wunder also, dass immer mehr Systeme aus immer kleineren Komponenten bestehen – die sogenannten Microservices. Die Vorteile: Entwickler können dadurch die Entwicklung, das Testing und das Deployment ­eines Systems unabhängig voneinander durchführen. Außerdem lässt sich wenig Code besser überblicken und warten. Fällt eine ­Komponente aus, ist im Idealfall nur eine einzelne Funktionalität betroffen.

So jedenfalls die Theorie. In der Praxis haben diese mehr oder weniger klassisch verteilten Anwendungen auch Nachteile. Wenn einzelne Teile ausfallen oder – schlimmer noch – nur noch teilweise funktionieren, betrifft das oft eben doch das ganze System. Das hat zu neuen Anforderungen an die Softwarearchitektur, die Steuerung eines Netzwerks und das Monitoring geführt.

Cloud Native

Anwendungen, die konsequent auf Microservices und eine Cloud-­Infrastruktur setzen, bezeichnet die Fachwelt auch als “Cloud Native”. Solche Applikationen setzen konsequent auf die Infrastrukurvorteile der Cloud-Architektur. Container und Containermanager wie ­Kubernetes sorgen dabei für Ordnung in der Welt der Microservices. Dadurch können Entwicklerteams nicht nur die Entwicklung der Applikationen, des CI und CD sowie die ­Nutzer- oder Rollenkonzepte harmonisieren. Auch optimieren sie die Kommunikation der verschiedenen Module einer Applikation miteinander.

Als Plattform ist Kubernetes quasi zum Standard für die Verwaltung von Applikationen in einem Cluster geworden. Es macht im Wesentlichen nichts anderes, als Container zu starten, zu überwachen, gegebenenfalls wieder zu beenden und mit einem Cluster von Knoten zu arbeiten. Dabei sorgt es auch für eine Vernetzung der ­Container. Die kleinste Einheit ist allerdings kein Container, sondern ein Pod – also ein Behälter für einen oder mehrere Container. Die Container eines Pods teilen sich ­Ressourcen wie Volumes oder die IP-Adresse. Dies ermöglicht einige interessante Optionen, um Applikationen zu modularisieren: Ähnlich wie bei den aus der OOP bekannten Patterns gibt es hier sogenannte Container-Patterns. Dieses Konzept machen sich auch die Service-Meshes zunutze.

Was ist ein Service-Mesh?

Service-Meshes sind steuerbare Komponenten, die sich zwischen die Service-zu-Service-Kommunikation einer Applikation schalten. Dies muss nicht zwangsläufig in einem Kubernetes ­Cluster passieren, ist dort aber besonders einfach. Service-­Meshes nutzen dazu die Tatsache, dass sich Pods mithilfe gängiger ­Container-Patterns flexibel erweitern lassen. Dies kann etwa in Form eines Sidecars geschehen, was ein zusätzlicher Container in einem Pod ist, der als Proxy dient. Der Proxy kann als Teil eines Service-Meshs die gesamte Kommunikation kontrollieren und lässt sich durch ein sogenanntes Control Plane steuern. Dadurch kann ein Service-Mesh folgende Fragen beantworten:

Dazu nutzt ein Service-Mesh eine Reihe von Maßnahmen wie ­automatisches Monitoring, Traffic-Management oder ­Multi­cluster. Im Folgenden gibt es einen Überblick über die wichtigsten Maßnahmen:

Automatisches Monitoring

Ein Service-Mesh kann die gesamte Kommunikation über die ­Proxies in einem Monitoring-System wie Prometheus protokollieren. Dadurch lässt sich beispielsweise auswerten, wie, wann oder mit welcher Latenz die Services miteinander kommunizieren. Es ist oft wertvoll, zu wissen, welche Services im realen Umfeld überhaupt miteinander kommunizieren. Alleine aus dem, was ­deployed ist, können Entwickler das als eine Art ­statische ­Analyse schlecht erkennen. Ebenso interessant ist es für Entwickler zu sehen, welche Fehler es bei der Kommunikation gibt, wie hoch die Fehlerrate ist und welche Services die Ursache dafür sind. Und schließlich hilft Entwicklern ein schneller und einfacher Überblick über die Durchlaufzeiten und die beteiligten Services einer komplexen Transaktion, die durch mehrere unterschiedliche Services ausgeführt wird.

Traffic-Management

Kubernetes regelt den Zugriff auf Pods durch sogenannte Service­ressourcen. Dabei lassen sich im wesentlichen nur Rundlauf­verfahren (Round Robin) anwenden, bei denen die Prozesse in einer Warteschlange nacheinander ablaufen. Der prozentuale Anteil des Traffics ist somit durch die Anzahl der Instanzen eines Deployments bestimmt. Eine freie Steuerung des Traffics – etwa auf Basis von Nutzernamen oder Herkunft – ist nicht möglich. Ein Service-Mesh bietet hier wesentlich mehr Möglichkeiten. Sicherheitsmaßnahmen

Die Aufteilung einer monolithischen Applikation in einzelne ­Services – die über Netzwerke und nicht in einem einzelnen Prozess miteinander kommunizieren – bringen auch andere Anforderungen an die Sicherheit mit sich. Um sich zum Beispiel gegen einen Man-in-the-Middle-Angriff zu schützen, bei dem der Angreifer zwischen zwei Kommunikationspartnern steht, muss die Kommunikation verschlüsselt sein. Das gilt besonders, wenn Entwickler einem Netzwerk nicht trauen können. Darüber hinaus sind anpassbare Richtlinien für die Zugriffe und eine flexible Zugangskontrolle notwendig. Dazu gehört zum Beispiel Mutual TLS, mit dem sich Dienste gleichzeitig gegenseitig authentifizieren können. Wer prüfen möchte, wer was zu welchem Zeitpunkt getan hat, benötigt außerdem Audit-Werkzeuge.

Multicluster

In vielen Szenarien müssen Entwickler externe oder ­Legacy-Systeme in eine Anwendung einbauen. Außerdem befinden sich manchmal ­Teile einer größeren Applikation in mehr als einem Kubernetes-­Cluster. Für solche Fälle lassen sich ­Service-Meshes auch über Cluster- und Systemgrenzen hinweg spannen.

Robustere Infrastruktur

Wie bereits erwähnt, können bei komplexen Architekturen mit vielen kleinen Funktionsservices Fehler in diesen Ketten von Aufrufen entstehen. Das kann wiederum zu Folgefehlern führen. ­Service-Meshes versuchen die Auswirkungen solcher Fehler durch sogenannte ­Resilience Patterns zu minimieren, damit es nicht zu einem Ausfall der gesamten Anwendung kommt.

Anwendungsfälle

Die Vorteile von Service-Meshes treten zum einen in der Betriebs­phase einer Applikation auf, zum anderen während der Software-Entwicklung. Für den Betrieb einer Applikation sind die Ressourcen von Kubernetes in vielen Fällen ausreichend. Ist eine Anwendung jedoch komplexer und läuft sie über einen längeren Zeitraum, ist ein flexibleres und dynamisches Routing nötig. Auf diese Weise lässt sich zum Beispiel eine neue Version prozentual oder anhand gewisser Parameter (etwa nur für bestimmte Mobilgeräte, Browser oder Nutzer) ausspielen. So können Entwickler den Anteil des Traffics – vielleicht über mehrere Tage hinweg und unter ständiger Beobachtung der technischen und fachlichen Metriken – schrittweise von 0 auf 100 Prozent erhöhen. Mit einem Service-Mesh geht das leichtgewichtig, ohne dass ein Entwickler irgendeinen Programmcode oder ein ­Deployment verändern muss. Service-Meshes sind in der Betriebsphase aber auch dann von Vorteil, wenn eine Software Mängel aufweist. Solche einzelnen Fehler oder größer werdende Latenzen sollen bei einer produktiven Anwendung natürlich nicht zum Komplett­ausfall führen. Um dies zu verhindern oder zumindest die Wahrscheinlichkeit zu verringern, können Entwickler die Requests pro Sekunde dynamisch limitieren oder ein Circuit-Breaker-Pattern einbringen. Mit so einem Resilience-Pattern können Entwickler wiederkehrende Verbindungsfehler einfacher handhaben. Beides lässt sich dynamisch parametrisieren, ohne den Programmcode zu verändern.

Während der Softwareentwicklung bringen Service-Meshes vor allem bei der Simulation von Nutzerverhalten ­entscheidende Vorteile: Wenn zum Beispiel das Nutzerverhalten schwer vorherzusagen ist, lassen sich auch das Verhalten einer Applika­tion und die daraus eventuell resultierenden erhöhten ­Latenzen schlecht testen. Hier kann ein Service-Mesh beim Test den ­realen Live-Traffic einer Applikation abzweigen und auf ein Test-­Deployment leiten. Auch Fehler, die durch die erhöhten Latenzen anderer Services entstehen, können Entwickler durch die sogenannte Error-Injektion an bestimmten Stellen der Aufrufkette simulieren.

Aktuelle Service-Meshes

Mittlerweile gibt es mehrere Service-Meshes, die unter anderem auf Kubernetes optimiert sind. Der klare Platzhirsch ist hierbei ­Istio. Teams von Google und IBM haben das Opensource-Projekt auf ­GitHub in Zusammenarbeit mit dem Envoy-Team von Lyft gestartet. Die Mitarbeit von Google ist wohl auch der Grund dafür, dass sich ­Istio inzwischen für Googles Kubernetes-Engine auswählen lässt. Eine einfachere Ins­tallation gibt es nicht. Dennoch war Istio in der Vergangenheit ein ziemlich großer Satz sich bewegender Teile. Erst mit der Version 1 zeigt sie sich ein wenig modularisiert und als Ganzes etwas handlicher.

Ein weiteres Service-Mesh ist Linkerd von Buoyant. Es ­existierte schon vor Frameworks wie Kubernetes. Linkerd 2 will diese Erfahrungen nun als Service-Mesh für Kubernetes um­setzen und dabei dessen Möglichkeiten nutzen. Laut Buoyant ist Linkerd 2 nicht nur ultra­leicht und ultraschnell, sondern auch ein “<No-Hype-Service-Mesh”. Das “No-Hype” bezieht sich wohl darauf, dass Linkerd zwar weniger Funktionen hat, diese aber ausgereifter und performanter sind oder sein werden. Diese Einschätzung ist auch nicht ganz falsch – Istio hatte in der Vergangenheit öfter Qualitäts- und Latenzprobleme.

Cilium ist kein Service-Mesh, sondern ein Netzwerk-Plugin und setzt an einer ganz anderen Stelle an als Istio und Linkerd. Cilium soll Netzwerkverbindung zwischen Services transparent sichern – also damit eigentlich ein Netzwerk. Sicherlich ­lassen sich damit nicht alle Funktionen eines Service-Meshs realisieren, aber einige wichtige. Dazu gehören die transparente ­Verschlüsselung, L7 Firewalls, das Sammeln von Metriken, die Verbindung mehrerer Cluster oder die sichere Kommunikation zwischen Diensten basierend auf Identitäten. Cilium nutzt dabei Funktionen des Linux Kernels wie BPF, was es besonders latenz­arm und performant machen soll.

Service-Meshes verbinden die Pods eines Containermanagements wie Kubernetes über Sidecars in ein übergeordnetes Netzwerk ein. Dadurch können Entwickler die Kommunika­tion der Komponenten untereinander kontrollieren und steuern
Service-Meshes verbinden die Pods eines Containermanagements wie Kubernetes über Sidecars in ein übergeordnetes Netzwerk ein. Dadurch können Entwickler die Kommunika­tion der Komponenten untereinander kontrollieren und steuern

Herausforderungen in der Anwendung

So gut das alles klingt, Entwickler müssen sich darüber im ­Klaren sein, dass sie mit einem Service-Mesh immer auch eine ­große Menge weiterer Komponenten zu einem Cluster hinzu­fügen (etwa Admission Controller, Control Plane oder Proxies und vieles mehr). Sicherlich ist von Vorteil, dass sich zum Beispiel Istio als eine Art gesonderte Infra­strukturkomponente ansehen lässt, so wie es das Cloud-Native-­Paradigma vorschreibt. Dennoch müssen Entwickler diese betreiben, updaten und so weiter. Um die Anforderung zu erfüllen, dass Proxy-­Container möglichst transparent einzubringen sind, sind darüber hinaus teilweise gesonderte Rechte für die Proxy-Container notwendig. Diese möchten Entwickler aber nicht immer unbedingt erlauben. Dennoch bleibt ihnen oft nichts anderes übrig, wenn sie Service-­Meshes einsetzen. Dass sie dann den gesamten Traffic über Proxies leiten müssen, bedeutet zudem eine höhere Latenz bei den Zugriffen. Das kann eine stark verteilte Anwendung schon vor Probleme stellen.

Conclusion

Wer einen Einstieg in die Welt der Service-Meshes sucht, dem ­bietet sich Istio an. Die Dokumentation ist wirklich gut und es gibt viele Beispiele, wie Entwickler Istio installieren und verwenden. Außerdem gibt die Bookinfo-Anwendung eine gute Übersicht über die Möglichkeiten von Istio. Wer statt Minikube oder ähnlichem ein Cluster mit quasi vorinstalliertem Istio ­haben will, sollte sich bei der Google-Kubernetes-Engine umsehen. ­Entwickler, die verstehen wollen, wie das alles im Detail funktioniert – etwa, weil sie die Verantwortung in einem Projekt dafür übernehmen – müssen sich allerdings schon recht intensiv mit Kubernetes, Istios Konzept zur Konfiguration und dem ­Envoy- Proxy beschäftigen.

Entwickler sollten die Komplexität eines Service-Meshes nicht unterschätzen. Dennoch: Sie bieten eine Erweiterung an Funktionalität, die zum Betrieb einer realen Applikation notwendig ist. Bei Kubernetes ist Istio Teil der Infrastruktur und folgt somit dem Cloud-­Native-Paradigma. Die Plattform leistet nichtfunktionale Anforderungen und Routineaufgaben. Das ist großartig, da Entwickler dies durch ein ­simples Re-Deployment ihrer Applikation praktisch geschenkt bekommen: Sie müssen weder den Code anpassen noch Container. Dies und die umfangreiche Funktionalität machen Service-Meshes wie Istio so sexy.