Containere, mikrotjenester og servicemasker

På internett heap Artikkel о servicenett (servicenettverk), og her er en annen. Hurra! Men hvorfor? Deretter vil jeg uttrykke min mening om at det hadde vært bedre om tjenestenettverk dukket opp for 10 år siden, før ankomsten av containerplattformer som Docker og Kubernetes. Jeg sier ikke at mitt synspunkt er bedre eller dårligere enn andre, men siden servicemasker er ganske komplekse dyr, vil flere synspunkter bidra til å forstå dem bedre.

Jeg skal snakke om dotCloud-plattformen, som ble bygget på over hundre mikrotjenester og støttet tusenvis av containeriserte applikasjoner. Jeg vil forklare utfordringene vi møtte ved å utvikle og lansere den, og hvordan tjenestenettverk kan (eller ikke) hjelpe.

Historien til dotCloud

Jeg har skrevet om historien til dotCloud og arkitekturvalgene for denne plattformen, men jeg har ikke snakket mye om nettverkslaget. Hvis du ikke vil dykke ned i lesing siste artikkel om dotCloud, her er kjernen i et nøtteskall: det er en PaaS-plattform-som-en-tjeneste som lar kunder kjøre et bredt spekter av applikasjoner (Java, PHP, Python...), med støtte for et bredt spekter av data tjenester (MongoDB, MySQL, Redis...) og en arbeidsflyt som Heroku: Du laster opp koden din til plattformen, den bygger containerbilder og distribuerer dem.

Jeg vil fortelle deg hvordan trafikken ble dirigert til dotCloud-plattformen. Ikke fordi det var spesielt kult (selv om systemet fungerte bra for sin tid!), men først og fremst fordi et slikt design med moderne verktøy lett kan implementeres på kort tid av et beskjedent team hvis de trenger en måte å rute trafikk mellom en gjeng av mikrotjenester eller en haug med applikasjoner. På denne måten kan du sammenligne alternativene: hva skjer hvis du utvikler alt selv eller bruker et eksisterende servicenettverk. Standardvalget er å lage det selv eller kjøpe det.

Trafikkruting for vertsbaserte applikasjoner

Applikasjoner på dotCloud kan avsløre HTTP- og TCP-endepunkter.

HTTP-endepunkter dynamisk lagt til load balancer cluster-konfigurasjonen Hipache. Dette ligner på hva ressurser gjør i dag Ingress i Kubernetes og en lastbalanser som Traefik.

Klienter kobler til HTTP-endepunkter gjennom passende domener, forutsatt at domenenavnet peker til dotCloud-lastbalansere. Ikke noe spesielt.

TCP-endepunkter assosiert med et portnummer, som deretter sendes til alle beholdere i den stabelen via miljøvariabler.

Klienter kan koble til TCP-endepunkter ved å bruke riktig vertsnavn (noe som gateway-X.dotcloud.com) og portnummer.

Dette vertsnavnet løses til "nats"-serverklyngen (ikke relatert til NATS), som vil rute innkommende TCP-forbindelser til riktig container (eller, i tilfelle lastbalanserte tjenester, til riktige containere).

Hvis du er kjent med Kubernetes, vil dette sannsynligvis minne deg om Tjenester Nodeport.

Det var ingen tilsvarende tjenester på dotCloud-plattformen ClusterIP: For enkelhets skyld ble tjenester aksessert på samme måte både fra innsiden og utsiden av plattformen.

Alt var ganske enkelt organisert: de første implementeringene av HTTP- og TCP-rutenettverk var sannsynligvis bare noen få hundre linjer med Python hver. Enkle (jeg vil si naive) algoritmer som ble raffinert etter hvert som plattformen vokste og ytterligere krav dukket opp.

Omfattende refaktorisering av eksisterende kode var ikke nødvendig. Spesielt, 12 faktor apper kan direkte bruke adressen hentet gjennom miljøvariabler.

Hvordan er dette forskjellig fra et moderne servicenettverk?

Begrenset synlighet. Vi hadde ingen beregninger for TCP-rutingsnettverket i det hele tatt. Når det gjelder HTTP-ruting, introduserte senere versjoner detaljerte HTTP-målinger med feilkoder og responstider, men moderne tjenestenettverk går enda lenger, og gir integrasjon med metrikkinnsamlingssystemer som Prometheus, for eksempel.

Synlighet er viktig ikke bare fra et operasjonelt perspektiv (for å hjelpe til med å feilsøke problemer), men også når du slipper nye funksjoner. Det handler om trygt blågrønn utplassering и utplassering av kanarifugler.

Rutingeffektivitet er også begrenset. I dotCloud-rutingsnettverket måtte all trafikk gå gjennom en klynge med dedikerte rutingnoder. Dette betydde potensielt å krysse flere AZ-grenser (Availability Zone) og øke ventetiden betydelig. Jeg husker feilsøkingskoden som gjorde over hundre SQL-spørringer per side og åpnet en ny tilkobling til SQL-serveren for hver spørring. Når den kjøres lokalt, laster siden umiddelbart, men i dotCloud tar det noen sekunder å laste fordi hver TCP-tilkobling (og påfølgende SQL-spørring) tar titalls millisekunder. I dette spesielle tilfellet løste vedvarende tilkoblinger problemet.

Moderne tjenestenett er bedre til å håndtere slike problemer. Først og fremst sjekker de at forbindelsene er rutet i kilden. Den logiske flyten er den samme: клиент → меш → сервис, men nå fungerer mesh lokalt og ikke på eksterne noder, så tilkoblingen клиент → меш er lokal og veldig rask (mikrosekunder i stedet for millisekunder).

Moderne servicenettverk implementerer også smartere lastbalanseringsalgoritmer. Ved å overvåke helsen til backends kan de sende mer trafikk til raskere backends, noe som resulterer i forbedret generell ytelse.

Безопасность bedre også. DotCloud-rutingsnettverket kjørte utelukkende på EC2 Classic og krypterte ikke trafikk (basert på antakelsen om at hvis noen klarte å sette en sniffer på EC2-nettverkstrafikk, var du allerede i store problemer). Moderne tjenestenett beskytter gjennomsiktig all trafikken vår, for eksempel med gjensidig TLS-autentisering og påfølgende kryptering.

Ruting av trafikk for plattformtjenester

Ok, vi har diskutert trafikk mellom applikasjoner, men hva med selve dotCloud-plattformen?

Selve plattformen besto av rundt hundre mikrotjenester med ansvar for ulike funksjoner. Noen godtok forespørsler fra andre, og noen var bakgrunnsarbeidere som koblet seg til andre tjenester, men som ikke godtok tilknytninger selv. I alle fall må hver tjeneste kjenne endepunktene til adressene den må koble seg til.

Mange tjenester på høyt nivå kan bruke rutingnettverket beskrevet ovenfor. Faktisk har mange av dotClouds mer enn hundre mikrotjenester blitt distribuert som vanlige applikasjoner på selve dotCloud-plattformen. Men et lite antall tjenester på lavt nivå (spesielt de som implementerer dette rutenettverket) trengte noe enklere, med færre avhengigheter (siden de ikke kunne stole på at de skulle fungere - det gode gamle kylling- og eggproblemet).

Disse funksjonskritiske tjenestene på lavt nivå ble distribuert ved å kjøre containere direkte på noen få nøkkelnoder. I dette tilfellet ble ikke standard plattformtjenester brukt: linker, planlegger og løper. Hvis du vil sammenligne med moderne containerplattformer, er det som å kjøre et kontrollfly med docker run direkte på nodene, i stedet for å delegere oppgaven til Kubernetes. Det er ganske likt i konseptet statiske moduler (pods), som den bruker kubeadm eller bootkube når du starter opp en frittstående klynge.

Disse tjenestene ble eksponert på en enkel og grov måte: en YAML-fil listet opp navnene og adressene deres; og hver klient måtte ta en kopi av denne YAML-filen for distribusjon.

På den ene siden er den ekstremt pålitelig fordi den ikke krever støtte fra et eksternt nøkkel-/verdilager som Zookeeper (husk, etcd eller Consul fantes ikke på den tiden). På den annen side gjorde det det vanskelig å flytte tjenester. Hver gang et trekk ble gjort, ville alle klienter motta en oppdatert YAML-fil (og potensielt starte på nytt). Ikke veldig behagelig!

Deretter begynte vi å implementere et nytt opplegg, der hver klient koblet til en lokal proxy-server. I stedet for en adresse og port, trenger den bare å vite portnummeret til tjenesten, og koble til via localhost. Den lokale proxyen håndterer denne tilkoblingen og videresender den til den faktiske serveren. Nå, når du flytter backend til en annen maskin eller skalerer, i stedet for å oppdatere alle klienter, trenger du bare å oppdatere alle disse lokale proxyene; og en omstart er ikke lenger nødvendig.

(Det var også planlagt å kapsle inn trafikken i TLS-forbindelser og sette en annen proxy-server på mottakersiden, samt verifisere TLS-sertifikater uten deltakelse fra mottakstjenesten, som er konfigurert til å akseptere tilkoblinger kun på localhost. Mer om dette senere).

Dette er veldig likt SmartStack fra Airbnb, men den vesentlige forskjellen er at SmartStack er implementert og distribuert til produksjon, mens dotClouds interne rutingsystem ble skrinlagt da dotCloud ble Docker.

Jeg personlig anser SmartStack for å være en av forgjengerne til systemer som Istio, Linkerd og Consul Connect fordi de alle følger samme mønster:

  • Kjør en proxy på hver node.
  • Klienter kobler til proxyen.
  • Kontrollplanet oppdaterer proxy-konfigurasjonen når backends endres.
  • ... Profitt!

Moderne implementering av et servicenettverk

Hvis vi trengte å implementere et lignende nett i dag, kunne vi bruke lignende prinsipper. Konfigurer for eksempel en intern DNS-sone ved å tilordne tjenestenavn til adresser i rommet 127.0.0.0/8. Kjør deretter HAProxy på hver node i klyngen, og godta tilkoblinger på hver tjenesteadresse (i det undernettet 127.0.0.0/8) og omdirigere/balansere lasten til de riktige backends. HAProxy-konfigurasjon kan kontrolleres confd, slik at du kan lagre backend-informasjon i etcd eller Consul og automatisk sende oppdatert konfigurasjon til HAProxy når det er nødvendig.

Det er omtrent slik Istio fungerer! Men med noen forskjeller:

  • Bruker Utsendingsfullmektig i stedet for HAProxy.
  • Lagrer backend-konfigurasjon via Kubernetes API i stedet for etcd eller Consul.
  • Tjenester tildeles adresser på det interne subnettet (Kubernetes ClusterIP-adresser) i stedet for 127.0.0.0/8.
  • Har en ekstra komponent (Citadel) for å legge til gjensidig TLS-autentisering mellom klienten og serverne.
  • Støtter nye funksjoner som kretsbrudd, distribuert sporing, kanarie-utplassering, etc.

La oss ta en rask titt på noen av forskjellene.

Utsendingsfullmektig

Envoy Proxy ble skrevet av Lyft [Ubers konkurrent på taximarkedet - ca. kjørefelt]. Det ligner på mange måter andre proxyer (f.eks. HAProxy, Nginx, Traefik...), men Lyft skrev deres fordi de trengte funksjoner som andre proxyer manglet, og det virket smartere å lage en ny i stedet for å utvide den eksisterende.

Envoy kan brukes alene. Hvis jeg har en spesifikk tjeneste som trenger å koble til andre tjenester, kan jeg konfigurere den til å koble til Envoy, og deretter dynamisk konfigurere og rekonfigurere Envoy med plasseringen til andre tjenester, samtidig som jeg får mye flott tilleggsfunksjonalitet, for eksempel synlighet. I stedet for et tilpasset klientbibliotek eller å injisere samtalespor i koden, sender vi trafikk til Envoy, og den samler inn beregninger for oss.

Men Envoy er også i stand til å jobbe som dataplan (dataplan) for tjenestenettet. Dette betyr at Envoy nå er konfigurert for denne tjenesten kontrollfly (kontrollplan).

Kontrollfly

For kontrollplanet er Istio avhengig av Kubernetes API. Dette er ikke veldig forskjellig fra å bruke confd, som er avhengig av etcd eller Consul for å se settet med nøkler i datalageret. Istio bruker Kubernetes API for å vise et sett med Kubernetes-ressurser.

Mellom dette og da: Jeg personlig fant dette nyttig Kubernetes API-beskrivelsesom lyder:

Kubernetes API Server er en "dum server" som tilbyr lagring, versjonering, validering, oppdatering og semantikk for API-ressurser.

Istio er designet for å fungere med Kubernetes; og hvis du vil bruke den utenfor Kubernetes, må du kjøre en forekomst av Kubernetes API-serveren (og etcd-hjelpetjenesten).

Tjenesteadresser

Istio er avhengig av ClusterIP-adresser som Kubernetes tildeler, så Istio-tjenester mottar en intern adresse (ikke i området 127.0.0.0/8).

Trafikk til ClusterIP-adressen for en spesifikk tjeneste i en Kubernetes-klynge uten Istio blir fanget opp av kube-proxy og sendt til denne proxyens backend. Hvis du er interessert i de tekniske detaljene, setter kube-proxy opp iptables-regler (eller IPVS-lastbalansere, avhengig av hvordan den er konfigurert) for å omskrive destinasjons-IP-adressene til tilkoblinger som går til ClusterIP-adressen.

Når Istio er installert på en Kubernetes-klynge, endres ingenting før den er eksplisitt aktivert for en gitt forbruker, eller til og med hele navneområdet, ved å introdusere en beholder sidecar i tilpassede pods. Denne beholderen vil spinne opp en forekomst av Envoy og sette opp et sett med iptables-regler for å avskjære trafikk som går til andre tjenester og omdirigere den trafikken til Envoy.

Når integrert med Kubernetes DNS, betyr dette at koden vår kan kobles til med tjenestenavn og alt "bare fungerer." Med andre ord, koden vår utsteder spørsmål som http://api/v1/users/4242deretter api løse forespørsel om 10.97.105.48, vil iptables-reglene avskjære tilkoblinger fra 10.97.105.48 og videresende dem til den lokale Envoy-proxyen, og den lokale proxyen vil videresende forespørselen til den faktiske backend-APIen. Puh!

Ekstra dikkedarer

Istio gir også ende-til-ende-kryptering og autentisering via mTLS (mutual TLS). En komponent som kalles Citadel.

Det er også en komponent Mixer, som utsending kan be om av hver be om å ta en spesiell avgjørelse om den forespørselen avhengig av ulike faktorer som overskrifter, belastning på baksiden, osv... (ikke bekymre deg: det er mange måter å holde Mixer i gang, og selv om den krasjer, vil Envoy fortsette å jobbe fint som fullmektig).

Og selvfølgelig nevnte vi synlighet: Envoy samler inn en enorm mengde beregninger samtidig som den gir distribuert sporing. I en mikrotjenestearkitektur, hvis en enkelt API-forespørsel må passere gjennom mikrotjenestene A, B, C og D, vil distribuert sporing ved pålogging legge til en unik identifikator til forespørselen og lagre denne identifikatoren gjennom underforespørsler til alle disse mikrotjenestene, slik at alle relaterte anrop som skal fanges opp, forsinkelser osv.

Utvikle eller kjøpe

Istio har rykte på seg for å være kompleks. I kontrast er det relativt enkelt å bygge rutenettet som jeg beskrev i begynnelsen av dette innlegget ved å bruke eksisterende verktøy. Så, er det fornuftig å lage ditt eget servicenettverk i stedet?

Hvis vi har beskjedne behov (vi trenger ikke synlighet, en strømbryter og andre finesser), så kommer tankene til å utvikle vårt eget verktøy. Men hvis vi bruker Kubernetes, er det kanskje ikke engang nødvendig fordi Kubernetes allerede gir grunnleggende verktøy for tjenesteoppdagelse og lastbalansering.

Men hvis vi har avanserte krav, ser det ut til å være et mye bedre alternativ å "kjøpe" et servicenettverk. (Dette er ikke alltid et "kjøp" fordi Istio er åpen kildekode, men vi må fortsatt investere ingeniørtid for å forstå, distribuere og administrere det.)

Skal jeg velge Istio, Linkerd eller Consul Connect?

Så langt har vi kun snakket om Istio, men dette er ikke det eneste servicenettet. Populært alternativ - Linkerd, og det er mer Konsul Connect.

Hva skal jeg velge?

Ærlig talt, jeg vet ikke. For øyeblikket anser jeg meg ikke som kompetent nok til å svare på dette spørsmålet. Det er noen få interessant Artikkel med en sammenligning av disse verktøyene og til og med benchmarks.

En lovende tilnærming er å bruke et verktøy som SuperGloo. Den implementerer et abstraksjonslag for å forenkle og forene API-ene som er eksponert av tjenestenettverk. I stedet for å lære de spesifikke (og, etter min mening, relativt komplekse) API-ene til forskjellige tjenestenettverk, kan vi bruke SuperGloo sine enklere konstruksjoner – og enkelt bytte fra en til en annen, som om vi hadde et mellomkonfigurasjonsformat som beskriver HTTP-grensesnitt og backends å generere den faktiske konfigurasjonen for Nginx, HAProxy, Traefik, Apache...

Jeg har drevet litt med Istio og SuperGloo, og i neste artikkel vil jeg vise hvordan du legger til Istio eller Linkerd i en eksisterende klynge ved hjelp av SuperGloo, og hvordan sistnevnte får jobben gjort, det vil si lar deg bytte fra ett tjenestenett til et annet uten å overskrive konfigurasjoner.

Kilde: www.habr.com

Legg til en kommentar