Containrar, mikrotjänster och service-meshar

På internet massor av artiklar bästa resultatet (subjektiv åsikt). tjänst-meshar (service mesh), och här är en till. Hurrra! Men varför? För att jag vill uttrycka min åsikt att det hade varit bättre om service-meshar hade dykt upp för 10 år sedan, innan containerplattformar som Docker och Kubernetes kom. Jag hävdar inte att min åsikt är bättre eller sämre än andra, men eftersom service-meshar är ganska komplexa fenomen, kommer en mångfald av perspektiv att hjälpa till att förstå dem bättre.

Jag kommer att berätta om plattformen dotCloud, som byggdes på mer än hundra mikrotjänster och stödde tusentals applikationer i containrar. Jag kommer att förklara de problem vi stötte på under utvecklingen och lanseringen, och hur service-meshar kunde ha hjälpt (eller inte hjälpt).

Historien om dotCloud

Jag har redan skrivit om historien bakom dotCloud och valet av arkitektur för denna plattform, men jag har berättat lite om nätverksnivån. Om du inte vill dyka ner i läsningen tidigare artikel om dotCloud, så här är kärnan: det är en plattform-som-en-tjänst PaaS, som låter kunder köra ett brett spektrum av applikationer (Java, PHP, Python…), med stöd för ett brett spektrum av datatjänster (MongoDB, MySQL, Redis…) och arbetsflöde som Heroku: du laddar upp din kod till plattformen, den bygger containerbilder och distribuerar dem.

Jag kommer att berätta hur trafiken dirigerades till plattformen dotCloud. Inte för att det var särskilt imponerande (även om systemet fungerade ganska bra för sin tid!), utan framför allt för att med moderna verktyg kan en sådan design enkelt genomföras på kort tid av ett blygsamt team, om de behöver en lösning för att dirigera trafik mellan en mängd mikrotjänster eller applikationer. På så sätt kan man jämföra alternativ: vad får man om man utvecklar allt själva eller använder en befintlig service-mesh. Standardvalet: göra själv eller köpa.

Trafikdirigering för hosted-applikationer

Applikationer på dotCloud kan erbjuda HTTP- och TCP-slutpunkter.

HTTP-slutpunkter läggs dynamiskt till i konfigurationen av lastbalanserarkluster Hipache. Det liknar vad resurser gör idag Ingress i Kubernetes och lastbalanserare som Traefik.

Kunder ansluter till HTTP-slutpunkter via motsvarande domäner under förutsättning att domännamnet pekar på dotClouds lastbalanserare. Inget särskilt.

TCP-slutpunkter är kopplade till portnumret, som sedan överförs till alla containrar inom denna stapel via miljövariabler.

Kunder kan ansluta till TCP-slutpunkter med hjälp av motsvarande värdnamn (något i stil med gateway-X.dotcloud.com) och portnummer.

Detta värdnamn löses upp till en klustertjänst "nats" (har inget att göra med NATS), som kommer att dirigera inkommande TCP-anslutningar till rätt container (eller, i fallet med tjänster med lastbalansering, till rätt containrar).

Om du är bekant med Kubernetes kommer det troligen att påminna dig om tjänster NodePort.

Det fanns inget motsvarande för tjänster på dotCloud-plattformen ClusterIP: för enkelhetens skull skedde åtkomsten till tjänster på samma sätt både inifrån och utanför plattformen.

Allt var organiserat ganska enkelt: de ursprungliga implementationerna av HTTP- och TCP-routingnätverk, sannolikt bara några hundra rader Python. Enkla (jag skulle säga naiva) algoritmer som förbättrades i takt med plattformens tillväxt och framväxten av ytterligare krav.

Omfattande omstrukturering av den befintliga koden var inte nödvändig. I synnerhet 12-faktor-appar kan direkt använda adressen som erhållits via miljövariabler.

Hur skiljer detta sig från moderna service-mesh-system?

Begränsad observabilitet. Vi hade överhuvudtaget inga mätverktyg för TCP-routingnätet. När det gäller HTTP-routing i senare versioner introducerades detaljerade HTTP-mätvärden med felkoder och svarstider, men moderna service-mesh-system går ännu längre genom att erbjuda integration med system för mätinsamling, som Prometheus, till exempel.

Observabilitet är viktigt inte bara ur ett operativt perspektiv (för att hjälpa till med felsökning), utan också vid lanseringen av nya funktioner. Det handlar om säker blå-grön distribution och kanariedistribution.

Routingens effektivitet är också begränsad. I dotClouds routingnätverk måste all trafik passera genom en kluster av dedikerade routingnoder. Det innebar potentiell korsning av flera AZ-gränser (tillgänglighetszoner) och en avsevärt ökad fördröjning. Jag minns att jag felsökte kod som gjorde mer än hundra SQL-frågor per sida och för varje fråga öppnade en ny anslutning till SQL-servern. Vid lokal körning laddas sidan omedelbart, men i dotCloud tar det flera sekunder att ladda, eftersom varje TCP-anslutning (och efterföljande SQL-fråga) tar flera tiodelar av en millisekund. I detta specifika fall löstes problemet med permanenta anslutningar.

Moderna servicemeshar hanterar sådana problem bättre. Först och främst kontrollerar de att anslutningarna dirigeras till källan. Den logiska strömmen är densamma: klient → mesh → service, men nu arbetar mesh lokalt istället för på avlägsna noder, så anslutningen klient → mesh är lokal och mycket snabb (mikroskunder istället för millisekunder).

Moderna servicemeshar implementerar också smartare lastbalanseringsalgoritmer. Genom att övervaka backendens hälsa kan de skicka mer trafik till snabbare backends, vilket resulterar i ökad total prestanda.

Säkerhet är också bättre. DotClouds routingnätverk fungerade helt på EC2 Classic och krypterade inte trafiken (baserat på antagandet att om någon lyckas installera en sniffer på EC2:s nätverkstrafik har du redan stora problem). Moderna servicemeshar skyddar transparent hela vår trafik, till exempel med ömsesidig TLS-autentisering och efterföljande kryptering.

Routing av trafik för plattformstjänster

Okej, vi har diskuterat trafiken mellan applikationerna, men hur är det med själva dotCloud-plattformen?

Själva plattformen bestod av ungefär hundra mikrotjänster som svarade för olika funktioner. Vissa tog emot förfrågningar från andra, och vissa var bakgrundsarbetsare som kopplade till andra tjänster men inte själva tog emot anslutningar. I vilket fall som helst måste varje tjänst veta slutpunkterna för adresserna som den behöver ansluta till.

Många högkvalitativa tjänster kan använda det routingnät som beskrivs ovan. Faktum är att många av mer än hundra mikrotjänster på dotCloud har implementerats som vanliga applikationer på dotCloud-plattformen. Men ett fåtal låg-nivå tjänster (särskilt de som implementerar detta routingnät) behövde något enklare, med färre beroenden (eftersom de inte kunde vara beroende av sig själva – det gamla goda hönan och ägget-problemet).

Dessa låg-nivå viktiga tjänster implementerades genom att köra containrar direkt på flera nyckelnoder. Detta involverade inte de standardtjänster som plattformen erbjuder: schemaläggaren, planeraren och runner. Om man vill jämföra med moderna containrarplattformar, är detta likt att köra kontrollplanet med docker run direkt på noderna, istället för att delegera uppgiften till Kubernetes. Det är ganska likt konceptet. statiska moduler (pods), som används av kubeadm eller bootkube vid uppstart av en fristående kluster.

Dessa tjänster exponerades på ett enkelt och grovt sätt: deras namn och adresser listades i en YAML-fil; varje klient skulle ta en kopia av denna YAML-fil för deployment.

Å ena sidan är detta extremt pålitligt, eftersom det inte kräver stöd för ett externt nyckel/värde-lagringssystem som Zookeeper (kom ihåg, vid den tiden fanns det fortfarande inget etcd eller Consul). Å andra sidan försvårade det flytten av tjänster. Varje gång en flytt gjordes, var alla kunder tvungna att få den uppdaterade YAML-filen (och potentiellt starta om). Inte så praktiskt!

Senare började vi implementera ett nytt schema där varje klient kopplade upp sig mot en lokal proxyserver. Istället för adress och port räcker det med att veta tjänstens portnummer och koppla upp sig genom localhost. Den lokala proxyservern hanterar denna anslutning och dirigerar den till den faktiska servern. Nu när backend flyttas till en annan maskin eller skalar, behöver man istället för att uppdatera alla kunder bara uppdatera dessa lokala proxys; och omstart är inte längre nödvändig.

(Det var också planerat att kapsla in trafiken i TLS-anslutningar och sätta en till proxyserver på mottagarsidan, samt att kontrollera TLS-certifikat utan att involvera den mottagande tjänsten, som är inställd för att ta emot anslutningar endast på localhost. Mer om detta senare).

Det liknar mycket SmartStack från Airbnb, men den viktiga skillnaden är att SmartStack är implementerat och distribuerat i produktion, medan det interna routingsystemet dotCloud lades ner när dotCloud blev Docker.

Jag anser personligen att SmartStack är en av föregångarna till system som Istio, Linkerd och Consul Connect, eftersom alla följer samma mönster:

  • Kör en proxy på varje nod.
  • Klienterna ansluter till proxyn.
  • Kontrollplanet uppdaterar proxyserverns konfiguration när backend förändras.
  • … Vinst!

Modern implementation av service mesh

Om vi behöver implementera ett sådant nätverk idag kan vi använda liknande principer. Till exempel kan vi ställa in en intern DNS-zon som kopplar tjänsternas namn till adresser i området 127.0.0.0/8. Sedan kan vi köra HAProxy på varje nod i klustret, som tar emot anslutningar på varje tjänstadress (i detta subnät 127.0.0.0/8) och omdirigerar/balanserar lasten till motsvarande backends. Konfigurationen av HAProxy kan hanteras confd, vilket gör att man kan lagra information om backend i etcd eller Consul och automatiskt pusha den uppdaterade konfigurationen till HAProxy när det behövs.

Så här fungerar Istio ungefär! Men med vissa skillnader:

  • Använder Envoy Proxy i stället för HAProxy.
  • Lagrar backendkonfigurationen via Kubernetes API istället för etcd eller Consul.
  • Tjänster tilldelas adresser i det interna subnätet (Kubernetes ClusterIP adresser) istället för 127.0.0.0/8.
  • Har en extra komponent (Citadel) för att möjliggöra ömsesidig TLS-autentisering mellan klient och servrar.
  • Stöder nya funktioner som circuit breaking, distribuerad spårning, kanardepployment m.m.

Låt oss kort överväga några skillnader.

Envoy Proxy

Envoy Proxy skrevs av Lyft [en konkurrent till Uber på taximarknaden — övers. anm.]. Det liknar till stor del andra proxys (som HAProxy, Nginx, Traefik…), men Lyft skrev sin egen eftersom de behövde funktioner som saknades i andra proxys, och det verkade mer rimligt att göra en ny än att utvidga en befintlig.

Envoy kan användas självständigt. Om jag har en specifik tjänst som behöver ansluta till andra tjänster kan jag konfigurera den för att koppla till Envoy och sedan göra dynamiska konfigurationer och omkonfigurationer av Envoy med andra tjänsters placeringar, samtidigt som jag får många utmärkta tilläggsfunktioner, som exempelvis insyn. Istället för att använda en anpassad klientbibliotek eller infoga kod för spårning av anrop, dirigerar vi trafiken till Envoy, som samlar in statistik för oss.

Men Envoy kan också fungera som dataplan (data plane) för service mesh. Det betyder att för denna service mesh konfigureras Envoy kontrollplan (control plane).

Kontrollplanen

I kontrollplanen förlitar sig Istio på Kubernetes API. Detta skiljer sig inte mycket från att använda confd, som förlitar sig på etcd eller Consul för att se en uppsättning nycklar i datalagret. Istio läser en uppsättning Kubernetes-resurser via Kubernetes API.

Under tiden: personligen tyckte jag att detta var användbart beskrivning av Kubernetes API, som säger:

Kubernetes API-servern är en "dum server" som tillhandahåller lagring, versionering, validering, uppdatering och semantisk hantering av API-resurser.

Istio är utformat för att arbeta med Kubernetes; och om du vill använda det utanför Kubernetes måste du köra en instans av Kubernetes API-servern (och den stödjande tjänsten etcd).

Tjänsadresser

Istio förlitar sig på adresser ClusterIP som tilldelas av Kubernetes, så tjänster i Istio får en intern adress (inte i intervallet 127.0.0.0/8).

Trafik till ClusterIP-adressen för en viss tjänst i Kubernetes-klustret utan Istio fångas av kube-proxy och dirigeras till backend av denna proxy. Om du är intresserad av tekniska detaljer, så ställer kube-proxy in iptables-regler (eller IPVS belastningsbalanserare beroende på hur den är konfigurerad) för att skriva om destinations-IP-adresser för anslutningar som går till ClusterIP-adressen.

Efter installationen av Istio i Kubernetes-klustret förändras inget förrän det uttryckligen aktiveras för en specifik användare eller till och med hela namnrymden, genom att introducera en container sidecar i anpassade pods. Denna container kommer att starta en instans av Envoy och ställa in en uppsättning iptables-regler för att fånga trafiken som går till andra tjänster och omdirigera den trafiken till Envoy.

Vid integrering med Kubernetes DNS innebär det att vår kod kan ansluta med tjänstens namn, och allt "bara fungerar". Med andra ord, vår kod gör förfrågningar av typen http://api/v1/users/4242, då api löses förfrågan till 10.97.105.48, reglerna för iptables fångar upp anslutningar från 10.97.105.48 och omdirigerar dem till den lokala proxyservicen Envoy, och den lokala proxyn skickar förfrågan till den faktiska API-backenden. Phuh!

Ytterligare detaljer

Istio erbjuder också end-to-end kryptering och autentisering via mTLS (ömsesidig TLS). Detta ansvarar en komponent som kallas Citadel.

Det finns också en komponent Mixer, som Envoy kan begära för varje begäran, för att fatta ett specialbeslut om denna förfrågan beroende på olika faktorer, såsom headers, backend-belastning, osv... (oroliga inte: det finns många medel för att säkerställa att Mixer fungerar, och även om det skulle krascha kommer Envoy fortsatt fungera normalt som proxy).

Och, naturligtvis, vi nämnde spårbarhet: Envoy samlar in en enorm mängd metrik, samtidigt som det erbjuder distribuerad spårning. I en mikrotjänstarkitektur, om en API-förfrågan måste gå genom mikrotjänsterna A, B, C och D, så vid systemets ingång lägger den distribuerade spårningen till en unik identifierare till förfrågan och behåller denna identifierare genom undersökningar till alla dessa mikrotjänster, vilket möjliggör registrering av alla relaterade anrop, deras latens, osv.

Utveckla eller köpa

Istio har ett rykte av att vara ett komplext system. Tvärtom, att bygga det routingnätverk som jag beskrev i början av det här inlägget är relativt enkelt med hjälp av befintliga verktyg. Så, finns det en poäng i att istället skapa sin egen service mesh?

Om vi har blygsamma behov (inga krav på spårbarhet, kedjebrytare och andra detaljer) så tänker man på att utveckla sitt eget verktyg. Men om vi använder Kubernetes kan det till och med vara onödigt, eftersom Kubernetes redan tillhandahåller grundläggande verktyg för tjänstedetektering och lastbalansering.

Men om vi har avancerade krav, så framstår "köpandet" av en service mesh som ett mycket bättre alternativ. (Det är inte alltid just ett "köp", eftersom Istio är open source, men vi måste fortfarande investera ingenjörstid för att förstå hur det fungerar, distribuera och hantera det).

Vad ska man välja: Istio, Linkerd eller Consul Connect?

Hittills har vi bara pratat om Istio, men det är inte den enda service mesh. Ett populärt alternativ är Linkerd, och det finns även Consul Connect.

Vad ska man välja?

Ärligt talat vet jag inte. För tillfället tycker jag inte att jag är tillräckligt kompetent för att svara på den frågan. Det finns flera intressanta artiklar jämförelser av dessa verktyg och även benchmarking.

Ett av de lovande tillvägagångssätten är att använda ett verktyg som SuperGloo. Det implementerar ett abstraheringslager för att förenkla och enhetliggöra API:erna som tillhandahålls av service meshes. Istället för att behöva lära oss specifika (och, enligt mig, relativt komplicerade) API:er från olika service meshes, kan vi använda de enklare konstruktionerna i SuperGloo – och lätt växla från en till en annan, som om vi har ett mellanformat för konfiguration som beskriver HTTP-gränssnitt och backend, som kan generera faktisk konfiguration för Nginx, HAProxy, Traefik, Apache…

Jag har lekt lite med Istio och SuperGloo, och i nästa artikel vill jag visa hur man lägger till Istio eller Linkerd i en befintlig kluster med hjälp av SuperGloo, och hur bra det senare klarar av att göra sitt jobb, det vill säga möjligheten att växla mellan service meshes utan att behöva skriva om konfigurationerna.

Källa: habr.com

Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar 🔥 Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster