Netzwerk-Konfigurationsmanagement-System für Filterung Qrator

Netzwerk-Konfigurationsmanagement-System für Filterung Qrator

TL;DR: Beschreibung der Client-Server-Architektur unseres internen Netzwerk-Konfigurationsmanagement-Systems, QControl. Es basiert auf einem zweistufigen Transportprotokoll, das mit gzip-komprimierten Nachrichten ohne Dekomprimierung zwischen Endpunkten arbeitet. Verteilte Router und Endpunkte erhalten Konfigurationsupdates, während das Protokoll die Einrichtung lokalisierter Zwischenrelais ermöglicht. Das System ist nach dem Prinzip differenzieller Backups („recent-stable“, siehe unten) aufgebaut und verwendet die Abfragesprache JMESpath zusammen mit dem Template-Engine Jinja für das Rendern von Konfigurationsdateien.

Qrator Labs verwaltet ein global verteiltes Netzwerk zur Bekämpfung von Angriffen. Unser Netzwerk arbeitet nach dem Anycast-Prinzip, und Subnetze werden über BGP angekündigt. Als BGP Anycast-Netzwerk, das physisch in mehreren Regionen der Erde positioniert 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-Präsenzpunkten ist entscheidend für den Sicherheitsdienstleister, um eine konsistente Konfiguration aller Knoten im Netzwerk zu haben und diese rechtzeitig zu aktualisieren. Daher mussten wir, um das maximal mögliche Niveau der Kernleistung für den Verbraucher zu gewährleisten, 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 Grundpfeiler des Bestehens von QControl und gleichzeitig der Hauptgrund für die erhebliche Verschwendung von Zeit und Ressourcen beim Aufbau eines solchen Protokolls ist die Notwendigkeit, eine einheitliche, autoritative Konfigurationsquelle zu erhalten und letztendlich unsere Standorte 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 Standorten (TP), intelligente (und anpassbare) Methoden zur Datenvalidierung sowie Zugriffssteuerung. Darüber hinaus wollten wir ein solches System mit Befehlen verwalten und nicht durch Änderungen an Dateien. Vor QControl wurden die Daten praktisch manuell an die Standorte gesendet. Wenn einer der Standorte nicht verfügbar war und wir es vergaßen, ihn später zu aktualisieren, wurde die Konfiguration desynchronisiert — es musste Zeit aufgewendet werden, um sie wieder einsatzbereit zu machen.

Am Ende haben wir folgendes Schema entwickelt:
Netzwerk-Konfigurationsmanagement-System für Filterung Qrator
Der Konfigurationsserver ist verantwortlich für die Validierung der Daten und den Speicher, der Router hat mehrere Endpunkte, die Konfigurationsupdates von Kunden und dem Support-Team an den Server empfangen und weiterleiten, und vom Server zu den Standorten.

Die Qualität der Internetverbindung variiert immer noch erheblich in verschiedenen Teilen der Welt — um diese These zu veranschaulichen, betrachten wir einen einfachen MTR von Prag, Tschechische Republik nach Singapur und Hongkong.

Netzwerk-Konfigurationsmanagement-System für Filterung Qrator
MTR von Prag nach Singapur

Netzwerk-Konfigurationsmanagement-System für Filterung Qrator
Das gleiche nach Hongkong

Hohe Latenzen bedeuten geringere Geschwindigkeiten. Darüber hinaus gibt es Paketverluste. Die Bandbreite kompensiert nicht dieses Problem, das immer bei der Entwicklung dezentraler Systeme berücksichtigt werden muss.

Die vollständige Konfiguration eines Standorts ist ein erheblicher Datenumfang, der vielen Empfängern über unzuverlässige Verbindungen übermittelt werden muss. Glücklicherweise geschieht dies, obwohl die Konfiguration ständig ändert, in kleinen Portionen.

Design recent-stable

Man kann sagen, dass der Aufbau eines verteilten Netzwerks nach dem Prinzip inkrementeller Updates eine ziemlich offensichtliche Lösung ist. Aber mit Diffs sind viele Probleme verbunden. Wir müssen alle Diffs zwischen den Referenzpunkten speichern und in der Lage sein, sie nachzusenden, falls jemand einen Teil der Daten verpasst hat. Jeder Zielpunkt muss sie in einer genau bestimmten Reihenfolge anwenden. In der Regel kann eine solche Operation im Fall mehrerer Zielpunkte viel Zeit in Anspruch nehmen. Der Empfänger muss auch in der Lage sein, fehlende Teile anzufordern, und natürlich muss der zentrale Teil auf eine solche Anfrage korrekt antworten, indem er nur die fehlenden Daten sendet.

Schließlich sind wir zu einer ziemlich interessanten Lösung gekommen – wir haben nur eine Referenzschicht, fixiert, nennen wir sie stable, und nur einen Diff dafür – recent. Jeder recent basiert auf dem zuletzt erstellten stable und ist ausreichend, um die Konfigurationsdaten neu aufzubauen. Sobald ein frischer recent am Ziel ankommt, ist der alte nicht mehr notwendig.

Es bleibt nur, von Zeit zu Zeit eine frische stable-Konfiguration zu senden, beispielsweise weil recent zu groß geworden ist. Auch wichtig ist, dass wir all diese Updates im Broadcast-/Multicast-Modus versenden, ohne uns um einzelne Empfänger und deren Fähigkeit, die Datenstücke zusammenzufügen, zu kümmern. Sobald wir sichergestellt haben, dass alle einen korrekten stable haben, senden wir nur neue recent. Soll ich klarstellen, dass das funktioniert? Es funktioniert. Stable wird auf dem Konfigurationsserver und bei den Empfängern zwischengespeichert, recent wird bei Bedarf erstellt.

Architektur des zweistufigen Transports

Warum haben wir unseren Transport auf zwei Ebenen aufgebaut? Die Antwort ist ziemlich einfach – wir wollten die Routing-Logik von der hochrangigen Logik trennen, inspiriert von dem OSI-Modell mit seiner Transportschicht und der Anwendungsschicht. Für das Transportprotokoll haben wir Thrift gewählt, und für das hochrangige Format der Steuerungsnachrichten das Serialisierungsformat msgpack. Aus diesem Grund schaut der Router (der Multicast/Broadcast/Relay ausführt) nicht in msgpack hinein, entpackt es nicht und packt den Inhalt nicht zurück und führt nur die Datenweiterleitung durch.

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 kombiniert eine Programmierpipeline mit einer Code-Generierungs-Engine zur Entwicklung von Diensten, die mehr oder weniger effektiv und leicht zwischen Sprachen arbeiten.

Wir haben das Thrift-Framework wegen RPC und der Unterstützung vieler Sprachen gewählt. Wie üblich waren die Teile, die leichter zu handhaben waren, der Client und der Server. Der Router stellte sich jedoch als harte Nuss heraus, teilweise wegen des Mangels an einer fertigen Lösung während unserer Entwicklung.

Netzwerk-Konfigurationsmanagement-System für Filterung QratorEs gibt auch andere Optionen wie protobuf / gRPC, jedoch waren wir beim Start unseres Projekts gRPC noch relativ neu und wir zögerten, es zu übernehmen.

Natürlich hätten wir (und es wäre tatsächlich besser gewesen) ein eigenes Rad neu erfinden können. Es wäre einfacher gewesen, ein Protokoll für unsere Bedürfnisse zu erstellen, da die Client-Server-Architektur im Vergleich zum Bau eines Routers auf Thrift relativ unkompliziert umzusetzen ist. Dennoch gibt es eine traditionelle Vorurteil gegenüber selbstgeschriebenen Protokollen und Implementierungen beliebter Bibliotheken (zu Recht). Außerdem steht immer die Frage im Raum: „Wie werden wir das auf andere Sprachen portieren?“ Daher haben wir sofort die Ideen eines eigenen Rades verworfen.

Msgpack ist ein Pendant zu JSON, aber schneller und kleiner. Es handelt sich um ein binäres Datenformat zur Serialisierung, das den Datenaustausch zwischen vielen Sprachen ermöglicht.

Auf der ersten Ebene haben wir Thrift mit den minimal notwendigen Informationen für den Router, um Nachrichten weiterzuleiten. Auf der zweiten Ebene befinden sich die gepackten msgpack-Strukturen.

Wir haben msgpack gewählt, weil es schneller und kompakter im Vergleich zu JSON ist. Noch wichtiger ist, dass es benutzerdefinierte Datentypen unterstützt, die es uns ermöglichen, coole Funktionen wie den Austausch von Rohbinärdateien oder speziellen Objekten, die das Fehlen von Daten kennzeichnen, zu nutzen, was für unser Schema „recent-stable“ wichtig war.

JMESPath
JMESPath ist eine Abfragesprache für JSON.
So sieht die Beschreibung aus, die wir aus der offiziellen Dokumentation von JMESPath erhalten, aber tatsächlich bietet sie viel mehr. JMESPath ermöglicht das Durchsuchen und Filtern von Teilbäumen in beliebigen baumartigen Strukturen und das Anwenden von Änderungen an Daten in Echtzeit. Es erlaubt auch das Hinzufügen spezieller Filter und Datenumwandlungsverfahren. Natürlich erfordert es ein wenig geistige Anstrengung, um es zu verstehen.

Jinja
Für einige Verbraucher müssen wir die Konfiguration in eine Datei umwandeln – daher nutzen wir eine Template-Engine, und Jinja ist die naheliegende Wahl. Damit erzeugen wir eine Konfigurationsdatei aus einem Template und den in einem bestimmten Ziel erhaltenen Daten.

Um eine Konfigurationsdatei zu generieren, benötigen wir eine JMESPath-Abfrage, ein Template für den Speicherort der Datei im Dateisystem und ein Template für die Konfiguration selbst. Auch an diesem Punkt ist es sinnvoll, die Zugriffsrechte für die Datei zu klären. All das konnten wir erfolgreich in einer Datei kombinieren – vor dem Beginn des Konfigurationstemplates fügen wir einen Header im YAML-Format 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 die Konfigurationsdatei für einen neuen Dienst zu erstellen, fügen wir nur die neue Template-Datei hinzu. Es sind keine Änderungen am Quellcode oder der Software an den Einsatzorten erforderlich.

Was hat sich nach der Einführung von QControl in den Betrieb geändert? Das erste und wichtigste ist die konsistente und zuverlässige Lieferung von Konfigurationsupdates an alle Knoten im Netzwerk. Zweitens erhalten wir ein leistungsstarkes Werkzeug zur Überprüfung der Konfiguration und zur Durchführung von Änderungen durch unser Support-Team sowie die Endverbraucher.

All dies haben wir erreicht, indem wir das Update-Schema recent-stable genutzt haben, um die Kommunikation zwischen dem Konfigurationsserver und den Konfigurationsempfängern zu vereinfachen. Wir verwenden ein zweistufiges Protokoll zur Unterstützung einer inhaltsunabhängigen Datenrouting-Methode und haben erfolgreich eine Jinja-basierte Konfigurationsgenerierungs-Engine in ein verteiltes Filtersystem integriert. Dieses System unterstützt eine breite Palette von Konfigurationsmethoden für unsere verteilte und heterogene Peripherie.

Danke für die Hilfe beim Schreiben des Materials VolanDamrod, ruhe, NoN.

Englische Version Beitrag.

Quelle: habr.com

60GB SSD 8Gb DDR4