Containers, microservices en servicemeshes

Op internet hoop artikels о service mazen (servicegaas), en hier is er nog een. Hoera! Maar waarom? Vervolgens wil ik mijn mening geven dat service meshes 10 jaar geleden beter waren geweest, vóór de komst van containerplatforms zoals Docker en Kubernetes. Ik zeg niet dat mijn standpunt beter of slechter is dan dat van anderen, maar aangezien dienstnetwerken vrij complexe dieren zijn, zullen meerdere gezichtspunten helpen om ze beter te begrijpen.

Ik zal het hebben over het dotCloud-platform, dat is gebouwd op meer dan honderd microservices en duizenden applicaties in containers ondersteunde. Ik zal de uitdagingen uitleggen die we tegenkwamen bij het ontwikkelen en lanceren ervan, en hoe servicemeshes al dan niet kunnen helpen.

dotcloud-geschiedenis

Ik heb al geschreven over de geschiedenis van dotCloud en de architectuurkeuze voor dit platform, maar heb niet veel gesproken over de netwerklaag. Als je niet wilt lezen laatste artikel over dotCloud, hier is de essentie: het is een PaaS-platform-as-a-service waarmee klanten een breed scala aan applicaties kunnen uitvoeren (Java, PHP, Python...), met ondersteuning voor een breed scala aan dataservices ( MongoDB, MySQL, Redis...) en een workflow zoals Heroku: u uploadt uw code naar het platform, het bouwt containerimages en implementeert ze.

Ik zal je vertellen hoe verkeer naar het dotCloud-platform werd gestuurd. Niet omdat het bijzonder cool was (hoewel het systeem voor zijn tijd goed werkte!), maar vooral omdat een dergelijk ontwerp met behulp van moderne tools in korte tijd eenvoudig kan worden geïmplementeerd door een bescheiden team als ze een manier nodig hebben om te routeren verkeer tussen een stel microservices of een stel applicaties. Zo kun je opties vergelijken: wat gebeurt er als je alles zelf ontwikkelt of een bestaand servicemesh gebruikt. Standaard keuze: zelf maken of kopen.

Verkeersroutering voor gehoste applicaties

Applicaties op dotCloud kunnen HTTP- en TCP-eindpunten blootleggen.

HTTP-eindpunten dynamisch toegevoegd aan de load balancer-clusterconfiguratie Heuppijn. Dit is vergelijkbaar met wat bronnen vandaag doen Ingress in Kubernetes en een load balancer zoals Traefik.

Clients maken verbinding met HTTP-eindpunten via de juiste domeinen, op voorwaarde dat de domeinnaam verwijst naar dotCloud-loadbalancers. Niets speciaals.

TCP-eindpunten gekoppeld aan een poortnummer, dat vervolgens wordt doorgegeven aan alle containers in die stapel via omgevingsvariabelen.

Clients kunnen verbinding maken met TCP-eindpunten met behulp van de juiste hostnaam (zoiets als gateway-X.dotcloud.com) en poortnummer.

Deze hostnaam wordt omgezet in het "nats"-servercluster (niet gerelateerd aan NATS) die binnenkomende TCP-verbindingen naar de juiste container leidt (of, in het geval van load-balanced services, naar de juiste containers).

Als u bekend bent met Kubernetes, zal dit u waarschijnlijk aan services doen denken Knooppuntpoort.

Er was geen gelijkwaardige service op het dotCloud-platform ClusterIP: voor de eenvoud gebeurde de toegang tot diensten zowel van binnen als buiten het platform op dezelfde manier.

Alles was vrij eenvoudig georganiseerd: de oorspronkelijke implementaties van HTTP- en TCP-routeringsnetwerken bestonden waarschijnlijk uit slechts een paar honderd regels Python. Eenvoudige (ik zou zeggen naïeve) algoritmen die zijn verfijnd met de groei van het platform en de opkomst van aanvullende vereisten.

Uitgebreide refactoring van bestaande code was niet nodig. In het bijzonder, 12 Factor-apps kan het via omgevingsvariabelen verkregen adres direct gebruiken.

Hoe verschilt dit van een moderne servicemesh?

Beperkt zichtbaarheid. We hadden helemaal geen statistieken voor de TCP-routeringsmesh. Als het gaat om HTTP-routing, hebben recentere versies gedetailleerde HTTP-statistieken met foutcodes en responstijden, maar moderne servicemeshes gaan zelfs nog verder en bieden integratie met meetsystemen zoals Prometheus, bijvoorbeeld.

Zichtbaarheid is niet alleen belangrijk vanuit operationeel oogpunt (om problemen op te lossen), maar ook wanneer er nieuwe functies worden uitgebracht. Over veilig gesproken blauw-groene inzet и inzet van kanaries.

Routeringsefficiëntie is ook beperkt. In de dotCloud-routeringsmesh moest al het verkeer door een cluster van speciale routeringsknooppunten gaan. Dit betekende mogelijk het overschrijden van meerdere AZ-grenzen (beschikbaarheidszones) en een aanzienlijke toename van de latentie. Ik herinner me probleemoplossingscode die meer dan honderd SQL-query's per pagina deed en voor elke query een nieuwe verbinding met de SQL-server opende. Wanneer deze lokaal wordt uitgevoerd, wordt de pagina onmiddellijk geladen, maar op dotCloud duurt het een paar seconden om te laden omdat elke TCP-verbinding (en daaropvolgende SQL-query) tientallen milliseconden in beslag neemt. In dit specifieke geval losten aanhoudende verbindingen het probleem op.

Moderne servicenetwerken zijn beter in het omgaan met dergelijke problemen. Allereerst controleren ze of de verbindingen gerouteerd zijn in bron. De logische stroom is hetzelfde: клиент → меш → сервис, maar nu werkt de mesh lokaal en niet op externe knooppunten, dus de verbinding клиент → меш is lokaal en zeer snel (microseconden in plaats van milliseconden).

Moderne servicemeshes implementeren ook slimmere load balancing-algoritmen. Door de gezondheid van de backends te bewaken, kunnen ze meer verkeer naar snellere backends sturen, wat resulteert in betere algehele prestaties.

veiligheid is ook beter. De dotCloud-routeringsmesh draaide volledig op EC2 Classic en versleutelde het verkeer niet (in de veronderstelling dat als iemand erin slaagt een sniffer op EC2-netwerkverkeer te zetten, je al in grote problemen zit). Moderne service meshes beschermen al ons verkeer op een transparante manier, bijvoorbeeld met wederzijdse TLS-authenticatie en aansluitende encryptie.

Verkeersroutering voor platformservices

Oké, we hebben het verkeer tussen applicaties besproken, maar hoe zit het met het dotCloud-platform zelf?

Het platform zelf bestond uit een honderdtal microservices die verantwoordelijk waren voor verschillende functies. Sommigen accepteerden verzoeken van anderen en sommigen waren achtergrondwerkers die verbinding maakten met andere diensten, maar zelf geen verbindingen accepteerden. In beide gevallen moet elke service de eindpunten kennen van de adressen waarmee verbinding moet worden gemaakt.

Veel services op hoog niveau kunnen gebruikmaken van de hierboven beschreven routeringsmesh. Veel van de meer dan XNUMX dotCloud-microservices zijn zelfs ingezet als reguliere applicaties op het dotCloud-platform zelf. Maar een klein aantal services op laag niveau (met name degenen die deze routeringsmesh implementeren) had iets eenvoudiger nodig, met minder afhankelijkheden (omdat ze niet op zichzelf konden vertrouwen om te werken - het goede oude kip-en-ei-probleem).

Deze essentiële services op laag niveau werden geïmplementeerd door containers rechtstreeks op een paar belangrijke knooppunten uit te voeren. Tegelijkertijd waren de standaard platformdiensten niet betrokken: linker, planner en runner. Als je het wilt vergelijken met moderne containerplatforms, is het alsof je een besturingsvliegtuig lanceert docker run rechtstreeks op de knooppunten, in plaats van de taak te delegeren aan Kubernetes. Het is vrij gelijkaardig in concept statische modules (pods), die gebruikt Kubeadm of bootkube bij het opstarten van een zelfstandig cluster.

Deze services werden op een eenvoudige en grove manier ontmaskerd: een YAML-bestand vermeldde hun namen en adressen; en elke klant moest een kopie van dit YAML-bestand maken voor implementatie.

Aan de ene kant is dit uiterst betrouwbaar, omdat het geen ondersteuning van een externe sleutel/waarde-opslag vereist, zoals Zookeeper (onthoud dat etcd of Consul toen nog niet bestond). Aan de andere kant maakte het het moeilijk om diensten te verplaatsen. Elke keer dat er een beweging werd gemaakt, moesten alle klanten een bijgewerkt YAML-bestand krijgen (en mogelijk opnieuw laden). Niet erg comfortabel!

Vervolgens begonnen we een nieuw schema te implementeren, waarbij elke client verbinding maakte met een lokale proxyserver. In plaats van het adres en de poort hoeft het alleen het poortnummer van de service te weten en verbinding te maken via localhost. De lokale proxy handelt deze verbinding af en stuurt deze door naar de eigenlijke server. Bij het verplaatsen van de backend naar een andere machine of bij schaalvergroting hoeven nu alleen al deze lokale proxy's te worden bijgewerkt in plaats van alle clients bij te werken; en opnieuw opstarten is niet langer nodig.

(Het was ook de bedoeling om verkeer in TLS-verbindingen in te kapselen en een andere proxyserver aan de ontvangende kant te installeren, evenals TLS-certificaten te controleren zonder de deelname van de ontvangende service, die is geconfigureerd om alleen verbindingen te accepteren op localhost. Daarover later meer).

Dit lijkt erg op slimme stapel van Airbnb, maar het grote verschil is dat SmartStack is geïmplementeerd en ingezet voor productie, terwijl het interne routeringssysteem van dotCloud in de war raakte toen dotCloud in Docker veranderde.

Persoonlijk beschouw ik SmartStack als een van de voorlopers van systemen als Istio, Linkerd en Consul Connect omdat ze allemaal hetzelfde patroon volgen:

  • Voer een proxy uit op elk knooppunt.
  • Clients maken verbinding met de proxy.
  • Het besturingsvlak werkt de proxyconfiguratie bij wanneer backends veranderen.
  • … Winst!

Moderne Service Mesh-implementatie

Als we vandaag een soortgelijk raster moeten implementeren, kunnen we vergelijkbare principes gebruiken. Stel bijvoorbeeld een interne DNS-zone in door servicenamen toe te wijzen aan adressen in de ruimte 127.0.0.0/8. Voer vervolgens HAProxy uit op elk clusterknooppunt en accepteer verbindingen op elk serviceadres (op dat subnet 127.0.0.0/8) en het omleiden/balanceren van de belasting naar de juiste backends. HAProxy-configuratie kan worden beheerd conf, waardoor u backend-informatie kunt opslaan in etcd of Consul en automatisch de bijgewerkte configuratie naar HAProxy kunt pushen wanneer dat nodig is.

Zo werkt Istio! Maar met enkele verschillen:

  • спользует gezant volmacht in plaats van HAProxy.
  • Slaat backend-configuratie op via Kubernetes API in plaats van etcd of Consul.
  • Services krijgen adressen toegewezen op het interne subnet (Kubernetes ClusterIP-adressen) in plaats van 127.0.0.0/8.
  • Heeft een extra component (Citadel) om wederzijdse TLS-authenticatie tussen client en servers toe te voegen.
  • Ondersteunt nieuwe functies zoals circuitonderbreking, gedistribueerde tracering, canary-implementatie, enz.

Laten we even kijken naar enkele verschillen.

gezant volmacht

Envoy Proxy is geschreven door Lyft [Uber's concurrent op de taximarkt - ca. per.]. Het is in veel opzichten vergelijkbaar met andere proxy's (bijv. HAProxy, Nginx, Traefik...), maar Lyft heeft er zelf een geschreven omdat ze functies nodig hadden die andere proxy's niet hebben en het leek verstandiger om een ​​nieuwe te maken in plaats van uit te breiden een bestaande.

Envoy kan op zichzelf worden gebruikt. Als ik een specifieke service heb die verbinding moet maken met andere services, kan ik deze instellen om verbinding te maken met Envoy en vervolgens Envoy dynamisch configureren en opnieuw configureren met de locatie van andere services, terwijl ik veel geweldige extra's krijg, zoals zichtbaarheid. In plaats van een aangepaste clientbibliotheek of injectie in de code voor het traceren van oproepen, sturen we verkeer naar Envoy en verzamelt het statistieken voor ons.

Maar Envoy kan ook werken als gegevens vliegtuig (gegevensvlak) voor de servicemesh. Dit betekent dat nu voor deze service mesh Envoy is geconfigureerd controle vliegtuig (stuurvlak).

Controle vliegtuig

In het besturingsvlak vertrouwt Istio op de Kubernetes API. Dit verschilt niet veel van het gebruik van confd, die vertrouwt op etcd of Consul om een ​​set sleutels op te zoeken in de datastore. Istio doorzoekt een set Kubernetes-resources via de Kubernetes API.

Tussen dit en toen: Ik vond dit persoonlijk nuttig Beschrijving van de Kubernetes-APIdie luidt:

De Kubernetes API Server is een "stomme server" die opslag, versiebeheer, validatie, updates en semantiek van API-bronnen biedt.

Istio is ontworpen om met Kubernetes te werken; en als u het buiten Kubernetes wilt gebruiken, moet u een exemplaar van de Kubernetes API-server (en de etcd-helperservice) starten.

Service Adressen

Istio vertrouwt op de ClusterIP-adressen die Kubernetes toewijst, dus Istio-services krijgen een intern adres (niet in het bereik 127.0.0.0/8).

Verkeer naar een ClusterIP-adres voor een specifieke service in een Kubernetes-cluster zonder Istio wordt onderschept door kube-proxy en verzonden naar de backend van de proxy. Als u geïnteresseerd bent in de technische details, stelt kube-proxy iptables-regels in (of IPVS-loadbalancers, afhankelijk van hoe deze is geconfigureerd) om de doel-IP-adressen te herschrijven van verbindingen die naar het ClusterIP-adres gaan.

Zodra Istio op een Kubernetes-cluster is geïnstalleerd, verandert er niets totdat het expliciet wordt ingeschakeld voor een bepaalde consument, of zelfs voor de hele naamruimte, door een container te introduceren sidecar naar aangepaste pods. Deze container start een Envoy-instantie en stelt een reeks iptables-regels in om verkeer naar andere services te onderscheppen en dat verkeer om te leiden naar Envoy.

Wanneer geïntegreerd met Kubernetes DNS, betekent dit dat onze code verbinding kan maken op servicenaam en dat alles "gewoon werkt". Met andere woorden, onze code geeft query's uit zoals http://api/v1/users/4242, togda api het verzoek om oplossen 10.97.105.48, onderscheppen de iptables-regels verbindingen van 10.97.105.48 en leiden ze om naar de lokale Envoy-proxy, die het verzoek doorstuurt naar de eigenlijke API-backend. Opluchting!

Extra franje

Istio biedt ook end-to-end encryptie en authenticatie via mTLS (mutual TLS). Het onderdeel belde Citadel.

Er is ook een onderdeel Menger, waar de gezant om kan vragen elke verzoek om een ​​speciale beslissing te nemen over dat verzoek, afhankelijk van verschillende factoren zoals headers, laden van backend, enz... (maak je geen zorgen: er zijn veel tools om Mixer aan het werk te houden, en zelfs als het crasht, blijft Envoy werken als gevolmachtigde).

En natuurlijk hadden we het over zichtbaarheid: Envoy verzamelt een enorme hoeveelheid statistieken terwijl het gedistribueerde tracering biedt. Als in een microservices-architectuur een enkel API-verzoek door microservices A, B, C en D moet gaan, zal gedistribueerde tracering bij het inloggen een unieke identificatie aan het verzoek toevoegen en deze identificatie opslaan via subverzoeken voor al deze microservices, waardoor u om alle gerelateerde oproepen, hun vertragingen, enz.

Ontwikkelen of kopen

Istio heeft de reputatie een complex systeem te zijn. Het bouwen van de routing mesh die ik aan het begin van dit bericht heb beschreven, is daarentegen relatief eenvoudig met bestaande tools. Dus, heeft het zin om in plaats daarvan uw eigen servicemesh te maken?

Als we bescheiden behoeften hebben (we hebben geen zichtbaarheid, een stroomonderbreker en andere subtiliteiten nodig), dan komen er gedachten over het ontwikkelen van onze eigen tool. Maar als we Kubernetes gebruiken, is het misschien niet eens nodig omdat Kubernetes al de basistools biedt voor servicedetectie en taakverdeling.

Maar als we geavanceerde vereisten hebben, lijkt het "kopen" van een servicemesh een veel betere optie. (Dit is niet altijd een "aankoop", aangezien Istio open source is, maar we moeten nog steeds technische tijd investeren om het te begrijpen, te implementeren en te beheren.)

Wat te kiezen: Istio, Linkerd of Consul Connect?

Tot nu toe hebben we het alleen over Istio gehad, maar het is niet de enige service-mesh. Het populaire alternatief is Linkerd, maar er is meer Consulaat Connect.

Wat te kiezen?

Eerlijk gezegd weet ik het niet. Op dit moment acht ik mezelf niet competent genoeg om deze vraag te beantwoorden. Er zijn een paar interessant artikels met een vergelijking van deze tools en zelfs benchmarks.

Een veelbelovende aanpak is het gebruik van een tool zoals supergloo. Het implementeert een abstractielaag om de API's van servicemeshes te vereenvoudigen en te verenigen. In plaats van de specifieke (en naar mijn mening relatief complexe) API's van verschillende service-meshes te leren, kunnen we de eenvoudigere SuperGloo-constructies gebruiken - en gemakkelijk van de ene naar de andere overschakelen, alsof we een tussenliggend configuratieformaat hebben dat HTTP-interfaces en backends beschrijft in staat om de daadwerkelijke configuratie te genereren voor Nginx, HAProxy, Traefik, Apache...

Ik heb een beetje gespeeld met Istio en SuperGloo, en in het volgende artikel wil ik laten zien hoe je Istio of Linkerd kunt toevoegen aan een bestaand cluster met behulp van SuperGloo, en hoe de laatste zijn werk zal doen, dat wil zeggen, je kunt overschakelen van het ene servicemesh naar het andere zonder configuraties te herschrijven.

Bron: www.habr.com

Voeg een reactie