Naar de inhoud

Blog ·

Hoe een agent de juiste edge-server kiest

Meerdere A-records, edges op laag vier en PROXY protocol v2, met de metingen: wat een client met meerdere adressen doet, en waarom terugkeren het zware deel is.

Door Provibr

Draai je een agent op de hardware van je klanten, dan staat het adres dat hij belt op twee ongemakkelijke plekken: in het publieke DNS, en in welk gegevensbestand je bij de aanmelding ook hebt uitgedeeld. Allebei zijn ze achteraf lastig te wijzigen, en één ervan vertelt het hele internet waar je control plane staat.

Wij hebben er een laag edge-servers voor gezet. Dit is wat dat kostte, inclusief de delen die pas naar boven kwamen toen er echte agents doorheen liepen.

Het probleem is het adres, niet het verkeer

De hostnaam van de gateway wees naar de oorsprong. Elke aangemelde agent kende hem, en iedereen die dig kon typen ook. Een volumetrische stortvloed op dat adres haalt het control plane voor iedereen onderuit, en achteraf valt er niets slims meer te doen.

Het doel was smal, en het uitspreken ervan houdt het ontwerp eerlijk: het adres van de oorsprong verdwijnt uit het pad dat agents gebruiken, zodat een stortvloed een machine raakt die in tien minuten vervangen kan worden. Het was uitdrukkelijk geen doel dat de edges de inhoud zouden beschermen. Wederzijdse TLS doet dat al van begin tot eind, en een edge die het verkeer kon inspecteren zou een verslechtering zijn.

Laag vier, want TLS afsluiten zou breken waar het om gaat

Het voor de hand liggende ontwerp is een reverse proxy die TLS aan de edge afsluit. Dat is hier het verkeerde ontwerp, om twee redenen die eigen zijn aan dit soort systemen.

De gateway authenticeert agents op hun clientcertificaat, vastgezet tegen een vingerafdruk die per agent is opgeslagen. TLS aan de edge afsluiten beëindigt de wederzijdse handshake daar, dus de oorsprong zou moeten vertrouwen op wat de edge beweert. En het zou betekenen dat de privésleutel van de gateway naar meerdere goedkope virtuele machines wordt gekopieerd, waarvan het hele bestaansrecht is dat ze wegwerpbaar zijn.

Elke edge is dus een gewone TCP-doorstuurder. Hij ziet cijfertekst, houdt geen geheimen vast en draait geen applicatie, en dat is een klein genoeg oppervlak om met "vervang hem" de meeste problemen te beantwoorden.

Het kost één ding. Elke verbinding lijkt nu van de edge te komen, dus de oorsprong weet niet meer wie er belt.

PROXY protocol v2, en het verschil tussen een bewering en een meting

De standaardoplossing is PROXY protocol: de doorstuurder zet vóór elke verbinding een kleine binaire header met het oorspronkelijke adres. De gateway ontleedt hem en zet het echte adres terug in zijn logregels.

De ontwerpbeslissing die het kopiëren waard is, is om twee waarden aan te houden in plaats van één.

  • Het adres uit de header is wat de andere kant zegt dat de client is. Dat is een bewering.
  • Het adres van de buitenste socket, de machine waar de bytes werkelijk vandaan kwamen, is iets dat we zelf gemeten hebben.

Een vervalste header kan de eerste laten liegen. De tweede is nog steeds waar. Ze als aparte velden aanhouden betekent dat een logregel jaren later leesbaar is zonder dat je je afvraagt welke van de twee het is, en het maakt het eerlijke antwoord op "kunnen we het IP vertrouwen" makkelijk: er hangt niets van af behalve een logregel, want de identiteit hangt aan het certificaat van de tegenpartij.

Twee uitvoeringsnotities die ons ellende hebben bespaard:

Snuffelen, niet eisen. Een overstap naar PROXY protocol is niet atomair. Zien de eerste bytes eruit als een TLS ClientHello in plaats van als de handtekening van het protocol, duw ze dan terug op de stroom en stuur de verbinding onaangeroerd door. Daardoor zijn het uitrollen van de ontleder en het aanzetten ervan op de doorstuurders twee losse stappen.

Houd een tabel bij op de binnenste verbinding. Zodra de bytes bij een gRPC-bibliotheek aankomen is het adres van de tegenpartij een loopbackpoort, en zijn zowel de agent als de edge onvindbaar geworden. De echte tegenpartij bijhouden op de bronpoort van de interne verbinding, en hem wissen bij het sluiten, is de enige plek waar die informatie overleeft.

Er zit ook nog een kleine operationele winst in verstopt: een verbinding die opent en sluit zonder iets te sturen levert helemaal geen logregel op. Gezondheidscontroles doen dat voortdurend, en ze niet wegschrijven haalde tienduizenden nutteloze regels per dag weg.

Ontdekking: de A-verzameling is de lijst

Hoe komt de agent te weten dat er meer dan één edge is? De opties waren TXT-records met een gestructureerde inhoud, SRV-records, of gewoon meerdere A-records op dezelfde naam.

Meerdere A-records wonnen op één argument. De A-verzameling is al de lijst met plekken om mee te verbinden. Een TXT-record zou een tweede bron van waarheid voor hetzelfde feit zijn, en die twee kunnen het oneens worden. SRV zou gewichten toevoegen die we niet nodig hebben, want de agent meet zelf in plaats van te horen welke hij moet verkiezen. De TTL is kort, dus een edge weghalen is een DNS-wijziging plus één TTL geduld.

Wat een client werkelijk doet met meerdere A-records

Voordat we ook maar enige keuzelogica bouwden, was het de moeite waard om te meten wat er gratis gebeurt. Een moderne HTTP-client probeert de adressen in de recordverzameling en verdeelt zijn verbindingstimeout erover. We zetten een adres waarvan we wisten dat het dood was vooraan en maten:

Gedrag van het dode adresTijd tot verbinding met het levende
Verbinding geweigerd7 ms
Geen route naar host7,5 ms
Stil weggegooide SYN, verbindingstimeout 25 s12,5 s
Stil weggegooide SYN, verbindingstimeout 10 s5,0 s

Het patroon is duidelijk genoeg om op te ontwerpen: harde afwijzingen zijn gratis, en een zwart gat kost ruwweg de verbindingstimeout gedeeld door het aantal adressen. Uitwijken werkte daarmee al zonder enige wijziging aan de agent, dus de DNS-omzetting kon eerder live dan de keuzelogica. Het betekent ook dat je verbindingstimeout nu een functie is van hoeveel records je publiceert, en dat is niet vanzelfsprekend tot je deze tabel voor je hebt liggen.

Uitwijken is niet kiezen

Gratis uitwijken kiest het adres dat als eerste antwoordt, en dat is niet hetzelfde als het beste. Een agent in Amsterdam zat dagenlang op een edge in een ander land, omdat die als eerste antwoordde en de sessie daarna een week bleef leven.

Dus meet de agent. De details die ertoe deden:

Sondeer met een volledige TLS-handshake, niet met een TCP-verbinding. Een edge kan een verbinding aannemen en niets doorsturen, en dat is nu juist de storing die de sonde moet vangen. Een kale verbinding wordt door een kapotte doorstuurder net zo opgewekt beantwoord als door een werkende.

Geef elke sonde zijn eigen budget, parallel. Een edge die aanneemt en daarna zwijgt kan een handshake voorbij de verbindingstimeout laten hangen, dus het budget per adres staat ernaast in plaats van erop te leunen.

Bewaar de hele rangschikking, niet de winnaar. Het gekozen adres en de geordende lijst gaan het toestandsbestand van de agent in, samen met de adresverzameling waaruit ze gemeten zijn. Dat maakt een herstart goedkoop, en het betekent dat een uitwijking een herstart overleeft in plaats van opgewekt terug te keren naar het adres dat zojuist faalde.

Laat de meting vervallen op de verzameling, niet op de keuze. Wordt er een edge toegevoegd of weggehaald, dan is de meting verouderd, ook als het gekozen adres er nog in zit.

Doe niets als er niets te kiezen valt. Bij minder dan twee kandidaten wordt het hele mechanisme overgeslagen, en dat is wat er in ontwikkeling gebeurt, waar de naam naar één adres oplost.

En de regel die dit alles bestuurt: kiezen mag nooit een nieuwe storingsvorm worden. DNS weigert, niets antwoordt, het toestandsbestand kan niet geschreven worden: al die paden eindigen in "geen keuze", en de agent verbindt precies zoals hij deed voordat hier iets van bestond.

Terugkomen is lastiger dan uitwijken

Uitwijken is makkelijk. Het zware deel is dat de agent daarna op de tragere edge blijft zitten zolang die sessie leeft, en dat kunnen weken zijn.

Dus loopt er een wacht naast de levende sessie mee, die twee dingen doet en verder niets. Hij probeert de adressen die boven het huidige gerangschikt staan, met dezelfde volledige handshake, en hij hermeet alles zodra de laatste meting oud is. De getallen, en de reden per stuk:

  • Een tussenpoos van vijf minuten met volledige jitter, zodat sondes van een vloot niet gaan samenlopen. Elke minuut kost vijf keer zoveel en levert niets op, want de hysterese hieronder maakt elke terugkeer toch al een zaak van twee ronden.
  • Twee geslaagde sondes op rij voordat er wordt teruggeschakeld. Eén geslaagde sonde is een edge die net klaar is met opnieuw opstarten.
  • Twee drempels voor de periodieke hermeting: een absolute van 20 ms en een relatieve van 25%. Aan allebei moet voldaan zijn. De absolute alleen zou een verhuizing van 100 ms naar 79 ms goedkeuren; de relatieve alleen zou 4 ms naar 3 ms goedkeuren. Twee edges in hetzelfde datacenter schelen minder dan een milliseconde, en zonder dit zouden ze om de dag van plaats wisselen.

Omschakelen is een schone herverbinding met een eigen reden, die naast de andere geplande beëindigingen van een sessie staat in plaats van als een fout gemodelleerd te worden. Dat doet er meer toe dan het klinkt: een fout zou de exponentiële uitstelladder voeden, dus een agent die terugkeert naar zijn voorkeursedge zou met een groeiende vertraging gestraft worden voor het goede gedrag. De uitgaande berichtenwachtrij wordt opnieuw afgespeeld en de ontdubbelopslag zorgt dat er niets twee keer wordt uitgevoerd.

Meekijken vanaf de andere kant

Het platform kijkt vanaf zijn kant naar hetzelfde, en twee details daar zijn het stelen waard.

Los de naam op zoals de agent dat doet, via de resolver van het systeem in plaats van met een rechtstreekse DNS-vraag. Een rechtstreekse vraag mislukte volledig op een machine waarvan de resolver naar zichzelf wees, en belangrijker nog: de resolver van het systeem is wat het echte verkeer gebruikt, dus hij geeft dezelfde lijst terug.

Haal twee getallen uit één socket: de TCP-verbinding, wat de rondgang naar de edge zelf is, en de volledige TLS-handshake, die door de tunnel naar de oorsprong en terug moet reizen. De gezondheid hangt aan het tweede. Een doorstuurder met een dode achterkant beantwoordt het eerste feilloos.

De laatste les gaat over een firewall. Docker gaat volledig langs de firewall van de host heen. De regels dat alleen edges bij de poorten van de gateway mogen komen, moeten in de keten staan die Docker zelf raadpleegt, anders zijn ze decoratief terwijl ze er actief uitzien. En die keten ziet verkeer in beide richtingen, dus onze eigen uitgaande sondes van de oorsprong naar de edges kwamen overeen met dezelfde bestemmingspoort en werden weggegooid, waardoor de monitor elke edge als onbereikbaar meldde terwijl er met elke agent niets aan de hand was.

Wat het niet oplost

De edges beschermen de inhoud niet, en het zou een fout zijn om ze als beveiligingsgrens te gaan behandelen. Een adres dat tijdens een volledige meting onbereikbaar was, wordt pas door de volgende meting weer ontdekt, een bekend en aanvaard gat. En de TTL in het DNS is een ondergrens voor hoe snel een edge uit de rotatie verdwijnt, en dat is de prijs van de A-verzameling als lijst.

Zo bereikt de agent het platform bij Provibr, waar hij uitsluitend uitgaand verbindt en de inloggegevens op de eigen host van de klant houdt.

Begin met één host en één agent

Maak een account aan, installeer de agent met één commando en koppel je eerste Proxmox-host. De proefperiode loopt 14 dagen en vraagt geen betaalgegevens.