På internett (service mesh), og her er enda en. Hurra! Men hvorfor? Fordi jeg ønsker å dele min mening om at tjenestemesh burde ha kommet for 10 år siden, før containerplattformer som Docker og Kubernetes oppstod. Jeg hevder ikke at min mening er bedre eller dårligere enn andres, men ettersom tjenestemesh er ganske komplekse begreper, vil flere perspektiver bidra til å forstå dem bedre.
Jeg vil fortelle om plattformen dotCloud, som ble bygget på mer enn hundre mikrotjenester og støttet tusenvis av applikasjoner i containere. Jeg vil forklare hvilke problemer vi møtte under utviklingen og lanseringen, og hvordan tjenestemesh kunne ha hjulpet (eller ikke).
Historien om dotCloud
Jeg har allerede skrevet om historien til dotCloud og valget av arkitektur for denne plattformen, men jeg har lite omtalt nettverkslaget. Hvis du ikke ønsker å dykke ned i å lese om dotCloud, her er kort oppsummert: det er en plattform som tjeneste (PaaS) som tillater kunder å kjøre et bredt spekter av applikasjoner (Java, PHP, Python…), med støtte for et bredt spekter av datatjenester (MongoDB, MySQL, Redis…) og med arbeidsflyt lik Heroku: du laster opp koden din til plattformen, den bygger containerbilder og distribuerer dem.
Jeg vil fortelle hvordan trafikken ble dirigert til plattformen dotCloud. Ikke fordi det var spesielt imponerende (selv om systemet fungerte bra for sin tid!), men først og fremst fordi moderne verktøy lett kan implementere et slikt design på kort tid av et beskjeden team, hvis de trenger en måte å rute trafikk mellom en haug mikrotjenester eller flere applikasjoner. Dermed kan vi sammenligne alternativene: hva får man hvis man utvikler alt selv eller bruker en eksisterende tjenestemesh. Standardvalget: gjøre det selv eller kjøpe.
Ruting av trafikk for vertede applikasjoner
Applikasjoner på dotCloud kan tilby HTTP- og TCP-endepunkter.
HTTP-endepunkter legges dynamisk til konfigurasjonen av lastbalanseringsklusteret . Dette ligner det som ressurser gjør i dag i Kubernetes og en lastbalanserer som .
Kunder kobler seg til HTTP-endepunktene via de tilsvarende domenene, forutsatt at domenenavnet peker på lastbalansererne til dotCloud. Ingenting spesielt.
TCP-endepunkter er relatert til portnummeret, som deretter overføres til alle containere i denne stakken gjennom miljøvariabler.
Kunder kan koble til TCP-endepunkter ved å bruke det tilsvarende vertsnavnet (noe som gateway-X.dotcloud.com) og portnummer.
Dette vertsnavnet blir oppløst til en klynge av servere "nats" (har ikke noe å gjøre med ), som vil rute innkommende TCP-tilkoblinger til riktig container (eller, i tilfelle av tjenester med lastbalansering, til de riktige containerne).
Hvis du er kjent med Kubernetes, vil dette sannsynligvis minne deg om tjenester .
På dotCloud-plattformen var det ikke noe ekvivalent til tjenester : for enkelhets skyld skjedde tilgang til tjenester likt både innenfor og utenfor plattformen.
Alt var organisert ganske enkelt: de første implementasjonene av HTTP- og TCP-rutening sannsynligvis bare hundrevis av linjer med Python. Enkle (jeg vil si naive) algoritmer, som ble forbedret etter hvert som plattformen vokste og ytterligere krav oppstod.
Omfattende refaktorering av eksisterende kode var ikke nødvendig. Spesielt kan direkte bruke adressen som fårs gjennom miljøvariabler.
Hva skiller dette seg fra moderne service-mesh?
Begrenset oversikt. Vi hadde ingen metrikker for TCP-rutingen. Når det gjelder HTTP-rutingen, dukket det opp detaljerte HTTP-metrikker med feilkoder og responstider i senere versjoner, men moderne service-mesh går enda lengre, og tilbyr integrasjon med metrikksamlingssystemer som Prometheus for eksempel.
Oversikt er viktig ikke bare fra operasjonelt perspektiv (for å hjelpe med feilsøking), men også ved rulling ut nye funksjoner. Dette handler om sikker og .
Effektivitet i ruting er også begrenset. I dotClouds rutingsnettverk måtte all trafikk gå gjennom en klynge av dedikerte rutingsnoder. Dette innebar potensiell kryssing av flere AZ-grenser (tilgjengelighetssoner) og en betydelig økning i forsinkelse. Jeg husker jeg feilsøkte koden som lagde over hundre SQL-spørsmål per side, og for hver forespørsel åpnet en ny forbindelse til SQL-serveren. Ved lokal kjøring lastes siden inn nesten umiddelbart, men i dotCloud tar det flere sekunder, fordi hvert TCP-tilkobling (og påfølgende SQL-spørsmål) tar ti-ere av millisekunder. I dette spesifikke tilfellet ble problemet løst med vedvarende forbindelser.
Moderne servicemesh håndterer slike problemer mye bedre. Først og fremst sjekker de at forbindelsene rutes til kilden. Det logiske strømmen er den samme: klient → mesh → tjeneste, men nå fungerer meshen lokalt, ikke på fjerntliggende noder, slik at forbindelsen klient → mesh er lokal og veldig rask (mikrosider i stedet for millisekunder).
Moderne servicemesh implementerer også smartere lastbalanseringsalgoritmer. Ved å overvåke ytelsen til backendene kan de sende mer trafikk til raskere backends, noe som fører til bedre samlet ytelse.
Tryggleik også bedre. DotClouds rutingsnettverk fungerte helt på EC2 Classic og krypterte ikke trafikken (basert på antagelsen om at hvis noen klarte å sette opp en sniffer på EC2s nettverkstrafikk, har du allerede store problemer). Moderne servicemesh beskytter all vår trafikk transparent, for eksempel med gjensidig TLS-autentisering og påfølgende kryptering.
Ruting av trafikk for plattformtjenester
Greit, vi har diskutert trafikk mellom applikasjoner, men hva med selve dotCloud-plattformen?
Selve plattformen besto av omtrent hundre mikrotjenester som ansvarte for ulike funksjoner. Noen tok imot forespørsel fra andre, mens noen var bakgrunnsarbeidere som koblet seg til andre tjenester, men ikke selv tok imot forbindelser. Uansett må hver tjeneste vite om endpointene den skal koble til.
Mange høykvalitets tjenester kan bruke rutingsnettet beskrevet ovenfor. Faktisk har mange av mer enn hundre mikrotjenester på dotCloud blitt distribuert som vanlige applikasjoner på selve dotCloud-plattformen. Men et lite antall lavnivåtjenester (spesielt de som implementerer dette rutingsnettet) trengte noe enklere, med færre avhengigheter (siden de ikke kunne være avhengige av seg selv for å fungere – det klassiske problemet med høna og egget).
Disse lavnivå, viktige tjenestene ble distribuert ved å kjøre containere direkte på flere nøkkelknuter. Standard plattformtjenester ble ikke involvert: kompilatoren, planleggeren og runner. Om du vil sammenligne med moderne containerplattformer, ligner det på å kjøre kontrollplanet med docker run direkte på nodene, i stedet for å delegere oppgaven til Kubernetes. Dette er ganske likt konseptet med , som brukes av eller ved oppstart av en autonom klynge.
Disse tjenestene ble eksponert på en enkel og grov måte: i YAML-filen ble navnene og adressene deres listet; hver klient måtte ta en kopi av denne YAML-filen for distribusjon.
På den ene siden er dette ekstremt pålitelig, fordi det ikke krever støtte fra et eksternt nøkkel/verdi-lager, som Zookeeper (huske, på den tiden eksisterte ikke etcd eller Consul). På den andre siden vanskeliggjorde det flytting av tjenester. Hver gang ved flytting måtte alle klientene få oppdatert YAML-fil (og potensielt restarte). Ikke så praktisk!
Senere begynte vi å implementere et nytt skjema, der hver klient koblet seg til en lokal proxy-server. I stedet for adresse og port er det nok å vite bare portnummeret til tjenesten, og koble via localhost. Den lokale proxy-serveren håndterer denne forbindelsen og dirigerer den til den faktiske serveren. Nå ved flytting av backend til en annen maskin eller ved skalering, trenger man bare å oppdatere alle disse lokale proxyene, i stedet for å oppdatere alle klientene; og omstart er ikke lenger nødvendig.
(Det var også planlagt å kapsle inn trafikk i TLS-forbindelser og sette opp en ny proxy-server på mottakerens side, samt å sjekke TLS-sertifikatene uten deltakelse av tjenesten som er konfigurert for å ta imot forbindelser bare på localhost. Mer om dette senere).
Dette ligner veldig på fra Airbnb, men den vesentlige forskjellen er at SmartStack er implementert og distribuert i produksjon, mens det interne rutingssystemet til dotCloud ble lagt bort da dotCloud ble til Docker.
Jeg anser personlig SmartStack som en av forgjengerne til systemer som Istio, Linkerd og Consul Connect, fordi de alle følger et felles mønster:
- Kjøre proxy på hver node.
- Kunder kobler seg til proxy.
- Kontrollplanet oppdaterer konfigurasjonen til proxy-serveren ved endringer av backendene.
- … Profit!
Moderne implementering av service mesh
Hvis vi trenger å implementere noe lignende nettverket i dag, kan vi bruke tilsvarende prinsipper. For eksempel, sette opp et internt DNS-område som matcher tjenestenavn med adresser i rommet 127.0.0.0/8. Deretter kjøre HAProxy på hver nodes i klyngen, ta imot forbindelser til hver tjenestes adresse (i dette subnettet 127.0.0.0/8) og omdirigere/balanse belastningen til de tilsvarende backendene. Konfigurasjonen av HAProxy kan administreres , som lar oss lagre informasjon om backend i etcd eller Consul og automatisk pushe den oppdaterte konfigurasjonen til HAProxy når det er nødvendig.
Slik fungerer Istio omtrent! Men med noen forskjeller:
- Bruker i stedet for HAProxy.
- Lagrer backend-konfigurasjonen gjennom Kubernetes API i stedet for etcd eller Consul.
- Tjenester tildeles adresser i det interne subnettet (adresser for Kubernetes ClusterIP) i stedet for 127.0.0.0/8.
- Har en ekstra komponent (Citadel) for å legge til gjensidig TLS-autentisering mellom klient og servere.
- Støtter nye funksjoner som circuit breaking, distribuert sporing, kanardeployeringer osv.
La oss kort se på noen forskjeller.
Envoy Proxy
Envoy Proxy ble skrevet av Lyft [konkurrent til Uber i taxi-markedet – anm. red.]. Den ligner mye på andre proxyer (som HAProxy, Nginx, Traefik…), men Lyft laget sin egen fordi de trengte funksjoner som manglet i andre proxyer, og det virket mer fornuftig å lage en ny enn å utvide en eksisterende.
Envoy kan brukes alene. Hvis jeg har en spesifikk tjeneste som må kobles til andre tjenester, kan jeg konfigurere den til å kobles til Envoy, og deretter dynamisk konfigurere og omkonfigurere Envoy med plasseringen av andre tjenester, samtidig som jeg får mange gode ekstra funksjoner, som observabilitet. I stedet for å bruke et tilpasset klientbibliotek eller å implementere sporingsanrop i koden, dirigerer vi trafikken til Envoy, som samler inn metrikker for oss.
Men Envoy kan også fungere som dataflyt (data plane) for en servicemesh. Dette betyr at Envoy nå konfigureres til styringsplanen (control plane).
Styringsplanen
I styringsplanen stoler Istio på Kubernetes API. Dette er ikke veldig forskjellig fra bruken av confd, som stoler på etcd eller Consul for å se på settet med nøkler i datalageret. Istio ser opp settet av Kubernetes-ressurser gjennom Kubernetes API.
I mellomtiden: jeg syntes personlig dette var nyttig , som sier:
Kubernetes API-serveren er en 'dum server' som tilbyr oppbevaring, versjonskontroll, validering, oppdatering og semantikk av API-ressurser.
Istio er designet for å fungere med Kubernetes; og hvis du vil bruke det utenfor Kubernetes, må du kjøre en instans av Kubernetes API-serveren (og hjelpeprogrammet etcd).
Tjenesteadresser
Istio er avhengig av adresser ClusterIP som tildeles av Kubernetes, så Istio-tjenester får en intern adresse (ikke i området) 127.0.0.0/8).
Trafikk til ClusterIP-adressen for en spesifikk tjeneste i Kubernetes-klyngen uten Istio blir fanget opp av kube-proxy og sendt til backend for denne proxyen. Hvis du er interessert i tekniske detaljer, setter kube-proxy regler for iptables (eller IPVS-lastbalansere, avhengig av hvordan det er konfigurert), for å omskrive destinasjons-IP-adressene for forbindelser som går til ClusterIP-adressen.
Etter å ha installert Istio i Kubernetes-klyngen, endres ingenting før det blir eksplisitt aktivert for en bestemt forbruker eller til og med hele navnerommet, ved å introdusere en container sidecar i tilpassede podder. Denne containeren vil kjøre en instans av Envoy og sette opp en rekke iptables-regler for å fange opp trafikken som går til andre tjenester og omdirigere den til Envoy.
Når du integrerer med Kubernetes DNS, betyr det at koden vår kan koble til med tjenestenavnet, og alt fungerer "bare". Med andre ord, koden vår sender forespørselstype http://api/v1/users/4242, da api løser forespørselen mot 10.97.105.48, iptables-regler fanger opp forbindelser fra 10.97.105.48 og omdirigerer dem til den lokale Envoy-proxyen, og denne lokale proxyen vil sende forespørselen til den faktiske API-backenden. Puh!
Ytterligere funksjoner
Istio gir også ende-til-ende kryptering og autentisering via mTLS (mutual TLS). Dette håndteres av en komponent kalt Citadel.
Det finnes også en komponent Mixer, som Envoy kan be om for hver forespørsel for å ta en spesifikk beslutning om denne forespørselen basert på forskjellige faktorer, som overskrifter, backend-belastning, osv… (ikke bekymre deg: det finnes mange verktøy for å sikre at Mixer fungerer, og selv om det skulle svikte, vil Envoy fortsatt fungere som proxy).
Og selvfølgelig, vi nevnte synlighet: Envoy samler inn et stort antall målinger samtidig som den gir distribuert sporing. I mikrotjenestearkitekturen, hvis en API-forespørsel må gå gjennom mikrotjenestene A, B, C og D, vil den distribuerte sporing legge til en unik identifikator til forespørselen ved inngang til systemet, og beholde denne identifikatoren gjennom underforespørslene til alle disse mikrotjenestene, noe som gjør det mulig å loggføre alle relaterte anrop, deres forsinkelser, osv.
Utvikle eller kjøpe
Istio har rykte på seg for å være et komplisert system. Derimot er det relativt enkelt å bygge opp rute-nettverket jeg beskrev i begynnelsen av dette innlegget med eksisterende verktøy. Så, er det noe poeng i å lage sin egen service-mesh?
Hvis vi har beskjedne behov (uten krav til synlighet, circuit breaker og andre detaljer), dukker tanken på å utvikle et eget verktøy opp. Men hvis vi bruker Kubernetes, kan det hende vi ikke engang trenger det, fordi Kubernetes allerede gir grunnleggende verktøy for tjenesteoppdagelse og lastbalansering.
Men hvis vi har avanserte krav, virker det å "kjøpe" en service-mesh som en mye bedre løsning. (Det er ikke alltid faktisk "kjøp", ettersom Istio er tilgjengelig med åpen kildekode, men vi må fortsatt investere tid fra ingeniører for å forstå hvordan det fungerer, distribuere det og administrere det).
Hva skal du velge: Istio, Linkerd eller Consul Connect?
Vi har så langt bare snakket om Istio, men det er ikke den eneste service-mesh. Et populært alternativ er , og det finnes også .
Hva skal velges?
Ærlig talt, jeg vet ikke. Jeg anser meg selv for å ikke være kompetent nok til å svare på dette spørsmålet. Det finnes flere sammenligninger av disse verktøyene, og til og med .
En av de lovende tilnærmingene er å bruke et verktøy som . Det implementerer et abstraksjonslag for å forenkle og unifisere API-ene som tilbys av service-mesh. I stedet for å studere spesifikke (og etter min mening relativt komplekse) API-er til forskjellige service-mesh, kan vi bruke de enklere strukturene til SuperGloo — og lett bytte mellom hverandre, som om vi har et mellomliggende konfigurasjonsformat som beskriver HTTP-grensesnitt og backend-er som kan generere faktisk konfigurasjon for Nginx, HAProxy, Traefik, Apache…
Jeg har lekt med Istio og SuperGloo, og i neste artikkel ønsker jeg å vise hvordan man kan legge til Istio eller Linkerd i et eksisterende kluster med SuperGloo, og hvor godt det siste klarer jobben sin, nemlig å la oss bytte mellom en service-mesh til en annen uten å overskrive konfigurasjoner.
Kilde: habr.com
