På internettet Jeg vil fortælle om platformen dotCloud, som var bygget på mere end hundrede mikrotjenester og understøttede tusindvis af applikationer i containere. Jeg vil forklare de udfordringer, vi stødte på under udviklingen og lanceringen, og hvordan service meshes kunne have hjulpet (eller ikke hjulpet).
Historien om dotCloud
Jeg har allerede skrevet om historien bag dotCloud og valget af arkitektur til denne platform, men har ikke talt meget om netværksniveauet. Hvis du ikke ønsker at dykke ned i læsningen af
den forrige artikel Jeg vil fortælle, hvordan trafikken blev dirigeret til platformen dotCloud. Ikke fordi det var særligt fantastisk (selvom systemet fungerede godt for sin tid!), men først og fremmest fordi moderne værktøjer nemt kan realisere et sådant design på kort tid af et beskedent team, hvis de har brug for en metode til at dirigere trafik mellem en række mikrotjenester eller en masse applikationer. På den måde kan man sammenligne mulighederne: hvad opnår man, hvis man udvikler alt selv eller bruger et eksisterende service mesh. Den standardmæssige beslutning: gøre det selv eller købe.
Trafikdirigering til hosted-applikationer
Applikationer på dotCloud kan tilbyde HTTP- og TCP-endepunkter.
HTTP-endepunkter
bliver dynamisk tilføjet til konfigurationen af belastningsbalanceringsklusteret Hipache Kubernetes og belastningsbalanceren som Kunderne forbinder sig til HTTP-endepunkterne via de relevante domæner, forudsat at domænenavnet peger på dotClouds belastningsbalancer. Intet særligt. .
TCP-endepunkter
TCP-endepunkterne forbindes med portnummeret, som derefter videreføres til alle containere i denne stak via miljøvariabler.
Kunder kan oprette forbindelse til TCP-endepunkter ved at bruge det relevante værtsnavn (noget som gateway-X.dotcloud.com) og portnummeret.
Dette værtsnavn opløses til et klyngeserver, "nats" (som ikke har noget at gøre med ), som vil dirigere indkommende TCP-forbindelser til den rigtige container (eller, i tilfælde af tjenester med belastningsafbalancering, til de rigtige containere).
Hvis du er bekendt med Kubernetes, vil dette sandsynligvis minde dig om tjenesterne .
På dotCloud-platformen var der ikke nogen ækvivalent til tjenesterne : for enkelhed skyld skete adgangen til tjenesterne ens både indendørs og udendørs platformen.
Alt var organiseret ret enkelt: de første implementeringer af HTTP- og TCP-routingnetværk var sandsynligvis kun et par hundrede linjer Python. Enkle (jeg ville sige naive) algoritmer, som blev forbedret i takt med platformens vækst og nye krav.
Omfattende refaktorering af den eksisterende kode var ikke nødvendig. Især kan direkte bruge adressen, der opnås via miljøvariabler.
Hvordan adskiller dette sig fra moderne service meshes?
Begrænset oversigt. Vi havde slet ikke nogen målinger for TCP-routingnetværket. Hvad angår HTTP-routing, blev der i senere versioner introduceret detaljerede HTTP-målinger med fejlmeddelelser og svartider, men moderne service meshes går endnu længere ved at integrere med metrikindsamlingssystemer som Prometheus, for eksempel.
Oversigt er vigtig ikke kun fra et operationelt synspunkt (for at hjælpe med fejlfinding), men også ved lanceringen af nye funktioner. Det handler om sikker og .
Routings effektivitet er også begrænset. I dotCloud's routingnetværk skulle al trafik passere gennem en klynge af dedikerede routingnoder. Det betød potentiel krydsning af flere AZ-grænser (tilgængelighedszoner) og en betydelig forøgelse af latensen. Jeg husker, hvordan jeg fejlfandt kode, der lavede over hundrede SQL-forespørgsler pr. side og åbnede en ny forbindelse til SQL-serveren for hver forespørgsel. Ved lokal kørsel indlæses siden øjeblikkeligt, men i dotCloud tager indlæsningen et par sekunder, fordi hver TCP-forbindelse (og efterfølgende SQL-forespørgsel) kræver titusinder af millisekunder. I dette specifikke tilfælde blev problemet løst med vedvarende forbindelser.
Moderne service-meshes klarer sådanne problemer bedre. Først og fremmest sikrer de, at forbindelserne bliver routet til kilden. Den logiske strøm forbliver den samme: kunde → mesh → service, men nu arbejder mesh lokalt i stedet for på fjerntliggende noder, så forbindelsen kunde → mesh bliver lokal og meget hurtig (mikroseunder i stedet for millisekunder).
Moderne service-meshes implementerer også smartere belastningsbalanceringsalgoritmer. Ved at overvåge sundheden af backend-tjenesterne kan de sende mere trafik til hurtigere backends, hvilket resulterer i bedre samlet ydelse.
Sikkerhed bliver også bedre. DotClouds routingnetværk arbejdede helt på EC2 Classic og krypterede ikke trafikken (givet antagelsen om, at hvis nogen har formået at opsnappe netværkstrafikken fra EC2, så har du allerede store problemer). Moderne service-meshes beskytter alt vores trafik transparent, f.eks. med gensidig TLS-godkendelse og efterfølgende kryptering.
Routing af trafik for platformtjenester
Okay, vi har talte om trafik mellem applikationer, men hvad med selve dotCloud-platformen?
Selve platformen bestod af omkring hundrede mikrotjenester, der var ansvarlige for forskellige funktioner. Nogle modtog anmodninger fra andre, mens nogle var baggrundsarbejdere, der oprettede forbindelse til andre tjenester, men ikke selv modtog forbindelser. Under alle omstændigheder skal hver tjeneste kende de slutpunkter, den skal forbinde til.
Mange højniveau tjenester kan bruge den routing-netværk, der er beskrevet ovenfor. Faktisk er mange af de mere end hundrede mikrotjenester fra dotCloud blevet implementeret som almindelige applikationer på selve dotCloud-platformen. Men et lille antal lavniveau tjenester (især dem, der implementerer denne routing-netværk) havde brug for noget mere enkelt med færre afhængigheder (da de ikke kunne være afhængige af sig selv - det gode gamle problem med hønen og ægget).
Disse lavniveau, vigtige tjenester blev implementeret ved at køre containere direkte på flere nøgle-noder. I dette tilfælde blev de standard platformtjenester ikke brugt: kompileringsværktøj, planlægger og runner. Hvis du vil sammenligne med moderne containerplatforme, er det som at køre kontrolplanet docker run direkte på noderne, i stedet for at delegere opgaven til Kubernetes. Det ligner ret meget konceptet med , som bruges af eller ved opstart af en autonom klynge.
Disse tjenester blev eksponeret på en simpel og grov måde: i en YAML-fil blev deres navne og adresser opført; hver klient skulle tage en kopi af denne YAML-fil for deployment.
På den ene side er dette ekstremt pålideligt, da det ikke kræver støtte fra et eksternt key/value-lager som Zookeeper (husk, der fandtes ikke etcd eller Consul på det tidspunkt). På den anden side besværligede det flytningen af tjenester. Hver gang, der blev flyttet, skulle alle klienter modtage den opdaterede YAML-fil (og potentielt genstarte). Ikke særlig praktisk!
Senere begyndte vi at implementere et nyt schema, hvor hver klient opkoblede til en lokal proxyserver. I stedet for adresse og port skulle det blot kende portnummeret for tjenesten og tilslutte sig gennem localhost. Den lokale proxyserver håndterer denne forbindelse og dirigere den til den faktiske server. Nu, når backend flyttes til en anden maskine eller skaleres, behøver man kun at opdatere alle disse lokale proksys i stedet for at opdatere alle klienter; og genstart er ikke længere nødvendig.
(Det var også planlagt at indkapsle trafik i TLS-forbindelser og tilføje endnu en proxy-server på modtagersiden samt at kontrollere TLS-certifikater uden involvering af den modtagende service, som er konfigureret til at modtage forbindelser kun til localhost. Mere om dette senere).
Det ligner meget fra Airbnb, men den væsentlige forskel er, at SmartStack er implementeret og udrullet i produktion, mens det interne rute-system fra dotCloud blev lagt i skuffen, da dotCloud blev til Docker.
Jeg anser personligt SmartStack for at være en af forkæmperne for systemer som Istio, Linkerd og Consul Connect, fordi de alle følger et bestemt mønster:
- Kørsel af proxy på hver node.
- Klienter forbinder til proxien.
- Styringslaget opdaterer konfigurationen af proxyserveren ved ændringer i backend.
- … Profit!
Moderne implementering af service mesh
Hvis vi skulle implementere et sådant netværk i dag, kunne vi anvende lignende principper. For eksempel konfigurere en intern DNS-zone, der matcher servicenavne til adresser i rummet 127.0.0.0/8. Derefter køre HAProxy på hver node i klyngen, tage imod forbindelser fra hver serviceadresse (i dette subnet 127.0.0.0/8) og omdirigere/balancere belastningen til de tilsvarende backends. Konfigurationen af HAProxy kan administreres , hvilket giver mulighed for at gemme backend-information i etcd eller Consul og automatisk pushe den opdaterede konfiguration til HAProxy, når det er nødvendigt.
Sådan fungerer Istio! Men med nogle forskelle:
- Bruger i stedet for HAProxy.
- Beholder backend-konfigurationen via Kubernetes API i stedet for etcd eller Consul.
- Tjenester tildeles adresser i det interne subnet (Kubernetes ClusterIP-adresser) i stedet for 127.0.0.0/8.
- Har en ekstra komponent (Citadel) til at tilføje gensidig TLS-godkendelse mellem klient og servere.
- Understøtter nye funktioner såsom circuit breaking, distribueret tracing, kanariefugle-udrulning og mere.
Lad os kort gennemgå nogle af forskellene.
Envoy Proxy
Envoy Proxy blev skrevet af firmaet Lyft [en konkurrent til Uber på taximarkedet — bemærkning fra oversætteren]. Den ligner meget andre proxies (f.eks. HAProxy, Nginx, Traefik…), men Lyft skrev deres eget, fordi de havde brug for funktioner, der ikke var til stede i andre proxies, og det virkede mere fornuftigt at skabe noget nyt end at udvide eksisterende.
Envoy kan bruges selvstændigt. Hvis jeg har en bestemt tjeneste, der skal forbindes til andre tjenester, kan jeg konfigurere den til at oprette forbindelse til Envoy og derefter dynamisk konfigurere og omkonfigurere Envoy med placeringen af de andre tjenester, hvilket giver mange fantastiske yderligere funktioner, såsom overvågning. I stedet for en tilpasset klientbibliotek eller integration af kode til opkalds sporingssystemet, dirigerer vi trafikken til Envoy, som indsamler metrics for os.
Men Envoy er også i stand til at fungere som data planen (data plane) til service mesh. Det betyder, at Envoy nu konfigureres for denne service mesh kontrolplanen (control plane).
Kontrolplane
På kontrolplanet afhænger Istio af Kubernetes API. Det adskiller sig ikke meget fra at bruge confd, som afhænger af etcd eller Consul for at se et sæt nøgler i datalageret. Istio gennem Kubernetes API ser et sæt Kubernetes-ressourcer.
I mellemtiden: jeg fandt denne , som siger:
Kubernetes API-serveren er en "dum server", der tilbyder lagring, versionsstyring, validering, opdatering og semantik for API-ressourcer.
Istio er designet til at arbejde med Kubernetes; og hvis du ønsker at bruge det uden for Kubernetes, skal du køre en instans af Kubernetes API-serveren (og hjælpeprogrammet etcd).
Tjenesteadresser
Istio er afhængig af ClusterIP-adresser, som tildeles af Kubernetes, så Istio-tjenester får en intern adresse (ikke i området 127.0.0.0/8).
Trafik til ClusterIP-adressen for en bestemt tjeneste i Kubernetes-klyngen uden Istio bliver opfanget af kube-proxy og sendt til backend'en for denne proxy. Hvis du er interesseret i tekniske detaljer, vejer kube-proxy iptables-regler (eller IPVS-lastefordelere, afhængigt af hvordan de er konfigureret) for at omskrive destinations-IP'erne for forbindelser, der går til ClusterIP-adressen.
Efter installation af Istio i Kubernetes-klyngen ændres der ikke noget, indtil det eksplicit aktiveres for en bestemt forbruger eller endda hele navneområdet ved at introducere en sidecar container i tilpassede pods. Denne container vil køre en instans af Envoy og installere en række iptables-regler for at opfange trafikken, der går til andre tjenester, og omdirigere denne trafik til Envoy.
Ved integration med Kubernetes DNS betyder det, at vores kode kan oprette forbindelse ved hjælp af tjenestenavn, og alt fungerer "bare". Med andre ord returnerer vores kode forespørgsler af typen http://api/v1/users/4242, så api løser det forespørgslen til 10.97.105.48, iptables-reglerne opfanger forbindelser til 10.97.105.48 og videresender dem til den lokale proxy Envoy, og denne lokale proxy vil sende forespørgslen til den faktiske API-bagved. Puh!
Yderligere features
Istio tilbyder også end-to-end kryptering og autentificering via mTLS (mutual TLS). Det styres af en komponent kaldet Citadel.
Der er også en komponent Mixer, som Envoy kan anmode om for hver forespørgslen for at træffe en særlig beslutning om denne forespørgsel baseret på forskellige faktorer såsom overskrifter, backend-belastning osv... (bare rolig: der er mange midler til at sikre, at Mixer fungerer, og selv hvis det fejler, vil Envoy fortsætte med at fungere korrekt som proxy).
Og, selvfølgelig, vi nævnte sporbarhed: Envoy indsamler en enorm mængde metrics, samtidig med at den sikrer distribueret sporing. I en mikrotjenestearkitektur, hvis en API-forespørgsel skal gå gennem mikrotjenesterne A, B, C og D, så ved indgangen til systemet tilføjer den distribuerede sporing en unik identifikator til forespørgslen og bevarer den identifikator gennem underforespørgsler til alle disse mikrotjenester, hvilket muliggør registrering af alle relaterede opkald, deres forsinkelser osv.
At udvikle eller købe
Istio har et ry for at være et komplekst system. I modsætning hertil er det relativt enkelt at opbygge det routing-netværk, jeg beskrev i begyndelsen af dette indlæg, ved hjælp af eksisterende værktøjer. Så giver det mening at bygge sin egen service mesh i stedet?
Hvis vi har beskedne behov (ikke har brug for sporbarhed, båndbro og andre nuancer), så kommer tanken om at udvikle vores eget værktøj. Men hvis vi bruger Kubernetes, kan vi endda ikke have brug for det, fordi Kubernetes allerede leverer de grundlæggende værktøjer til tjenesteopdagelse og load balancing.
Men hvis vi har avancerede krav, så virker det at "købe" en service mesh langt bedre. (Det er ikke altid helt "køb", da Istio leveres med åbent kildekode, men vi skal stadig investere ingeniørtid for at forstå, hvordan det fungerer, implementere det og administrere det).
Hvad skal man vælge: Istio, Linkerd eller Consul Connect?
Indtil videre har vi kun talt om Istio, men det er ikke den eneste service mesh. En populær alternativ er , og der er også .
Hvad skal man vælge?
Ærligt talt, jeg ved det ikke. I øjeblikket anser jeg mig ikke for at være kompetent nok til at besvare dette spørgsmål. Der er flere sammenligninger af disse værktøjer og endda .
En af de lovende tilgange er at bruge et værktøj som . Det implementerer et abstraheringslag for at forenkle og unificere de API'er, som service meshene tilbyder. I stedet for at skulle lære de specifikke (og, efter min mening, relativt komplekse) API'er fra forskellige service meshes, kan vi bruge de mere enkle konstruktioner fra SuperGloo – og nemt skifte fra den ene til den anden, som om vi har et mellemliggende konfigurationsformat, der beskriver HTTP-grænseflader og backends, i stand til at generere den faktiske konfiguration til Nginx, HAProxy, Traefik, Apache…
Jeg har leget lidt med Istio og SuperGloo, og i den næste artikel vil jeg vise, hvordan man tilføjer Istio eller Linkerd til et eksisterende cluster ved hjælp af SuperGloo, og hvordan sidstnævnte klarer sin opgave, nemlig at lade os skifte fra en service mesh til en anden uden at omskrive konfigurationerne.
Kilde: habr.com
