Istio ist der Platzhirsch unter den Service-Meshes, aber die Alternative Linkerd weiß durch schnelle Konfiguration und leichte Bedienbarkeit zu überzeugen.
Neue Anforderungen an zeitgemäße Webservices erfordern oft einen Wechsel von alten, monolithischen Architekturen hin zu Microservices. Das Einführen der Architektur schafft ein verteiltes System, dessen Betrieb äußerst komplex sein kann. Zusätzlich kann die Kommunikation zwischen Services fehlschlagen, da sie über das Netzwerk verläuft. Der Container-Orchestrierer Kubernetes bietet eine Plattform, um das Betreiben von Microservices zu vereinfachen. Sie automatisiert Aufgaben, die früher Administratoren manuell ausgeführt haben.
Kubernetes hat einen klaren Zuständigkeitsbereich, der aber einige Probleme von Microservices ungelöst lässt. Es ist deshalb immer noch komplex, Microservices in einem Kubernetes-Cluster zu betreiben. Service-Meshes können helfen, die Komplexität zu verringern.
Was ist ein Service-Mesh?
Service-Meshes lösen Probleme die weder im Zuständigkeitsbereich der technischen Infrastruktur noch der Fachlichkeit liegen. Ein Beispiel ist die sichere Kommunikation zwischen Microservices: Kubernetes stellt eine Netzwerk- und eine Serviceabstraktion zur Verfügung, mit denen Microservices kommunizieren können. Es forciert aber nicht, dass die Kommunikation verschlüsselt stattfindet.
Funktionen wie das Verschlüsseln von Netzwerkkommunikation, Monitoring, Routing und andere Aspekte, die jeder Microservice benötigt, kann man als Aufgaben der Applikationsinfrastruktur bezeichnen. Entwickler setzen sie häufig mit Bibliotheken in den Microservices um. Mit einem Service-Mesh können sie die Funktionen der Applikationsinfrastruktur jedoch aus den Microservices in die Infrastruktur heben.
Wenn Aspekte der Applikationsinfrastruktur durch Bibliotheken wie Hystrix ergänzt werden, setzt das allerdings voraus, dass alle Microservices dasselbe Framework und häufig dieselbe Programmiersprache nutzen. Zusätzlich kann das Einbinden solcher Bibliotheken das lokale Entwickeln erschweren. Beim Beispiel der verschlüsselten Kommunikation muss man dazu entweder Zertifikate erstellen oder, je nach Umgebung, die Verschlüsselung ein- beziehungsweise ausschalten. Zertifikate lokal zu pflegen ist aufwendig. Die Verschlüsselung umgebungsabhängig ein- und auszuschalten ist Logik, die nicht in Microservices gehört.
Service-Meshes verlagern die Applikationsinfrastruktur in Proxys, die Anfragen an und von Microservices abfangen, analysieren, eventuell anreichern und erst anschließend an den jeweiligen Empfänger weiterleiten. Die Proxys ähneln Reverse-Proxys wie nginx oder HAProxy. Sie stellen neben den klassischen Aufgaben wie Load Balancing und Routing noch andere Funktionen zur Verfügung und können über eine API dynamisch konfiguriert werden.
Mit dem simplen Konzept der Proxys lassen sich Probleme wie Verschlüsselung, Metriken zum Netzwerkverkehr, Canary Releasing oder Resilienz lösen. Die enge Lokalität eines Microservices mit seinem Service-Proxy lässt sich in Kubernetes einfach mit Pods realisieren. Jedem Applikations-Container können Entwickler im selben Pod automatisch einen Service-Proxy-Container zur Seite stellen. Die Komposition von Applikationen und der Applikationsinfrastruktur geschieht mit Standardbordmitteln von Kubernetes. Dabei müssen Entwickler die Proxys nicht einmal explizit definieren, sondern können sie hinter den Kulissen automatisch in die Pods injizieren.
Die Gesamtheit aller Proxys bilden bei einem Service-Mesh die Data Plane. Neben der Data Plane weist ein Service-Mesh als zweite Komponente die Control Plane auf.
Die Control Plane steuert alle Proxys der Data Plane. Damit kann man die Proxys dynamisch zur Laufzeit anpassen. Service-Meshes, die auf Kubernetes laufen, können aktuelle Service-Instanzen und deren Identität über die Kubernetes API abfragen. Service Meshes können auf Veränderungen im Cluster reagieren, indem sie auf Kubernetes API-Events reagieren.
Kubernetes und Service-Meshes ergänzen sich gut und sind daher oft zusammen im Einsatz. Manche Varianten, zum Beispiel Linkerd 2, setzen Kubernetes als Laufzeitumgebung voraus. Andere wie das Service-Mesh Istio oder Consul, sind flexibler und unterstützen verschiedene Umgebungen. Linkerd 2 verlässt sich auf die Funktionen, die Kubernetes zur Verfügung stellt und integriert sich, anstatt eigene Konzepte zu Kubernetes hinzuzufügen. Im Gegensatz dazu hat Istio keine Scheu, neue Konzepte einzuführen.
Istio: Der Platzhirsch mit Schwächen
Wer über Service-Meshes spricht, kommt um Istio nicht herum. Es ist wohl das bekannteste Service-Mesh, maßgeblich entwickelt von Google und IBM. Google hat mit der Entwicklung von Kubernetes die Bedürfnisse der Industrie auf den Punkt getroffen. Entsprechend hoch waren der Vertrauensvorschuss, aber auch die Erwartungen an Istio.
Istio war von Anfang an mit vielen Features ausgestattet und wurde seitens Google intensiv beworben. Das hat Istio viel Aufmerksamkeit beschert. Die erste produktionsreife Istio-Version stellte sich jedoch als überkomplex heraus und es gibt nur wenige Berichte von der Nutzung Istios in Produktivsystemen. Die aktuelle Version 1.2 hat die Komplexität bereits reduziert und weitere Veränderungen sind geplant. Die extreme Konfigurierbarkeit und Flexibilität sind allerdings zentrale Designentscheidungen von Istio und bleiben dem Projekt daher auf absehbare Zeit erhalten.
Linkerd 2: Für Kubernetes neu aufgelegt
Linkerd 1 von Buoyant war das erste Produkt, das ein Service-Mesh implementierte und somit den Begriff definiert hat. Als Technik-Stack haben die Erfinder Scala in Verbindung mit Netty und Finagle benutzt. Die nahe Verwandtschaft zum Twitter-Stack ist kein Zufall: Beide Buoyant-Gründer sind ehemalige Twitter-Mitarbeiter. Sie haben 2015 erkannt, dass der Twitter-Stack generell für verteilte Systeme einen großen Nutzen hat und Buoyant gegründet, um ein Service-Mesh zu bauen. Die Auswahl der JVM als Laufzeitumgebung für die Data Plane hat sich jedoch als ungeeignet herausgestellt: Wenn zu jeder Microservice-Instanz ein Service-Proxy hinzugefügt wird, sind die Latenz und Ressourcenanforderungen der JVM zu hoch.
Ein weiteres Problem von Linkerd 1 ist dessen Komplexität: Ähnlich wie Istio kann Linkerd 1 in verschiedenen Umgebungen zum Einsatz kommen und ist dementsprechend umfassend konfigurierbar. Um die Probleme von Linkerd 1 zu beheben, hat Buoyant ein neues Service-Mesh Produkt namens Linkerd 2 (ursprünglich Conduit) von Grund auf neu entwickelt. Wie Linkerd 1 ist auch die neue Version komplett unter der Apache-2.0-Lizenz verfügbar. Der Artikel bezieht sich, sofern nicht explizit angegeben, immer auf Version 2 von Linkerd.
Das neue Linkerd nutzt Go für die Control Plane und Rust für die Data Plane und reduziert damit Ressourcenanforderungen und Latenzeinbußen gegenüber Linkerd 1 erheblich. Ziele sind unter anderem eine einfache Installation und ein komfortabler Betrieb. Version 2 hat sich dazu auf Kubernetes als Plattform beschränkt. Im Gegensatz zu Linkerd 1 setzt Linkerd 2 also eine spezifische Umgebung voraus, die es weniger flexibel, aber leichter zu pflegen machen.
Ähnlich wie bei Istio müssen Entwickler den Namespace oder den Pod mit bestimmten Annotationen markieren, damit eine Anwendung in das Service-Mesh aufgenommen wird. Die Service-Proxys werden dann automatisch in den jeweiligen Pod injiziert. Alternativ können Nutzer Kubernetes-Konfigurationsdateien per Skript manipulieren, bevor sie angewendet werden.
Mehr Details zum Service Mesh gibt es im Service Mesh Primer, im Blogbeitrag „Service Mesh und Kubernetes“ und im Podcast zum Thema Service Mesh Teil 1 und Teil 2
SMI: Eine gemeinsame API für Service-Mesh Features
Linkerd und Istio sind nicht die einzigen Service-Mesh-Implementierungen. Auch HashiCorp und Amazon Web Services haben eigene Service-Meshes entwickelt. Entstanden ist eine Vielzahl an Konzepten und APIs. Mittlerweile gibt es viele Tools, die auf Service-Meshes aufbauen:
- Flagger, zur Automatisierung von Canary Releasing und A/B-Testing,
- Squash, ein Debugger für laufende Microservice-Anwendungen und
- Kiali, ein Service-Mesh-Dashboard, das aktuell nur für Istio verfügbar ist.
Damit nicht jedes Service-Mesh derartige Tools entwickeln muss, haben Microsoft, HashiCorp, Buoyant und Solo.io gemeinsam eine API-Spezifikation für Service-Mesh Features entwickelt. Das im Mai 2019 auf der KubeCon vorgestellte Service-Mesh Interface (SMI) wird von Linkerd und Istio unterstützt, wenn auch nur zu einem kleinen Teil.
Alle Microservices im Blick dank Metriken und Dashboards
Eines der wichtigsten Features eines Service-Meshes sind das flächendeckende Erheben von Metriken zu den Zuständen und Netzwerkaktivitäten von Microservices. Dazu gehören:
- Anzahl der Anfragen
- Erfolgsrate der Anfragen (anhand des HTTP Status Codes)
- Zusammensetzung der Antwortzeiten bei verschachtelten Aufrufen
Linkerd erfasst diese und weitere Metriken standardmäßig für jeden Service. Durch sogenannte Service Profiles können Anwender Metriken auch pro Endpunkt erzeugen. Linkerd verwendet das Monitoring-Werkzeug Prometheus für das Sammeln der Metriken und enthält eine vorkonfigurierte Instanz von Grafana mit vorkonfigurierten Dashboards. Einen Überblick über die laufende Anwendung verschafft das Linkerd Dashboard. Es bereitet erfasste Metriken beispielsweise visuell durch Abhängigkeitsgraphen auf.
Mit der Funktion Linkerd Tap können Entwickler den aktuellen Netzwerkverkehr beobachten. Das kann ein hilfreiches Werkzeug bei der Bekämpfung von Fehlern sein. Anwender können Linkerd Tap sowohl über die Linkerd-Oberfläche als auch über das CLI nutzen.
Routing unter Kontrolle: Von Load Balancing bis Canary Deployment
Da jeglicher ein- und ausgehende Netzwerkverkehr durch den Linkerd Service-Proxy geroutet wird, können Anwender das Load Balancing zwischen zwei Services mit Linkerd effizienter implementieren als bei Standard-Kubernetes-Services. Statt Round-Robin, also simples Durchrotieren durch alle Pods eines Services, nutzt Linkerd für HTTP, HTTP/2 und gRPC den EWMA-Algorithmus (exponentially weighted moving average), wodurch sich die Latenz von Anfragen verringern kann.
Linkerd implementiert bereits die TrafficSplit API des Kubernetes Service-Mesh Interfaces. Sie ermöglicht es, Netzwerkverkehr, der für einen Service bestimmt ist, anteilig an einen anderen Service zu schicken, beispielsweise an eine neue Version des Services. Das kann man für Rolling, Canary oder Blue/Green Deployments benutzen.
In Kombination mit weiteren Tools wie Flagger kann man ein Canary Deployment automatisch durchführen. Flagger rollt neue Versionen schrittweise aus und verifiziert sie durch Prometheus-Metriken. Nur wenn sich die neue Version entsprechend der definierten Parameter verhält, wird sie weiter ausgerollt. Da Flagger Prometheus-Querys direkt unterstützt, können anspruchsvolle Canary Deployments definiert werden. Mit Flagger und Linkerd können Entwickler ebenso A/B-Tests auf Basis von Headern durchführen.
Gewappnet gegen Ausfälle mit Retry und Timeout
Microservices müssen jederzeit mit Netzwerkproblemen oder defekten Kommunikationspartnern rechnen. Wichtig ist, dass das Gesamtsystem trotz Ausfällen einzelner Microservices lauffähig bleibt und Endnutzer möglichst wenig Beeinträchtigung erfahren. Eine Maßnahme, die zumindest die Auswirkungen von kurzfristigen Netzwerkausfällen entschärft, ist das erneute Versuchen von fehlschlagenden Netzwerkanfragen, sogenannte Retries.
Retries können allerdings dazu führen, dass ein überlastetes System durch wiederholte Anfragen endgültig in die Knie gezwungen wird. Außerdem ist per se nicht jede Anfrage wiederholbar, etwa das Absenden einer Bestellung. Retries müssen also mit Bedacht konfiguriert werden. Linkerd kann das Retry-Verhalten losgelöst von der Applikation und für jeden HTTP-Endpunkt einzeln konfigurieren.
Entwickler können Timeouts nutzen, um dem Problem kaskadierender Retries zwischen Microservices beizukommen. Linkerd kann für jeden Endpunkt eine solche maximale Antwortzeit konfigurieren.
mTLS: Vertrauen ist gut, Automatisierung ist besser
Um Sicherheit und Vertrauen innerhalb des Clusters zu garantieren, setzt Linkerd, genau wie andere Service-Meshes, auf mTLS (mutual TLS authentication). Die Sicherheit wird über die Verschlüsselung mittels TLS garantiert. Anders als bei normalen TLS-Verbindungen wie https, überprüft nicht nur der Client die Identität des Serverzertifikats, sondern der Server überprüft zusätzlich das Zertifikat, das der Client mitschickt. Das Vertrauen ist gegenseitig (mutual) garantiert.
Wenn Microservices in einem Service-Mesh wie Linkerd miteinander kommunizieren, geschieht das immer über die Service-Proxys, die eine sichere mTLS-Verbindung auf- und abbauen können. Linkerd automatisiert das Ausstellen der Zertifikate und kann deshalb die Gültigkeit auf 24 Stunden reduzieren. Das ist ein erheblicher Sicherheitsfortschritt im Vergleich zu unverschlüsselter Kommunikation, einfachem TLS oder mTLS mit langlebigen Zertifikaten. Wie andere Service-Meshes setzt Linkerd mTLS vollständig automatisch um, sodass allein dieses Feature ein Grund für den Einsatz eines Service-Mesh sein kann.
Features von Istio und Linkerd im Vergleich
Wie Abbildung 6 zeigt, bietet Istio zur Zeit mehr Features als Linkerd. Beispielsweise unterstützt Istio das Ende-zu-Ende-Tracing von Anfragen sowie die Konfiguration von Circuit Breakern und Autorisierungsregeln. Doch bereits im nächsten Release von Linkerd soll Circuit Breaking folgen. So sind aktuell nur Tracing und die Konfiguration von Autorisierungsregeln Vorteile von Istio.
Wichtiger als die Anzahl kann die Umsetzung einzelner Features sein. Häufig sind die Ansätze und Konzepte von Istio und Linkerd sehr unterschiedlich. Bei Linkerd können beispielsweise Retry- und Timeout-Regeln pro Endpunkt und HTTP-Methode konfiguriert werden, während sich derartige Regeln bei Istio immer auf alle Anfragen eines Services beziehen. In vielen anderen Fällen bietet Istio mehr Flexibilität, was jedoch zu einer hohen Komplexität führt.
Dann wäre da noch die Performance: Service-Meshes haben ihren Preis. Zum einen verbrauchen die Service-Proxys und die Control Plane CPU- und Arbeitsspeicherressourcen. Zum anderen führt der Zwischenstopp, den jede Anfrage bei den Service-Proxys einlegt, zu einer höheren Latenz. Generelle Aussagen zum Ressourcenverbrauch und der Latenz sind aufgrund der Vielfalt der Implementierungen und Konfigurationsmöglichkeiten schwierig. In Experimenten der Autoren hat Linkerd deutlich weniger Ressourcen benötigt und die Antwortzeit weniger beeinträchtigt als Istio. Aber dennoch ist es ratsam, die Latenz und den Ressourcenverbrauch in der eigenen Umgebung ohne und mit Service-Mesh zu vergleichen.
Service-Mesh: Ja oder nein? Linkerd oder Istio?
Braucht jede Microservice-Anwendung ein Service-Mesh? Das hängt vor allem von den tatsächlichen Problemen oder Anforderungen ab. Häufig sind Funktionen wie Monitoring, Retry und Timeout, Canary Releasing und mTLS bereits mit Bibliotheken oder API Gateways implementiert. Wenn eine solche Vorgehensweise erfolgreich umgesetzt wurde und problemlos im Betrieb ist, ist es nicht unbedingt nötig, ein Service-Mesh einzuführen. Bei neuen Microservice-Anwendungen, die auf Kubernetes laufen, ist die Einführung eines Service-Meshes sinnvoll, da mit überschaubarem Aufwand viel Mehrwert folgt. Generell ist ein Service-Mesh dann sinnvoll, wenn es Probleme oder Anforderungen gibt, die mit flächendeckenden Einblicken, Kontrolle des Netzwerkverkehrs und abgesicherter Kommunikation gelöst werden können.
Nicht ratsam sind Service-Meshes bei Anwendungen mit extremen Latenzanforderungen. Für Anwendungen, die weder mit Consul noch in Kubernetes laufen, gibt es noch keine ausgereiften Alternativen.
Wenn ein Service-Mesh infrage kommt, bleibt noch die Qual der Wahl. Setzt man auf Istio, den Platzhirsch mit großen Namen wie Google und IBM im Rücken? Oder zieht man den Underdog Linkerd vor, der sich zwar hinsichtlich Usability und Performance erheblich von den schlechten Erfahrungen mit Linkerd 1 absetzt, aber weniger Funktionen bietet? Folgendes sollte man bei der Entscheidung beachten:
- Sind Autorisierungsregeln oder Tracing ein essenzielles Feature, ist Istio aktuell vorzuziehen.
- Wenn Istio in Erwägung gezogen wird, muss genug Bereitschaft und Zeit für die Einarbeitung in die Konzepte und Konfigurationsoptionen zur Verfügung stehen.
- Wenn mittel- oder langfristig ein Teil der Microservices außerhalb von Kubernetes betrieben wird, ist die Flexibilität von Istio hilfreich, um das Service-Mesh außerhalb von Kubernetes zu erweitern.
Minimale Konfiguration, sofortiger Mehrwert
Linkerd bietet ohne viel Aufwand und Betriebsanforderungen ein schlankes Service-Mesh, das mit minimaler Konfiguration sofort Mehrwert bietet. Zusätzliche Features können Entwickler bei Bedarf später hinzukonfigurieren. Linkerd widersteht der Versuchung, möglichst viele Funktionen in kurzer Zeit zu implementieren. Die vohandenen wirken durchdacht und sind ergonomisch in der Benutzung. Im Gegensatz zu Istio überschneiden sich die Linkerd-Funktionen nicht mit Kubernetes. Das macht die Einführung von Linkerd einfach, da anders als bei Istio kaum neue Konzepte verstanden und konfiguriert werden müssen.
Es scheint fast, als hätte Istio den Vertrauensvorschuss durch zu viel Komplexität und unreife Releases verspielt. Linkerd hingegen hat, nach ähnlichen Problemen der ersten Version, einen in vielerlei Hinsicht überzeugenden Neustart gewagt. Viele fehlende Features soll Linkerd in kommenden Versionen nachreichen.
Kurz gesagt: Die Autoren empfehlen, Linkerd zu testen. Bevor man es an der eigenen Anwendung ausprobiert, können die Features an den zwei offiziellen Beispielanwendungen Books und Emojivoto ausprobiert werden. Die begleitenden Tutorials lassen sich problemlos in einem Nachmittag unterbringen.
Zum Thema Service-Mesh gibt es noch viel mehr zu sagen. Der kostenlose Service-Mesh Primer enthält ein ausführliches Beispiel für die Konfiguration von Service-Mesh-Funktionen wie Monitoring, Resilienz, Routing und mTLS mit Istio. Außerdem gibt es einen Vergleich der vier wichtigsten Service-Mesh-Implementierungen Istio, Linkerd, Consul und AWS App Mesh.
Vielen Dank an Jochen Christ, Anja Kammer und Eberhard Wolff für Korrekturen und hilfreiche Hinweise zu diesem Artikel.