Es gibt
Unmengen von Artikeln im Service-Mesh
im Internet, und hier ist noch einer. Hurra! Aber warum? Dann möchte ich meine Meinung zum Ausdruck bringen, dass es besser wäre, wenn Service-Meshes vor 10 Jahren vor dem Aufkommen von Containerplattformen wie Docker und Kubernetes auftauchen würden. Ich behaupte nicht, dass meine Sichtweise besser oder schlechter ist als die anderer, aber da Dienstnetze recht komplexe Tiere sind, hilft die Vielzahl der Sichtweisen, sie besser zu verstehen.
Ich werde über die dotCloud-Plattform sprechen, die auf mehr als hundert Microservices basiert und Tausende von Anwendungen in Containern unterstützt. Ich werde die Probleme erläutern, auf die wir während der Entwicklung und des Starts gestoßen sind, und wie Service-Meshes helfen können (oder nicht).
Geschichte von dotCloud
Ich habe bereits über die Geschichte von dotCloud und die Wahl der Architektur für diese Plattform geschrieben, aber ein wenig über die Netzwerkebene gesprochen. Wenn Sie nicht in den
vorherigen Artikel über dotCloud eintauchen möchten, finden Sie hier eine kurze Zusammenfassung: Es handelt sich um eine PaaS-Plattform als Service, mit der Clients eine Vielzahl von Anwendungen (Java, PHP, Python ...) mit Unterstützung für eine Vielzahl von Datendiensten (MongoDB,) starten können. MySQL, Redis ...) und ein Workflow wie Heroku: Wenn Sie Ihren Code auf die Plattform hochladen, werden Bilder von Containern erstellt und bereitgestellt.
Ich werde Ihnen sagen, wie der Datenverkehr auf die dotCloud-Plattform geleitet wurde. Nicht weil es besonders cool war (obwohl das System für seine Zeit gut funktioniert hat!), Sondern vor allem, weil mit Hilfe moderner Tools ein solches Design von einem bescheidenen Team in kurzer Zeit problemlos implementiert werden kann, wenn es eine Möglichkeit benötigt, den Verkehr zwischen einer Reihe von Microservices oder zu leiten eine Reihe von Anwendungen. So können Sie die Optionen vergleichen: Was passiert, wenn Sie alles selbst entwickeln oder das vorhandene Service-Mesh verwenden? Standardwahl: Selber machen oder kaufen.
Verkehrsrouting für gehostete Anwendungen
DotCloud-Anwendungen können HTTP- und TCP-Endpunkte bereitstellen.
HTTP-Endpunkte werden dynamisch zur Konfiguration des
Hipache Load Balancer-Clusters hinzugefügt. Dies ähnelt dem, was Kubernetes
Ingress- Ressourcen und ein Load Balancer wie
Traefik heute tun .
Clients stellen über ihre jeweiligen Domänen eine Verbindung zu HTTP-Endpunkten her, sofern der Domänenname auf dotCloud-Load-Balancer verweist. Nichts Besonderes.
TCP-Endpunkte sind einer Portnummer zugeordnet, die dann über Umgebungsvariablen an alle Container dieses Stapels übergeben wird.
Clients können über den entsprechenden Hostnamen (z. B. gateway-X.dotcloud.com) und die Portnummer eine Verbindung zu TCP-Endpunkten herstellen.
Dieser Hostname wird in den Servercluster "nats" (nicht mit
NATS verwandt) aufgelöst, der eingehende TCP-Verbindungen an den richtigen Container (oder bei Diensten mit Lastenausgleich an die richtigen Container) weiterleitet.
Wenn Sie mit Kubernetes vertraut sind, werden Sie wahrscheinlich an
NodePort- Dienste erinnert.
Auf der
dotCloud-Plattform gab es kein Äquivalent zu
ClusterIP- Diensten: Der Einfachheit halber war der Zugriff auf die Dienste sowohl von innen als auch von außen auf der Plattform gleich.
Alles war ganz einfach organisiert: die ersten Implementierungen der HTTP- und TCP-Routing-Netzwerke, wahrscheinlich nur ein paar hundert Zeilen Python. Einfache (ich würde sagen naive) Algorithmen, die mit dem Wachstum der Plattform und dem Aufkommen zusätzlicher Anforderungen fertiggestellt wurden.
Ein umfangreiches Refactoring des vorhandenen Codes war nicht erforderlich. Insbesondere
12-Faktor-Anwendungen können die durch Umgebungsvariablen erhaltene Adresse direkt verwenden.
Wie unterscheidet sich dies von einem modernen Service-Mesh?
Eingeschränkte
Sichtbarkeit . Wir hatten im Allgemeinen keine Metriken für das TCP-Routing-Grid. Was das HTTP-Routing betrifft, so wurden in späteren Versionen detaillierte HTTP-Metriken mit Fehlercodes und Antwortzeiten angezeigt. Moderne Service-Meshes gehen jedoch noch weiter und ermöglichen die Integration in Metrik-Erfassungssysteme wie beispielsweise Prometheus.
Die Sichtbarkeit ist nicht nur aus betrieblicher Sicht wichtig (um Probleme zu beheben), sondern auch, wenn neue Funktionen veröffentlicht werden. Es geht um eine sichere
blaugrüne Bereitstellung und
Bereitstellung von Kanaren .
Die Routing-Effizienz ist ebenfalls begrenzt. Im dotCloud-Routing-Grid musste der gesamte Datenverkehr durch einen Cluster dedizierter Routing-Knoten geleitet werden. Dies bedeutete ein mögliches Überschreiten mehrerer AZ-Grenzen (Zugänglichkeitszonen) und eine signifikante Zunahme der Verzögerung. Ich erinnere mich, wie ich Probleme mit Code behoben habe, der mehr als hundert SQL-Abfragen pro Seite durchgeführt und für jede Abfrage eine neue Verbindung zum SQL-Server hergestellt hat. Beim lokalen Start wird die Seite sofort geladen, in dotCloud dauert das Laden jedoch einige Sekunden, da für jede TCP-Verbindung (und nachfolgende SQL-Abfrage) mehrere zehn Millisekunden benötigt werden. In diesem speziellen Fall lösten dauerhafte Verbindungen das Problem.
Moderne Service-Meshes können mit solchen Problemen besser umgehen. Zunächst überprüfen sie, ob die Verbindungen
an der Quelle weitergeleitet werden . Der logische Ablauf ist der gleiche:
→ →
, aber jetzt funktioniert das Mesh lokal und nicht auf Remote-Knoten, sodass die
→
Verbindung lokal und sehr schnell ist (Mikrosekunden statt Millisekunden).
Moderne Service-Meshes implementieren auch intelligentere Lastausgleichsalgorithmen. Durch die Steuerung der Leistung von Backends können sie mehr Datenverkehr an schnellere Backends senden, was zu einer Steigerung der Gesamtleistung führt.
Sicherheit ist auch besser. Das dotCloud-Routing-Grid funktionierte vollständig auf EC2 Classic und verschlüsselte den Datenverkehr nicht (vorausgesetzt, wenn jemand es geschafft hat, den EC2-Netzwerkverkehr zu überwachen, haben Sie bereits große Probleme). Moderne Service-Meshes schützen unseren gesamten Datenverkehr transparent, beispielsweise durch gegenseitige TLS-Authentifizierung und anschließende Verschlüsselung.
Verkehrsrouting für Plattformdienste
Ok, wir haben den Datenverkehr zwischen Anwendungen besprochen, aber was ist mit der dotCloud-Plattform selbst?
Die Plattform selbst bestand aus etwa hundert Mikrodiensten, die für verschiedene Funktionen verantwortlich waren. Einige erhielten Anfragen von anderen, und einige waren Hintergrundarbeiter, die sich mit anderen Diensten verbanden, aber keine Verbindungen akzeptierten. In jedem Fall muss jeder Dienst die Endpunkte der Adressen kennen, zu denen eine Verbindung hergestellt werden muss.
Viele High-Level-Dienste können das oben beschriebene Routing-Grid verwenden. Tatsächlich wurden viele der mehr als Hunderte von dotCloud-Mikrodiensten als reguläre Anwendungen auf der dotCloud-Plattform selbst bereitgestellt. Eine kleine Anzahl von Diensten auf niedriger Ebene (insbesondere, die dieses Routing-Raster implementieren) benötigte jedoch etwas Einfacheres mit weniger Abhängigkeiten (da sie sich bei der Arbeit nicht auf sich selbst verlassen konnten - ein gutes altes Henne-Ei-Problem).
Diese wichtigen Dienste auf niedriger Ebene wurden bereitgestellt, indem Container direkt auf mehreren Schlüsselknoten ausgeführt wurden. Gleichzeitig waren Standardplattformdienste nicht beteiligt: Linker, Scheduler und Runner. Wenn Sie mit modernen Containerplattformen vergleichen möchten, ist dies wie das Starten einer Steuerebene mit
docker run
direkt auf den Knoten ausgeführt wird, anstatt die Kubernetes-Aufgabe zu delegieren. Dies ist dem Konzept der
statischen Module (Herde) ziemlich ähnlich, die
kubeadm oder
bootkube beim Laden eines eigenständigen Clusters verwenden.
Diese Dienste wurden auf einfache und grobe Weise verfügbar gemacht: Ihre Namen und Adressen wurden in der YAML-Datei aufgeführt; und jeder Client musste eine Kopie dieser YAML-Datei für die Bereitstellung erstellen.
Einerseits ist es äußerst zuverlässig, da kein externer Schlüssel- / Wertspeicher wie Zookeeper unterstützt werden muss (vergessen Sie nicht, dass zu diesem Zeitpunkt etcd oder Consul noch nicht vorhanden waren). Auf der anderen Seite war es schwierig, Dienste zu verschieben. Bei jedem Umzug sollten alle Clients eine aktualisierte YAML-Datei erhalten haben (und möglicherweise neu starten). Nicht sehr praktisch!
Anschließend haben wir ein neues Schema eingeführt, bei dem jeder Client mit einem lokalen Proxyserver verbunden ist. Anstelle der Adresse und des Ports reicht es aus, nur die Portnummer des Dienstes zu kennen und eine Verbindung über
localhost
. Der lokale Proxyserver verarbeitet diese Verbindung und leitet sie an den eigentlichen Server weiter. Wenn Sie das Backend auf einen anderen Computer verschieben oder skalieren, anstatt alle Clients zu aktualisieren, müssen Sie nur alle diese lokalen Proxys aktualisieren. Ein Neustart ist nicht mehr erforderlich.
(Es war auch geplant, den Datenverkehr in TLS-Verbindungen zu kapseln und einen anderen Proxyserver auf die Empfangsseite zu stellen sowie TLS-Zertifikate ohne Teilnahme des Empfangsdienstes zu überprüfen, der so konfiguriert ist, dass Verbindungen nur auf
localhost
akzeptiert werden. Mehr dazu später).
Dies ist dem
SmartStack von Airbnb sehr ähnlich, aber der wesentliche Unterschied besteht darin, dass SmartStack in der Produktion implementiert und bereitgestellt wird, während das interne dotCloud-Routing-System in einer Box untergebracht wurde, als dotCloud zu Docker wurde.
Ich persönlich betrachte SmartStack als einen der Vorgänger von Systemen wie Istio, Linkerd und Consul Connect, da alle dem gleichen Muster folgen:
- Ausführen von Proxys auf jedem Knoten.
- Clients stellen eine Verbindung zum Proxy her.
- Die Verwaltungsebene aktualisiert die Proxy-Konfiguration beim Ändern von Backends.
- ... Gewinn!
Moderne Implementierung eines Service-Mesh
Wenn wir heute ein ähnliches Raster implementieren müssen, können wir ähnliche Prinzipien anwenden. Konfigurieren Sie beispielsweise die interne DNS-Zone, indem Sie Dienstnamen Adressen in
127.0.0.0/8
. Führen Sie dann HAProxy auf jedem Knoten des Clusters aus, akzeptieren Sie Verbindungen zu jeder Dienstadresse (
127.0.0.0/8
in diesem Subnetz) und leiten Sie die Last zu den entsprechenden Backends um. Die HAProxy-Konfiguration kann über
confd gesteuert werden, sodass Sie Backend-Informationen in etcd oder Consul speichern und die aktualisierte Konfiguration bei Bedarf automatisch an HAProxy senden können.
So funktioniert Istio! Aber mit einigen Unterschieden:
- Verwendet Envoy Proxy anstelle von HAProxy.
- Speichert die Backend-Konfiguration über die Kubernetes-API anstelle von etcd oder Consul.
- Den Diensten werden Adressen im internen Subnetz (Kubernetes ClusterIP-Adressen) anstelle von 127.0.0.0/8 zugewiesen.
- Es verfügt über eine optionale Komponente (Citadel) zum Hinzufügen einer gegenseitigen TLS-Authentifizierung zwischen Client und Servern.
- Unterstützt neue Funktionen wie Unterbrechung, verteilte Verfolgung, Bereitstellung von Kanarienvögeln usw.
Lassen Sie uns einen kurzen Blick auf einige der Unterschiede werfen.
Stellvertreter des Gesandten
Enftoy Proxy wurde von Lyft [Uber Konkurrent auf dem Taximarkt geschrieben - ca. trans.]. Es ist anderen Proxys in vielerlei Hinsicht sehr ähnlich (zum Beispiel HAProxy, Nginx, Traefik ...), aber Lyft hat ihre eigenen geschrieben, weil sie Funktionen benötigten, die nicht in anderen Proxys enthalten sind, und es schien vernünftiger, eine neue zu erstellen, als die vorhandene zu erweitern.
Der Gesandte kann alleine eingesetzt werden. Wenn ich einen bestimmten Dienst habe, der eine Verbindung zu anderen Diensten herstellen soll, kann ich ihn für die Verbindung mit Envoy konfigurieren und dann Envoy dynamisch mit dem Standort anderer Dienste konfigurieren und neu konfigurieren, während ich viele hervorragende zusätzliche Funktionen erhalte, z. B. Sichtbarkeit. Anstelle einer benutzerdefinierten Clientbibliothek oder der Einbettung der Anrufverfolgung in den Code leiten wir den Datenverkehr an Envoy weiter und sammeln Metriken für uns.
Envoy kann aber auch als Datenebene für ein Service-Mesh arbeiten. Dies bedeutet, dass Envoy für dieses Servicenetz jetzt
von der Steuerebene konfiguriert
wird .
Steuerebene
In der Verwaltungsebene verlässt sich Istio auf die Kubernetes-API.
Dies unterscheidet sich nicht wesentlich von der Verwendung von confd , bei der etcd oder Consul zum Anzeigen eines Schlüsselsatzes in einem Data Warehouse verwendet werden. Istio zeigt über die Kubernetes-API den Kubernetes-Ressourcensatz an.
Zwischen dem Fall : Ich persönlich fand diese
Beschreibung der Kubernetes-API nützlich , die lautet:
Der Kubernetes API Server ist ein „dummer Server“, der Speicherung, Versionierung, Validierung, Aktualisierung und Semantik von API-Ressourcen bietet.
Istio wurde für die Zusammenarbeit mit Kubernetes entwickelt. Wenn Sie es außerhalb von Kubernetes verwenden möchten, müssen Sie eine Instanz des Kubernetes-API-Servers (und des Hilfsdienstes usw.) ausführen.
Serviceadressen
Istio stützt sich auf die von Kubernetes zugewiesenen ClusterIP-Adressen, sodass Istio-Dienste eine interne Adresse erhalten (nicht im Bereich
127.0.0.0/8
).
Der Datenverkehr zur ClusterIP-Adresse für einen bestimmten Dienst im Kubernetes-Cluster ohne Istio wird vom kube-Proxy abgefangen und an den Serverteil dieses Proxys gesendet. Wenn Sie an technischen Details interessiert sind, legt kube-proxy die iptables-Regeln (oder IPVS-Load-Balancer, je nachdem, wie Sie sie konfigurieren) fest, um die Ziel-IP-Adressen der Verbindungen zur ClusterIP-Adresse neu zu schreiben.
Nach der Installation von Istio im Kubernetes-Cluster ändert sich nichts, bis es für den angegebenen Verbraucher oder sogar den gesamten Namespace explizit aktiviert wird, indem der
sidecar
in benutzerdefinierte Herde eingeführt wird. Dieser Container startet eine Instanz von Envoy und legt eine Reihe von iptables-Regeln fest, um den Verkehr zu anderen Diensten abzufangen und diesen Verkehr zu Envoy umzuleiten.
Bei der Integration in Kubernetes DNS bedeutet dies, dass unser Code über den Namen des Dienstes eine Verbindung herstellen kann und alles "einfach funktioniert". Mit anderen Worten, unser Code gibt Anforderungen wie
http://api/v1/users/4242
, dann löst
api
die Anforderung in
10.97.105.48
, iptables-Regeln fangen Verbindungen von 10.97.105.48 ab und leiten sie an den lokalen Envoy-Proxy weiter, und dieser lokale Proxy leitet sie weiter Anfrage für die eigentliche Backend-API. Fuh!
Extra kleine Dinger
Istio bietet auch End-to-End-Verschlüsselung und Authentifizierung über mTLS (gegenseitiges TLS). Verantwortlich dafür ist die Komponente
Citadel .
Es gibt auch eine
Mixer- Komponente, die Envoy für
jede Anfrage anfordern kann, um eine spezielle Entscheidung über diese Anfrage zu treffen, abhängig von verschiedenen Faktoren wie Headern, Laden des Backends usw. (keine Sorge: Es gibt viele Möglichkeiten, um sicherzustellen, dass der Mixer funktioniert und sogar Wenn es abstürzt, arbeitet Envoy weiterhin normal als Proxy.
Und natürlich haben wir die Sichtbarkeit erwähnt: Envoy sammelt eine große Anzahl von Metriken und bietet verteilte Ablaufverfolgung. Wenn in der Architektur von Microservices eine API-Anforderung die Microservices A, B, C und D durchlaufen muss, fügt der verteilte Trace bei der Anmeldung am System der Anforderung eine eindeutige Kennung hinzu und speichert diese Kennung über Unterabfragen an alle diese Mikrodienste, sodass Sie alle zugehörigen Aufrufe aufzeichnen können Verzögerungen usw.
Entwickeln oder kaufen
Istio hat den Ruf, ein komplexes System zu sein. Im Gegensatz dazu ist das Erstellen eines Routing-Rasters, das ich zu Beginn dieses Beitrags beschrieben habe, mit vorhandenen Tools relativ einfach. Ist es also sinnvoll, stattdessen ein eigenes Service-Mesh zu erstellen?
Wenn wir bescheidene Bedürfnisse haben (Sie brauchen keine Sichtbarkeit, einen Leistungsschalter und andere Feinheiten), dann denken Sie darüber nach, Ihr eigenes Werkzeug zu entwickeln. Wenn wir jedoch Kubernetes verwenden, ist dies möglicherweise nicht einmal erforderlich, da Kubernetes bereits grundlegende Tools für die Serviceerkennung und den Lastenausgleich bereitstellt.
Wenn wir jedoch erweiterte Anforderungen haben, scheint der „Kauf“ eines Service-Netzes eine viel bessere Option zu sein. (Dies ist nicht immer ein „Kauf“, da Istio mit Open Source-Code geliefert wird. Wir müssen jedoch noch Engineering-Zeit investieren, um seine Arbeit zu verstehen, ihn bereitzustellen und zu verwalten.)
Was zur Auswahl: Istio, Linkerd oder Consul Connect?
Bisher haben wir nur über Istio gesprochen, aber dies ist nicht das einzige Service-Mesh. Eine beliebte Alternative ist
Linkerd , und es gibt auch
Consul Connect .
Was soll ich wählen?
Ehrlich gesagt, ich weiß es nicht. Im Moment halte ich mich nicht für kompetent genug, um diese Frage zu beantworten. Es gibt einige
interessante Artikel, in denen diese Tools und sogar
Benchmarks verglichen werden.
Ein vielversprechender Ansatz ist die Verwendung eines Tools wie
SuperGloo . Es implementiert eine Abstraktionsschicht, um die von Service-Meshes bereitgestellten APIs zu vereinfachen und zu vereinheitlichen. Anstatt bestimmte (und meiner Meinung nach relativ komplexe) APIs verschiedener Service-Meshes zu untersuchen, können wir einfachere SuperGloo-Konstruktionen verwenden - und einfach von einer zur anderen wechseln, als hätten wir ein Zwischenkonfigurationsformat, das HTTP-Schnittstellen und beschreibt Backends, die die eigentliche Konfiguration für Nginx, HAProxy, Traefik, Apache ... generieren können.
Ich habe mich ein wenig mit Istio und SuperGloo beschäftigt, und im nächsten Artikel möchte ich zeigen, wie man Istio oder Linkerd mit SuperGloo zu einem vorhandenen Cluster hinzufügt und wie viel dieser mit seiner Arbeit fertig wird, dh Sie können von einem Service-Mesh zu einem anderen wechseln, ohne die Konfigurationen neu zu schreiben.