Container, Microservices und Service-Meshes

Im Internet eine Menge Artikel größten Service-Meshes (Service Mesh), und hier ist noch eins. Hurra! Aber warum? Weil ich meine Meinung darlegen möchte, dass es besser gewesen wäre, wenn Service-Meshes vor 10 Jahren erschienen wären, bevor Container-Plattformen wie Docker und Kubernetes entstanden. Ich behaupte nicht, dass meine Sichtweise besser oder schlechter ist als andere, aber da Service-Meshes eine recht komplexe Angelegenheit sind, wird eine Vielzahl von Perspektiven helfen, sie besser zu verstehen.

Ich werde über die Plattform dotCloud berichten, die auf mehr als hundert Mikrodiensten basierte und Tausende von Anwendungen in Containern unterstützte. Ich werde die Probleme erklären, auf die wir bei der Entwicklung und dem Start gestoßen sind, und wie Service-Meshes hätten helfen können (oder nicht).

Die Geschichte von dotCloud

Ich habe bereits über die Geschichte von dotCloud und die Wahl der Architektur für diese Plattform geschrieben, aber wenig über die Netzwerkinfrastruktur erzählt. Wenn Sie nicht in die Lektüre eintauchen möchten vorherigen Artikel Über dotCloud lässt sich folgendermaßen zusammenfassen: Es handelt sich um eine Platform-as-a-Service (PaaS), die es den Kunden ermöglicht, eine Vielzahl von Anwendungen (Java, PHP, Python usw.) zu starten und unterstützt eine breite Palette an Datenservices (MongoDB, MySQL, Redis usw.). Der Arbeitsablauf ähnelt dem von Heroku: Sie laden Ihren Code auf die Plattform hoch, die dann Container-Images erstellt und bereitstellt.

Ich werde erläutern, wie der Traffic zur dotCloud-Plattform geleitet wurde. Nicht, weil das besonders innovativ war (obwohl das System zu seiner Zeit recht gut funktionierte!), sondern vor allem, weil mit modernen Werkzeugen ein solches Design innerhalb kurzer Zeit von einem kleinen Team realisiert werden kann, sofern sie einen Weg zur Verkehrslenkung zwischen vielen Mikrodiensten oder einer Vielzahl von Anwendungen benötigen. So können die Optionen verglichen werden: Was geschieht, wenn man alles selbst entwickelt oder einen bestehenden Service nutzt. Die Standardentscheidung: selbst machen oder kaufen.

Traffic-Routing für gehostete Anwendungen

Anwendungen auf dotCloud können HTTP- und TCP-Endpunkte bereitstellen.

HTTP-Endpunkte werden dynamisch zur Konfiguration des Clusters der Load Balancer hinzugefügt HipacheDas ähnelt dem, was heute Ressourcen Ingress in Kubernetes und einem Load Balancer wie github.com/containous/traefik.

Clients verbinden sich über entsprechende Domains mit HTTP-Endpunkten, vorausgesetzt, der Domainname verweist auf die Load Balancer von dotCloud. Nichts Besonderes.

TCP-Endpunkte sind mit einer Portnummer verbunden, die dann an alle Container dieses Stacks über Umgebungsvariablen weitergegeben wird.

Clients können sich mit den TCP-Endpunkten verbinden, indem sie den entsprechenden Hostnamen verwenden (etwas wie gateway-X.dotcloud.com) und die Portnummer.

Dieser Hostname wird auf ein Cluster von "nats"-Servern aufgelöst (hat nichts mit NATS), die eingehende TCP-Verbindungen an den richtigen Container weiterleiten (oder, im Fall von Diensten mit Lastenausgleich, an die richtigen Container).

Wenn Sie mit Kubernetes vertraut sind, wird Sie das wahrscheinlich an Dienste erinnern. NodePort.

Auf der Plattform dotCloud gab es kein Äquivalent zu Diensten. ClusterIP: Zur Vereinfachung war der Zugriff auf Dienste sowohl von innen als auch außen auf der Plattform gleich.

Alles wurde recht unkompliziert organisiert: die ursprüngliche Implementierung von HTTP- und TCP-Routing-Netzwerken umfasste wahrscheinlich nur ein paar hundert Zeilen Python. Es waren einfache (ich würde sagen, naive) Algorithmen, die mit dem Wachstum der Plattform und den neuen Anforderungen weiterentwickelt wurden.

Umfassendes Refactoring des bestehenden Codes war nicht erforderlich. Insbesondere 12-Faktor-Anwendungen können direkt die über Umgebungsvariablen erhaltenen Adressen nutzen.

Wie unterscheidet sich das von modernen Service-Meshes?

Eingeschränkte Sichtbarkeit. Wir hatten überhaupt keine Metriken für das TCP-Routing. Was das HTTP-Routing betrifft, so wurden in späteren Versionen detaillierte HTTP-Metriken mit Fehlercodes und Antwortzeiten eingeführt, aber moderne Service-Meshes gehen noch weiter, indem sie eine Integration mit Metriksammlersystemen wie Prometheus ermöglichen.

Sichtbarkeit ist nicht nur aus operativer Sicht wichtig (um bei der Problemlösung zu helfen), sondern auch beim Einführen neuer Funktionen. Es geht um sicheres Blue-Green-Deployment und Canary-Deployment.

Effizienz des Routings ist ebenfalls begrenzt. Im Routing-Netzwerk von dotCloud musste der gesamte Datenverkehr durch ein Cluster von dedizierten Routing-Knoten geleitet werden. Das bedeutete potenziell, dass mehrere AZ-Grenzen (Verfügbarkeitszonen) überschritten werden mussten, was zu einer erheblichen Erhöhung der Latenz führte. Ich erinnere mich, wie ich Probleme mit einem Code behoben habe, der über hundert SQL-Anfragen pro Seite erzeugte und für jede Anfrage eine neue Verbindung zum SQL-Server öffnete. Bei lokalem Start lädt die Seite sofort, aber in dotCloud dauert das Laden mehrere Sekunden, da jede TCP-Verbindung (und die anschließenden SQL-Anfragen) Dutzende von Millisekunden in Anspruch nimmt. In diesem speziellen Fall wurden die Probleme durch dauerhafte Verbindungen gelöst.

Moderne Service-Meshes bewältigen solche Probleme besser. Zunächst prüfen sie, dass die Verbindungen routiert werden aus der Quelle. Der logische Fluss bleibt gleich: Client → Mesh → Dienst, aber jetzt arbeitet das Mesh lokal und nicht auf entfernten Knoten, weshalb die Verbindung Client → Mesh lokal und sehr schnell ist (Mikrosekunden statt Millisekunden).

Moderne Service-Meshes implementieren auch intelligentere Lastenausgleichsalgorithmen. Indem sie die Verfügbarkeit der Backends überwachen, können sie mehr Traffic an schnellere Backends leiten, was die Gesamtleistung erhöht.

Sicherheit funktionierte ebenfalls besser. Das Routing-Netzwerk von dotCloud lief vollständig auf EC2 Classic und verschlüsselte den Traffic nicht (unter der Annahme, dass wenn jemand einen Sniffer auf den EC2-Netzwerkverkehr gesetzt hat, Sie bereits größere Probleme haben). Moderne Service-Meshes schützen unseren gesamten Traffic transparent, beispielsweise durch gegenseitige TLS-Authentifizierung und anschließende Verschlüsselung.

Traffic-Routing für Plattformdienste

Gut, wir haben über den Traffic zwischen Anwendungen gesprochen, aber wie sieht es mit der Plattform selbst aus, dotCloud?

Die Plattform selbst bestand aus etwa hundert Mikrodiensten, die für verschiedene Funktionen verantwortlich waren. Einige empfingen Anfragen von anderen, während einige Hintergrundarbeiter waren, die sich mit anderen Diensten verbanden, aber keine Verbindungen empfingen. In jedem Fall muss jeder Dienst die Endpunkte kennen, zu denen eine Verbindung hergestellt werden muss.

Viele hochgestellte Dienste können das oben beschriebene Routing-Netzwerk nutzen. Tatsächlich wurden viele der mehr als hundert Mikroservices von dotCloud als reguläre Anwendungen auf der Plattform selbst bereitgestellt. Aber eine kleine Anzahl von Low-Level-Diensten (insbesondere diejenigen, die dieses Routing-Netzwerk implementieren) benötigte etwas Einfacheres mit weniger Abhängigkeiten (da sie für ihren Betrieb nicht von sich selbst abhängig sein konnten – das altbekannte Huhn-und-Ei-Problem).

Diese wichtigen Low-Level-Dienste wurden bereitgestellt, indem Container direkt auf mehreren Schlüssel-Knoten gestartet wurden. Dabei wurden die standardmäßigen Plattformdienste: Orchestrator, Scheduler und Runner nicht verwendet. Wenn Sie es mit modernen Containerplattformen vergleichen möchten, ist es ähnlich wie das Starten eines Kontrollplans docker run direkt auf den Knoten, anstatt die Aufgabe an Kubernetes zu delegieren. Es ähnelt stark dem Konzept von statischen Modulen (Pods), das von kubeadm zu wechseln. oder bootkube beim Starten eines autonomen Clusters verwendet wird.

Diese Dienste wurden auf einfache und grobe Weise bereitgestellt: In der YAML-Datei wurden ihre Namen und Adressen aufgelistet; jeder Kunde musste beim Deployment eine Kopie dieser YAML-Datei verwenden.

Einerseits ist das äußerst zuverlässig, da es keine Unterstützung für externe Schlüssel-/Wert-Speicher wie Zookeeper erfordert (denken Sie daran, dass es zu dieser Zeit noch kein etcd oder Consul gab). Andererseits erschwerte es die Migration der Dienste. Jedes Mal, wenn eine Migration stattfand, mussten alle Kunden die aktualisierte YAML-Datei erhalten (und potenziell neu gestartet werden). Nicht sehr benutzerfreundlich!

Später begannen wir, ein neues Schema umzusetzen, bei dem sich jeder Kunde mit einem lokalen Proxy-Server verband. Anstatt der Adresse und des Ports reicht es aus, nur die Portnummer des Dienstes zu kennen und sich über localhost. Der lokale Proxy-Server verwaltet diese Verbindung und leitet sie an den tatsächlichen Server weiter. Jetzt müssen bei einer Verschiebung des Backends auf eine andere Maschine oder bei der Skalierung anstelle der Aktualisierung aller Kunden nur die lokalen Proxys aktualisiert werden; ein Neustart ist nicht mehr erforderlich.

(Es war auch geplant, den Verkehr in TLS-Verbindungen zu kapseln und einen weiteren Proxy-Server auf der Empfangsseite zu setzen sowie TLS-Zertifikate ohne Beteiligung des Empfangsdienstes zu überprüfen, der nur auf localhost. Darüber später).

Das ähnelt sehr SmartStack von Airbnb, jedoch gibt es einen wesentlichen Unterschied: SmartStack ist implementiert und in der Produktion, während das interne Routing-System von dotCloud in die Schublade gelegt wurde, als dotCloud zu Docker wurde.

Ich persönlich betrachte SmartStack als einen der Vorläufer von Systemen wie Istio, Linkerd und Consul Connect, da sie alle einem Muster folgen:

  • Proxy auf jedem Knoten starten.
  • Clients verbinden sich mit dem Proxy.
  • Die Steuerungsebene aktualisiert die Konfiguration des Proxy-Servers, wenn sich die Backends ändern.
  • … Profit!

Moderne Implementierung eines Service Mesh

Wenn wir heute ein ähnliches Mesh implementieren müssen, können wir ähnliche Prinzipien verwenden. Zum Beispiel eine interne DNS-Zone einrichten, die Dienstnamen mit Adressen im Raum 127.0.0.0/8. Dann HAProxy auf jedem Knoten des Clusters starten, um Verbindungen zu jeder Dienstadresse (in diesem Subnetz 127.0.0.0/8) und leitet/balanciert die Last auf die entsprechenden Backends. Die Konfiguration von HAProxy kann verwaltet werden confd, wodurch Informationen über das Backend in etcd oder Consul gespeichert und die aktualisierte Konfiguration automatisch auf HAProxy gepusht werden kann, wenn dies erforderlich ist.

So funktioniert ungefähr Istio! Aber mit einigen Unterschieden:

  • Verwendet Envoy Proxy anstatt HAProxy.
  • Speichert die Backend-Konfiguration über die Kubernetes API anstelle von etcd oder Consul.
  • Dienste erhalten Adressen im internen Subnetz (Kubernetes ClusterIP-Adressen) statt 127.0.0.0/8.
  • Verfügt über eine zusätzliche Komponente (Citadel) zur Implementierung der gegenseitigen TLS-Authentifizierung zwischen Client und Servern.
  • Unterstützt neue Funktionen wie Circuit Breaking, verteiltes Tracing, Canary Deployments usw.

Lassen Sie uns einige Unterschiede kurz betrachten.

Envoy Proxy

Envoy Proxy wurde von Lyft [einem Konkurrenten von Uber im Taximarkt – Anmerkung der Übersetzung] entwickelt. Es ähnelt in vielerlei Hinsicht anderen Proxys (wie HAProxy, Nginx, Traefik…), aber Lyft hat sein eigenes geschrieben, weil sie Funktionen benötigten, die in anderen Proxys fehlten, und es schien sinnvoller, etwas Neues zu schaffen, als ein bestehendes zu erweitern.

Envoy kann eigenständig verwendet werden. Wenn ich einen bestimmten Dienst habe, der mit anderen Diensten verbunden werden soll, kann ich ihn so konfigurieren, dass er Verbindungen zu Envoy herstellt, und dann Envoy dynamisch mit den Standorten anderer Dienste einrichten und umkonfigurieren, wobei ich viele hervorragende zusätzliche Funktionen wie Überblick erhalten kann. Anstatt eine kundenspezifische Client-Bibliothek oder Code-Injection für die Nachverfolgung von Aufrufen zu verwenden, leiten wir den Verkehr zu Envoy, der dann die Metriken für uns sammelt.

Aber Envoy kann auch als Datenebene (data plane) für das Service Mesh fungieren. Das bedeutet, dass Envoy jetzt für dieses Service Mesh als Steuerungsebene (control plane) konfiguriert wird.

Steuerungsebene

In der Steuerungsebene verlässt sich Istio auf die Kubernetes-API. Das unterscheidet sich nicht stark von der Verwendung von confd, das auf etcd oder Consul angewiesen ist, um eine Schlüsselsets in einem Datenspeicher anzuzeigen. Istio betrachtet über die Kubernetes-API eine Menge von Kubernetes-Ressourcen.

Übrigens: Ich fand diese Beschreibung der Kubernetes-API, die besagt:

Der Kubernetes API-Server ist ein "dummer Server", der Speicherung, Versionsverwaltung, Validierung, Aktualisierung und Semantik von API-Ressourcen bietet.

Istio wurde für die Integration mit Kubernetes entwickelt; wenn Sie es außerhalb von Kubernetes verwenden möchten, müssen Sie eine Instanz des Kubernetes API-Servers (und den unterstützenden Dienst etcd) starten.

Diensteadressen

Istio verlässt sich auf die ClusterIP-Adressen, die Kubernetes zuweist; daher erhalten Istio-Dienste eine interne Adresse (nicht im Bereich) 127.0.0.0/8).

Der Traffic auf die ClusterIP-Adresse eines bestimmten Dienstes im Kubernetes-Cluster wird ohne Istio von kube-proxy abgefangen und an den Backend-Teil dieses Proxys gesendet. Wenn Sie sich für technische Details interessieren, dann setzt kube-proxy iptables-Regeln (oder IPVS-Lastenausgleicher, je nachdem, wie es konfiguriert ist), um die Ziel-IP-Adressen der Verbindungen, die zur ClusterIP-Adresse gelangen, umzuschreiben.

Nach der Installation von Istio im Kubernetes-Cluster ändert sich nichts, bis es ausdrücklich für den jeweiligen Verbraucher oder sogar für den gesamten Namensraum aktiviert wird, indem ein Container eingeführt wird. sidecar in benutzerdefinierte Pods. Dieser Container startet eine Instanz von Envoy und setzt eine Reihe von iptables-Regeln, um den Verkehr, der zu anderen Diensten geht, abzufangen und auf Envoy umzuleiten.

Bei der Integration mit Kubernetes DNS bedeutet dies, dass unser Code über den Dienstnamen eine Verbindung herstellen kann und alles "einfach funktioniert". Mit anderen Worten, unser Code führt Anfragen durch wie http://api/v1/users/4242, dann api auflöst die Anfrage an 10.97.105.48, die iptables-Regeln fangen Verbindungen mit 10.97.105.48 ab und leiten sie an den lokalen Envoy-Proxy weiter, der diese Anfrage an das zugrunde liegende API-Backend sendet. Puh!

Zusätzliche Features

Istio bietet auch End-to-End-Verschlüsselung und Authentifizierung über mTLS (mutual TLS). Dies wird durch ein Component namens Citadel.

Es gibt auch eine Komponente Mixer, die Envoy anfragen kann, um jede eine Anfrage zu stellen, um eine spezielle Entscheidung über diese Anfrage basierend auf verschiedenen Faktoren, wie z. B. Header, Backend-Last usw., zu treffen… (Keine Sorge: Es gibt viele Mittel, um die Funktionsfähigkeit von Mixer sicherzustellen, und selbst wenn er ausfällt, funktioniert Envoy weiterhin normal als Proxy).

Und natürlich haben wir die Überwachungsmöglichkeiten erwähnt: Envoy sammelt eine riesige Menge an Metriken und ermöglicht gleichzeitig verteiltes Tracing. In einer Mikrodienstarchitektur, wenn eine API-Anfrage durch die Mikrodienste A, B, C und D geleitet werden muss, fügt das verteilte Tracing bei der Eingabe der Anfrage eine eindeutige Kennung hinzu und behält diese Kennung durch alle Unteranfragen zu den genannten Mikrodiensten bei, wodurch alle damit verbundenen Aufrufe, deren Verzögerungen usw. aufgezeichnet werden können.

Entwickeln oder kaufen

Istio hat den Ruf, ein kompliziertes System zu sein. Im Gegensatz dazu ist der Aufbau des Routingnetzwerks, das ich zu Beginn dieses Beitrags beschrieben habe, mit den vorhandenen Werkzeugen relativ einfach. Macht es also Sinn, stattdessen einen eigenen Service-Mesh zu erstellen?

Wenn wir bescheidene Anforderungen haben (keine Überwachung, keine Kettenunterbrechung und andere Feinheiten erforderlich), kommen Gedanken an die Entwicklung eines eigenen Tools auf. Aber wenn wir Kubernetes verwenden, könnte das sogar überflüssig sein, denn Kubernetes bietet bereits grundlegende Werkzeuge für die Dienstentdeckung und Lastverteilung.

Wenn wir jedoch erweiterte Anforderungen haben, erscheint der "Kauf" eines Service-Mesh als die bessere Option. (Es ist nicht immer wirklich ein "Kauf", da Istio Open Source ist, aber wir müssen trotzdem Ingenieurzeit investieren, um es zu verstehen, bereitzustellen und zu verwalten).

Was wählen: Istio, Linkerd oder Consul Connect?

Bisher haben wir nur über Istio gesprochen, aber das ist nicht das einzige Service-Mesh. Eine beliebte Alternative ist Linkerd, und es gibt auch Consul Connect.

Was wählen?

Ehrlich gesagt, ich weiß es nicht. Momentan fühle ich mich nicht kompetent genug, um diese Frage zu beantworten. Es gibt einige interessante Artikel Vergleiche dieser Tools und sogar Benchmarks.

Einer der vielversprechenden Ansätze ist, ein Tool wie SuperGloo. Es bietet 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 erforschen, können wir einfachere Konstrukte von SuperGloo verwenden – und problemlos zwischen ihnen wechseln, als hätten wir ein Zwischenformat der Konfiguration, das HTTP-Schnittstellen und Backends beschreibt und in der Lage ist, echte Konfigurationen für Nginx, HAProxy, Traefik, Apache… zu generieren.

Ich habe ein wenig mit Istio und SuperGloo experimentiert, und im nächsten Artikel möchte ich zeigen, wie man Istio oder Linkerd in ein bestehendes Cluster mit SuperGloo hinzufügt und wie gut letzteres seine Aufgabe erfüllt, das heißt, den Wechsel von einem Service-Mesh zu einem anderen ohne Neuschreiben der Konfigurationen zu ermöglichen.

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster