Contenitori, microservizi e service mesh

Su Internet un sacco di articoli un servizi mesh (service mesh), ecco un altro. Evviva! Ma perché? Perché voglio esprimere la mia opinione secondo cui sarebbe stato meglio se i servizi mesh fossero apparsi 10 anni fa, prima delle piattaforme container come Docker e Kubernetes. Non sostengo che il mio punto di vista sia migliore o peggiore degli altri, ma poiché i servizi mesh sono animali piuttosto complessi, la molteplicità di punti di vista aiuterà a comprenderli meglio.

Parlerò della piattaforma dotCloud, che è stata costruita su oltre cento microservizi e ha supportato migliaia di applicazioni nei container. Spiegherò i problemi che abbiamo affrontato durante la sua sviluppo e lancio, e come i servizi mesh potrebbero aver aiutato (o meno).

La storia di dotCloud

Ho già scritto della storia di dotCloud e della scelta dell'architettura per questa piattaforma, ma ho parlato poco del livello di rete. Se non vuoi immergerti nella lettura dell'articolo precedente su dotCloud, ecco in breve il succo: è una piattaforma come servizio PaaS, che consente ai clienti di eseguire una vasta gamma di applicazioni (Java, PHP, Python…), con il supporto di un ampio assortimento di servizi di dati (MongoDB, MySQL, Redis…) e un flusso di lavoro simile a quello di Heroku: carichi il tuo codice sulla piattaforma, essa crea immagini dei container e le distribuisce.

Spiegherò come veniva indirizzato il traffico sulla piattaforma dotCloud. Non perché fosse particolarmente notevole (anche se per il suo tempo il sistema funzionava abbastanza bene!), ma soprattutto perché con gli strumenti moderni un design simile può essere realizzato facilmente in poco tempo da un piccolo team se hanno bisogno di un metodo per instradare il traffico tra un certo numero di microservizi o di applicazioni. In questo modo, è possibile confrontare le opzioni: cosa si ottiene se si sviluppa tutto da soli o si utilizza un servizio mesh esistente. La scelta standard: farlo da soli o acquistarlo.

Indirizzamento del traffico per applicazioni hosted

Le applicazioni su dotCloud possono fornire endpoint HTTP e TCP.

Endpoint HTTP vengono aggiunti dinamicamente alla configurazione del cluster dei bilanciatori di carico Hipache. Questo è simile a ciò che fanno oggi i sistemi Ingress in Kubernetes e i bilanciatori di carico come Traefik.

I clienti si collegano agli endpoint HTTP attraverso i domini appropriati, purché il nome di dominio punti ai bilanciatori di carico dotCloud. Niente di speciale.

Endpoint TCP sono legati al numero di porta, che viene poi passato a tutti i contenitori di questo stack tramite variabili di ambiente.

I client possono connettersi ai punti finali TCP utilizzando il corrispondente nome host (qualcosa come gateway-X.dotcloud.com) e il numero di porta.

Questo nome host viene risolto su un cluster di server 'nats' (non ha a che fare con NATS), che instraderanno le connessioni TCP in entrata nel contenitore corretto (o, nel caso dei servizi con bilanciamento del carico, nei contenitori appropriati).

Se sei familiare con Kubernetes, probabilmente ti ricorderà i servizi NodePort.

Sulla piattaforma dotCloud non c'era un equivalente dei servizi ClusterIP: per semplicità, l'accesso ai servizi avveniva allo stesso modo sia dall'interno che dall'esterno della piattaforma.

Tutto era organizzato in modo piuttosto semplice: le implementazioni iniziali delle reti di instradamento HTTP e TCP, probabilmente composte da poche centinaia di righe di Python. Algoritmi semplici (direi naïf) che sono stati sviluppati con la crescita della piattaforma e l'emergere di nuove esigenze.

Non era necessaria una revisione approfondita del codice esistente. In particolare, le applicazioni a 12 fattori possono utilizzare direttamente l'indirizzo ottenuto tramite le variabili di ambiente.

Cosa rende questo diverso da una moderna service mesh?

Limitata osservabilità. Non avevamo affatto metriche per la rete di instradamento TCP. Per quanto riguarda l'instradamento HTTP, versioni successive hanno introdotto metriche HTTP dettagliate con codici di errore e tempi di risposta, ma le moderne service mesh vanno ancora oltre, fornendo integrazione con sistemi di raccolta di metriche, come Prometheus, ad esempio.

L'osservabilità è importante non solo dal punto di vista operativo (per aiutare nella risoluzione dei problemi), ma anche nel rilascio di nuove funzionalità. Si tratta di un deploy sicuro blu-verde e deploy di canarini.

L'efficienza dell'instradamento è anch'essa limitata. Nella rete di instradamento di dotCloud, tutto il traffico doveva passare attraverso un cluster di nodi di instradamento dedicati. Ciò significava una possibile sovrapposizione di diversi confini AZ (zone di disponibilità) e un notevole aumento della latenza. Ricordo di aver risolto problemi con un codice che effettuava più di cento query SQL per pagina e per ogni query apriva una nuova connessione con il server SQL. In esecuzione locale, la pagina si carica istantaneamente, ma in dotCloud il caricamento richiede alcuni secondi, poiché ciascuna connessione TCP (e la successiva query SQL) richiede decine di millisecondi. In questo caso specifico, il problema è stato risolto con connessioni persistenti.

I moderni service mesh gestiscono meglio tali problemi. Innanzitutto, verificano che le connessioni siano instradate alla fonte. Il flusso logico è lo stesso: cliente → mesh → servizio, ma ora il mesh funziona localmente, non su nodi remoti, quindi la connessione cliente → mesh è locale e molto veloce (microsecondi invece di millisecondi).

I moderni service mesh implementano anche algoritmi di bilanciamento del carico più intelligenti. Monitorando la disponibilità dei backend, possono indirizzare più traffico verso i backend più veloci, migliorando così le prestazioni complessive.

Sicurezza è migliorato anch'esso. La rete di instradamento di dotCloud funzionava completamente su EC2 Classic e non cifrava il traffico (supponendo che se qualcuno fosse riuscito a installare un sniffatore sul traffico di rete EC2, avresti già grossi problemi). I moderni service mesh proteggono in modo trasparente tutto il nostro traffico, ad esempio, con l'autenticazione TLS mutua e la successiva crittografia.

Instradamento del traffico per i servizi della piattaforma

Bene, abbiamo discusso del traffico tra le applicazioni, ma che dire della piattaforma stessa di dotCloud?

La piattaforma stessa era composta da circa un centinaio di microservizi responsabili di varie funzioni. Alcuni ricevevano richieste da altri, mentre alcuni erano worker di background che si collegavano ad altri servizi, ma non accettavano connessioni. In ogni caso, ogni servizio deve conoscere gli endpoint degli indirizzi a cui collegarsi.

Molti servizi di alto livello possono utilizzare la rete di instradamento descritta sopra. Infatti, molti dei più di cento microservizi di dotCloud sono stati distribuiti come normali applicazioni sulla piattaforma stessa di dotCloud. Tuttavia, un numero ristretto di servizi a basso livello (in particolare quelli che implementano questa rete di instradamento) aveva bisogno di qualcosa di più semplice, con meno dipendenze (dato che per funzionare non potevano dipendere da se stessi - il vecchio e caro problema dell'uovo e della gallina).

Questi servizi a basso livello, essenziali, sono stati distribuiti avviando contenitori direttamente su diversi nodi chiave. In questo caso non sono stati coinvolti i servizi standard della piattaforma: compositore, pianificatore e runner. Se vuoi confrontarlo con le moderne piattaforme per container, assomiglia all'avvio della superficie di controllo con docker run direttamente sui nodi, invece di delegare il compito a Kubernetes. Questo è piuttosto simile al concetto di moduli statici (pod), utilizzati da kubeadm o bootkube per l'avvio di un cluster autonomo.

Questi servizi erano esposti in modo semplice e grossolano: i loro nomi e indirizzi erano elencati in un file YAML; ogni cliente doveva prendere una copia di questo file YAML per il deploy.

Da un lato, è estremamente affidabile, perché non richiede il supporto di un archivio esterno di chiavi/valori, come Zookeeper (non dimenticate, all'epoca non esistevano ancora etcd o Consul). D'altra parte, questo complicava il movimento dei servizi. Ogni volta che si spostava, tutti i clienti dovevano ricevere un file YAML aggiornato (e potenzialmente riavviarsi). Non è molto pratico!

Successivamente, abbiamo iniziato a implementare un nuovo schema, dove ogni cliente si collegava a un server proxy locale. Invece dell'indirizzo e della porta, era sufficiente conoscere solo il numero di porta del servizio e connettersi tramite localhost. Il server proxy locale gestisce questa connessione e la indirizza al server effettivo. Ora, quando il backend viene spostato su un'altra macchina o scalato, invece di aggiornare tutti i clienti è sufficiente aggiornare solo tutti questi proxy locali; e non è più necessario riavviare.

(Era previsto anche di incapsulare il traffico nelle connessioni TLS e di installare un ulteriore proxy sul lato ricevente, oltre a verificare i certificati TLS senza il coinvolgimento del servizio ricevente, che è configurato per accettare connessioni solo su localhost. Di questo parleremo più tardi).

È molto simile a SmartStack di Airbnb, ma la differenza fondamentale è che SmartStack è stato implementato e distribuito in produzione, mentre il sistema di routing interno di dotCloud è stato accantonato quando dotCloud si è trasformato in Docker.

Personalmente considero SmartStack uno dei precursori di sistemi come Istio, Linkerd e Consul Connect, poiché tutti seguono lo stesso modello:

  • Avvio del proxy su ogni nodo.
  • I client si connettono al proxy.
  • Il piano di controllo aggiorna la configurazione del server proxy quando cambiano i backend.
  • … Profitto!

L'implementazione moderna del service mesh

Se dovessimo implementare una rete simile oggi, potremmo utilizzare principi simili. Ad esempio, configurare una zona DNS interna, associando nomi di servizi a indirizzi nello spazio 127.0.0.0/8. Poi avviare HAProxy su ogni nodo del cluster, accettando connessioni a ciascun indirizzo di servizio (in questa subnet 127.0.0.0/8) e reindirizzando/bilanciando il carico sui rispettivi backend. La configurazione di HAProxy può essere gestita confd, consentendo di memorizzare informazioni sui backend in etcd o Consul e di inviare automaticamente la configurazione aggiornata a HAProxy quando necessario.

Funziona più o meno così Istio! Ma con alcune differenze:

  • reti definite dal software anche uno dei fornitori di cloud locali negli Stati Uniti. Envoy Proxy invece di HAProxy.
  • Mantiene la configurazione del backend tramite l'API Kubernetes invece di etcd o Consul.
  • I servizi ottengono indirizzi nella subnet interna (indirizzi Kubernetes ClusterIP) invece di 127.0.0.0/8.
  • Ha un componente aggiuntivo (Citadel) per aggiungere l'autenticazione reciproca TLS tra client e server.
  • Supporta nuove funzionalità come il circuit breaking, tracciamento distribuito, deployment delle canarini e altro.

Esaminiamo brevemente alcune differenze.

Envoy Proxy

Envoy Proxy è stato scritto da Lyft [concorrente di Uber nel mercato dei taxi - nota del traduttore]. È molto simile ad altri proxy (ad esempio, HAProxy, Nginx, Traefik…), ma Lyft ha scritto il proprio perché avevano bisogno di funzionalità mancanti in altri proxy, ed è sembrato più sensato crearne uno nuovo piuttosto che espandere uno esistente.

Envoy può essere utilizzato da solo. Se ho un servizio specifico che deve connettersi ad altri servizi, posso configurarlo per connettersi a Envoy, e poi configurare e riconfigurare dinamicamente Envoy con la posizione degli altri servizi, ottenendo molte eccellenti funzionalità aggiuntive, come la visibilità. Invece di una libreria client personalizzata o di un'integrazione nel codice per la tracciabilità delle chiamate, indirizziamo il traffico verso Envoy, che raccoglie per noi metriche.

Ma Envoy è anche in grado di funzionare come piano dei dati (data plane) per il service mesh. Questo significa che ora, per questo service mesh, Envoy è configurato piano di controllo (control plane).

Piano di controllo

Nel piano di controllo, Istio si affida all'API di Kubernetes. Non è molto diverso dall'uso di confd, che si affida a etcd o Consul per visualizzare un insieme di chiavi nello storage. Istio esamina tramite l'API di Kubernetes un insieme di risorse Kubernetes.

Nel frattempo: personalmente ho trovato utile questa descrizione dell'API di Kubernetes, che afferma:

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

Istio è progettato per funzionare con Kubernetes; e se vuoi usarlo al di fuori di Kubernetes, devi avviare un'istanza del server API di Kubernetes (e del servizio ausiliario etcd).

Indirizzi dei servizi

Istio si affida agli indirizzi ClusterIP che Kubernetes assegna, quindi i servizi Istio ricevono un indirizzo interno (non nell'intervallo 127.0.0.0/8).

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

Dopo aver installato Istio nel cluster Kubernetes, nulla cambia finché non viene esplicitamente attivato per questo consumatore o anche per l'intero namespace, introducendo un contenitore sidecar nei pod personalizzati. Questo contenitore avvierà un'istanza di Envoy e installerà una serie di regole iptables per intercettare il traffico diretto verso altri servizi e reindirizzare quel traffico a Envoy.

Nell'integrazione con Kubernetes DNS, questo significa che il nostro codice può connettersi per nome del servizio, e tutto "funziona semplicemente". In altre parole, il nostro codice genera richieste di tipo http://api/v1/users/4242, quindi api risolve la richiesta su 10.97.105.48, le regole iptables intercettano le connessioni con 10.97.105.48 e le reindirizzano a un proxy locale Envoy, e questo proxy locale inoltrerà la richiesta al backend API effettivo. Fiu!

Ulteriori dettagli

Istio fornisce anche crittografia end-to-end e autenticazione tramite mTLS (mutual TLS). Questo è gestito da un componente chiamato Citadel.

C'è anche un componente Mixer, che Envoy può richiedere per ogni richiesta, per prendere una decisione speciale su questa richiesta in base a vari fattori, come intestazioni, carico del backend e così via... (non preoccupatevi: ci sono molte risorse per garantire il funzionamento di Mixer, e anche se fallisse, Envoy continuerà a funzionare normalmente come proxy).

E, ovviamente, abbiamo accennato alla tracciabilità: Envoy raccoglie un'enorme quantità di metriche, assicurando nel contempo la tracciabilità distribuita. Nell'architettura dei microservizi, se una richiesta API deve passare attraverso i microservizi A, B, C e D, al momento dell'ingresso nel sistema, la tracciabilità distribuita aggiungerà un identificatore unico alla richiesta e manterrà tale identificatore attraverso i sotto-insiemi a tutti questi microservizi, permettendo di registrare tutte le chiamate correlate, le loro latenze e così via.

Sviluppare o acquistare

Istio ha la reputazione di un sistema complesso. Al contrario, costruire la rete di routing che ho descritto all'inizio di questo post è relativamente semplice usando gli strumenti esistenti. Quindi, ha senso, invece, creare un proprio service mesh?

Se abbiamo esigenze modeste (non abbiamo bisogno di tracciabilità, interruzione della catena e altri dettagli), sorgono pensieri sullo sviluppo di un proprio strumento. Ma se stiamo utilizzando Kubernetes, potrebbe anche non essere necessario, poiché Kubernetes fornisce già gli strumenti di base per la scoperta dei servizi e il bilanciamento del carico.

Ma se abbiamo requisiti avanzati, allora "acquistare" un service mesh risulta molto più vantaggioso. (Non sempre è davvero "acquisto", poiché Istio è offerto con codice sorgente aperto, ma dobbiamo comunque investire tempo ingegneristico per comprenderne il funzionamento, implementarlo e gestirlo.)

Cosa scegliere: Istio, Linkerd o Consul Connect?

Finora abbiamo parlato solo di Istio, ma non è l'unico service mesh. Un'alternativa popolare è Linkerd, e ce ne sono ancora Consul Connect.

Cosa scegliere?

A dire il vero, non lo so. Al momento non mi sento abbastanza competente per rispondere a questa domanda. Ci sono diversi interessanti articoli confronti di questi strumenti e anche benchmark.

Uno degli approcci più promettenti è utilizzare uno strumento come SuperGloo. Esso implementa uno strato di astrazione per semplificare e unificare le API fornite dai service mesh. Invece di studiare le API specifiche (e, a mio parere, relativamente complesse) di vari service mesh, possiamo utilizzare costruzioni più semplici di SuperGloo — e passare facilmente da uno all'altro, come se avessimo un formato intermedio di configurazione che descrive le interfacce HTTP e i backend, capace di generare la configurazione effettiva per Nginx, HAProxy, Traefik, Apache…

Mi sono divertito un po' con Istio e SuperGloo, e nel prossimo articolo voglio mostrare come aggiungere Istio o Linkerd a un cluster esistente utilizzando SuperGloo, e quanto bene quest'ultimo riesca a svolgere il proprio compito, ovvero consentire di passare da un service mesh all'altro senza riscrivere le configurazioni.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster