Netramesh – eine leichte Service-Mesh-Lösung

Beim Übergang von einer monolithischen Anwendung zu einer Mikroservice-Architektur sehen wir uns neuen Herausforderungen gegenüber.

In einer monolithischen Anwendung reicht es in der Regel aus, nur zu bestimmen, in welchem Teil des Systems der Fehler aufgetreten ist. Wahrscheinlich liegt das Problem im Code des Monolithen oder in der Datenbank. Aber wenn wir beginnen, das Problem in einer Mikroservice-Architektur zu suchen, ist es bereits nicht mehr so offensichtlich. Wir müssen den gesamten Weg finden, den die Anfrage von Anfang bis Ende zurückgelegt hat, und ihn aus Hunderten von Mikroservices herausheben. Viele von ihnen haben zudem eigene Speicherorte, wo sowohl logische Fehler als auch Leistungs- und Ausfallsicherheitsprobleme auftreten können.

Netramesh – eine leichte Service-Mesh-Lösung

Ich habe lange nach einem Werkzeug gesucht, das mir hilft, mit solchen Problemen umzugehen (ich habe darüber auf Habré geschrieben: 1, 2), aber letztendlich habe ich eine eigene Open-Source-Lösung entwickelt. In dem Artikel spreche ich über die Vorteile des Service-Mesh-Ansatzes und teile ein neues Werkzeug zur Umsetzung.

Verteiltes Tracing ist eine verbreitete Lösung für die Fehlersuche in verteilten Systemen. Aber was ist, wenn in dem System ein solcher Ansatz zur Informationssammlung über Netzinteraktionen noch nicht implementiert ist oder, was schlimmer ist, er in einem Teil des Systems bereits gut funktioniert, im anderen Teil jedoch nicht, da er nicht zu den alten Services hinzugefügt wurde? Um die genaue Wurzelursache des Problems zu bestimmen, ist es notwendig, ein vollständiges Bild dessen zu haben, was im System passiert. Besonders wichtig ist es, zu verstehen, welche Mikroservices an den kritischen Geschäftspfaden beteiligt sind.

Hier kann uns der Ansatz des Service Mesh helfen, der sich um die gesamte Infrastruktur zur Sammlung von Netzwerkdaten auf einer Ebene kümmert, die unterhalb der Funktionsweise der Services liegt. Dieser Ansatz ermöglicht es uns, den gesamten Verkehr abzufangen und in Echtzeit zu analysieren. Dabei müssen die Anwendungen davon nichts wissen.

Service-Mesh-Ansatz

Die Hauptidee des Service-Mesh-Ansatzes besteht darin, eine zusätzliche Infrastrukturebene über dem Netzwerk hinzuzufügen, die es uns ermöglicht, beliebige Dinge mit der Kommunikation zwischen Diensten zu tun. Die meisten Implementierungen funktionieren folgendermaßen: Zu jedem Mikrodienst wird ein zusätzlicher Sidecar-Container mit einem transparenten Proxy hinzugefügt, über den der gesamte eingehende und ausgehende Verkehr des Dienstes geleitet wird. Und dies ist der Punkt, an dem wir Lastenausgleich für Clients durchführen, Sicherheitsrichtlinien anwenden, Beschränkungen für die Anzahl der Anfragen einführen und wichtige Informationen über die Interaktion der Dienste in der Produktion sammeln können.

Netramesh – eine leichte Service-Mesh-Lösung

Lösungen

Es gibt bereits mehrere Implementierungen dieses Ansatzes: Istio und linkerd2. Sie bieten viele Funktionen „out of the box“. Aber gleichzeitig bringt das auch einen großen Overhead an Ressourcen mit sich. Je größer der Cluster, in dem ein solches System läuft, desto mehr Ressourcen sind erforderlich, um die neue Infrastruktur aufrechtzuerhalten. Bei Avito betreiben wir Kubernetes-Cluster, die Tausende von Dienst-Instanzen beherbergen (und deren Zahl wächst schnell). In der aktuellen Implementierung benötigt Istio ~300 MB RAM pro Dienst-Instanz. Aufgrund der vielen Funktionen hat die transparente Lastenverteilung auch Einfluss auf die Gesamtlatenz der Dienste (bis zu 10 ms).

Letztendlich haben wir uns angesehen, welche Funktionen wir derzeit benötigen, und beschlossen, dass der Hauptgrund, warum wir angefangen haben, solche Lösungen zu implementieren, die Fähigkeit war, Tracing-Informationen transparent aus dem gesamten System zu sammeln. Außerdem wollten wir die Kontrolle über die Interaktion zwischen den Diensten haben und verschiedene Manipulationen an den Headern vornehmen, die zwischen den Diensten übertragen werden.

Am Ende kamen wir zu unserer eigenen Lösung: Netramesh.

Netramesh

Netramesh ist eine leichte Service-Mesh-Lösung mit unendlichen Skalierungsmöglichkeiten, unabhängig von der Anzahl der Dienste im System.

Die Hauptziele der neuen Lösung waren ein geringer Ressourcen-Overhead und hohe Leistung. Zu den grundlegenden Funktionen wollten wir sofort die Möglichkeit haben, transparently Tracing-Spans in unser Jaeger-System zu senden.

Heute werden die meisten Cloud-Lösungen in Golang realisiert. Und natürlich gibt es dafür seine Gründe. Es ist einfach und angenehm, Netzwerkapplikationen in Golang zu schreiben, die asynchron mit Ein- und Ausgabe arbeiten und sich je nach Bedarf auf die Kerne skalieren. Und was ebenfalls sehr wichtig ist, die Leistung ist ausreichend, um diese Aufgabe zu bewältigen. Daher haben wir uns ebenfalls für Golang entschieden.

Leistung

Wir haben unsere Bemühungen auf die Maximierung der Leistung fokussiert. Für eine Lösung, die neben jeder Instanz des Dienstes bereitgestellt wird, ist ein geringer Speicher- und CPU-Verbrauch erforderlich. Und natürlich sollte auch die Antwortzeit so niedrig wie möglich sein.

Lassen Sie uns sehen, welche Ergebnisse erzielt wurden.

RAM

Netramesh verbraucht ~10Mb ohne Verkehr und maximal 50Mb mit einer Last von bis zu 10000 RPS pro Instanz.

Der Istio Envoy-Proxy verbraucht in unseren Clustern mit Tausenden von Instanzen immer ~300Mb. Das erlaubt es nicht, ihn im gesamten Cluster zu skalieren.

Netramesh – eine leichte Service-Mesh-Lösung

Netramesh – eine leichte Service-Mesh-Lösung

Mit Netramesh haben wir den Speicherverbrauch um etwa das 10-fache reduziert.

CPU

Die CPU-Nutzung ist unter Last relativ gleich. Sie hängt von der Anzahl der Anfragen pro Zeiteinheit an das Sidecar ab. Werte bei 3000 Anfragen pro Sekunde im Höchstfall:

Netramesh – eine leichte Service-Mesh-Lösung

Netramesh – eine leichte Service-Mesh-Lösung

Es gibt noch einen weiteren wichtigen Punkt: Netramesh ist eine Lösung ohne Control Plane und verbraucht unter Last keine CPU-Zeit. Bei Istio aktualisieren die Sidecars immer die Endpunkte der Dienste. Infolgedessen sehen wir bei fehlender Last folgendes Bild:

Netramesh – eine leichte Service-Mesh-Lösung

Wir verwenden HTTP/1 für die Interaktion zwischen den Diensten. Die Erhöhung der Antwortzeit bei Istio beim Proxyn über Envoy lag bei bis zu 5-10ms, was für Dienste, die bereit sind, in Millisekunden zu antworten, ziemlich viel ist. Mit Netramesh wurde diese Zeit auf 0.5-2ms reduziert.

Skalierbarkeit

Die geringe Menge an Ressourcen, die von jedem Proxy verbraucht wird, ermöglicht es, ihn neben jedem Dienst zu platzieren. Netramesh wurde absichtlich ohne einen Control Plane-Komponenten entwickelt, um die Leichtigkeit jedes Sidecars zu gewährleisten. Oft verbreitet der Control Plane in Service Mesh-Lösungen die Informationen zur Service-Entdeckung in jedes Sidecar. Damit kommen auch Informationen über Timeouts und Lastverteilungseinstellungen. All dies ermöglicht viele nützliche Dinge, bläht jedoch leider die Größe der Sidecars auf.

Service-Entdeckung

Netramesh – eine leichte Service-Mesh-Lösung

Netramesh fügt keine zusätzlichen Mechanismen für die Service-Entdeckung hinzu. Der gesamte Verkehr wird transparent über das Netra Sidecar geleitet.

Netramesh unterstützt das HTTP/1-Anwendungsprotokoll. Zur Definition wird eine konfigurierbare Liste von Ports verwendet. In der Regel gibt es in einem System mehrere Ports, über die die Interaktion über HTTP erfolgt. Zum Beispiel verwenden wir für die Interaktion zwischen Diensten und externen Anfragen die Ports 80, 8890, 8080. In diesem Fall können diese über eine Umgebungsvariable festgelegt werden. NETRA_HTTP_PORTS.

Wenn Sie Kubernetes als Orchestrator nutzen und dessen Service-Mechanismus für die internen Interaktionen zwischen Diensten verwenden, bleibt der Mechanismus unverändert. Zunächst erhält der Microservice die Service-IP-Adresse über kube-dns und öffnet eine neue Verbindung. Diese Verbindung wird zunächst mit dem lokalen netra-sidecar hergestellt, und alle TCP-Pakete gelangen zunächst an netra. Anschließend stellt netra-sidecar die Verbindung zum ursprünglichen Ziel her. Das NAT auf die Pod-IP auf dem Knoten bleibt genau wie ohne netra.

Verteiltes Tracing und Weitergabe des Kontextes

Netramesh bietet die Funktionalität, die erforderlich ist, um Tracing-Spans über HTTP-Interaktionen zu senden. Netra-sidecar analysiert das HTTP-Protokoll, misst die Verzögerungen von Anfragen und extrahiert die notwendigen Informationen aus den HTTP-Headern. Letztendlich erhalten wir alle Traces in einem einheitlichen Jaeger-System. Für eine feinere Konfiguration können auch Umgebungsvariablen verwendet werden, die die offizielle Bibliothek bereitstellt. jaeger go library.

Netramesh – eine leichte Service-Mesh-Lösung

Netramesh – eine leichte Service-Mesh-Lösung

Aber es gibt ein Problem. Solange die Dienste keinen speziellen Uber-Header generieren und weitergeben, werden wir die verbundenen Tracing-Spans im System nicht sehen. Und genau das benötigen wir, um schnell die Ursachen von Problemen zu finden. Hier hat Netramesh erneut eine Lösung. Proxys lesen die HTTP-Header; wenn der Uber Trace ID nicht vorhanden ist, wird er generiert. Netramesh speichert außerdem Informationen über eingehende und ausgehende Anfragen im Sidecar und ordnet sie an, indem erforderliche Header aus den ausgehenden Anfragen hinzugefügt werden. Alles, was in den Diensten getan werden muss, ist, einen einzigen Header weiterzugeben. X-Request-Id, der über eine Umgebungsvariable konfiguriert werden kann. NETRA_HTTP_REQUEST_ID_HEADER_NAME. Um die Größe des Kontextes in Netramesh zu verwalten, können die folgenden Umgebungsvariablen festgelegt werden: NETRA_TRACING_CONTEXT_EXPIRATION_MILLISECONDS (die Zeit, während der der Kontext gespeichert wird) und NETRA_TRACING_CONTEXT_CLEANUP_INTERVAL (die Häufigkeit der Kontextbereinigung).

Es ist auch möglich, mehrere Pfade in Ihrem System zu kombinieren, indem Sie sie mit einem speziellen Sitzungsmarker kennzeichnen. Netra ermöglicht es, HTTP_HEADER_TAG_MAP um HTTP-Header in entsprechende Tracing-Span-Tags umzuwandeln. Dies kann besonders nützlich für Tests sein. Nach dem Durchlaufen eines Funktionstests kann man sehen, welcher Teil des Systems betroffen war, indem man nach dem entsprechenden Sitzungs-Schlüssel filtert.

Bestimmung der Anforderungsquelle

Um festzustellen, woher die Anfrage kam, können Sie die Funktion zur automatischen Hinzufügung des Quell-Headers nutzen. Mit der Umgebungsvariablen NETRA_HTTP_X_SOURCE_HEADER_NAME kann der Headername festgelegt werden, der automatisch gesetzt wird. Mit NETRA_HTTP_X_SOURCE_VALUE kann der Wert festgelegt werden, der den X-Source-Header für alle ausgehenden Anfragen erhalten soll.

Dies ermöglicht eine einheitliche Verbreitung dieses nützlichen Headers im gesamten Netzwerk. Anschließend kann er in Diensten verwendet und in Protokollen oder Metriken hinzugefügt werden.

Traffic-Routing und die Inneren von Netramesh

Netramesh besteht aus zwei Hauptkomponenten. Die erste, netra-init, legt Netzwerkregeln zum Abfangen von Traffic fest. Sie nutzt iptables redirect Regeln zum Abfangen des gesamten oder eines Teils des Traffics auf den Sidecar, der die zweite Hauptkomponente von Netramesh ist. Es kann konfiguriert werden, welche Ports für eingehende und ausgehende TCP-Sitzungen abgefangen werden sollen: INBOUND_INTERCEPT_PORTS, OUTBOUND_INTERCEPT_PORTS.

Das Tool bietet auch eine interessante Möglichkeit: probabilistisches Routing. Wenn Sie Netramesh ausschließlich zur Sammlung von Tracing-Spans verwenden, können Sie im Produktionsumfeld Ressourcen sparen und probabilistisches Routing mithilfe der Variablen NETRA_INBOUND_PROBABILITY und NETRA_OUTBOUND_PROBABILITY (von 0 bis 1) aktivieren. Der Standardwert beträgt 1 (der gesamte Traffic wird abgefangen).

Nach erfolgreichem Abfangen nimmt der Netra-Sidecar eine neue Verbindung an und verwendet die SO_ORIGINAL_DST Socket-Option, um den ursprünglichen Zielort zu erhalten. Anschließend öffnet Netra eine neue Verbindung zur ursprünglichen IP-Adresse und stellt eine bidirektionale TCP-Kommunikation zwischen den Parteien her, indem es den gesamten durchlaufenden Traffic überwacht. Wenn der Port als HTTP festgelegt ist, versucht Netra, diesen zu parsen und zu tracen. Sollte das Parsen von HTTP nicht erfolgreich sein, fällt Netra auf TCP zurück und proxyed die Bytes transparent.

Aufbau eines Abhängigkeitsdiagramms

Nach dem Sammeln einer großen Menge an Tracing-Informationen in Jaeger möchte man ein vollständiges Interaktionsdiagramm im System erhalten. Wenn Ihr System jedoch stark ausgelastet ist und im Laufe eines Tages Milliarden von Tracing-Spans angehäuft werden, wird es nicht so einfach sein, diese zu aggregieren. Es gibt einen offiziellen Weg dafür: spark-dependencies. Dennoch wird es Stunden dauern, um ein vollständiges Diagramm zu erstellen und das gesamte Dataset von Jaeger für die vergangenen 24 Stunden herunterzuladen.

Wenn Sie Elasticsearch zur Speicherung von Tracing-Spans verwenden, können Sie ein einfaches Tool in Golang, das ein ähnliches Diagramm in Minuten erstellt, nutzen, indem es die Funktionen und Möglichkeiten von Elasticsearch nutzt.

Netramesh – eine leichte Service-Mesh-Lösung

Netramesh verwenden

Netra kann einfach zu jedem Dienst hinzugefügt werden, der unter einem beliebigen Orchestrator arbeitet. Sie können ein Beispiel ansehen. hier.

Momentan hat Netra nicht die Möglichkeit, eine Sidecar automatisch in Dienste einzufügen, aber es gibt Pläne für eine Umsetzung.

Zukunft von Netramesh

Das Hauptziel Netramesh besteht darin, minimale Ressourcenaufwand und hohe Leistung zu erreichen, indem grundlegende Funktionen für Observability und Kontrolle der inter-Service-Interaktionen bereitgestellt werden.

In Zukunft wird Netramesh Unterstützung für andere Anwendungsprotokolle neben HTTP erhalten. Bald wird es die Möglichkeit des L7-Routings geben.

Verwenden Sie Netramesh, wenn Sie mit ähnlichen Problemen konfrontiert sind, und senden Sie uns Ihre Fragen und Vorschläge.

Quelle: habr.com

60GB SSD 8Gb DDR4