
TL;DR: Beskrivning av klient-server-arkitekturen för vÄrt interna system för nÀtverkskonfiguration, QControl. Grunden bestÄr av ett tvÄnivÄtransportprotokoll som arbetar med gzip-paketerade meddelanden utan dekomprimering mellan Àndpunkterna. Distribuerade routrar och Àndpunkter fÄr konfigurationsuppdateringar, och sjÀlva protokollet möjliggör installation av lokalt anpassade mellanliggande relay. Systemet Àr byggt pÄ principen ('recent-stable', förklaras nedan) och anvÀnder JMESpath frÄgesprÄk tillsammans med Jinja-mallar för rendering av konfigurationsfiler.
Qrator Labs hanterar ett globalt distribuerat nĂ€tverk för attackneutralisering. VĂ„rt nĂ€tverk arbetar pĂ„ basis av anycast, och subnett annonseras via BGP. Som ett BGP anycast-nĂ€tverk, som fysiskt Ă€r belĂ€get i flera regioner pĂ„ jorden, kan vi bearbeta och filtrera illegitim trafik nĂ€rmare internets kĂ€rna â Tier-1 operatörer.
à andra sidan Àr det inte enkelt att vara ett geografiskt distribuerat nÀtverk. Kommunikationen mellan nÀtverkspunkter Àr kritiskt viktig för sÀkerhetstjÀnsteleverantören, för att upprÀtthÄlla en konsekvent konfiguration av alla nÀtverksnoder, och att uppdatera dem i rÀtt tid. SÄledes, för att erbjuda den maximala möjliga nivÄn av grundlÀggande tjÀnster för konsumenten, var vi tvungna att hitta ett sÀtt att pÄ ett pÄlitligt sÀtt synkronisera konfigurationsdata mellan kontinenter.
I begynnelsen var Ordet. Det blev snabbt ett kommunikationsprotokoll som behövde en uppdatering.
Grunden till QControl:s existens och den primĂ€ra anledningen till att betydande tid och resurser har lagts pĂ„ att bygga denna typ av protokoll Ă€r behovet av att fĂ„ en enhetlig och auktoritativ konfigurationskĂ€lla, och i slutĂ€ndan synkronisera vĂ„ra nĂ€rvaro-punkter med den. SjĂ€lva lagringen var bara ett av flera krav under utvecklingen av QControl. Utöver detta behövde vi Ă€ven integrationer med befintliga och planerade tjĂ€nster pĂ„ nĂ€rvaro-punkterna (TP), smarta (och anpassningsbara) sĂ€tt att validera data, samt Ă„tkomstkontroll. Vi ville ocksĂ„ kunna hantera ett sĂ„dant system via kommandon, snarare Ă€n att göra Ă€ndringar i filer. Före QControl skickades data till nĂ€rvaro-punkterna praktiskt taget manuellt. Om en av nĂ€rvaro-punkterna var otillgĂ€nglig och vi glömde att uppdatera den senare, blev konfigurationen osynkroniserad â vi tvingades lĂ€gga tid pĂ„ att fĂ„ den i fungerande skick igen.
Resultatet blev att vi kom pÄ följande schema:

Konfigurationsservern ansvarar för validering av data och lagring, routern har flera slutpunkter som tar emot och överför konfigurationsuppdateringar frÄn klienter och supportteam till servern, och frÄn servern till nÀrvaro-punkterna.
Kvaliteten pĂ„ internetanslutningen varierar fortfarande kraftigt i olika delar av vĂ€rlden â för att illustrera detta pĂ„stĂ„ende, lĂ„t oss titta pĂ„ en enkel MTR frĂ„n Prag, Tjeckien till Singapore och Hongkong.
MTR frÄn Prag till Singapore
Samma sak till Hongkong
Höga fördröjningar innebÀr lÀgre hastighet. Dessutom finns det paketförluster. Bandbredden kompenserar inte för detta problem, vilket alltid bör beaktas vid byggandet av decentraliserade system.
Den fullstÀndiga konfigurationen av en nÀrvaro-punkt Àr en betydande mÀngd data som mÄste överföras till mÄnga mottagare över opÄlitliga anslutningar. Lyckligtvis, Àven om konfigurationen stÀndigt förÀndras, görs det i smÄ portioner.
Design recent-stable
Man kan sÀga att konstruktionen av ett distribuerat nÀtverk baserat pÄ inkrementella uppdateringar Àr en ganska uppenbar lösning. Men det finns mÄnga problem kopplade till differenserna. Vi mÄste spara alla differenser mellan referenspunkterna, samt kunna skicka dem pÄ nytt om nÄgon har missat en del av data. Varje destinationspunkt mÄste tillÀmpa dem i en strikt definierad sekvens. Vanligtvis kan en liknande operation ta lÄng tid i fall av flera destinationspunkter. Mottagaren mÄste ocksÄ kunna begÀra de saknade delarna och sjÀlvklart mÄste huvuddelen svara korrekt pÄ en sÄdan begÀran genom att skicka endast de missade datadelarna.
Till slut kom vi fram till en ganska intressant lösning â vi har bara ett referenslager, som Ă€r fast, lĂ„t oss kalla det stable, och endast en diff för det â recent. Varje recent baseras pĂ„ den senaste skapade stable och Ă€r tillrĂ€cklig för att rekonstruera konfigurationsdata. SĂ„ snart en fĂ€rsk recent nĂ„r sin destination, behövs den gamla inte lĂ€ngre.
Det Ă„terstĂ„r bara att dĂ„ och dĂ„ skicka den senaste stable-konfigurationen, till exempel eftersom recent har blivit för stor. Det viktiga hĂ€r Ă€r ocksĂ„ att vi sĂ€nder alla dessa uppdateringar i broadcast/multicast-lĂ€ge, utan att oroa oss för enskilda mottagare och deras förmĂ„ga att samla ihop datadelarna. SĂ„ snart vi har bekrĂ€ftat att alla har en korrekt stable â skickar vi endast nya recent. Ska jag förtydliga att detta fungerar? Det fungerar. Stable cachas pĂ„ konfigurationsservern och mottagarna, recent skapas efter behov.
Arkitektur för tvÄnivÄtransport
Varför byggde vi vĂ„r transport pĂ„ tvĂ„ nivĂ„er? Svaret Ă€r ganska enkelt â vi ville separera routingen frĂ„n högre nivĂ„ers logik, inspirerade av OSI-modellen med dess transportnivĂ„ och applikationsnivĂ„. Som transportprotokoll valde vi Thrift, och för det högre nivĂ„formatet av styrmeddelanden â msgpack serialiseringsformat. Det Ă€r dĂ€rför routern (som hanterar multicast/broadcast/relais) inte tittar inuti msgpack, inte packar upp eller packar innehĂ„llet tillbaka och endast utför datatransfer.
Thrift (frÄn engelskan 'thrift', uttalas som [Ξrift]) Àr ett grÀnssnittsbeskrivningssprÄk som anvÀnds för att definiera och skapa tjÀnster för olika programmeringssprÄk. Det Àr ett ramverk för fjÀrrproceduranrop (RPC). Det kombinerar en programpipeline med en kodgenereringsmotor för att utveckla tjÀnster som fungerar effektivt och enkelt mellan olika sprÄk.
Vi valde Thrift-ramverket pÄ grund av RPC och stödet för mÄnga sprÄk. Som vanligt blev klienten och servern de enkla delarna. Dock visade sig routern vara en hÄrd nöt, delvis pÄ grund av bristen pÄ en fÀrdig lösning under vÄr utveckling.
Det finns andra alternativ, som protobuf / gRPC, men nÀr vi började vÄrt projekt var gRPC fortfarande ganska ungt och vi kÀnde oss osÀkra pÄ att ta det ombord.
SjÀlvklart hade vi kunnat (och faktiskt borde ha) skapa vÄr egen cykel. Det skulle ha varit enklare att skapa ett protokoll för vÄra behov, eftersom klient-server-arkitekturen Àr relativt rak i sin implementering jÀmfört med att bygga en router med Thrift. Hur som helst, det finns en traditionell partiskhet mot egenutvecklade protokoll och implementationer av populÀra bibliotek (inte utan anledning), och det uppstÄr alltid frÄgan: "Hur kommer vi att portera detta till andra sprÄk?" DÀrför slÀppte vi omedelbart idéerna om cykeln.
Msgpack Àr ett alternativ till JSON, men snabbare och mindre. Det Àr ett binÀrt dataserialiseringsformat som möjliggör datautbyte mellan flera sprÄk.
PÄ första nivÄn har vi Thrift med minimum nödvÀndig information för routern för att skicka meddelandet. PÄ den andra nivÄn finns de packade strukturerna i msgpack.
Vi valde msgpack eftersom det Àr snabbare och mer kompakt jÀmfört med JSON. Men Ànnu viktigare Àr att det stöder anpassade datatyper, vilket gör att vi kan anvÀnda hÀftiga funktioner som att skicka rÄa binÀrdata eller speciella objekt som representerar avsaknaden av data, vilket var viktigt för vÄrt schema "recent-stable".
JMESPath
JMESPath Àr ett frÄgesprÄk för JSON.
Det hĂ€r Ă€r exakt hur beskrivningen ser ut som vi fĂ„r frĂ„n den officiella dokumentationen för JMESPath, men i verkligheten ger den mycket mer. JMESPath möjliggör sökning och filtrering av undertrĂ€d i vilken trĂ€dstruktur som helst samt att göra förĂ€ndringar i data i realtid. Dessutom tillĂ„ter den att lĂ€gga till speciella filter och datakonverteringsprocedurer. Ăven om det naturligtvis krĂ€ver en hel del tankekraft för att förstĂ„.
Jinja
För vissa anvĂ€ndare behöver vi omvandla konfigurationen till en fil â dĂ€rför anvĂ€nder vi en mallmotor och Jinja Ă€r ett sjĂ€lvklart val. Med den genererar vi konfigurationsfilen frĂ„n en mall och data som erhĂ„llits pĂ„ destinationspunkten.
För att generera konfigurationsfilen behöver vi en JMESPath-frĂ„ga, en mall för platsen för filen i filsystemet, och en mall för sjĂ€lva konfigurationen. Det Ă€r ocksĂ„ bra att precisera filens Ă„tkomstrĂ€ttigheter i detta skede. Allt detta har vi lyckats kombinera snyggt i en fil â före början av konfigurationsmallen lĂ€gger vi en rubrik i YAML-format som beskriver resten.
Till exempel:
---
vÀljare: "[@][?@.fft._meta.version == `42`] | items([0].fft_config || `{}`)"
destination_filename: "fft/{{ match[0] }}.json"
fil_lÀge: 0644
ladda_om_daemons: [fft]
...
{{ dict(match[1]) | json(indent=2, sort_keys=True) }}
För att skapa en konfigurationsfil för den nya tjÀnsten lÀgger vi bara till en ny mallen. Inga Àndringar krÀvs i koden eller programvaran pÄ nÀrvaropunkterna.
Vad har förÀndrats efter att QControl infördes i den operativa verksamheten? Det första och viktigaste Àr en konsekvent och pÄlitlig leverans av konfigurationsuppdateringar över alla noder i nÀtverket. Det andra Àr att vÄrt supportteam och tjÀnstekonsumenter fÄr ett kraftfullt verktyg för att kontrollera konfigurationen och göra Àndringar.
Allt detta har vi lyckats göra genom att anvÀnda uppdateringsschemat recent-stable för att förenkla kommunikationen mellan konfiguratorn och mottagarna av konfigurationen. Genom att anvÀnda ett tvÄ nivÄers protokoll för att stödja en oberoende av innehÄll metod för dataroutning. Vi har framgÄngsrikt integrerat en Jinja-baserad konfigurationsgenereringsmotor i ett distribuerat filtreringsnÀtverk. Detta system stöder ett brett utbud av konfigurationsmetoder för vÄr distribuerade och heterogena periferin.
Tack för hjÀlpen med att skriva materialet , , .
inlÀgget.
KĂ€lla: habr.com
