Vid övergÄngen frÄn en monolitisk applikation till en mikrotjÀnstarkitektur stöter vi pÄ nya problem.
I en monolitisk applikation Àr det vanligtvis tillrÀckligt att helt enkelt faststÀlla var felet intrÀffade i systemet. Troligen ligger problemet i monolitens kod eller i databasen. Men nÀr vi börjar söka efter problemet i mikrotjÀnstarkitekturen Àr det inte lÀngre sÄ uppenbart. Vi mÄste hitta hela vÀgen som en begÀran har tagit frÄn början till slut och sÀrskilja den frÄn hundratals mikrotjÀnster. MÄnga av dem har dessutom egna datalagringslösningar dÀr det ocksÄ kan uppstÄ bÄde logiska fel och problem med prestanda och tillförlitlighet.

Jag har lÀnge letat efter ett verktyg som skulle hjÀlpa till att hantera sÄdana problem (jag skrev om detta pÄ Habré: , ), men i slutÀndan gjorde jag en egen öppen kÀllkods-lösning. I artikeln berÀttar jag om fördelarna med service mesh-ansatsen och delar med mig av ett nytt verktyg för dess implementering.
Distribuerad spÄrning Àr en vanlig lösning pÄ problemet med att hitta fel i distribuerade system. Men vad hÀnder om ett sÄdant tillvÀgagÄngssÀtt för att samla in information om nÀtverksinteraktioner Ànnu inte har implementerats i systemet, eller, Ànnu vÀrre, om det fungerar bra i en del av systemet men inte i en annan, eftersom det inte har lagts till i de Àldre tjÀnsterna? För att bestÀmma den exakta grundorsaken till problemet mÄste vi ha en fullstÀndig bild av vad som hÀnder i systemet. Det Àr sÀrskilt viktigt att förstÄ vilka mikrotjÀnster som Àr involverade i de mest kritiska affÀrsvÀgarna.
HÀr kan service mesh-ansatsen hjÀlpa oss, som tar hand om hela maskineriet för att samla in nÀtverksinformation pÄ en nivÄ under dÀr sjÀlva tjÀnsterna arbetar. Denna ansats gör att vi kan avlyssna all trafik och analysera den i realtid. Dessutom behöver sjÀlva applikationerna inte ens veta nÄgot om det.
Service mesh-ansatsen
Huvudidén med service mesh-ansatsen Àr att lÀgga till Ànnu ett infrastrukturlager ovanför nÀtverket, vilket gör att vi kan hantera interaktionen mellan mikrotjÀnster. De flesta implementationer fungerar pÄ följande sÀtt: varje mikrotjÀnst fÄr en extra sidecar-container med en transparent proxy, genom vilken all inkommande och utgÄende trafik frÄn tjÀnsten passerar. Och det Àr just hÀr vi kan utföra klientsidebalansering, tillÀmpa sÀkerhetspolicyer, införa begrÀnsningar pÄ antalet förfrÄgningar och samla in viktig information om hur tjÀnster interagerar i produktion.

Lösningar
Det finns redan flera implementationer av detta tillvÀgagÄngssÀtt: och . De erbjuder mÄnga funktioner direkt ur boxen. Men samtidigt kommer det med en stor overhead av resurser. Ju större klustret dÀr ett sÄdant system körs, desto mer resurser krÀvs för att upprÀtthÄlla den nya infrastrukturen. PÄ Avito anvÀnder vi Kubernetes-kluster dÀr tusentals tjÀnsteexemplar finns (och antalet fortsÀtter att vÀxa snabbt). I den nuvarande implementationen anvÀnder Istio cirka 300 Mb RAM per tjÀnsteexemplar. PÄ grund av det stora antalet funktioner pÄverkar den transparenta balanseringen ocksÄ den totala svarstiden för tjÀnsterna (upp till 10 ms).
Till slut tittade vi pÄ vilka specifika funktioner vi behöver just nu och beslutade att en av de viktigaste anledningarna till att vi började implementera sÄdana lösningar var möjligheten att transparent samla in spÄrningsinformation frÄn hela systemet. Vi ville ocksÄ ha kontroll över tjÀnsternas interaktioner och kunna göra olika manipulationer med de rubriker som skickas mellan tjÀnster.
Som ett resultat kom vi fram till vÄr lösning: .
Netramesh
â en lĂ€ttviktig service mesh-lösning med möjlighet till oĂ€ndlig skalbarhet oavsett antal tjĂ€nster i systemet.
MÄlen med den nya lösningen var att ha lÄg resursöverhead och hög prestanda. Vi ville omedelbart ha möjligheten att transparent skicka spÄrningsspaningar till vÄrt Jaeger-system.
Idag implementeras de flesta molnlösningarna i Golang. Och det finns sjÀlvklart skÀl till detta. Att skriva nÀtverksapplikationer i Golang som arbetar asynkront med in- och utmatning och som kan skalas upp efter behov Àr bÄde bekvÀmt och ganska enkelt. Dessutom Àr prestandan tillrÀcklig för att lösa denna uppgift. DÀrför valde vi ocksÄ Golang.
Performance
Vi har fokuserat vÄra anstrÀngningar pÄ att uppnÄ maximal prestanda. För en lösning som implementeras nÀra varje tjÀnsteinstans krÀvs en liten mÀngd minne och processortid. SjÀlvklart bör Àven svarstiden vara sÄ kort som möjligt.
LÄt oss se vilka resultat vi har fÄtt.
RAM
Netramesh anvÀnder ~10Mb utan trafik och 50Mb maximalt under belastning upp till 10000 RPS pÄ en instans.
Istio envoy proxy anvÀnder alltid ~300Mb i vÄra kluster med tusentals instanser. Detta förhindrar att det kan skalas över hela klustret.


Med Netramesh har vi fÄtt en minskning av minnesanvÀndningen pÄ ~10 gÄnger.
CPU
CPU-anvÀndningen förblir relativt konstant under belastning. Den beror pÄ antalet förfrÄgningar per tidsenhet till sidecar. VÀrden vid 3000 förfrÄgningar per sekund i topparna:


Det finns en annan viktig punkt: Netramesh Àr en lösning utan control plane och utan belastning anvÀnder den ingen processortid. Med Istio uppdaterar sidecars alltid tjÀnsternas endpoints. Resultatet Àr att vi kan se följande situation utan belastning:

Vi anvĂ€nder HTTP/1 för interaktion mellan tjĂ€nster. Ăkningen av svarstiden hos Istio vid proxying genom envoy var upp till 5-10ms, vilket Ă€r ganska mycket för tjĂ€nster som Ă€r beredda att svara inom en millisekund. Med Netramesh minskade denna tid till 0,5-2ms.
Skalbarhet
Den lilla mÀngd resurser som varje proxy anvÀnder gör det möjligt att placera den nÀra varje tjÀnst. Netramesh skapades medvetet utan en control plane-komponent för att enkelt upprÀtthÄlla lÀttvikten av varje sidecar. Ofta i service mesh-lösningar sprider control plane service discovery-information till varje sidecar. Med den kommer ocksÄ information om timeouter och balansinstÀllningar. Allt detta gör det möjligt att utföra mÄnga anvÀndbara saker, men tyvÀrr ökar det storleken pÄ sidecars.
TjÀnstupptÀckning

Netramesh lÀgger inte till nÄgra extra mekanismer för service discovery. All trafik proxyas transparent genom netra sidecar.
Netramesh stödjer HTTP/1 applikationsprotokollet. För att definiera det anvÀnds en konfigurerbar lista över portar. Vanligtvis finns det flera portar i systemet som anvÀnds för HTTP-interaktion. Till exempel anvÀnder vi 80, 8890, 8080 för interaktion mellan tjÀnster och externa förfrÄgningar. I sÄ fall kan de anges med hjÀlp av en miljövariabel. NETRA_HTTP_PORTS.
Om du anvÀnder Kubernetes som orkestrator och dess Service-mekanismer för interna klusterinteraktioner mellan tjÀnster, förblir mekanismen densamma. Först fÄr mikrotjÀnsten service IP-adressen via kube-dns och öppnar en ny anslutning till den. Denna anslutning etableras först med den lokala netra-sidecaren och alla TCP-paket kommer först till netra. DÀrefter etablerar netra-sidecaren anslutningen till den ursprungliga destinationen. NAT pÄ pod IP pÄ noden förblir densamma som utan netra.
Distribuerad spÄrning och vidarebefordran av kontext
Netramesh tillhandahÄller funktionalitet som behövs för att skicka spÄrningsspann om HTTP-interaktion. Netra-sidecaren parsar HTTP-protokollet, mÀter fördröjningar pÄ förfrÄgningar och hÀmtar nödvÀndig information frÄn HTTP-headers. I slutÀndan fÄr vi alla spÄr i ett enhetligt Jaeger-system. För detaljerad konfiguration kan miljövariablerna som tillhandahÄlls av det officiella biblioteket ocksÄ anvÀndas. .


Men det finns ett problem. SÄ lÀnge tjÀnsterna inte genererar och vidarebefordrar en sÀrskild uber-header, kommer vi inte att se de sammanlÀnkade spÄrningsspann i systemet. Och det Àr vad vi behöver för att snabbt identifiera problemens orsaker. HÀr har Netramesh Äterigen en lösning. Proxy lÀser HTTP-headers och, om de inte innehÄller uber trace id, genererar den. Netramesh lagrar ocksÄ information om inkommande och utgÄende förfrÄgningar i sidecaren och matchar dem genom att berika dem med nödvÀndiga headers för utgÄende förfrÄgningar. Allt som behövs i tjÀnsterna Àr att vidarebefordra en enda header. X-Request-Id, som kan konfigureras med hjÀlp av en miljövariabel. NETRA_HTTP_REQUEST_ID_HEADER_NAME. För att hantera storleken pÄ kontexten i Netramesh kan följande miljövariabler stÀllas in: NETRA_TRACING_CONTEXT_EXPIRATION_MILLISECONDS (tiden under vilken kontexten kommer att lagras) och NETRA_TRACING_CONTEXT_CLEANUP_INTERVAL (frekvensen för stÀdning av kontext).
Det Àr ocksÄ möjligt att kombinera flera vÀgar i ditt system genom att mÀrka dem med en speciell sessionsmarkör. Netra tillÄter att stÀlla in HTTP_HEADER_TAG_MAP för att omvandla HTTP-rubriker till motsvarande tracing span-taggar. Detta kan vara sÀrskilt anvÀndbart för testning. Efter att ha genomfört ett funktionellt test kan du se vilken del av systemet som pÄverkades genom att filtrera med hjÀlp av den aktuella sessionsnyckeln.
Identifiering av förfrÄgningskÀlla
För att avgöra varifrÄn förfrÄgan kom kan funktionaliteten för automatisk tillÀggning av rubrik med kÀlla anvÀndas. Med hjÀlp av miljövariabeln NETRA_HTTP_X_SOURCE_HEADER_NAME kan du ange namnet pÄ rubriken som automatiskt kommer att stÀllas in. Med NETRA_HTTP_X_SOURCE_VALUE kan du stÀlla in vÀrdet som X-Source-rubriken ska ha för alla utgÄende förfrÄgningar.
Detta gör det möjligt att enhetligt sprida denna anvÀndbara rubrik över hela nÀtverket. Du kan sedan anvÀnda den i tjÀnster och lÀgga till den i loggar och metrik.
Ruttning av trafik och intern funktionalitet i Netramesh
Netramesh bestÄr av tvÄ huvudkomponenter. Den första, netra-init, stÀller in nÀtverksregler för att fÄnga trafik. Den anvÀnder för att fÄnga all eller delar av trafiken till sidecar, som Àr den andra huvudkomponenten i Netramesh. Du kan konfigurera vilka portar som ska fÄngas för inkommande och utgÄende TCP-sessioner: INBOUND_INTERCEPT_PORTS, OUTBOUND_INTERCEPT_PORTS.
Verktyget har ocksĂ„ en intressant funktion â sannolikhetsbaserad ruttning. Om du anvĂ€nder Netramesh enbart för att samla tracing spans kan du spara resurser i produktionsmiljön och aktivera sannolikhetsbaserad ruttning med hjĂ€lp av variablerna NETRA_INBOUND_PROBABILITY och NETRA_OUTBOUND_PROBABILITY (frĂ„n 0 till 1). StandardvĂ€rdet Ă€r 1 (all trafik fĂ„ngas).
Efter en lyckad fÄngst tar netra sidecar emot en ny anslutning och anvÀnder SO_ORIGINAL_DST socket-alternativet för att fÄ den ursprungliga destinationspunkten. Sedan öppnar Netra en ny anslutning till den ursprungliga IP-adressen och upprÀttar en tvÄvÀgs TCP-kommunikation mellan parterna, och lyssnar pÄ all passerande trafik. Om porten Àr definierad som HTTP, försöker Netra att parsa den och spÄra. Om HTTP-parsing inte Àr framgÄngsrik, faller Netra tillbaka pÄ TCP och transparent proxyar byte.
Byggande av beroendegraf
Efter att ha fÄtt en stor mÀngd tracinginformation i Jaeger vill man gÀrna fÄ en fullstÀndig graf över interaktionerna i systemet. Men om ditt system Àr tillrÀckligt belastat och det samlas miljarder tracing spans under en dag, blir det inte sÄ enkelt att aggreggera dem. Det finns ett officiellt sÀtt att göra detta: . Det kommer dock att ta timmar att bygga en fullstÀndig graf och tvinga dig att ladda ner hela datasetet frÄn Jaeger för de senaste 24 timmarna.
Om du anvÀnder Elasticsearch för att lagra tracing spans kan du anvÀnda en , som bygger en liknande graf pÄ nÄgra minuter, genom att anvÀnda funktionerna och möjligheterna i Elasticsearch.

Hur man anvÀnder Netramesh
Netra kan enkelt lÀggas till i vilken tjÀnst som helst som körs under nÄgon orkestratör. Du kan se ett exempel .
För tillfÀllet har Netra ingen möjlighet till automatisk implementering av sidecar i tjÀnster, men det finns planer pÄ att genomföra detta.
Framtiden för Netramesh
HuvudmÄlet Àr att uppnÄ minimala resurskostnader och hög prestanda, samtidigt som man erbjuder grundlÀggande funktioner för observabilitet och kontroll över mellanserviceinteraktioner.
I framtiden kommer Netramesh att fÄ stöd för andra applikationsprotokoll utöver HTTP. Snart kommer möjligheten för L7-routing att finnas.
AnvÀnd Netramesh om du stöter pÄ liknande problem, och skicka oss dina frÄgor och förslag.
KĂ€lla: habr.com
