Contenitori, microservizi e service mesh

In rete moltitudine articoli un mesh di servizio (service mesh), ecco un altro. Evviva! Ma perché? Perché voglio esprimere la mia opinione che sarebbe stato meglio se i mesh di servizio fossero apparsi 10 anni fa, prima dell'avvento di piattaforme container come Docker e Kubernetes. Non dico che la mia opinione sia migliore o peggiore delle altre, ma poiché i mesh di servizio sono animali piuttosto complessi, avere molteplici punti di vista aiuterà a comprenderli meglio.

Vi parlerò della piattaforma dotCloud, che è stata costruita su più di cento microservizi e supportava migliaia di applicazioni nei container. Spiegherò i problemi che abbiamo affrontato durante il suo sviluppo e lancio, e come i mesh di servizio avrebbero potuto aiutare (o meno).

Storia di dotCloud

Ho già scritto sulla storia di dotCloud e sulla scelta dell'architettura per questa piattaforma, ma ho parlato poco del livello di rete. Se non volete immergervi nella lettura articolo precedente dotCloud, in breve, è una piattaforma come servizio (PaaS) che consente ai clienti di avviare un'ampia gamma di applicazioni (Java, PHP, Python…) supportando diversi servizi di database (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.

Ti racconterò come veniva instradato il traffico sulla piattaforma dotCloud. Non perché fosse particolarmente eccezionale (anche se per il suo tempo il sistema funzionava abbastanza bene!), ma soprattutto perché con gli strumenti moderni un design del genere può essere realizzato rapidamente da un team modesto, se hanno bisogno di un modo per instradare il traffico tra un insieme di microservizi o una serie di applicazioni. Si possono quindi confrontare le opzioni: cosa succede se sviluppi tutto tu stesso o se utilizzi un servizio esistente. La scelta standard: farlo da soli o acquistare.

Instradamento 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. Sembra quello che oggi fanno le risorse Ingress in Kubernetes e un bilanciatore di carico come Traefik.

I clienti si connettono ai punti finali HTTP attraverso i corrispondenti domini, a condizione che il nome di dominio punti ai bilanciatori di carico dotCloud. Niente di speciale.

I punti finali TCP sono associati a un numero di porta, che viene poi passato a tutti i contenitori di questo stack tramite variabili d'ambiente.

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

Questo nome host si risolve su un cluster di server "nats" (non ha nulla a che vedere con NATS), i quali instraderanno le connessioni TCP in ingresso verso il contenitore corretto (o, nel caso di servizi con bilanciamento del carico, verso i contenitori giusti).

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

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

Tutto è stato organizzato in modo abbastanza semplice: le prime implementazioni delle reti di routing HTTP e TCP, probabilmente, contavano solo poche centinaia di righe di Python. Algoritmi semplici (direi naivi) che sono stati perfezionati con la crescita della piattaforma e l'emergere di requisiti aggiuntivi.

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

Qual è la differenza rispetto a un moderno service mesh?

Limitata visibilità. Non avevamo affatto metriche per la rete di routing TCP. Per quanto riguarda il routing HTTP, nelle versioni più recenti sono state introdotte metriche HTTP dettagliate con codici di errore e tempi di risposta, ma i moderni service mesh vanno oltre, assicurando integrazione con sistemi di raccolta metriche come Prometheus, ad esempio.

La visibilità è importante non solo dal punto di vista operativo (per aiutare nella risoluzione dei problemi), ma anche al momento del rilascio di nuove funzionalità. Si tratta di un deployment sicuro blue-green e e del deployment delle canarie..

L'efficacia del routing. è anche limitata. Nella rete di instradamento di dotCloud tutto il traffico doveva passare attraverso un cluster di nodi di instradamento dedicati. Ciò significava potenziali sovrapposizioni tra più confini AZ (zone di disponibilità) e un notevole aumento della latenza. Ricordo di aver risolto problemi con un codice che effettuava oltre cento query SQL per pagina, aprendo una nuova connessione al server SQL per ogni richiesta. In locale, la pagina si carica istantaneamente, ma in dotCloud il caricamento richiede diversi secondi, poiché ogni connessione TCP (e la successiva query SQL) richiede decine di millisecondi. In questo caso specifico, il problema è stato risolto grazie a connessioni permanenti.

I moderni service mesh affrontano meglio tali problemi. Prima di tutto, controllano che le connessioni siano instradate sorgente. Il flusso logico è lo stesso: cliente → mesh → servizio, ma ora il mesh opera localmente, invece che su nodi remoti, quindi la connessione cliente → mesh è locale e molto veloce (microsecondi invece di millisecondi).

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

Sicurezza è anche migliore. La rete di routing dotCloud funzionava completamente su EC2 Classic e non crittografava il traffico (supponendo che se qualcuno fosse riuscito a installare uno sniffer sul traffico di rete EC2, avresti già grossi problemi). I moderni service mesh proteggono in modo trasparente tutto il nostro traffico, ad esempio, con autenticazione TLS mutua e successiva crittografia.

Routing 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 diverse funzioni. Alcuni ricevevano richieste da altri, mentre alcuni erano lavoratori in background che si collegavano ad altri servizi, ma non ricevevano connessioni. In ogni caso, ogni servizio deve conoscere gli endpoint degli indirizzi a cui connettersi.

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

Questi servizi di basso livello, fondamentali, sono stati distribuiti avviando contenitori direttamente su alcuni nodi chiave. In questo modo non sono stati coinvolti i servizi standard della piattaforma: compositore, pianificatore e runner. Se vuoi confrontarlo con le moderne piattaforme di containerizzazione, è simile all'esecuzione dell'applane di controllo su docker run nodi direttamente, invece di delegare il compito a Kubernetes. Ricorda molto il concetto di moduli statici (pod), utilizzati da kubeadm o bootkube all'avvio di un cluster autonomo.

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

Da un lato, questo era estremamente affidabile, poiché non richiedeva la gestione di un archivio esterno di chiavi/valori, come Zookeeper (ricordate, all'epoca non esistevano ancora etcd o Consul). Dall'altro, rendeva difficile il movimento dei servizi. Ogni volta che si effettuava un trasferimento, tutti i clienti dovevano ottenere un file YAML aggiornato (e potenzialmente riavviarsi). Non era molto comodo!

In seguito, abbiamo iniziato a implementare un nuovo schema, in cui ogni cliente si collegava a un proxy locale. Invece dell'indirizzo e della porta, gli bastava conoscere solo il numero di porta del servizio e connettersi tramite localhost. Il proxy locale gestisce questa connessione e la instrada verso il server reale. Ora, spostando il backend su un'altra macchina o scalando, invece di aggiornare tutti i clienti, è sufficiente aggiornare solo questi proxy locali; e il riavvio non è più necessario.

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

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

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

  • Avviare 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 del service mesh

Se oggi dobbiamo implementare una rete simile, possiamo usare principi analoghi. Ad esempio, configurare una zona DNS interna, associando i nomi dei servizi agli indirizzi nello spazio 127.0.0.0/8. Poi avviare HAProxy su ogni nodo del cluster, accettando connessioni su ciascun indirizzo del servizio (in questa sottorete 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.

Ecco come funziona Istio! Ma con alcune differenze:

  • Utilizza Envoy Proxy invece di HAProxy.
  • Memorizza la configurazione del backend tramite l'API Kubernetes invece di etcd o Consul.
  • Le utenze vengono assegnate indirizzi nella rete interna (indirizzi ClusterIP di Kubernetes) invece di 127.0.0.0/8.
  • Dispone di un componente aggiuntivo (Citadel) per aggiungere l'autenticazione reciproca TLS tra client e server.
  • Supporta nuove funzionalità come il circuit breaking, il tracciamento distribuito, il deployment delle canary, e altro ancora.

Diamo un'occhiata rapida ad alcune delle differenze.

Envoy Proxy

Envoy Proxy è stato scritto da Lyft [competitor di Uber nel settore dei taxi – n.d.t.]. È simile ad altri proxy (come HAProxy, Nginx, Traefik…), ma Lyft ha sviluppato il proprio perché necessitava di funzioni mancanti in altri proxy, e si è ritenuto più sensato crearne uno nuovo piuttosto che ampliare quelli esistenti.

Envoy può essere utilizzato autonomamente. 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 ottime funzionalità aggiuntive, come l'osservabilità. Invece di utilizzare una libreria client personalizzata o di integrare la tracciabilità delle chiamate nel codice, inoltriamo il traffico a Envoy, che raccoglie metriche per noi.

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

Piano di controllo

Nel piano di controllo, Istio si basa sull'API di Kubernetes. Non è molto diverso dall'uso di confd,che si basa su etcd o Consul per visualizzare un insieme di chiavi in uno store dati. Istio esamina un insieme di risorse Kubernetes tramite l'API di Kubernetes.

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

Il server Kubernetes API è un "server stupido" che offre memorizzazione, gestione delle versioni, verifica, aggiornamento e semantica delle risorse API.

Istio è progettato per funzionare con Kubernetes; e se desideri usarlo al di fuori di Kubernetes, dovrai eseguire un'istanza del server Kubernetes API (e dei servizi aggiuntivi etcd).

Indirizzi dei servizi

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

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

Dopo l'installazione di Istio nel cluster Kubernetes, nulla cambia finché non viene esplicitamente abilitato per un determinato consumatore o persino per l'intero namespace, introducendo il container sidecar nei pod personalizzati. Questo container avvia un'istanza di Envoy e stabilisce alcune regole iptables per intercettare il traffico destinato ad altri servizi, reindirizzando tale traffico a Envoy.

Con l'integrazione con Kubernetes DNS, ciò significa che il nostro codice può connettersi utilizzando il nome del servizio, e tutto "funziona semplicemente". In altre parole, il nostro codice effettua richieste come http://api/v1/users/4242, allora api risolve la richiesta su 10.97.105.48, mentre le regole iptables intercettano le connessioni con 10.97.105.48 e le reindirizzano al proxy locale Envoy, e questo proxy locale invierà la richiesta al backend API effettivo. Uff!

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ò interrogare per ogni richiedere una decisione speciale su questa richiesta in base a vari fattori, come intestazioni, carico del backend, ecc… (non preoccuparti: ci sono molti modi per garantire che Mixer funzioni, e anche se dovesse bloccarsi, Envoy continuerà a funzionare normalmente come proxy).

E, certo, abbiamo menzionato la visibilità: Envoy raccoglie un'enorme quantità di metriche, garantendo nel contempo il tracciamento distribuito. Nell'architettura dei microservizi, se una richiesta API deve passare attraverso i microservizi A, B, C e D, quando entra nel sistema il tracciamento distribuito aggiungerà un identificatore unico alla richiesta e conserverà quel identificatore attraverso le sotto-richieste a tutti questi microservizi, consentendo di registrare tutte le chiamate correlate, i loro ritardi e così via.

Sviluppare o acquistare

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

Se abbiamo esigenze modeste (non serve la visibilità, interruzione della catena e altre complessità), iniziamo a pensare di sviluppare il nostro strumento. Ma se utilizziamo Kubernetes, potrebbe anche non essere necessario, perché Kubernetes fornisce già gli strumenti di base per la scoperta dei servizi e il bilanciamento del carico.

Ma se abbiamo requisiti avanzati, allora l'acquisto di un service mesh sembra essere un'opzione molto migliore. (Non è sempre esattamente un «acquisto», poiché Istio è fornito con codice sorgente aperto, ma dobbiamo comunque investire tempo ingegneristico per capire come funziona, 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 c'è anche Consul Connect.

Cosa scegliere?

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

Uno degli approcci promettenti è utilizzare uno strumento come SuperGloo. Fornisce uno strato di astrazione per semplificare e unificare le API fornite dalle mesh di servizio. Invece di dover approfondire API specifiche (che sono, a mio avviso, relativamente complesse) delle diverse mesh di servizio, possiamo utilizzare strutture più semplici di SuperGloo — e passare facilmente da una all'altra, come se avessimo un formato di configurazione intermedio che descrive interfacce HTTP e backend, in grado di generare la configurazione effettiva per Nginx, HAProxy, Traefik, Apache…

Ho sperimentato 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 svolga il proprio lavoro, vale a dire permettere di passare da una mesh di servizio all'altra 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