Netzwerkfiltersystem zur Konfiguration, Qrator

Netzwerkfiltersystem zur Konfiguration, Qrator

TL;DR: Beschreibung der Client-Server-Architektur unseres internen Konfigurationsmanagementsystems, QControl. Die Grundlage bildet ein zweistufiges Transportprotokoll, das mit in gzip komprimierten Nachrichten ohne Dekomprimierung zwischen den Endpunkten arbeitet. Verteilte Router und Endpunkte erhalten Konfigurationsupdates, und das Protokoll ermöglicht die Installation lokalisierter Zwischenschalter. Das System basiert auf differenziellen Backups („recent-stable“, wird weiter unten erläutert) und nutzt die Abfragesprache JMESpath zusammen mit dem Template-Generator Jinja zur Erstellung von Konfigurationsdateien.

Qrator Labs verwaltet ein global verteiltes Netzwerk zur Neutralisierung von Angriffen. Unser Netzwerk funktioniert nach dem Anycast-Prinzip, und die Subnetze werden über BGP angekündigt. Als BGP-Anycast-Netzwerk, das physisch an mehreren Standorten weltweit angesiedelt ist, können wir illegitimen Traffic näher am Kern des Internets — bei Tier-1-Anbietern — verarbeiten und filtern.

Andererseits ist es nicht einfach, ein geografisch verteiltes Netzwerk zu sein. Die Kommunikation zwischen den Netzwerk-Punkten ist entscheidend für den Sicherheitsdienstleister, um eine einheitliche Konfiguration aller Knoten im Netzwerk zu gewährleisten und diese rechtzeitig zu aktualisieren. Daher mussten wir, um das maximal mögliche Niveau der Hauptdienstleistung für den Kunden bereitzustellen, einen Weg finden, die Konfigurationsdaten zuverlässig zwischen den Kontinenten zu synchronisieren.

Am Anfang war das Wort. Es wurde schnell zu einem Kommunikationsprotokoll, das ein Update benötigte.


Der Grundstein für die Existenz von QControl und gleichzeitig die Hauptursache für den Zeit- und Ressourcenaufwand beim Aufbau eines solchen Protokolls ist die Notwendigkeit, eine einheitliche, autoritative Konfigurationsquelle zu erhalten und schließlich unsere Presence Points damit zu synchronisieren. Der Speicher selbst war nur eines von mehreren Anforderungen während der Entwicklung von QControl. Darüber hinaus benötigten wir auch Integrationen mit bestehenden und geplanten Diensten an den Presence Points, intelligente (und anpassbare) Methoden zur Datenvalidierung sowie eine Zugriffskontrolle. Zudem wollten wir ein solches System über Kommandos und nicht durch Änderungen an Dateien steuern. Vor QControl wurden Daten praktisch manuell an die Presence Points gesendet. Wenn einer der Presence Points nicht verfügbar war und wir vergaßen, die Aktualisierung später durchzuführen, geriet die Konfiguration aus der Synchronisation – wir mussten Zeit aufwenden, um sie wieder funktionsfähig zu machen.

Am Ende haben wir folgendes Schema entwickelt:
Netzwerkfiltersystem zur Konfiguration, Qrator
Der Konfigurationsserver ist verantwortlich für die Validierung von Daten und die Speicherung. Der Router verfügt über mehrere Endpunkte, die Konfigurationsupdates von den Kunden und dem Support-Team an den Server empfangen und von dort zu den PoPs übermitteln.

Die Qualität der Internetverbindung variiert nach wie vor erheblich in verschiedenen Teilen der Welt. Um diese Aussage zu veranschaulichen, betrachten wir einen einfachen MTR von Prag, Tschechische Republik, nach Singapur und Hongkong.

Netzwerkfiltersystem zur Konfiguration, Qrator
MTR von Prag nach Singapur

Netzwerkfiltersystem zur Konfiguration, Qrator
Das gleiche nach Hongkong

Hohe Latenzen bedeuten geringere Geschwindigkeiten. Darüber hinaus gibt es Paketverluste. Die Bandbreite allein kann dieses Problem nicht ausgleichen, das immer bei der Konstruktion dezentraler Systeme berücksichtigt werden sollte.

Die vollständige Konfiguration eines PoPs ist ein erheblicher Datenumfang, der über unzuverlässige Verbindungen an viele Empfänger gesendet werden muss. Glücklicherweise, obwohl die Konfiguration ständig geändert wird, geschieht dies in kleinen Portionen.

Design recent-stable

Es lässt sich sagen, dass der Aufbau eines verteilten Netzwerks auf Grundlage inkrementeller Updates eine recht offensichtliche Lösung ist. Allerdings gibt es eine Vielzahl von Problemen im Zusammenhang mit Diffs. Wir müssen alle Diffs zwischen den Referenzpunkten speichern und in der Lage sein, sie nachzuliefern, falls jemand Teile der Daten verpasst hat. Jeder Zielpunkt muss sie in einer strengen Reihenfolge anwenden. In der Regel kann eine solche Operation bei mehreren Zielpunkten zeitaufwändig sein. Der Empfänger muss außerdem in der Lage sein, fehlende Teile anzufordern, und selbstverständlich muss das zentrale System auf eine solche Anfrage korrekt reagieren, indem es nur die fehlenden Daten sendet.

Schließlich sind wir zu einer recht interessanten Lösung gekommen – wir haben nur eine feste Referenzschicht, nennen wir sie stable, und nur ein Diff dafür – recent. Jeder recent basiert auf dem zuletzt erstellten stable und ist ausreichend, um die Konfigurationsdaten neu zu erstellen. Sobald ein frisches recent am Ziel ankommt, wird das alte nicht mehr benötigt.

Es bleibt nur, von Zeit zu Zeit eine frische stabile Konfiguration zu senden, beispielsweise weil die aktuelle Version zu groß geworden ist. Wichtig ist auch, dass wir all diese Updates im Broadcast-/Multicast-Modus versenden, ohne uns um einzelne Empfänger und deren Fähigkeit, die Datenstücke zusammenzuführen, kümmern zu müssen. Sobald wir bestätigt haben, dass alle die korrekte stabile Version haben, senden wir nur die neuen aktuellen Updates. Muss ich betonen, dass das funktioniert? Es funktioniert. Die stabile Version wird auf dem Konfigurationsserver und bei den Empfängern zwischengespeichert, die aktuellen Updates werden bei Bedarf erstellt.

Architektur des zweistufigen Transports

Warum haben wir unser Transportmittel auf zwei Ebenen aufgebaut? Die Antwort ist recht einfach – wir wollten die Routing-Funktion von der hochgradigen Logik trennen und ließen uns von dem OSI-Modell mit seiner Transportschicht und der Anwendungsschicht inspirieren. Als Transportprotokoll haben wir Thrift gewählt, während wir das msgpack-Format für hochgradige Steuerungsnachrichten verwendeten. Daher betrachtet der Router (der Multicast/Broadcast/Relay durchführt) nicht den Inhalt von msgpack, er entpackt ihn nicht und verpackt ihn nicht zurück, sondern führt lediglich die Datenweiterleitung aus.

Thrift (von engl. 'Sparsamkeit', ausgesprochen als [θrift]) ist eine Schnittstellensprache, die verwendet wird, um Dienste für verschiedene Programmiersprachen zu definieren und zu erstellen. Es handelt sich um ein Framework für Remote Procedure Calls (RPC). Es verbindet eine Programmpipeline mit einer Code-Generierungs-Engine zur Entwicklung von Diensten, die in unterschiedlichem Maße effizient und einfach zwischen Sprachen arbeiten.

Wir haben das Thrift-Framework wegen RPC und der Unterstützung mehrerer Sprachen gewählt. Wie so oft waren die Client- und Serverkomponenten relativ einfach umzusetzen. Der Router stellte sich jedoch als schwierig heraus, partly aufgrund der fehlenden fertigen Lösungen während unserer Entwicklung.

Netzwerkfiltersystem zur Konfiguration, QratorEs gibt auch andere Optionen wie Protobuf/gRPC, jedoch war gRPC zu Beginn unseres Projekts noch recht neu, sodass wir uns nicht traifen konnten, es zu integrieren.

Natürlich hätten wir (und das wäre vielleicht auch der bessere Weg gewesen) ein eigenes Konzept entwickeln können. Es wäre einfacher gewesen, ein Protokoll für unsere spezifischen Anforderungen zu erstellen, da die Client-Server-Architektur im Vergleich zum Bau eines Routers mit Thrift relativ unkompliziert ist. Letztendlich gibt es jedoch eine gewisse Vorurteil gegenüber selbst geschriebene Protokolle und Implementierungen beliebter Bibliotheken, was oft die Frage aufwirft: "Wie portieren wir das auf andere Sprachen?" Daher haben wir die Idee des eigenen Designs schnell verworfen.

Msgpack ist ein schnellerer und kompakterer Alternativ zu JSON. Es handelt sich um ein binäres Datenserialisierungsformat, das den Datenaustausch zwischen vielen Programmiersprachen ermöglicht.

Auf der ersten Ebene haben wir Thrift mit den minimal erforderlichen Informationen für den Router zur Nachrichtenübertragung. Auf der zweiten Ebene befinden sich die verpackten msgpack-Strukturen.

Wir haben uns für msgpack entschieden, weil es schneller und kompakter ist als JSON. Noch wichtiger ist jedoch, dass es benutzerdefinierte Datentypen unterstützt, die es uns ermöglichen, coole Funktionen wie den Austausch von Roh-Binärdaten oder speziellen Objekten, die das Fehlen von Daten darstellen, zu nutzen. Das war entscheidend für unser „recent-stable“ Schema.

JMESPath
JMESPath ist eine Abfragesprache für JSON.
So sieht die Beschreibung aus, die wir aus der offiziellen JMESPath-Dokumentation erhalten, aber in Wirklichkeit bietet sie viel mehr. JMESPath ermöglicht die Suche und Filterung von Unterbäumen in beliebigen baumartigen Strukturen sowie die Anwendung von Änderungen an Daten in Echtzeit. Außerdem können spezielle Filter und Transformationsverfahren hinzugefügt werden. Natürlich erfordert es ein gewisses Maß an geistiger Anstrengung, um es zu verstehen.

Jinja
Für einige Verbraucher müssen wir die Konfiguration in eine Datei umwandeln – daher verwenden wir einen Template-Engine, und Jinja ist die naheliegende Wahl. Damit generieren wir die Konfigurationsdatei aus einer Vorlage und den an der Zielstelle erhaltenen Daten.

Um eine Konfigurationsdatei zu erstellen, benötigen wir eine JMESPath-Abfrage, eine Vorlage für den Speicherort der Datei im Dateisystem und eine Vorlage für die Konfiguration selbst. An diesem Punkt ist es auch ratsam, die Zugriffsrechte auf die Datei festzulegen. All dies konnte erfolgreich in einer Datei kombiniert werden – vor Beginn der Konfiguration fügen wir einen YAML-Kopf hinzu, der den Rest beschreibt.

Zum Beispiel:

---
selector: "[@][?@.fft._meta.version == `42`] | items([0].fft_config || `{}`)"
destination_filename: "fft/{{ match[0] }}.json"
file_mode: 0644
reload_daemons: [fft]
...
{{ dict(match[1]) | json(indent=2, sort_keys=True) }}

Um eine Konfigurationsdatei für einen neuen Dienst zu erstellen, fügen wir lediglich eine neue Vorlage hinzu. Es sind keine Änderungen am Quellcode oder an der Software an den Standorten erforderlich.

Was hat sich nach der Einführung von QControl in den Betriebsablauf geändert? Das Wichtigste ist die konsistente und zuverlässige Bereitstellung von Konfigurationsupdates über alle Knoten im Netzwerk. Zweitens – unser Support-Team sowie die Endverbraucher des Dienstes erhalten ein leistungsfähiges Werkzeug zur Überprüfung und Anpassung der Konfiguration.

All dies haben wir erreicht, indem wir das recent-stable Update-System verwendet haben, um die Kommunikation zwischen dem Konfigurationsserver und den Konfigurationsempfängern zu vereinfachen. Dabei kam ein zweistufiges Protokoll zum Einsatz, das eine inhaltsunabhängige Datenübertragung ermöglicht. Zudem wurde eine auf Jinja basierende Konfigurationsgenerierungs-Engine erfolgreich in das verteilte Filtersystem integriert. Dieses System unterstützt eine Vielzahl von Konfigurationsmöglichkeiten für unsere verteilte und vielfältige Peripherie.

Vielen Dank für die Unterstützung bei der Erstellung des Materials. VolanDamrod, serenheit, NoN.

Englische Version des Beitrags.

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