Op het internet (service mesh), en hier is er nog een. Hoera! Maar waarom? Omdat ik mijn mening wil delen dat het beter was geweest als service-meshes 10 jaar geleden waren verschenen, vóór de opkomst van containerplatforms zoals Docker en Kubernetes. Ik beweer niet dat mijn standpunt beter of slechter is dan andere, maar aangezien service-meshes behoorlijk complexe entiteiten zijn, zal een variëteit aan standpunten 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 problemen uitleggen waarmee we tijdens de ontwikkeling en lancering werden geconfronteerd, en hoe service-meshes hadden kunnen helpen (of niet).
Het verhaal van dotCloud
Ik heb eerder geschreven over het verhaal van dotCloud en de architectuurkeuze voor dit platform, maar ik heb weinig verteld over het netwerkniveau. Als je niet wilt lezen over dotCloud, hier is de kern: het is een platform-as-a-service PaaS dat klanten in staat stelt een breed scala aan applicaties (Java, PHP, Pythonā¦) te draaien, met ondersteuning voor een breed scala aan datadiensten (MongoDB, MySQL, Redisā¦) en een workflow zoals Heroku: je uploadt je code naar het platform, het bouwt container afbeeldingen en implementeert ze.
Ik zal uitleggen hoe het verkeer op het dotCloud-platform werd geleid. Niet omdat het bijzonder geweldig was (hoewel het in zijn tijd redelijk functioneerde!), maar vooral omdat met de moderne tools zo'n ontwerp gemakkelijk in korte tijd kan worden gerealiseerd door een bescheiden team, als ze een manier nodig hebben om verkeer tussen een wirwar van microservices of applicaties te routeren. Zo kunnen de opties worden vergeleken: wat gebeurt er als je alles zelf ontwikkelt of een bestaande service mesh gebruikt. De standaard keuze: zelf doen of kopen.
Verkeersroutering voor gehoste applicaties
Applicaties op dotCloud kunnen HTTP- en TCP-eindpunten aanbieden.
HTTP-eindpunten worden dynamisch toegevoegd aan de configuratie van de load balancer-cluster . Dit lijkt op wat vandaag de dag bronnen doen in Kubernetes en een load balancer zoals .
Klanten verbinden zich met de HTTP-eindpunten via de bijbehorende domeinen, op voorwaarde dat de domeinnaam naar de load balancers van dotCloud verwijst. Niets bijzonders.
TCP-eindpunten zijn gerelateerd aan het poortnummer, dat vervolgens aan alle containers in deze stack wordt doorgegeven via omgevingsvariabelen.
Klanten kunnen verbinding maken met TCP-eindpunten met behulp van de juiste hostnaam (iets als gateway-X.dotcloud.com) en poortnummer.
Deze hostnaam wordt opgelost naar een cluster van servers "nats" (niet gerelateerd aan ), die binnenkomende TCP-verbindingen naar de juiste container zullen routeren (of, in het geval van services met load balancing, naar de juiste containers).
Als je bekend bent met Kubernetes, doet het je waarschijnlijk denken aan services .
Op het dotCloud-platform was er geen equivalent van services : voor de eenvoud vond toegang tot services zowel intern als extern op dezelfde manier plaats.
Alles was vrij eenvoudig georganiseerd: de oorspronkelijke implementaties van HTTP- en TCP-routingnetwerken, waarschijnlijk slechts enkele honderden regels Python. Eenvoudige (ik zou zeggen, naĆÆeve) algoritmen die zijn verfijnd met de groei van het platform en de opkomst van extra vereisten.
Er was geen uitgebreide refactoring van de bestaande code nodig. In het bijzonder, kunnen rechtstreeks gebruik maken van het adres dat via omgevingsvariabelen is verkregen.
Hoe verschilt dit van moderne service meshes?
Beperkte zichtbaarheid. We hadden helemaal geen metrics voor de TCP-routing. Wat betreft HTTP-routing, verschenen in latere versies gedetailleerde HTTP-metrics met foutcodes en responstijden, maar moderne service meshes gaan nog verder door integratie met metrics verzamelingssystemen zoals Prometheus te bieden.
Zichtbaarheid is niet alleen belangrijk vanuit operationeel oogpunt (om te helpen bij probleemoplossing), maar ook bij het uitrollen van nieuwe functies. Het gaat om veilige en .
Routing efficiƫntie is also limited. In the dotCloud routing mesh, all traffic had to go through a cluster of dedicated routing nodes. This meant a potential crossing of multiple AZ (availability zone) boundaries and a significant increase in latency. I remember troubleshooting code that made over a hundred SQL queries per page and opened a new connection to the SQL server for each query. When running locally, the page loads instantly, but on dotCloud, it takes several seconds because each TCP connection (and subsequent SQL query) takes tens of milliseconds. In this particular case, persistent connections solved the problem.
Modern service meshes handle such issues better. First of all, they check that the connections are routed from the source. The logical flow is the same: client ā mesh ā service, but now the mesh operates locally rather than on remote nodes, so the connection client ā mesh is local and extremely fast (microseconds instead of milliseconds).
Modern service meshes also implement smarter load balancing algorithms. By monitoring the health of backends, they can send more traffic to faster backends, resulting in improved overall performance.
Beveiliging is also better. The dotCloud routing mesh operated entirely on EC2 Classic and did not encrypt traffic (based on the assumption that if someone managed to place a sniffer on the EC2 network traffic, you already have big problems). Modern service meshes transparently protect all our traffic, for example, with mutual TLS authentication and subsequent encryption.
Traffic routing for platform services
Alright, weāve discussed traffic between applications, but what about the dotCloud platform itself?
The platform itself consisted of about a hundred microservices responsible for various functions. Some accepted requests from others, while some were background workers that connected to other services but did not accept connections themselves. In any case, each service needs to know the endpoint addresses it should connect to.
Veel hoogwaardige diensten kunnen het routeringsnetwerk gebruiken dat hierboven is beschreven. In feite zijn veel van de meer dan honderd microservices van dotCloud uitgerold als reguliere applicaties op het dotCloud-platform zelf. Maar een klein aantal laagdrempelige diensten (met name diegene die dit routeringsnetwerk implementeren) hadden iets eenvoudigers nodig, met minder afhankelijkheden (aangezien ze niet van zichzelf afhankelijk konden zijn - het oude goede probleem van de kip en het ei).
Deze laagdrempelige, belangrijke diensten zijn uitgerold door containers direct op verschillende belangrijke knooppunten te lanceren. Bij dit proces werden de standaardplatformdiensten niet gebruikt: de orchestrator, scheduler en runner. Als je het wilt vergelijken met moderne containerplatforms, lijkt het op het draaien van een controlepaneel met docker run direct op de knooppunten, in plaats van de taak aan Kubernetes te delegeren. Dit lijkt behoorlijk op het concept van , dat gebruikt wordt door of bij het opstarten van een autonome cluster.
Deze diensten werden op een eenvoudige en ruwe manier geconfigureerd: hun namen en adressen werden in een YAML-bestand vermeld; elke client moest een kopie van dit YAML-bestand ophalen voor implementatie.
Aan de ene kant is dit uiterst betrouwbaar, omdat het geen externe sleutel/waarde-opslag zoals Zookeeper vereist (vergeet niet dat etcd of Consul toen nog niet bestonden). Aan de andere kant bemoeilijkt het het verplaatsen van diensten. Elke keer dat ze verplaatst werden, moesten alle clients het bijgewerkte YAML-bestand krijgen (en mogelijk opnieuw opstarten). Niet erg handig!
Later begonnen we een nieuw schema te implementeren, waarbij elke client verbinding maakte met een lokale proxyserver. In plaats van het adres en de poort is het voldoende om alleen het servicenummer te weten en verbinding te maken via localhost. De lokale proxyserver verwerkt deze verbinding en leidt deze door naar de daadwerkelijke server. Nu hoeft bij het verplaatsen van de backend naar een andere machine of het schalen alleen deze lokale proxies bijgewerkt te worden; en herstarten is niet meer nodig.
(Ook was het gepland om verkeer in TLS-verbindingen te encapsuleren en een extra proxyserver aan de ontvangende kant te plaatsen, evenals de TLS-certificaten te controleren zonder de tussenkomst van de ontvangende dienst, die is ingesteld om verbindingen alleen te accepteren op localhost).
Dit lijkt erg op van Airbnb, maar het belangrijke verschil is dat SmartStack is gerealiseerd en uitgerold in productie, terwijl het interne routeringssysteem van dotCloud in de la is gestopt toen dotCloud veranderde in Docker.
Persoonlijk beschouw ik SmartStack als een van de voorlopers van systemen zoals Istio, Linkerd en Consul Connect, omdat ze allemaal ƩƩn patroon volgen:
- Een proxy op elke node draaien.
- Klanten verbinden met de proxy.
- Het beheerlaag werkt de configuratie van de proxyserver bij wanneer de backends veranderen.
- ⦠Profit!
Moderne implementatie van service mesh
Als we vandaag zo'n mesh willen implementeren, kunnen we soortgelijke principes gebruiken. Bijvoorbeeld een interne DNS-zone instellen die servicenamen koppelt aan adressen in het 127.0.0.0/8. Vervolgens HAProxy op elke node in de cluster draaien, verbindingen accepteren op elk service-adres (in dit subnet 127.0.0.0/8) en verkeer omleiden/balanceren naar de bijbehorende backends. De configuratie van HAProxy kan worden beheerd , waardoor backend-informatie in etcd of Consul kan worden opgeslagen en de bijgewerkte configuratie automatisch naar HAProxy kan worden gepusht wanneer dat nodig is.
Zo werkt Istio ongeveer! Maar met enkele verschillen:
- Een van de lokale cloudproviders in de VS gebruikt in plaats van HAProxy.
- Beheert backend-configuratie via de Kubernetes API in plaats van etcd of Consul.
- Diensten krijgen adressen in het interne subnet (Kubernetes ClusterIP-adressen) in plaats van 127.0.0.0/8.
- Heeft een extra component (Citadel) voor het toevoegen van wederzijdse TLS-authenticatie tussen client en servers.
- Ondersteunt nieuwe functies zoals circuit breaking, gedistribueerde tracing, canary deployments, enz.
Laten we kort enkele verschillen bekijken.
Envoy Proxy
Envoy Proxy is ontwikkeld door Lyft [een concurrent van Uber op de taximarkt ā vert.]. Het lijkt in veel opzichten op andere proxies (zoals HAProxy, Nginx, Traefikā¦), maar Lyft heeft hun versie geschreven omdat ze functies nodig hadden die ontbreken in andere proxies, en het leek verstandiger een nieuwe te maken dan de bestaande uit te breiden.
Envoy kan op zichzelf gebruikt worden. Als ik een specifieke dienst heb die verbinding moet maken met andere diensten, kan ik deze configureren om verbinding te maken met Envoy, en vervolgens Envoy dynamisch instellen en opnieuw instellen met de locaties van andere diensten, terwijl ik veel geweldige extra functies krijg, zoals zichtbaarheid. In plaats van een aangepaste clientbibliotheek of het implementeren van traceerkode, leiden we het verkeer naar Envoy, die metrics voor ons verzamelt.
Maar Envoy kan ook functioneren als datavlak (data plane) voor service mesh. Dit betekent dat Envoy voor deze service mesh nu kan worden ingesteld beheerlaag (control plane).
Beheerlaag
In de beheerlaag vertrouwt Istio op de Kubernetes API. Dit verschilt niet veel van het gebruik van confd, dat vertrouwt op etcd of Consul voor het bekijken van een set sleutels in de datastore. Istio bekijkt een set Kubernetes-bronnen via de Kubernetes API.
Tussen haakjes: persoonlijk vond ik deze , die luidt:
De Kubernetes API-server is een "domme server" die opslag, versiebeheer, validatie, upgrade en semantiek van API-resources biedt.
Istio is ontworpen om met Kubernetes te werken; en als je het buiten Kubernetes wilt gebruiken, moet je een exemplaar van de Kubernetes API-server (en de ondersteunende etcd-dienst) draaien.
Serviceadressen
Istio vertrouwt op de ClusterIP-adressen die door Kubernetes worden toegewezen, zodat Istio-diensten een intern adres krijgen (niet in het bereik 127.0.0.0/8).
Verkeer naar het ClusterIP-adres voor een specifieke dienst in de Kubernetes-cluster zonder Istio wordt onderschept door kube-proxy en verzonden naar de backend van deze proxy. Als je geĆÆnteresseerd bent in technische details, stelt kube-proxy iptables-regels in (of IPVS-load balancers, afhankelijk van hoe het is ingesteld) om de bestemmings-IP-adressen van verbindingen die naar het ClusterIP-adres gaan, opnieuw te schrijven.
Na de installatie van Istio in de Kubernetes-cluster verandert er niets totdat het expliciet wordt ingeschakeld voor een bepaalde consument of zelfs de hele namespace, door een sidecar container in aangepaste pods in te voeren. Deze container start een exemplaar van Envoy en stelt een reeks iptables-regels in om verkeer dat naar andere diensten gaat, te onderscheppen en dat verkeer naar Envoy te omleiden.
Bij de integratie met Kubernetes DNS betekent dit dat onze code verbinding kan maken via de servicenaam, en alles "werkt gewoon". Met andere woorden, onze code doet verzoeken zoals http://api/v1/users/4242, dan api wordt het verzoek opgelost naar 10.97.105.48, iptables regels onderscheppen verbindingen met 10.97.105.48 en leiden deze om naar een lokale Envoy proxy, en deze lokale proxy zal het verzoek doorsturen naar de daadwerkelijke API backend. Phew!
Extra's
Istio biedt ook end-to-end encryptie en authenticatie via mTLS (mutual TLS). Dit wordt verzorgd door een component genaamd , ā die gezamenlijk de Envoys configureren voor traffic routing, beleidsregels toepassen en telemetriegegevens verzamelen. Diagrammatig ziet dit er als volgt uit:.
Er is ook een component Citadel, die Envoy kan aanroepen voor van elke een verzoek, om een speciale beslissing over dit verzoek te nemen afhankelijk van verschillende factoren, zoals headers, backendbelasting, enz. (Maak je geen zorgen: er zijn veel middelen om de werking van Mixer te waarborgen, en zelfs als deze faalt, blijft Envoy normaal functioneren als proxy).
En natuurlijk hebben we het gehad over observability: Envoy verzamelt een enorme hoeveelheid metrics en voorziet tegelijkertijd in gedistribueerde tracing. In een microservicesarchitectuur, als een API-verzoek door microservices A, B, C en D moet gaan, voegt gedistribueerde tracing een unieke identificatie toe aan het verzoek en behoudt deze identificatie door subverzoeken naar al deze microservices, waardoor alle gerelateerde oproepen, hun vertragingen, enz. kunnen worden vastgelegd.
Ontwikkelen of kopen
Istio heeft de reputatie een complex systeem te zijn. In tegenstelling tot dat is het bouwen van de routingmesh die ik aan het begin van deze post heb beschreven relatief eenvoudig met bestaande tools. Dus, is er een reden om in plaats daarvan een eigen service mesh te creƫren?
Als we bescheiden behoeften hebben (geen behoefte aan observability, circuit breaker en andere nuances), dan komt de gedachte op om een eigen tool te ontwikkelen. Maar als we Kubernetes gebruiken, is deze mogelijk helemaal niet nodig, omdat Kubernetes al basis tools voor service discovery en load balancing biedt.
Maar als we geavanceerde eisen hebben, lijkt het "aankopen" van een service mesh een veel betere optie. (Het is niet altijd letterlijk "aankopen", aangezien Istio open-source is, maar we moeten nog steeds engineeringtijd investeren om het te begrijpen, te implementeren en te beheren).
Wat te kiezen: Istio, Linkerd of Consul Connect?
Tot nu toe hebben we alleen over Istio gesproken, maar dit is niet de enige service mesh. Een populaire alternatief is , en er is ook nog .
Wat te kiezen?
Eerlijk gezegd weet ik het niet. Op dit moment beschouw ik mezelf niet als voldoende bekwaam om deze vraag te beantwoorden. Er zijn verschillende vergelijkingen van deze tools en zelfs .
Een van de veelbelovende benaderingen is het gebruik van een tool als . Het implementeert een abstractielaag om de API's die door service meshes worden aangeboden te vereenvoudigen en te uniformiseren. In plaats van de specifieke (en naar mijn mening relatief complexe) API's van verschillende service meshes te bestuderen, kunnen we eenvoudigere constructies van SuperGloo gebruiken - en gemakkelijk schakelen van de ene naar de andere, alsof we een tussenformaat van configuratie hebben die HTTP-interfaces en backends beschrijft, in staat om de daadwerkelijke configuratie voor Nginx, HAProxy, Traefik, Apache⦠te genereren.
Ik heb een beetje geƫxperimenteerd met Istio en SuperGloo, en in het volgende artikel wil ik laten zien hoe je Istio of Linkerd aan een bestaand cluster kunt toevoegen met behulp van SuperGloo, en hoe de laatste zijn werk doet, dat wil zeggen schakelen van de ene service mesh naar de andere zonder configuraties opnieuw te schrijven.
Bron: habr.com
