Contenitori, microservizi e service mesh

Su internet mucchio articoli о rete di servizi (service mesh), ed eccone un altro. Evviva! Ma perché? Poi, voglio esprimere la mia opinione che sarebbe stato meglio se le mesh di servizi fossero apparse 10 anni fa, prima dell'avvento delle piattaforme container come Docker e Kubernetes. Non sto dicendo che il mio punto di vista sia migliore o peggiore di altri, ma poiché le service mesh sono animali piuttosto complessi, più punti di vista aiuteranno a comprenderli meglio.

Parlerò della piattaforma dotCloud, che è stata costruita su oltre un centinaio di microservizi e supportava migliaia di applicazioni containerizzate. Spiegherò le sfide che abbiamo dovuto affrontare nello sviluppo e nel lancio e in che modo le mesh di servizi potrebbero (o non potrebbero) essere d'aiuto.

Storia di dotCloud

Ho scritto della storia di dotCloud e delle scelte architetturali per questa piattaforma, ma non ho parlato molto del livello di rete. Se non vuoi tuffarti nella lettura ultimo articolo riguardo a dotCloud, ecco il succo in poche parole: si tratta di una piattaforma PaaS-as-a-service che consente ai clienti di eseguire un'ampia gamma di applicazioni (Java, PHP, Python...), con supporto per un'ampia gamma di dati servizi (MongoDB, MySQL, Redis...) e un flusso di lavoro come Heroku: carichi il tuo codice sulla piattaforma, crea immagini del contenitore e le distribuisce.

Ti dirò come il traffico è stato indirizzato alla piattaforma dotCloud. Non perché fosse particolarmente interessante (sebbene il sistema funzionasse bene per l'epoca!), ma soprattutto perché con gli strumenti moderni un progetto del genere può essere facilmente implementato in breve tempo da un team modesto se hanno bisogno di un modo per instradare il traffico tra un gruppo di microservizi o un insieme di applicazioni. In questo modo puoi confrontare le opzioni: cosa succede se sviluppi tutto da solo o utilizzi una rete di servizi esistente. La scelta standard è realizzarlo da soli o acquistarlo.

Instradamento del traffico per le applicazioni ospitate

Le applicazioni su dotCloud possono esporre endpoint HTTP e TCP.

Endpoint HTTP aggiunto dinamicamente alla configurazione del cluster del sistema di bilanciamento del carico hipache. Questo è simile a ciò che fanno le risorse oggi Ingresso in Kubernetes e un bilanciatore del carico simile Traefik.

I client si connettono agli endpoint HTTP tramite domini appropriati, a condizione che il nome del dominio punti ai bilanciatori del carico dotCloud. Niente di speciale.

Endpoint TCP associato a un numero di porta, che viene poi passato a tutti i contenitori in quello stack tramite variabili di ambiente.

I client possono connettersi agli endpoint TCP utilizzando il nome host appropriato (qualcosa come gateway-X.dotcloud.com) e il numero di porta.

Questo nome host si risolve nel cluster di server "nats" (non correlato a NATS), che instraderà le connessioni TCP in entrata al contenitore corretto (o, nel caso di servizi con carico bilanciato, ai contenitori corretti).

Se hai familiarità con Kubernetes, questo probabilmente ti ricorderà Services NodoPort.

Non c'erano servizi equivalenti sulla piattaforma dotCloud IP cluster: Per semplicità, l'accesso ai servizi è avvenuto allo stesso modo sia dall'interno che dall'esterno della piattaforma.

Tutto era organizzato in modo abbastanza semplice: le implementazioni iniziali delle reti di routing HTTP e TCP erano probabilmente solo poche centinaia di righe di Python ciascuna. Algoritmi semplici (direi ingenui) che sono stati perfezionati man mano che la piattaforma cresceva e apparivano requisiti aggiuntivi.

Non è stato necessario un ampio refactoring del codice esistente. In particolare, App a 12 fattori può utilizzare direttamente l'indirizzo ottenuto tramite variabili di ambiente.

In cosa differisce da una moderna rete di servizi?

Limitato visibilità. Non avevamo alcuna metrica per la mesh di routing TCP. Quando si tratta di routing HTTP, le versioni successive hanno introdotto metriche HTTP dettagliate con codici di errore e tempi di risposta, ma le moderne mesh di servizi vanno oltre, fornendo integrazione con sistemi di raccolta di metriche come Prometheus, ad esempio.

La visibilità è importante non solo dal punto di vista operativo (per aiutare a risolvere i problemi), ma anche quando si rilasciano nuove funzionalità. Si tratta di sicurezza schieramento blu-verde и schieramento canarino.

Efficienza del percorso è anche limitato. Nella rete di routing dotCloud, tutto il traffico doveva passare attraverso un cluster di nodi di routing dedicati. Ciò significava potenzialmente attraversare più confini AZ (Availability Zone) e aumentare significativamente la latenza. Ricordo di aver risolto i problemi del codice che eseguiva oltre un centinaio di query SQL per pagina e di aprire una nuova connessione al server SQL per ogni query. Quando viene eseguita localmente, la pagina si carica istantaneamente, ma in dotCloud il caricamento richiede alcuni secondi perché ogni connessione TCP (e la successiva query SQL) impiega decine di millisecondi. In questo caso particolare, le connessioni persistenti hanno risolto il problema.

Le moderne mesh di servizi sono più efficaci nel gestire tali problemi. Innanzitutto controllano che le connessioni vengano instradate nella fonte. Il flusso logico è lo stesso: клиент → меш → сервис, ma ora la mesh funziona localmente e non su nodi remoti, quindi la connessione клиент → меш è locale e molto veloce (microsecondi invece di millisecondi).

Le moderne mesh di servizi implementano anche algoritmi di bilanciamento del carico più intelligenti. Monitorando lo stato dei backend, possono inviare più traffico a backend più veloci, con conseguente miglioramento delle prestazioni complessive.

sicurezza anche meglio. La mesh di routing dotCloud funzionava interamente su EC2 Classic e non crittografava il traffico (sulla base del presupposto che se qualcuno fosse riuscito a mettere uno sniffer sul traffico di rete EC2, eri già in grossi guai). Le moderne mesh di servizi proteggono in modo trasparente tutto il nostro traffico, ad esempio con l'autenticazione TLS reciproca e la successiva crittografia.

Instradamento del traffico per i servizi della piattaforma

Ok, abbiamo discusso del traffico tra le applicazioni, ma per quanto riguarda la piattaforma dotCloud stessa?

La piattaforma stessa era composta da un centinaio di microservizi responsabili di varie funzioni. Alcuni accettavano richieste da altri e alcuni erano lavoratori in background che si connettevano ad altri servizi ma non accettavano connessioni loro stessi. In ogni caso ogni servizio deve conoscere gli endpoint degli indirizzi a cui deve connettersi.

Molti servizi di alto livello possono utilizzare la rete di routing descritta sopra. In effetti, molti degli oltre cento microservizi di dotCloud sono stati distribuiti come normali applicazioni sulla stessa piattaforma dotCloud. Ma un piccolo numero di servizi di basso livello (in particolare quelli che implementano questa mesh di routing) necessitavano di qualcosa di più semplice, con meno dipendenze (poiché non potevano dipendere da se stessi per funzionare: il buon vecchio problema dell'uovo e della gallina).

Questi servizi mission-critical di basso livello sono stati distribuiti eseguendo container direttamente su alcuni nodi chiave. In questo caso non sono stati utilizzati i servizi standard della piattaforma: linker, scheduler e runner. Se vuoi fare un paragone con le moderne piattaforme container, è come gestire un piano di controllo con docker run direttamente sui nodi, invece di delegare l'attività a Kubernetes. È abbastanza simile nel concetto moduli statici (pod), che utilizza kubeadm o bootkube quando si avvia un cluster autonomo.

Questi servizi sono stati esposti in modo semplice e crudo: un file YAML ne elencava i nomi e gli indirizzi; e ogni cliente doveva prendere una copia di questo file YAML per la distribuzione.

Da un lato, è estremamente affidabile perché non richiede il supporto di un archivio di chiavi/valori esterno come Zookeeper (ricorda, etcd o Consul non esistevano a quel tempo). D’altro canto, ha reso difficile lo spostamento dei servizi. Ogni volta che veniva effettuato uno spostamento, tutti i client ricevevano un file YAML aggiornato (e potenzialmente si riavviavano). Non molto comodo!

Successivamente, abbiamo iniziato a implementare un nuovo schema in cui ogni client si connetteva a un server proxy locale. Invece di un indirizzo e una porta, è sufficiente conoscere il numero di porta del servizio e connettersi tramite localhost. Il proxy locale gestisce questa connessione e la inoltra al server vero e proprio. Ora, quando si sposta il backend su un'altra macchina o si esegue il ridimensionamento, invece di aggiornare tutti i client, è necessario aggiornare solo tutti questi proxy locali; e non è più necessario un riavvio.

(Si prevedeva inoltre di incapsulare il traffico nelle connessioni TLS e di mettere un altro server proxy sul lato ricevente, nonché di verificare i certificati TLS senza la partecipazione del servizio ricevente, che è configurato per accettare connessioni solo su localhost. Ne parleremo più avanti).

Questo è molto simile a Smart Stack da Airbnb, ma la differenza significativa è che SmartStack è implementato e distribuito in produzione, mentre il sistema di routing interno di dotCloud è stato accantonato quando dotCloud è diventato Docker.

Personalmente considero SmartStack uno dei predecessori di sistemi come Istio, Linkerd e Consul Connect perché seguono tutti lo stesso schema:

  • Esegui un proxy su ciascun nodo.
  • I client si connettono al proxy.
  • Il piano di controllo aggiorna la configurazione del proxy quando cambiano i backend.
  • ... Profitto!

Implementazione moderna di una rete di servizi

Se oggi avessimo bisogno di implementare una griglia simile, potremmo utilizzare principi simili. Ad esempio, configura una zona DNS interna mappando i nomi dei servizi agli indirizzi nello spazio 127.0.0.0/8. Quindi esegui HAProxy su ciascun nodo del cluster, accettando connessioni a ciascun indirizzo di servizio (in quella sottorete 127.0.0.0/8) e reindirizzare/bilanciare il carico sui backend appropriati. È possibile controllare la configurazione di HAProxy conf, consentendoti di archiviare informazioni di backend in etcd o Consul e di inviare automaticamente la configurazione aggiornata a HAProxy quando necessario.

Questo è praticamente il modo in cui funziona Istio! Ma con alcune differenze:

  • Usi Inviato Procuratore invece di HAProxy.
  • Memorizza la configurazione del backend tramite l'API Kubernetes anziché etcd o Consul.
  • Ai servizi vengono allocati indirizzi sulla sottorete interna (indirizzi Kubernetes ClusterIP) invece di 127.0.0.0/8.
  • Dispone di un componente aggiuntivo (Citadel) per aggiungere l'autenticazione TLS reciproca tra client e server.
  • Supporta nuove funzionalità come interruzione del circuito, tracciamento distribuito, distribuzione canary, ecc.

Diamo una rapida occhiata ad alcune delle differenze.

Inviato Procuratore

Envoy Proxy è stato scritto da Lyft [il concorrente di Uber nel mercato dei taxi - ca. sentiero]. È simile in molti modi ad altri proxy (ad esempio HAProxy, Nginx, Traefik...), ma Lyft ha scritto il proprio perché avevano bisogno di funzionalità che mancavano ad altri proxy e sembrava più intelligente crearne uno nuovo piuttosto che estendere quello esistente.

Envoy può essere utilizzato da solo. Se ho un servizio specifico che deve connettersi ad altri servizi, posso configurarlo per connettersi a Envoy, quindi configurare e riconfigurare dinamicamente Envoy con la posizione di altri servizi, ottenendo allo stesso tempo molte ottime funzionalità aggiuntive, come la visibilità. Invece di una libreria client personalizzata o di inserire tracce di chiamata nel codice, inviamo il traffico a Envoy e lui raccoglie i parametri per noi.

Ma Envoy è anche in grado di funzionare come piano dati (piano dati) per la rete di servizi. Ciò significa che Envoy è ora configurato per questa rete di servizi piano di controllo (piano di controllo).

Piano di controllo

Per il piano di controllo, Istio si affida all'API Kubernetes. Questo non è molto diverso dall'usare confd, che si basa su etcd o Consul per visualizzare il set di chiavi nell'archivio dati. Istio utilizza l'API Kubernetes per visualizzare un set di risorse Kubernetes.

Tra questo e poi: Personalmente l'ho trovato utile Descrizione dell'API Kubernetesche recita:

Il server API Kubernetes è un "server stupido" che offre archiviazione, controllo delle versioni, convalida, aggiornamento e semantica per le risorse API.

Istio è progettato per funzionare con Kubernetes; e se desideri utilizzarlo al di fuori di Kubernetes, devi eseguire un'istanza del server API Kubernetes (e il servizio di supporto etcd).

Indirizzi di servizio

Istio si basa sugli indirizzi ClusterIP assegnati da Kubernetes, quindi i servizi Istio ricevono un indirizzo interno (non compreso nell'intervallo 127.0.0.0/8).

Il traffico verso l'indirizzo ClusterIP per un servizio specifico in un cluster Kubernetes senza Istio viene intercettato da kube-proxy e inviato al backend di quel proxy. Se sei interessato ai dettagli tecnici, kube-proxy imposta regole iptables (o bilanciatori di carico IPVS, a seconda di come è configurato) per riscrivere gli indirizzi IP di destinazione delle connessioni dirette all'indirizzo ClusterIP.

Una volta installato Istio su un cluster Kubernetes, non cambia nulla finché non viene abilitato esplicitamente per un determinato consumatore, o anche per l'intero spazio dei nomi, introducendo un contenitore sidecar in pod personalizzati. Questo contenitore avvierà un'istanza di Envoy e imposterà una serie di regole iptables per intercettare il traffico diretto ad altri servizi e reindirizzarlo a Envoy.

Se integrato con Kubernetes DNS, ciò significa che il nostro codice può connettersi in base al nome del servizio e tutto "funziona". In altre parole, il nostro codice invia query come http://api/v1/users/4242, quindi api risolvere la richiesta di 10.97.105.48, le regole iptables intercetteranno le connessioni da 10.97.105.48 e le inoltreranno al proxy Envoy locale e quel proxy locale inoltrerà la richiesta all'API backend effettiva. Uff!

Ulteriori fronzoli

Istio fornisce inoltre crittografia e autenticazione end-to-end tramite mTLS (mutual TLS). Un componente chiamato Cittadella.

C'è anche un componente Mixer, che Envoy può richiedere ogni richiesta di prendere una decisione speciale in merito a tale richiesta in base a vari fattori come intestazioni, carico del backend, ecc... (non preoccuparti: ci sono molti modi per mantenere Mixer in esecuzione e, anche se si blocca, Envoy continuerà a funzionare bene come procura).

E, naturalmente, abbiamo menzionato la visibilità: Envoy raccoglie un'enorme quantità di parametri fornendo al contempo una tracciabilità distribuita. In un'architettura di microservizi, se una singola richiesta API deve passare attraverso i microservizi A, B, C e D, al momento dell'accesso, la traccia distribuita aggiungerà un identificatore univoco alla richiesta e memorizzerà questo identificatore tramite sottorichieste a tutti questi microservizi, consentendo tutte le chiamate correlate da catturare, ritardi, ecc.

Sviluppa o acquista

Istio ha la reputazione di essere complesso. Al contrario, costruire la mesh di routing che ho descritto all’inizio di questo post è relativamente semplice utilizzando gli strumenti esistenti. Quindi, ha senso creare invece la propria mesh di servizi?

Se abbiamo esigenze modeste (non abbiamo bisogno di visibilità, interruttore automatico e altre sottigliezze), allora viene in mente lo sviluppo del nostro strumento. Ma se utilizziamo Kubernetes, potrebbe non essere nemmeno necessario perché Kubernetes fornisce già strumenti di base per il rilevamento dei servizi e il bilanciamento del carico.

Ma se disponiamo di requisiti avanzati, “acquistare” un service mesh sembra essere un’opzione molto migliore. (Questo non è sempre un "acquisto" perché Istio è open source, ma dobbiamo comunque investire tempo di progettazione per comprenderlo, distribuirlo e gestirlo.)

Dovrei scegliere Istio, Linkerd o Consul Connect?

Finora abbiamo parlato solo di Istio, ma questa non è l’unica rete di servizi. Alternativa popolare - Linkerd, e c'è di più Console Connetti.

Cosa scegliere?

Onestamente, non lo so. Al momento non mi ritengo sufficientemente competente per rispondere a questa domanda. Ci sono alcuni interessante articoli con un confronto di questi strumenti e persino punti di riferimenti.

Un approccio promettente è utilizzare uno strumento come SuperGloo. Implementa un livello di astrazione per semplificare e unificare le API esposte dalle mesh di servizi. Invece di apprendere le API specifiche (e, a mio parere, relativamente complesse) di diverse mesh di servizi, possiamo utilizzare i costrutti più semplici di SuperGloo e passare facilmente dall'uno all'altro, come se avessimo un formato di configurazione intermedio che descrive le interfacce HTTP e i backend in grado di di generare la configurazione effettiva per Nginx, HAProxy, Traefik, Apache...

Mi sono dilettato un po' con Istio e SuperGloo e nel prossimo articolo voglio mostrare come aggiungere Istio o Linkerd a un cluster esistente utilizzando SuperGloo e come quest'ultimo esegue il lavoro, ovvero consente di passare da una rete di servizi all'altra senza sovrascrivere le configurazioni.

Fonte: habr.com

Aggiungi un commento