Netramesh – leichte Service-Mesh-Lösung

Beim Übergang von einer monolithischen Anwendung zu einer Microservices-Architektur stehen wir vor neuen Herausforderungen.

Bei einer monolithischen Anwendung lässt sich in der Regel recht einfach feststellen, in welchem ​​Teil des Systems der Fehler aufgetreten ist. Höchstwahrscheinlich liegt das Problem im Code des Monolithen selbst oder in der Datenbank. Aber wenn wir anfangen, nach einem Problem in einer Microservice-Architektur zu suchen, ist nicht mehr alles so offensichtlich. Wir müssen den gesamten Pfad finden, den die Anfrage von Anfang bis Ende genommen hat, und ihn aus Hunderten von Microservices auswählen. Darüber hinaus verfügen viele von ihnen auch über eigene Speichermöglichkeiten, was ebenfalls zu logischen Fehlern sowie Problemen mit der Leistung und Fehlertoleranz führen kann.

Netramesh – leichte Service-Mesh-Lösung

Ich habe lange nach einem Tool gesucht, das bei der Bewältigung solcher Probleme hilft (ich habe darüber auf Habré geschrieben: 1, 2), aber am Ende habe ich meine eigene Open-Source-Lösung erstellt. In diesem Artikel spreche ich über die Vorteile des Service-Mesh-Ansatzes und stelle ein neues Tool für seine Implementierung vor.

Verteiltes Tracing ist eine gängige Lösung für das Problem der Fehlersuche in verteilten Systemen. Was aber, wenn dieser Ansatz zum Sammeln von Informationen über Netzwerkinteraktionen noch nicht im System implementiert ist oder, schlimmer noch, in einem Teil des Systems bereits ordnungsgemäß funktioniert, in einem Teil jedoch nicht, da er nicht zu alten Diensten hinzugefügt wurde? ? Um die genaue Ursache eines Problems zu ermitteln, ist es notwendig, ein vollständiges Bild davon zu haben, was im System passiert. Besonders wichtig ist es zu verstehen, welche Microservices an wichtigen geschäftskritischen Pfaden beteiligt sind.

Hier kann uns der Service-Mesh-Ansatz helfen, der sich mit allen Mechanismen zum Sammeln von Netzwerkinformationen auf einer Ebene befasst, die niedriger ist als die der Dienste selbst. Dieser Ansatz ermöglicht es uns, den gesamten Datenverkehr abzufangen und im laufenden Betrieb zu analysieren. Darüber hinaus müssen Anwendungen nicht einmal etwas darüber wissen.

Service-Mesh-Ansatz

Die Hauptidee des Service-Mesh-Ansatzes besteht darin, eine weitere Infrastrukturschicht über das Netzwerk hinzuzufügen, die es uns ermöglicht, alles mit der Interaktion zwischen Diensten zu tun. Die meisten Implementierungen funktionieren wie folgt: Zu jedem Microservice wird ein zusätzlicher Sidecar-Container mit einem transparenten Proxy hinzugefügt, über den der gesamte ein- und ausgehende Datenverkehr des Dienstes geleitet wird. Und genau hier können wir den Client-Balancing durchführen, Sicherheitsrichtlinien anwenden, die Anzahl der Anfragen beschränken und wichtige Informationen über das Zusammenspiel von Diensten in der Produktion sammeln.

Netramesh – leichte Service-Mesh-Lösung

Решения

Es gibt bereits mehrere Implementierungen dieses Ansatzes: Istio и linkerd2. Sie bieten viele sofort einsatzbereite Funktionen. Gleichzeitig entsteht jedoch ein großer Ressourcenaufwand. Darüber hinaus gilt: Je größer der Cluster, in dem ein solches System betrieben wird, desto mehr Ressourcen werden für die Aufrechterhaltung der neuen Infrastruktur benötigt. Bei Avito betreiben wir Kubernetes-Cluster, die Tausende von Serviceinstanzen enthalten (und ihre Zahl wächst weiterhin schnell). In seiner aktuellen Implementierung verbraucht Istio etwa 300 MB RAM pro Dienstinstanz. Aufgrund der Vielzahl an Möglichkeiten wirkt sich das transparente Balancing auch auf die Gesamtantwortzeit von Diensten (bis zu 10ms) aus.

Daher haben wir uns genau angesehen, welche Funktionen wir gerade benötigen, und sind zu dem Schluss gekommen, dass der Hauptgrund, warum wir mit der Implementierung solcher Lösungen begonnen haben, die Möglichkeit ist, Tracing-Informationen aus dem gesamten System transparent zu sammeln. Wir wollten auch die Kontrolle über die Interaktion von Diensten haben und verschiedene Manipulationen an den Headern vornehmen, die zwischen Diensten übertragen werden.

Als Ergebnis kamen wir zu unserer Entscheidung:  Netramesh.

Netramesh

Netramesh ist eine leichtgewichtige Service-Mesh-Lösung mit unbegrenzter Skalierbarkeit, unabhängig von der Anzahl der Dienste im System.

Die Hauptziele der neuen Lösung waren geringer Ressourcenaufwand und hohe Leistung. Zu den Hauptfunktionen gehörte, dass wir sofort in der Lage sein wollten, Tracing-Spans transparent an unser Jaeger-System zu senden.

Heutzutage werden die meisten Cloud-Lösungen in Golang implementiert. Und natürlich gibt es dafür Gründe. Das Schreiben von Netzwerkanwendungen in Golang, die asynchron mit I/O arbeiten und je nach Bedarf über Kerne hinweg skaliert werden, ist praktisch und recht einfach. Und was auch ganz wichtig ist: Die Leistung reicht aus, um dieses Problem zu lösen. Deshalb haben wir uns auch für Golang entschieden.

Leistung

Wir haben unsere Bemühungen darauf konzentriert, maximale Produktivität zu erreichen. Für eine Lösung, die neben jeder Instanz des Dienstes bereitgestellt wird, ist ein geringer Verbrauch an RAM und CPU-Zeit erforderlich. Und natürlich sollte auch die Reaktionsverzögerung gering sein.

Mal sehen, welche Ergebnisse wir erzielt haben.

RAM

Netramesh verbraucht ca. 10 MB ohne Datenverkehr und maximal 50 MB bei einer Auslastung von bis zu 10000 RPS pro Instanz.

Der Istio-Envoy-Proxy verbraucht in unseren Clustern mit Tausenden von Instanzen immer etwa 300 MB. Eine Skalierung auf den gesamten Cluster ist dadurch nicht möglich.

Netramesh – leichte Service-Mesh-Lösung

Netramesh – leichte Service-Mesh-Lösung

Mit Netramesh konnten wir den Speicherverbrauch um das Zehnfache reduzieren.

CPU

Die CPU-Auslastung ist unter Last relativ gleich. Dies hängt von der Anzahl der Anfragen pro Zeiteinheit an den Sidecar ab. Werte bei 3000 Anfragen pro Sekunde in der Spitze:

Netramesh – leichte Service-Mesh-Lösung

Netramesh – leichte Service-Mesh-Lösung

Es gibt noch einen wichtigen Punkt: Netramesh – eine Lösung ohne Steuerungsebene und ohne Last verbraucht keine CPU-Zeit. Bei Istio aktualisieren Sidecars immer Dienstendpunkte. Als Ergebnis können wir dieses Bild ohne Last sehen:

Netramesh – leichte Service-Mesh-Lösung

Wir verwenden HTTP/1 für die Kommunikation zwischen Diensten. Die Erhöhung der Antwortzeit für Istio bei der Proxy-Verteilung über Envoy betrug bis zu 5–10 ms, was für Dienste, die in einer Millisekunde antwortbereit sind, ziemlich viel ist. Mit Netramesh hat sich diese Zeit auf 0.5-2 ms verringert.

Skalierbarkeit

Die geringe Menge an Ressourcen, die jeder Proxy verbraucht, ermöglicht es, ihn neben jedem Dienst zu platzieren. Netramesh wurde bewusst ohne eine Steuerebenenkomponente entwickelt, um die einzelnen Beiwagen einfach zu halten. In Service-Mesh-Lösungen verteilt die Steuerungsebene häufig Service-Erkennungsinformationen an jeden Sidecar. Außerdem erhalten Sie Informationen zu Timeouts und Balancing-Einstellungen. All dies ermöglicht es Ihnen, viele nützliche Dinge zu tun, aber leider bläht es die Größe der Beiwagen auf.

Serviceerkennung

Netramesh – leichte Service-Mesh-Lösung

Netramesh fügt keine zusätzlichen Mechanismen zur Diensterkennung hinzu. Der gesamte Datenverkehr wird transparent über Netra Sidecar weitergeleitet.

Netramesh unterstützt das HTTP/1-Anwendungsprotokoll. Zur Definition wird eine konfigurierbare Liste von Ports verwendet. Normalerweise verfügt das System über mehrere Ports, über die die HTTP-Kommunikation erfolgt. Für die Interaktion zwischen Diensten und externen Anfragen verwenden wir beispielsweise 80, 8890, 8080. In diesem Fall können sie über eine Umgebungsvariable festgelegt werden NETRA_HTTP_PORTS.

Wenn Sie Kubernetes als Orchestrator und seinen Service-Entitätsmechanismus für die Intra-Cluster-Kommunikation zwischen Services verwenden, bleibt der Mechanismus genau derselbe. Zunächst ruft der Microservice mithilfe von kube-dns eine Dienst-IP-Adresse ab und stellt eine neue Verbindung zu dieser her. Diese Verbindung wird zunächst mit dem lokalen Netra-Sidecar aufgebaut und alle TCP-Pakete kommen zunächst bei Netra an. Als nächstes stellt netra-sidecar eine Verbindung mit dem ursprünglichen Ziel her. NAT auf der Pod-IP auf dem Knoten bleibt genau das gleiche wie ohne Netra.

Verteilte Ablaufverfolgung und Kontextweiterleitung

Netramesh bietet die Funktionalität, die zum Senden von Tracing-Spans über HTTP-Interaktionen erforderlich ist. Netra-Sidecar analysiert das HTTP-Protokoll, misst Anforderungsverzögerungen und extrahiert die erforderlichen Informationen aus HTTP-Headern. Letztendlich erhalten wir alle Spuren in einem einzigen Jaeger-System. Für eine detaillierte Konfiguration können Sie auch die von der offiziellen Bibliothek bereitgestellten Umgebungsvariablen verwenden Jaeger Go-Bibliothek.

Netramesh – leichte Service-Mesh-Lösung

Netramesh – leichte Service-Mesh-Lösung

Aber es gibt ein Problem. Solange die Dienste keinen speziellen Uber-Header generieren und senden, werden im System keine verbundenen Tracing-Spans angezeigt. Und das ist es, was wir brauchen, um die Ursache von Problemen schnell zu finden. Auch hier hat Netramesh eine Lösung. Proxys lesen HTTP-Header und generieren einen, wenn sie die Uber-Trace-ID nicht enthalten. Netramesh speichert außerdem Informationen über ein- und ausgehende Anfragen in einem Sidecar und gleicht diese ab, indem es sie mit den erforderlichen Headern für ausgehende Anfragen anreichert. Alles, was Sie in den Diensten tun müssen, ist, nur einen Header zu senden X-Request-Id, die mithilfe einer Umgebungsvariablen konfiguriert werden kann NETRA_HTTP_REQUEST_ID_HEADER_NAME. Um die Größe des Kontexts in Netramesh zu steuern, können Sie die folgenden Umgebungsvariablen festlegen: NETRA_TRACING_CONTEXT_EXPIRATION_MILLISECONDS (die Zeit, für die der Kontext gespeichert wird) und NETRA_TRACING_CONTEXT_CLEANUP_INTERVAL (Häufigkeit der Kontextbereinigung).

Es ist auch möglich, mehrere Pfade auf Ihrem System zu kombinieren, indem Sie sie mit einem speziellen Sitzungstoken markieren. Netra ermöglicht Ihnen die Installation HTTP_HEADER_TAG_MAP um HTTP-Header in entsprechende Tracing-Span-Tags umzuwandeln. Dies kann besonders zum Testen nützlich sein. Nach bestandenem Funktionstest können Sie anhand der Filterung anhand des entsprechenden Sitzungsschlüssels erkennen, welcher Teil des Systems betroffen war.

Bestimmen der Anforderungsquelle

Um festzustellen, woher die Anfrage kam, können Sie die Funktion des automatischen Hinzufügens eines Headers mit der Quelle nutzen. Verwendung einer Umgebungsvariablen NETRA_HTTP_X_SOURCE_HEADER_NAME Sie können einen Headernamen angeben, der automatisch installiert wird. Mit Hilfe NETRA_HTTP_X_SOURCE_VALUE Sie können den Wert festlegen, auf den der X-Source-Header für alle ausgehenden Anfragen gesetzt wird.

Dadurch kann die Verteilung dieses nützlichen Headers gleichmäßig im gesamten Netzwerk verteilt werden. Anschließend können Sie es in Diensten verwenden und zu Protokollen und Metriken hinzufügen.

Traffic-Routing und Netramesh-Interna

Netramesh besteht aus zwei Hauptkomponenten. Die erste, netra-init, legt Netzwerkregeln fest, um Datenverkehr abzufangen. Er benutzt iptables-Umleitungsregeln um den gesamten oder einen Teil des Verkehrs auf dem Beiwagen abzufangen, der die zweite Hauptkomponente von Netramesh darstellt. Sie können konfigurieren, welche Ports für eingehende und ausgehende TCP-Sitzungen abgefangen werden müssen: INBOUND_INTERCEPT_PORTS, OUTBOUND_INTERCEPT_PORTS.

Das Tool verfügt außerdem über eine interessante Funktion – probabilistisches Routing. Wenn Sie Netramesh ausschließlich zum Sammeln von Tracing-Spans verwenden, können Sie in einer Produktionsumgebung Ressourcen sparen und probabilistisches Routing mithilfe von Variablen ermöglichen NETRA_INBOUND_PROBABILITY и NETRA_OUTBOUND_PROBABILITY (von 0 bis 1). Der Standardwert ist 1 (der gesamte Datenverkehr wird abgefangen).

Nach erfolgreichem Abfangen akzeptiert Netra Sidecar die neue Verbindung und verwendet sie SO_ORIGINAL_DST socket-Option, um das ursprüngliche Ziel zu erhalten. Anschließend stellt Netra eine neue Verbindung zur ursprünglichen IP-Adresse her und stellt eine bidirektionale TCP-Kommunikation zwischen den Parteien her, wobei der gesamte durchlaufende Datenverkehr überwacht wird. Wenn der Port als HTTP definiert ist, versucht Netra, ihn zu analysieren und zu verfolgen. Wenn die HTTP-Analyse fehlschlägt, greift Netra auf TCP zurück und leitet die Bytes transparent weiter.

Erstellen eines Abhängigkeitsdiagramms

Nachdem ich in Jaeger eine große Menge an Ablaufverfolgungsinformationen erhalten habe, möchte ich ein vollständiges Diagramm der Interaktionen im System erhalten. Wenn Ihr System jedoch sehr ausgelastet ist und sich pro Tag Milliarden von Tracing-Spans ansammeln, wird die Aggregation dieser Spannen keine so einfache Aufgabe. Es gibt eine offizielle Möglichkeit, dies zu tun: Spark-Abhängigkeiten. Es wird jedoch Stunden dauern, ein vollständiges Diagramm zu erstellen, und Sie müssen den gesamten Datensatz der letzten XNUMX Stunden von Jaeger herunterladen.

Wenn Sie Elasticsearch zum Speichern von Tracing-Spans verwenden, können Sie Folgendes verwenden ein einfaches Golang-Dienstprogramm, das mithilfe der Funktionen und Fähigkeiten von Elasticsearch in wenigen Minuten dasselbe Diagramm erstellt.

Netramesh – leichte Service-Mesh-Lösung

So verwenden Sie Netramesh

Netra kann problemlos zu jedem Dienst hinzugefügt werden, auf dem ein beliebiger Orchestrator ausgeführt wird. Sie können ein Beispiel sehen hier.

Derzeit ist Netra nicht in der Lage, Sidecars für Dienste automatisch zu implementieren, es gibt jedoch Pläne für eine Implementierung.

Die Zukunft von Netramesh

Hauptzweck Netramesh Ziel ist es, minimale Ressourcenkosten und hohe Leistung zu erzielen und grundlegende Funktionen für die Beobachtbarkeit und Steuerung der Kommunikation zwischen Diensten bereitzustellen.

In Zukunft wird Netramesh neben HTTP auch andere Protokolle der Anwendungsschicht unterstützen. L7-Routing wird in naher Zukunft verfügbar sein.

Nutzen Sie Netramesh, wenn Sie auf ähnliche Probleme stoßen und schreiben Sie uns mit Fragen und Anregungen.

Source: habr.com

Kommentar hinzufügen