Blog ·
Wie ein Agent den richtigen Edge-Server wählt
Mehrere A-Einträge, Edges auf Schicht vier und PROXY-Protokoll v2, mit Messungen: was ein Client mit mehreren Adressen tut und warum Rückkehr schwerer ist.
Von Provibr
Betreiben Sie einen Agenten auf der Hardware Ihrer Kunden, dann wohnt die Adresse, die er anwählt, an zwei unbequemen Orten: im öffentlichen DNS und in der Zugangsdatei, die Sie bei der Einschreibung ausgegeben haben. Beides ist hinterher schwer zu ändern, und eines davon verrät dem gesamten Internet, wo Ihre Steuerungsebene steht.
Wir haben eine Schicht Edge-Server vor unsere Steuerungsebene gesetzt. Das hier war dafür nötig, einschließlich der Teile, die erst auftauchten, als echte Agenten hindurchliefen.
Das Problem ist die Adresse, nicht der Verkehr
Der Hostname des Gateways löste auf den Ursprung auf. Jeder eingeschriebene Agent kannte ihn, und jeder, der dig tippen kann, ebenfalls. Eine volumetrische Flut auf diese Adresse nimmt die Steuerungsebene für alle vom Netz, und hinterher gibt es nichts Kluges mehr zu tun.
Das Ziel war eng, und es auszusprechen hält den Entwurf ehrlich: Die Adresse des Ursprungs verschwindet aus dem Weg, den Agenten benutzen, damit eine Flut eine Maschine trifft, die sich in zehn Minuten ersetzen lässt. Ausdrücklich nicht das Ziel war, dass die Edges den Inhalt schützen. Beidseitiges TLS tut das bereits von Ende zu Ende, und ein Edge, der den Verkehr einsehen könnte, wäre eine Verschlechterung.
Schicht vier, denn TLS zu terminieren würde genau das kaputt machen, worauf es ankommt
Der naheliegende Entwurf ist ein Reverse Proxy, der TLS am Edge terminiert. Er ist hier der falsche, aus zwei Gründen, die für diese Art System eigen sind.
Das Gateway authentifiziert Agenten anhand ihres Client-Zertifikats, angeheftet an einen je Agent gespeicherten Fingerabdruck. TLS am Edge zu terminieren beendet den beidseitigen Handschlag dort, der Ursprung müsste also glauben, was immer der Edge behauptet. Und es hieße, den privaten Schlüssel des Gateways auf mehrere billige virtuelle Maschinen zu kopieren, deren ganzer Zweck es ist, wegwerfbar zu sein.
Jeder Edge ist deshalb ein schlichter TCP-Weiterleiter. Er sieht Geheimtext, hält keine Geheimnisse und betreibt keine Anwendung, was eine so kleine Angriffsfläche ist, dass „ihn ersetzen“ die meisten Probleme beantwortet.
Es kostet eine Sache. Jede Verbindung scheint nun vom Edge auszugehen, der Ursprung weiß also nicht mehr, wer anruft.
PROXY-Protokoll v2, und der Unterschied zwischen einer Behauptung und einer Messung
Die übliche Behebung ist das PROXY-Protokoll: Der Weiterleiter stellt jeder Verbindung ein kleines binäres Kopffeld voran, das die ursprüngliche Adresse trägt. Das Gateway liest es und schreibt die echte Adresse zurück in seine Protokolle.
Die Entwurfsentscheidung, die es zu übernehmen lohnt, ist, zwei Werte zu behalten statt einen.
- Die Adresse aus dem Kopffeld ist das, was die Gegenseite über den Client sagt. Sie ist eine Behauptung.
- Die äußere Socket-Adresse, also die Maschine, von der die Bytes tatsächlich kamen, ist etwas, das wir selbst gemessen haben.
Ein gefälschtes Kopffeld kann den ersten Wert lügen lassen. Der zweite bleibt trotzdem wahr. Sie als getrennte Felder zu führen bedeutet, dass sich eine Protokollzeile Jahre später lesen lässt, ohne zu rätseln, welcher der beiden es ist, und es macht die ehrliche Antwort auf „können wir der IP trauen“ leicht: An ihr hängt nichts außer einer Protokollzeile, denn die Identität hängt am Zertifikat der Gegenstelle.
Zwei Umsetzungsnotizen, die uns Ärger erspart haben:
Schnuppern, nicht verlangen. Eine Umstellung auf das PROXY-Protokoll ist nicht atomar. Sehen die ersten Bytes eher nach einem TLS-ClientHello aus als nach der Protokollsignatur, schieben Sie sie in den Strom zurück und leiten die Verbindung unangetastet weiter. Damit werden das Ausrollen des Parsers und sein Einschalten auf den Weiterleitern zu zwei voneinander unabhängigen Schritten.
Eine Zuordnung auf die innere Verbindung führen. Sobald die Bytes eine gRPC-Bibliothek erreichen, ist die Adresse der Gegenstelle ein Port auf dem Loopback, und sowohl der Agent als auch der Edge sind unauffindbar geworden. Die echte Gegenstelle am Quellport der internen Verbindung festzumachen und beim Schließen zu löschen ist die einzige Stelle, an der diese Angabe überlebt.
Darin versteckt sich noch ein kleiner Gewinn für den Betrieb: Eine Verbindung, die sich öffnet und schließt, ohne etwas zu senden, erzeugt überhaupt keine Protokollzeile. Zustandsprüfungen tun das ununterbrochen, und sie nicht zu schreiben hat Zehntausende nutzloser Zeilen pro Tag entfernt.
Entdeckung: die A-Menge ist die Liste
Woher erfährt der Agent, dass es mehr als einen Edge gibt? Zur Wahl standen TXT-Einträge mit strukturiertem Inhalt, SRV-Einträge oder schlicht mehrere A-Einträge auf demselben Namen.
Mehrere A-Einträge gewannen mit einem Argument. Die A-Menge ist bereits die Liste der Orte, zu denen man sich verbinden kann. Ein TXT-Eintrag wäre eine zweite Quelle der Wahrheit für dieselbe Tatsache, und die beiden können sich widersprechen. SRV würde Gewichte hinzufügen, die wir nicht brauchen, denn der Agent misst selbst, statt gesagt zu bekommen, welchen er vorziehen soll. Die TTL ist kurz, einen Edge zu entfernen ist also eine DNS-Änderung plus eine TTL Geduld.
Was ein Client mit mehreren A-Einträgen tatsächlich tut
Bevor überhaupt eine Auswahllogik gebaut wurde, lohnte es sich zu messen, was umsonst passiert. Ein moderner HTTP-Client probiert die Adressen der Eintragsmenge durch und verteilt sein Verbindungszeitlimit auf sie. Wir haben eine bekanntermaßen tote Adresse nach vorn gesetzt und gemessen:
| Verhalten der toten Adresse | Zeit bis zur Verbindung mit der lebenden |
|---|---|
| Verbindung abgelehnt | 7 ms |
| Kein Weg zum Host | 7,5 ms |
| Stilles Verwerfen des SYN, 25 s Verbindungszeitlimit | 12,5 s |
| Stilles Verwerfen des SYN, 10 s Verbindungszeitlimit | 5,0 s |
Das Muster ist deutlich genug, um darauf zu entwerfen: Harte Ablehnungen sind umsonst, und ein schwarzes Loch kostet ungefähr das Verbindungszeitlimit geteilt durch die Anzahl der Adressen. Der Ausweichbetrieb funktionierte damit bereits ohne jede Änderung am Agenten, die DNS-Umstellung konnte also vor der Auswahllogik ausgeliefert werden. Es heißt außerdem, dass Ihr Verbindungszeitlimit nun eine Funktion davon ist, wie viele Einträge Sie veröffentlichen, was nicht offensichtlich ist, solange man diese Tabelle nicht vor sich hat.
Ausweichen ist keine Auswahl
Der kostenlose Ausweichbetrieb nimmt die Adresse, die zuerst antwortet, und das ist nicht dasselbe wie die beste. Ein Agent in Amsterdam saß tagelang auf einem Edge in einem anderen Land, weil dieser zuerst geantwortet hatte und die Sitzung danach eine Woche lebte.
Also misst der Agent. Die Einzelheiten, auf die es ankam:
Mit einem vollständigen TLS-Handschlag prüfen, nicht mit einem TCP-Verbindungsaufbau. Ein Edge kann eine Verbindung annehmen und nichts weiterleiten, und genau diesen Fehler muss die Prüfung fangen. Einen nackten Verbindungsaufbau beantwortet ein kaputter Weiterleiter ebenso munter wie ein funktionierender.
Jeder Prüfung ein eigenes Budget geben, parallel. Ein Edge, der annimmt und dann verstummt, kann einen Handschlag über das Verbindungszeitlimit hinaus hängen lassen, das Budget je Adresse steht deshalb daneben, statt sich darauf zu verlassen.
Die gesamte Rangfolge festhalten, nicht den Sieger. Die gewählte Adresse und die geordnete Liste wandern in die Zustandsdatei des Agenten, zusammen mit der Adressmenge, an der sie gemessen wurden. Das macht einen Neustart billig, und es heißt, dass ein Ausweichen einen Neustart übersteht, statt fröhlich zu der Adresse zurückzukehren, die gerade versagt hat.
Anhand der Menge verwerfen, nicht anhand der Wahl. Kommt ein Edge hinzu oder fällt einer weg, ist die Messung veraltet, auch wenn die gewählte Adresse noch darin steht.
Nichts tun, wenn es nichts zu wählen gibt. Bei weniger als zwei Kandidaten wird der ganze Mechanismus übersprungen, und genau das passiert in der Entwicklung, wo der Name auf eine Adresse auflöst.
Und die Regel, die über allem steht: Die Auswahl darf niemals selbst zu einer neuen Fehlerquelle werden. DNS lehnt ab, nichts antwortet, die Zustandsdatei lässt sich nicht schreiben: Jeder dieser Wege endet bei „keine Wahl“, und der Agent verbindet sich genau so, wie er es vor alledem tat.
Zurückzukommen ist schwerer als auszuweichen
Auszuweichen ist leicht. Die schwere Hälfte ist, dass der Agent danach so lange auf dem langsameren Edge bleibt, wie diese Sitzung lebt, und das können Wochen sein.
Also läuft neben der lebenden Sitzung eine Wache, und die tut zwei Dinge und sonst nichts. Sie probiert die Adressen, die über der aktuellen liegen, mit demselben vollständigen Handschlag, und sie misst alles neu, wenn die letzte Messung alt ist. Die Zahlen, und der Grund für jede:
- Ein Abstand von fünf Minuten mit voller Streuung, damit sich die Prüfungen einer Flotte nicht gleichschalten. Jede Minute kostet das Fünffache und gewinnt nichts, denn die Hysterese weiter unten lässt jede Rückkehr ohnehin zwei Runden dauern.
- Eine Serie von zwei, bevor zurückgewechselt wird. Eine einzelne erfolgreiche Prüfung ist ein Edge, der gerade mit dem Neustart fertig geworden ist.
- Zwei Schwellen für die regelmäßige Neumessung: eine absolute von 20 ms und eine relative von 25 %. Beide müssen erfüllt sein. Die absolute allein würde einen Wechsel von 100 ms auf 79 ms gutheißen; die relative allein würde 4 ms auf 3 ms gutheißen. Zwei Edges im selben Rechenzentrum unterscheiden sich um weniger als eine Millisekunde, und ohne das würden sie jeden zweiten Tag die Plätze tauschen.
Der Wechsel ist eine saubere Neuverbindung mit einem eigenen Grund und steht neben den anderen geplanten Sitzungsenden, statt als Fehler abgebildet zu werden. Das zählt mehr, als es klingt: Ein Fehler würde die Leiter des exponentiellen Rückzugs füttern, ein Agent, der zu seinem bevorzugten Edge zurückkehrt, würde also mit einer wachsenden Verzögerung dafür bestraft, dass er das Richtige tut. Der Nachrichtenausgang spielt nach, und der Entdopplungsspeicher sorgt dafür, dass nichts zweimal ausgeführt wird.
Von der anderen Seite zuschauen
Die Plattform beobachtet dasselbe von ihrem Ende aus, und zwei Einzelheiten dort sind es wert, übernommen zu werden.
Den Namen so auflösen, wie der Agent es tut, über den Systemauflöser statt über eine direkte DNS-Abfrage. Eine direkte Abfrage scheiterte auf einer Maschine, deren Auflöser auf sich selbst zeigte, rundheraus, und wichtiger noch: Der Systemauflöser ist das, was der echte Verkehr benutzt, er liefert also dieselbe Liste.
Aus einem Socket zwei Zahlen nehmen: den TCP-Verbindungsaufbau, also die Umlaufzeit zum Edge selbst, und den vollständigen TLS-Handschlag, der durch den Tunnel bis zum Ursprung und zurück muss. Die Gesundheit hängt an der zweiten. Ein Weiterleiter mit totem Hintersystem beantwortet die erste einwandfrei.
Die letzte Lehre betrifft die Firewall. Docker umgeht die Firewall des Hosts vollständig. Die Regeln, dass nur Edges die Ports des Gateways erreichen dürfen, müssen in der Kette wohnen, die Docker selbst befragt, sonst sind sie Zierde und sehen aktiv aus. Und diese Kette sieht Verkehr in beide Richtungen, unsere eigenen ausgehenden Prüfungen vom Ursprung zu den Edges passten also auf denselben Zielport und wurden verworfen, sodass die Überwachung jeden Edge als tot meldete, während es jedem Agenten gut ging.
Was es nicht löst
Die Edges schützen den Inhalt nicht, und es wäre ein Fehler, sie als Sicherheitsgrenze zu behandeln. Eine Adresse, die während einer vollständigen Messung ausgefallen war, wird erst von der nächsten wiederentdeckt, eine bekannte und hingenommene Lücke. Und die DNS-TTL ist eine Untergrenze dafür, wie schnell ein Edge aus der Rotation fällt, was der Preis dafür ist, die A-Menge zur Liste zu machen.
So erreicht der Agent die Plattform bei Provibr, wo er sich ausschließlich nach außen verbindet und die Zugangsdaten auf dem eigenen Host des Kunden hält.