Containere, mikrotjenester og servicenet

På internettet bunke artikler о servicenet (servicenet), og her er endnu et. Hurra! Men hvorfor? Så vil jeg gerne udtrykke min mening om, at det ville have været bedre, hvis servicemasker dukkede op for 10 år siden, før fremkomsten af ​​containerplatforme som Docker og Kubernetes. Jeg siger ikke, at mit synspunkt er bedre eller værre end andre, men da servicemasker er ret komplekse dyr, vil flere synspunkter hjælpe med at forstå dem bedre.

Jeg vil tale om dotCloud-platformen, som blev bygget på over hundrede mikrotjenester og understøttede tusindvis af containeriserede applikationer. Jeg vil forklare de udfordringer, vi stod over for ved at udvikle og lancere det, og hvordan servicenet kunne (eller ikke kunne) hjælpe.

DotClouds historie

Jeg har skrevet om dotClouds historie og arkitekturvalgene for denne platform, men jeg har ikke talt meget om netværkslaget. Hvis du ikke vil dykke ned i at læse sidste artikel om dotCloud, her er kernen i en nøddeskal: det er en PaaS platform-som-en-tjeneste, der giver kunderne mulighed for at køre en bred vifte af applikationer (Java, PHP, Python...) med understøttelse af en bred vifte af data tjenester (MongoDB, MySQL, Redis...) og en arbejdsgang som Heroku: Du uploader din kode til platformen, den bygger containerbilleder og implementerer dem.

Jeg vil fortælle dig, hvordan trafikken blev dirigeret til dotCloud-platformen. Ikke fordi det var specielt fedt (selvom systemet fungerede godt til sin tid!), men primært fordi et sådant design med moderne værktøjer nemt kan implementeres på kort tid af et beskedent team, hvis de har brug for en måde at dirigere trafik mellem en flok på af mikrotjenester eller en masse applikationer. På denne måde kan du sammenligne mulighederne: hvad sker der, hvis du udvikler alt selv eller bruger et eksisterende servicenet. Standardvalget er at lave det selv eller købe det.

Trafikdirigering for hostede applikationer

Applikationer på dotCloud kan afsløre HTTP- og TCP-endepunkter.

HTTP-endepunkter dynamisk tilføjet til load balancer-klyngekonfigurationen Hipache. Dette svarer til, hvad ressourcer gør i dag Ingress i Kubernetes og en load balancer som Traefik.

Klienter opretter forbindelse til HTTP-endepunkter gennem passende domæner, forudsat at domænenavnet peger på dotCloud-belastningsbalancer. Ikke noget specielt.

TCP-endepunkter forbundet med et portnummer, som derefter sendes til alle containere i den stak via miljøvariabler.

Klienter kan oprette forbindelse til TCP-slutpunkter ved hjælp af det relevante værtsnavn (noget som gateway-X.dotcloud.com) og portnummer.

Dette værtsnavn løses til "nats"-serverklyngen (ikke relateret til NATS), som vil dirigere indgående TCP-forbindelser til den korrekte container (eller, i tilfælde af belastningsbalancerede tjenester, til de korrekte containere).

Hvis du er bekendt med Kubernetes, vil dette sandsynligvis minde dig om Tjenester Node Port.

Der var ingen tilsvarende tjenester på dotCloud-platformen ClusterIP: For nemheds skyld blev tjenesterne tilgået på samme måde både indefra og uden for platformen.

Alt var organiseret ganske enkelt: De indledende implementeringer af HTTP- og TCP-routingnetværk var sandsynligvis kun et par hundrede linjer Python hver. Simple (jeg vil sige naive) algoritmer, der blev forfinet, efterhånden som platformen voksede og yderligere krav dukkede op.

Omfattende refaktorering af eksisterende kode var ikke påkrævet. I særdeleshed, 12 faktor apps kan direkte bruge adressen opnået gennem miljøvariabler.

Hvordan adskiller dette sig fra et moderne servicenet?

Begrænset sigtbarhed. Vi havde overhovedet ingen metrics for TCP-routingmesh. Når det kommer til HTTP-routing, introducerede senere versioner detaljerede HTTP-metrikker med fejlkoder og svartider, men moderne servicenetværk går endnu længere, hvilket giver integration med metrikindsamlingssystemer som for eksempel Prometheus.

Synlighed er vigtig ikke kun fra et operationelt perspektiv (for at hjælpe med at fejlfinde problemer), men også når nye funktioner frigives. Det handler om sikkert blå-grøn indsættelse и kanarie-indsættelse.

Routing effektivitet er også begrænset. I dotCloud routing mesh skulle al trafik gå gennem en klynge af dedikerede routing noder. Dette betød potentielt at krydse flere AZ-grænser (Availability Zone) og øge latensen markant. Jeg kan huske fejlfindingskode, der lavede over hundrede SQL-forespørgsler pr. side og åbnede en ny forbindelse til SQL-serveren for hver forespørgsel. Når den kører lokalt, indlæses siden øjeblikkeligt, men i dotCloud tager det et par sekunder at indlæse, fordi hver TCP-forbindelse (og efterfølgende SQL-forespørgsel) tager titusvis af millisekunder. I dette særlige tilfælde løste vedvarende forbindelser problemet.

Moderne servicenet er bedre til at håndtere sådanne problemer. Først og fremmest tjekker de, at forbindelser er dirigeret i kilden. Det logiske flow er det samme: клиент → меш → сервис, men nu fungerer masken lokalt og ikke på fjernknuder, så forbindelsen клиент → меш er lokal og meget hurtig (mikrosekunder i stedet for millisekunder).

Moderne servicenet implementerer også smartere belastningsbalanceringsalgoritmer. Ved at overvåge backends sundhed kan de sende mere trafik til hurtigere backends, hvilket resulterer i forbedret overordnet ydeevne.

Безопасность også bedre. DotCloud routing mesh kørte udelukkende på EC2 Classic og krypterede ikke trafik (baseret på den antagelse, at hvis nogen formåede at sætte en sniffer på EC2 netværkstrafik, var du allerede i store problemer). Moderne servicemasker beskytter gennemsigtigt al vores trafik, for eksempel med gensidig TLS-godkendelse og efterfølgende kryptering.

Routing af trafik til platformtjenester

Okay, vi har diskuteret trafik mellem applikationer, men hvad med selve dotCloud-platformen?

Selve platformen bestod af omkring hundrede mikrotjenester, der var ansvarlige for forskellige funktioner. Nogle accepterede anmodninger fra andre, og nogle var baggrundsarbejdere, der koblede sig til andre tjenester, men som ikke selv accepterede forbindelser. Under alle omstændigheder skal hver tjeneste kende endepunkterne for de adresser, den skal oprette forbindelse til.

Mange tjenester på højt niveau kan bruge det ovenfor beskrevne routingmesh. Faktisk er mange af dotClouds mere end hundrede mikrotjenester blevet implementeret som almindelige applikationer på selve dotCloud-platformen. Men et lille antal tjenester på lavt niveau (især dem, der implementerer dette routing-mesh) havde brug for noget enklere, med færre afhængigheder (da de ikke kunne stole på, at de selv kunne arbejde - det gode gamle hønse- og ægproblem).

Disse missionskritiske tjenester på lavt niveau blev implementeret ved at køre containere direkte på nogle få nøgleknuder. I dette tilfælde blev standardplatformstjenester ikke brugt: linker, skemalægger og løber. Hvis du vil sammenligne med moderne containerplatforme, er det som at køre et kontrolfly med docker run direkte på noderne, i stedet for at uddelegere opgaven til Kubernetes. Det er ret ens i konceptet statiske moduler (pods), som den bruger kubeadm eller bootkube ved opstart af en selvstændig klynge.

Disse tjenester blev afsløret på en enkel og grov måde: en YAML-fil angav deres navne og adresser; og hver klient skulle tage en kopi af denne YAML-fil til implementering.

På den ene side er det ekstremt pålideligt, fordi det ikke kræver støtte fra et eksternt nøgle/værdilager såsom Zookeeper (husk, etcd eller Consul fandtes ikke på det tidspunkt). På den anden side gjorde det det svært at flytte tjenester. Hver gang en flytning blev foretaget, ville alle klienter modtage en opdateret YAML-fil (og potentielt genstarte). Ikke særlig behageligt!

Efterfølgende begyndte vi at implementere en ny ordning, hvor hver klient tilsluttede sig en lokal proxyserver. I stedet for en adresse og port skal den kun kende portnummeret på tjenesten, og oprette forbindelse via localhost. Den lokale proxy håndterer denne forbindelse og videresender den til den faktiske server. Nu, når du flytter backend til en anden maskine eller skalerer, i stedet for at opdatere alle klienter, behøver du kun at opdatere alle disse lokale proxyer; og en genstart er ikke længere nødvendig.

(Det var også planlagt at indkapsle trafikken i TLS-forbindelser og sætte en anden proxy-server på modtagersiden, samt verificere TLS-certifikater uden deltagelse af den modtagende tjeneste, som er konfigureret til kun at acceptere forbindelser på localhost. Mere om dette senere).

Dette minder meget om SmartStack fra Airbnb, men den væsentlige forskel er, at SmartStack er implementeret og implementeret til produktion, mens dotClouds interne routingsystem blev skrinlagt, da dotCloud blev Docker.

Jeg anser personligt SmartStack for at være en af ​​forgængerne til systemer som Istio, Linkerd og Consul Connect, fordi de alle følger det samme mønster:

  • Kør en proxy på hver node.
  • Klienter opretter forbindelse til proxyen.
  • Kontrolplanet opdaterer proxy-konfigurationen, når backends ændres.
  • ... Fortjeneste!

Moderne implementering af et servicenet

Hvis vi skulle implementere et lignende net i dag, kunne vi bruge lignende principper. Konfigurer f.eks. en intern DNS-zone ved at tilknytte tjenestenavne til adresser i rummet 127.0.0.0/8. Kør derefter HAProxy på hver node i klyngen, og accepter forbindelser på hver tjenesteadresse (i det undernet 127.0.0.0/8) og omdirigere/afbalancere belastningen til de relevante backends. HAProxy-konfiguration kan styres confd, så du kan gemme backend-oplysninger i etcd eller Consul og automatisk skubbe opdateret konfiguration til HAProxy, når det er nødvendigt.

Det er stort set sådan Istio fungerer! Men med nogle forskelle:

  • Bruger Udsendings fuldmagt i stedet for HAProxy.
  • Gemmer backend-konfiguration via Kubernetes API i stedet for etcd eller Consul.
  • Tjenester tildeles adresser på det interne undernet (Kubernetes ClusterIP-adresser) i stedet for 127.0.0.0/8.
  • Har en ekstra komponent (Citadel) for at tilføje gensidig TLS-godkendelse mellem klienten og serverne.
  • Understøtter nye funktioner såsom kredsløbsbrud, distribueret sporing, kanarie-implementering osv.

Lad os tage et hurtigt kig på nogle af forskellene.

Udsendings fuldmagt

Envoy Proxy er skrevet af Lyft [Ubers konkurrent på taxamarkedet - ca. bane]. Det ligner på mange måder andre proxyer (f.eks. HAProxy, Nginx, Traefik...), men Lyft skrev deres, fordi de havde brug for funktioner, som andre proxyer manglede, og det virkede smartere at lave en ny i stedet for at udvide den eksisterende.

Envoy kan bruges alene. Hvis jeg har en specifik tjeneste, der skal oprette forbindelse til andre tjenester, kan jeg konfigurere den til at oprette forbindelse til Envoy og derefter dynamisk konfigurere og omkonfigurere Envoy med placeringen af ​​andre tjenester, samtidig med at jeg får en masse fantastisk ekstra funktionalitet, såsom synlighed. I stedet for et brugerdefineret klientbibliotek eller at indsætte opkaldsspor i koden, sender vi trafik til Envoy, og den indsamler metrics for os.

Men Envoy er også i stand til at arbejde som dataplan (dataplan) for servicenettet. Det betyder, at Envoy nu er konfigureret til denne service mesh kontrolplan (kontrolplan).

Kontrol fly

For kontrolplanet er Istio afhængig af Kubernetes API. Dette er ikke meget forskelligt fra at bruge confd, som er afhængig af etcd eller Consul for at se nøglesættet i datalageret. Istio bruger Kubernetes API til at se et sæt Kubernetes-ressourcer.

Mellem dette og derefter: Jeg fandt personligt dette nyttigt Kubernetes API beskrivelsesom lyder:

Kubernetes API Server er en "dum server", der tilbyder lagring, versionering, validering, opdatering og semantik for API-ressourcer.

Istio er designet til at fungere med Kubernetes; og hvis du vil bruge det uden for Kubernetes, så skal du køre en instans af Kubernetes API-serveren (og etcd-hjælpetjenesten).

Serviceadresser

Istio er afhængig af ClusterIP-adresser, som Kubernetes tildeler, så Istio-tjenester modtager en intern adresse (ikke i området 127.0.0.0/8).

Trafik til ClusterIP-adressen for en specifik tjeneste i en Kubernetes-klynge uden Istio opfanges af kube-proxy og sendes til denne proxys backend. Hvis du er interesseret i de tekniske detaljer, opsætter kube-proxy iptables-regler (eller IPVS-belastningsbalancere, afhængigt af hvordan det er konfigureret) for at omskrive destinations-IP-adresserne for forbindelser, der går til ClusterIP-adressen.

Når først Istio er installeret på en Kubernetes-klynge, ændres intet, før det udtrykkeligt er aktiveret for en given forbruger, eller endda hele navnerummet, ved at introducere en container sidecar i brugerdefinerede pods. Denne container opretter en instans af Envoy og opsætter et sæt iptables-regler for at opsnappe trafik, der går til andre tjenester og omdirigere denne trafik til Envoy.

Når det er integreret med Kubernetes DNS, betyder det, at vores kode kan oprette forbindelse med tjenestenavn, og alt "bare virker." Med andre ord, vores kode udsteder forespørgsler som http://api/v1/users/4242derefter api løse anmodning om 10.97.105.48, vil iptables-reglerne opsnappe forbindelser fra 10.97.105.48 og videresende dem til den lokale Envoy proxy, og den lokale proxy vil videresende anmodningen til den faktiske backend API. Pyha!

Yderligere dikkedarer

Istio leverer også end-to-end-kryptering og autentificering via mTLS (gensidig TLS). En komponent kaldet Citadel.

Der er også en komponent Mixer, som udsending kan anmode om hver anmode om at træffe en særlig beslutning om denne anmodning afhængigt af forskellige faktorer såsom headere, backend-belastning osv... (bare rolig: der er mange måder at holde Mixer kørende på, og selvom det går ned, vil Envoy fortsætte med at arbejde bøde som fuldmægtig).

Og selvfølgelig nævnte vi synlighed: Envoy indsamler en enorm mængde målinger, mens de sørger for distribueret sporing. I en mikroservicearkitektur, hvis en enkelt API-anmodning skal passere gennem mikrotjenester A, B, C og D, vil distribueret sporing ved login tilføje en unik identifikator til anmodningen og gemme denne identifikator gennem underanmodninger til alle disse mikrotjenester, hvilket giver mulighed for alle relaterede opkald, der skal fanges, forsinkelser osv.

Udvikle eller købe

Istio har ry for at være kompleks. I modsætning hertil er det relativt enkelt at bygge det routingnet, som jeg beskrev i begyndelsen af ​​dette indlæg, ved at bruge eksisterende værktøjer. Så giver det mening at skabe dit eget servicenet i stedet for?

Hvis vi har beskedne behov (vi har ikke brug for synlighed, en strømafbryder og andre finesser), så kommer tankerne til at udvikle vores eget værktøj. Men hvis vi bruger Kubernetes, er det måske ikke engang nødvendigt, fordi Kubernetes allerede leverer grundlæggende værktøjer til serviceopdagelse og belastningsbalancering.

Men hvis vi har avancerede krav, så ser det ud til at være en meget bedre mulighed at "købe" et servicenet. (Dette er ikke altid et "køb", fordi Istio er open source, men vi skal stadig investere ingeniørtid for at forstå, implementere og administrere det.)

Skal jeg vælge Istio, Linkerd eller Consul Connect?

Indtil videre har vi kun talt om Istio, men dette er ikke det eneste servicenet. Populært alternativ - Linkerd, og der er mere Konsul Connect.

Hvad skal man vælge?

Helt ærligt, jeg ved det ikke. I øjeblikket anser jeg mig ikke for kompetent nok til at besvare dette spørgsmål. Der er et par stykker interessant artikler med en sammenligning af disse værktøjer og endda benchmarks.

En lovende tilgang er at bruge et værktøj som SuperGloo. Det implementerer et abstraktionslag for at forenkle og forene de API'er, der eksponeres af servicemasker. I stedet for at lære de specifikke (og, efter min mening, relativt komplekse) API'er af forskellige servicemasker, kan vi bruge SuperGloo's enklere konstruktioner - og nemt skifte fra den ene til den anden, som om vi havde et mellemliggende konfigurationsformat, der beskriver HTTP-grænseflader og backends at generere den faktiske konfiguration for Nginx, HAProxy, Traefik, Apache...

Jeg har puslet lidt med Istio og SuperGloo, og i den næste artikel vil jeg vise, hvordan man tilføjer Istio eller Linkerd til en eksisterende klynge ved hjælp af SuperGloo, og hvordan sidstnævnte får arbejdet gjort, det vil sige giver dig mulighed for at skifte fra en service mesh til en anden uden at overskrive konfigurationer.

Kilde: www.habr.com

Tilføj en kommentar