Container, Microservices und Service Meshes

Im Internet Hund Artikel о Service-Mesh (Service Mesh), und hier ist noch eines. Hurra! Aber warum? Dann möchte ich meine Meinung zum Ausdruck bringen, dass es besser gewesen wäre, wenn Service Meshes vor 10 Jahren erschienen wären, vor dem Aufkommen von Containerplattformen wie Docker und Kubernetes. Ich sage nicht, dass mein Standpunkt besser oder schlechter ist als der anderer, aber da es sich bei Service Meshes um ziemlich komplexe Wesen handelt, helfen mehrere Standpunkte, sie besser zu verstehen.

Ich werde über die dotCloud-Plattform sprechen, die auf über hundert Microservices aufbaute und Tausende von Containeranwendungen unterstützte. Ich werde die Herausforderungen erläutern, denen wir bei der Entwicklung und Einführung gegenüberstanden, und wie Service Meshes helfen konnten (oder auch nicht).

Geschichte von dotCloud

Ich habe über die Geschichte von dotCloud und die Architekturoptionen für diese Plattform geschrieben, aber nicht viel über die Netzwerkschicht gesprochen. Wenn Sie nicht ins Lesen eintauchen möchten letzter Artikel Was dotCloud betrifft, hier das Wesentliche auf den Punkt gebracht: Es handelt sich um eine PaaS-Plattform-as-a-Service, die es Kunden ermöglicht, eine breite Palette von Anwendungen (Java, PHP, Python...) mit Unterstützung für eine breite Palette von Daten auszuführen Dienste (MongoDB, MySQL, Redis...) und einen Workflow wie Heroku: Sie laden Ihren Code auf die Plattform hoch, sie erstellt Container-Images und stellt sie bereit.

Ich erzähle Ihnen, wie der Datenverkehr auf die dotCloud-Plattform geleitet wurde. Nicht, weil es besonders cool war (obwohl das System für seine Zeit gut funktionierte!), sondern vor allem, weil ein solches Design mit modernen Tools von einem kleinen Team in kurzer Zeit problemlos implementiert werden kann, wenn es eine Möglichkeit braucht, den Verkehr zwischen mehreren Gruppen zu leiten von Microservices oder einer Reihe von Anwendungen. Auf diese Weise können Sie die Optionen vergleichen: Was passiert, wenn Sie alles selbst entwickeln oder ein vorhandenes Service-Mesh verwenden? Die Standardauswahl besteht darin, es selbst herzustellen oder zu kaufen.

Datenverkehrsrouting für gehostete Anwendungen

Anwendungen auf dotCloud können HTTP- und TCP-Endpunkte verfügbar machen.

HTTP-Endpunkte dynamisch zur Load-Balancer-Clusterkonfiguration hinzugefügt Hipache. Dies ähnelt dem, was Ressourcen heute tun Eintritt in Kubernetes und einem Load Balancer wie Traefik.

Clients stellen über entsprechende Domänen eine Verbindung zu HTTP-Endpunkten her, vorausgesetzt, dass der Domänenname auf dotCloud-Load-Balancer verweist. Nichts Besonderes.

TCP-Endpunkte mit einer Portnummer verknüpft, die dann über Umgebungsvariablen an alle Container in diesem Stapel übergeben wird.

Clients können über den entsprechenden Hostnamen (z. B. „gateway-X.dotcloud.com“) und die entsprechende Portnummer eine Verbindung zu TCP-Endpunkten herstellen.

Dieser Hostname wird in den „nats“-Servercluster aufgelöst (nicht verwandt mit NATS), der eingehende TCP-Verbindungen an den richtigen Container weiterleitet (oder, im Fall von Diensten mit Lastausgleich, an die richtigen Container).

Wenn Sie mit Kubernetes vertraut sind, wird Sie das wahrscheinlich an Services erinnern KnotenPort.

Auf der dotCloud-Plattform gab es keine entsprechenden Dienste ClusterIP: Der Einfachheit halber wurde auf Dienste sowohl innerhalb als auch außerhalb der Plattform auf die gleiche Weise zugegriffen.

Alles war ganz einfach organisiert: Die ersten Implementierungen von HTTP- und TCP-Routing-Netzwerken umfassten wahrscheinlich jeweils nur ein paar hundert Zeilen Python. Einfache (ich würde sagen naive) Algorithmen, die verfeinert wurden, als die Plattform wuchs und zusätzliche Anforderungen auftauchten.

Eine umfangreiche Umgestaltung des vorhandenen Codes war nicht erforderlich. Insbesondere, 12-Faktor-Apps kann die über Umgebungsvariablen erhaltene Adresse direkt verwenden.

Wie unterscheidet sich das von einem modernen Service Mesh?

Begrenzt Sichtbarkeit. Wir hatten überhaupt keine Metriken für das TCP-Routing-Mesh. Wenn es um HTTP-Routing geht, führten spätere Versionen detaillierte HTTP-Metriken mit Fehlercodes und Antwortzeiten ein, aber moderne Service Meshes gehen noch weiter und ermöglichen die Integration mit Metrikerfassungssystemen wie beispielsweise Prometheus.

Sichtbarkeit ist nicht nur aus betrieblicher Sicht wichtig (um bei der Fehlerbehebung zu helfen), sondern auch bei der Veröffentlichung neuer Funktionen. Es geht um Sicherheit blau-grüner Einsatz и Canary-Bereitstellung.

Routing-Effizienz ist auch begrenzt. Im dotCloud-Routing-Mesh musste der gesamte Datenverkehr über einen Cluster dedizierter Routing-Knoten geleitet werden. Dies bedeutete möglicherweise die Überschreitung mehrerer AZ-Grenzen (Availability Zone) und eine deutlich erhöhte Latenz. Ich erinnere mich an die Fehlerbehebung bei Code, der über hundert SQL-Abfragen pro Seite durchführte und für jede Abfrage eine neue Verbindung zum SQL-Server öffnete. Bei lokaler Ausführung wird die Seite sofort geladen, in dotCloud dauert das Laden jedoch einige Sekunden, da jede TCP-Verbindung (und nachfolgende SQL-Abfrage) mehrere zehn Millisekunden benötigt. In diesem speziellen Fall wurde das Problem durch dauerhafte Verbindungen gelöst.

Moderne Service-Netze können solche Probleme besser bewältigen. Zunächst prüfen sie, ob Verbindungen geroutet sind in der Quelle. Der logische Ablauf ist derselbe: клиент → меш → сервис, aber jetzt funktioniert das Mesh lokal und nicht auf Remote-Knoten, also die Verbindung клиент → меш ist lokal und sehr schnell (Mikrosekunden statt Millisekunden).

Moderne Service Meshes implementieren auch intelligentere Lastausgleichsalgorithmen. Durch die Überwachung des Zustands von Backends können sie mehr Datenverkehr an schnellere Backends senden, was zu einer verbesserten Gesamtleistung führt.

Sicherheit auch besser. Das dotCloud-Routing-Mesh lief vollständig auf EC2 Classic und verschlüsselte den Datenverkehr nicht (basierend auf der Annahme, dass man bereits in großen Schwierigkeiten steckte, wenn es jemandem gelang, den EC2-Netzwerkdatenverkehr auszuspionieren). Moderne Service Meshes schützen unseren gesamten Datenverkehr transparent, beispielsweise durch gegenseitige TLS-Authentifizierung und anschließende Verschlüsselung.

Weiterleitung des Datenverkehrs für Plattformdienste

Okay, wir haben den Datenverkehr zwischen Anwendungen besprochen, aber was ist mit der dotCloud-Plattform selbst?

Die Plattform selbst bestand aus etwa hundert Microservices, die für verschiedene Funktionen verantwortlich waren. Einige akzeptierten Anfragen von anderen, und einige waren Hintergrundarbeiter, die sich mit anderen Diensten verbanden, selbst aber keine Verbindungen akzeptierten. In jedem Fall muss jeder Dienst die Endpunkte der Adressen kennen, zu denen er eine Verbindung herstellen muss.

Viele High-Level-Dienste verwenden möglicherweise das oben beschriebene Routing-Mesh. Tatsächlich wurden viele der mehr als hundert Microservices von dotCloud als reguläre Anwendungen auf der dotCloud-Plattform selbst bereitgestellt. Aber eine kleine Anzahl von Low-Level-Diensten (insbesondere diejenigen, die dieses Routing-Netz implementieren) benötigten etwas Einfacheres mit weniger Abhängigkeiten (da sie sich nicht auf sich selbst verlassen konnten, um zu funktionieren – das gute alte Henne-Ei-Problem).

Diese geschäftskritischen Low-Level-Dienste wurden bereitgestellt, indem Container direkt auf einigen Schlüsselknoten ausgeführt wurden. In diesem Fall wurden keine Standardplattformdienste verwendet: Linker, Scheduler und Runner. Wenn Sie es mit modernen Containerplattformen vergleichen möchten, ist es so, als würde man eine Steuerungsebene mit betreiben docker run direkt auf den Knoten, anstatt die Aufgabe an Kubernetes zu delegieren. Vom Konzept her ist es ziemlich ähnlich statische Module (Pods), die es verwendet kubeadm oder bootkube beim Booten eines eigenständigen Clusters.

Diese Dienste wurden auf einfache und grobe Weise offengelegt: Eine YAML-Datei listete ihre Namen und Adressen auf; und jeder Client musste eine Kopie dieser YAML-Datei für die Bereitstellung erstellen.

Einerseits ist es äußerst zuverlässig, da es nicht die Unterstützung eines externen Schlüssel-/Wertspeichers wie Zookeeper erfordert (denken Sie daran, dass etcd oder Consul zu diesem Zeitpunkt noch nicht existierten). Andererseits wurde es schwierig, Dienste zu verlagern. Bei jedem Umzug erhielten alle Clients eine aktualisierte YAML-Datei (und führten möglicherweise einen Neustart durch). Nicht sehr bequem!

Anschließend begannen wir mit der Implementierung eines neuen Schemas, bei dem jeder Client eine Verbindung zu einem lokalen Proxyserver herstellte. Anstelle einer Adresse und eines Ports muss es lediglich die Portnummer des Dienstes kennen und über diese eine Verbindung herstellen localhost. Der lokale Proxy wickelt diese Verbindung ab und leitet sie an den eigentlichen Server weiter. Wenn Sie nun das Backend auf einen anderen Computer verschieben oder skalieren, müssen Sie nicht alle Clients aktualisieren, sondern nur alle diese lokalen Proxys aktualisieren. und ein Neustart ist nicht mehr erforderlich.

(Es war auch geplant, den Datenverkehr in TLS-Verbindungen zu kapseln und einen weiteren Proxyserver auf der Empfangsseite zu platzieren sowie TLS-Zertifikate ohne Beteiligung des Empfangsdienstes zu überprüfen, der so konfiguriert ist, dass er nur Verbindungen akzeptiert localhost. Mehr dazu später).

Das ist sehr ähnlich SmartStack von Airbnb, aber der wesentliche Unterschied besteht darin, dass SmartStack implementiert und in der Produktion bereitgestellt wird, während das interne Routing-System von dotCloud zurückgestellt wurde, als dotCloud zu Docker wurde.

Ich persönlich halte SmartStack für einen der Vorgänger von Systemen wie Istio, Linkerd und Consul Connect, weil sie alle dem gleichen Muster folgen:

  • Führen Sie auf jedem Knoten einen Proxy aus.
  • Clients stellen eine Verbindung zum Proxy her.
  • Die Steuerungsebene aktualisiert die Proxy-Konfiguration, wenn sich Backends ändern.
  • ... Gewinn!

Moderne Implementierung eines Service Mesh

Wenn wir heute ein ähnliches Raster implementieren müssten, könnten wir ähnliche Prinzipien verwenden. Konfigurieren Sie beispielsweise eine interne DNS-Zone, indem Sie Dienstnamen Adressen im Bereich zuordnen 127.0.0.0/8. Führen Sie dann HAProxy auf jedem Knoten im Cluster aus und akzeptieren Sie Verbindungen an jeder Dienstadresse (in diesem Subnetz). 127.0.0.0/8) und Umleitung/Verteilung der Last auf die entsprechenden Backends. Die HAProxy-Konfiguration kann gesteuert werden confd, sodass Sie Backend-Informationen in etcd oder Consul speichern und aktualisierte Konfigurationen bei Bedarf automatisch an HAProxy übertragen können.

So funktioniert Istio im Wesentlichen! Allerdings mit einigen Unterschieden:

  • Verwendet Gesandter-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.
  • Verfügt über eine zusätzliche Komponente (Citadel), um eine gegenseitige TLS-Authentifizierung zwischen dem Client und den Servern hinzuzufügen.
  • Unterstützt neue Funktionen wie Circuit Breaking, Distributed Tracing, Canary Deployment usw.

Werfen wir einen kurzen Blick auf einige der Unterschiede.

Gesandter-Proxy

Envoy Proxy wurde von Lyft [Ubers Konkurrent auf dem Taximarkt – ca. Fahrbahn]. Er ähnelt in vielerlei Hinsicht anderen Proxys (z. B. HAProxy, Nginx, Traefik ...), aber Lyft hat seinen Proxy geschrieben, weil sie Funktionen benötigten, die anderen Proxys fehlten, und es schien klüger, einen neuen zu erstellen, als den bestehenden zu erweitern.

Envoy kann allein verwendet werden. Wenn ich einen bestimmten Dienst habe, der eine Verbindung zu anderen Diensten herstellen muss, kann ich ihn so konfigurieren, dass er eine Verbindung zu Envoy herstellt, und dann Envoy dynamisch mit dem Standort anderer Dienste konfigurieren und neu konfigurieren, während ich viele tolle zusätzliche Funktionen wie Sichtbarkeit erhalte. Anstatt eine benutzerdefinierte Client-Bibliothek zu erstellen oder Anrufverfolgungen in den Code einzufügen, senden wir Datenverkehr an Envoy, das für uns Metriken sammelt.

Aber Envoy ist auch in der Lage, als zu arbeiten Datenebene (Datenebene) für das Service-Mesh. Das bedeutet, dass Envoy nun für dieses Service Mesh konfiguriert ist Kontrollebene (Kontrollebene).

Kontrollebene

Für die Steuerungsebene setzt Istio auf die Kubernetes-API. Dies unterscheidet sich nicht wesentlich von der Verwendung von confd, das auf etcd oder Consul angewiesen ist, um den Schlüsselsatz im Datenspeicher anzuzeigen. Istio verwendet die Kubernetes-API, um eine Reihe von Kubernetes-Ressourcen anzuzeigen.

Zwischen diesem und dann: Ich persönlich fand das nützlich Beschreibung der Kubernetes-APIwelches lautet:

Der Kubernetes-API-Server ist ein „dummer Server“, der Speicherung, Versionierung, Validierung, Aktualisierung und Semantik für API-Ressourcen bietet.

Istio ist für die Zusammenarbeit mit Kubernetes konzipiert; und wenn Sie es außerhalb von Kubernetes verwenden möchten, müssen Sie eine Instanz des Kubernetes-API-Servers (und des etcd-Hilfsdienstes) ausführen.

Serviceadressen

Istio verlässt sich auf ClusterIP-Adressen, die Kubernetes zuweist, 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 in einem Kubernetes-Cluster ohne Istio wird von kube-proxy abgefangen und an das Backend dieses Proxys gesendet. Wenn Sie an den technischen Details interessiert sind: kube-proxy richtet iptables-Regeln (oder IPVS-Load-Balancer, je nach Konfiguration) ein, um die Ziel-IP-Adressen von Verbindungen neu zu schreiben, die zur ClusterIP-Adresse führen.

Sobald Istio auf einem Kubernetes-Cluster installiert ist, ändert sich nichts, bis es durch die Einführung eines Containers explizit für einen bestimmten Verbraucher oder sogar den gesamten Namespace aktiviert wird sidecar in benutzerdefinierte Pods. Dieser Container startet eine Instanz von Envoy und richtet eine Reihe von iptables-Regeln ein, um den Datenverkehr zu anderen Diensten abzufangen und diesen Datenverkehr an Envoy umzuleiten.

Bei der Integration mit Kubernetes DNS bedeutet dies, dass unser Code eine Verbindung über den Dienstnamen herstellen kann und alles „einfach funktioniert“. Mit anderen Worten: Unser Code gibt Abfragen wie aus http://api/v1/users/4242, dann api Anfrage lösen für 10.97.105.48, fangen die iptables-Regeln Verbindungen von 10.97.105.48 ab und leiten sie an den lokalen Envoy-Proxy weiter, und dieser lokale Proxy leitet die Anfrage an die eigentliche Backend-API weiter. Puh!

Zusätzlicher Schnickschnack

Istio bietet außerdem eine Ende-zu-Ende-Verschlüsselung und Authentifizierung über mTLS (gegenseitiges TLS). Eine Komponente namens Zitadelle.

Es gibt auch eine Komponente Mixer, die Envoy anfordern kann jeder Anfrage, eine spezielle Entscheidung über diese Anfrage zu treffen, abhängig von verschiedenen Faktoren wie Headern, Backend-Last usw. (keine Sorge: Es gibt viele Möglichkeiten, Mixer am Laufen zu halten, und selbst wenn er abstürzt, funktioniert Envoy weiter gut als Proxy).

Und natürlich haben wir die Sichtbarkeit erwähnt: Envoy sammelt eine große Menge an Metriken und bietet gleichzeitig eine verteilte Nachverfolgung. Wenn in einer Microservices-Architektur eine einzelne API-Anfrage die Microservices A, B, C und D durchlaufen muss, fügt die verteilte Ablaufverfolgung beim Anmelden der Anfrage eine eindeutige Kennung hinzu und speichert diese Kennung über Unteranfragen an alle diese Microservices Alle zugehörigen Anrufe werden erfasst. Verzögerungen usw.

Entwickeln oder kaufen

Istio hat den Ruf, komplex zu sein. Im Gegensatz dazu ist der Aufbau des Routing-Netzes, 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 (wir brauchen keine Sicht, keinen Schutzschalter und keine anderen Feinheiten), dann denken wir darüber nach, ein 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 Lastausgleich bereitstellt.

Wenn wir jedoch erweiterte Anforderungen haben, scheint der „Kauf“ eines Service Mesh eine viel bessere Option zu sein. (Dies ist nicht immer ein „Kauf“, da Istio Open Source ist, aber wir müssen dennoch Entwicklungszeit investieren, um es zu verstehen, bereitzustellen und zu verwalten.)

Soll ich Istio, Linkerd oder Consul Connect wählen?

Bisher haben wir nur über Istio gesprochen, aber dies ist nicht das einzige Service-Mesh. Beliebte Alternative - Linkerd, und es gibt noch mehr Konsul Connect.

Was soll man wählen?

Ehrlich gesagt, ich weiß es nicht. Im Moment halte ich mich für nicht kompetent genug, diese Frage zu beantworten. Es gibt ein paar interessant Artikel mit einem Vergleich dieser Tools und sogar Maßstäbe.

Ein vielversprechender Ansatz ist der Einsatz eines Tools wie SuperGloo. Es implementiert eine Abstraktionsschicht, um die von Service Meshes bereitgestellten APIs zu vereinfachen und zu vereinheitlichen. Anstatt die spezifischen (und meiner Meinung nach relativ komplexen) APIs verschiedener Service-Meshes zu erlernen, können wir die einfacheren Konstrukte von SuperGloo verwenden – und problemlos von einem zum anderen wechseln, als ob wir ein Zwischenkonfigurationsformat hätten, das HTTP-Schnittstellen und Backends beschreibt der Generierung der tatsächlichen Konfiguration für Nginx, HAProxy, Traefik, Apache ...

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 mithilfe von SuperGloo zu einem vorhandenen Cluster hinzufügt und wie Letzteres die Aufgabe erledigt, d von einem Service Mesh zu einem anderen, ohne Konfigurationen zu überschreiben.

Source: habr.com

Kommentar hinzufügen