Netramesh – una soluzione service mesh leggera

Durante il passaggio da un'applicazione monolitica a un'architettura a microservizi, ci troviamo ad affrontare nuove sfide.

In un'applicazione monolitica, è solitamente sufficiente identificare in quale parte del sistema si è verificato un errore. Molto probabilmente, il problema risiede nel codice del monolito stesso o nel database. Tuttavia, quando iniziamo a cercare un problema in un'architettura a microservizi, le cose non sono così evidenti. È necessario rintracciare il percorso completo che ha seguito la richiesta dall'inizio alla fine, isolandola tra centinaia di microservizi. Inoltre, molti di questi hanno i propri archivi, nei quali possono sorgere errori logici, problemi di prestazioni e di resilienza.

Netramesh – una soluzione service mesh leggera

Ho cercato a lungo uno strumento che potesse aiutarmi a risolvere tali problemi (ne ho parlato su Habrahabr: 1, 2), ma alla fine ho creato una mia soluzione open source. In questo articolo, parlo dei vantaggi dell'approccio service mesh e condivido un nuovo strumento per la sua implementazione.

Il tracciamento distribuito è una soluzione comune per il problema della ricerca di errori nei sistemi distribuiti. Ma cosa succede se in un sistema non è ancora stato implementato questo approccio alla raccolta delle informazioni sulle interazioni di rete, o, cosa peggiore, se in parte del sistema funziona correttamente, mentre in un'altra parte no, perché non è stato aggiunto ai vecchi servizi? Per determinare la causa radice esatta del problema, è necessario avere un quadro completo di ciò che accade nel sistema. È particolarmente importante capire quali microservizi siano coinvolti nei percorsi critici per il business.

Qui arriva in nostro soccorso l'approccio del service mesh, che si occupa di tutta la macchina per la raccolta delle informazioni di rete a un livello più basso rispetto a quello in cui operano i servizi stessi. Questo approccio ci consente di intercettare tutto il traffico e di analizzarlo al volo. Inoltre, le applicazioni non devono nemmeno sapere nulla di esso.

Approccio del service mesh

L'idea principale dell'approccio del service mesh è l'aggiunta di un ulteriore strato infrastrutturale sopra la rete, che ci permetterà di gestire in modo più efficace l'interazione tra i servizi. La maggior parte delle implementazioni funziona aggiungendo a ciascun microservizio un container sidecar con un proxy trasparente, attraverso il quale viene instradato tutto il traffico in entrata e in uscita del servizio. È proprio in questo punto che possiamo effettuare il bilanciamento del carico lato client, applicare politiche di sicurezza, imporre limiti sul numero di richieste e raccogliere informazioni importanti sul funzionamento dei servizi in produzione.

Netramesh – una soluzione service mesh leggera

Soluzioni

Esistono già diverse implementazioni di questo approccio: Istio e linkerd2. Offrono molte funzionalità pronte all'uso. Tuttavia, ciò comporta anche un grande overhead sulle risorse. Inoltre, più grande è il cluster in cui opera tale sistema, maggiore sarà la necessità di risorse per mantenere la nuova infrastruttura. In Avito utilizziamo cluster Kubernetes che ospitano migliaia di istanze di servizi (e il loro numero continua a crescere rapidamente). Nella nostra attuale implementazione, Istio consuma circa 300 MB di memoria operativa per ogni istanza del servizio. A causa della vasta gamma di funzionalità, il bilanciamento trasparente influisce anche sul tempo di risposta totale dei servizi (fino a 10 ms).

Di conseguenza, abbiamo esaminato quali funzionalità ci servissero immediatamente e abbiamo concluso che la principale motivazione per cui abbiamo avviato l'implementazione di tali soluzioni era la possibilità di raccogliere informazioni di tracing da tutto il sistema in modo trasparente. Volevamo anche avere il controllo sulle interazioni tra i servizi e eseguire diverse manipolazioni con gli header trasmessi tra i servizi.

Alla fine, siamo giunti alla nostra soluzione:  Netramesh.

Netramesh

Netramesh — è una soluzione di service mesh leggera con possibilità di scalabilità infinita indipendentemente dal numero di servizi nel sistema.

Gli obiettivi principali della nuova soluzione erano un basso overhead delle risorse e alte prestazioni. Tra le funzionalità fondamentali volevamo poter inviare in modo trasparente le span di tracing al nostro sistema Jaeger.

Oggi la maggior parte delle soluzioni cloud è implementata in Golang. E ovviamente ci sono buone ragioni per questo. Scrivere applicazioni di rete in Golang, che lavorano in modo asincrono con input/output e scalano automaticamente sulle CPU, è comodo e relativamente semplice. E, cosa altrettanto importante, le prestazioni sono adeguate a questo compito. Pertanto, abbiamo anche scelto Golang.

Prestazioni

Abbiamo concentrato i nostri sforzi per ottenere le massime prestazioni. Per una soluzione che viene distribuita accanto a ogni istanza di servizio, è necessario un basso consumo di memoria e tempo di CPU. E, naturalmente, anche la latenza delle risposte deve essere ridotta al minimo.

Diamo un'occhiata ai risultati ottenuti.

RAM

Netramesh consuma ~10Mb senza traffico e fino a 50Mb con un carico massimo di 10000 RPS per singolo instance.

Il proxy Istio envoy consuma sempre ~300Mb nei nostri cluster con migliaia di instance. Questo impedisce di scalare su tutto il cluster.

Netramesh – una soluzione service mesh leggera

Netramesh – una soluzione service mesh leggera

Con Netramesh abbiamo ottenuto una riduzione del consumo di memoria di circa 10 volte.

CPU

L'uso della CPU è relativamente costante sotto carico. Dipende dal numero di richieste per unità di tempo inviate al sidecar. Valori durante picchi di 3000 richieste al secondo:

Netramesh – una soluzione service mesh leggera

Netramesh – una soluzione service mesh leggera

C'è un altro aspetto importante: Netramesh è una soluzione senza control plane e senza carico non consuma tempo di CPU. Con Istio, i sidecar aggiornano sempre gli endpoint dei servizi. Di conseguenza, possiamo osservare questa situazione senza carico:

Netramesh – una soluzione service mesh leggera

Utilizziamo HTTP/1 per l'interazione tra i servizi. L'aumento del tempo di risposta di Istio durante il proxying tramite envoy è stato fino a 5-10ms, il che è piuttosto significativo per i servizi che sono pronti a rispondere in millisecondi. Con Netramesh questo tempo è diminuito a 0.5-2ms.

Scalabilità

La piccola quantità di risorse utilizzate da ciascun proxy consente di collocarlo accanto a ogni servizio. Netramesh è stato volutamente creato senza un componente di control plane per mantenere la leggerezza di ciascun sidecar. Spesso, nelle soluzioni di service mesh, il control plane distribuisce le informazioni di service discovery a ciascun sidecar. Con esse arrivano anche informazioni sui timeout e sulle configurazioni di bilanciamento. Tutto ciò consente di realizzare molte cose utili, ma purtroppo appesantisce i sidecar.

Service discovery

Netramesh – una soluzione service mesh leggera

Netramesh non aggiunge meccanismi aggiuntivi per la service discovery. Tutto il traffico è instradato in modo trasparente attraverso il netra sidecar.

Netramesh supporta il protocollo applicativo HTTP/1. Per la sua definizione viene utilizzato un elenco di porte configurabile. Di solito, nel sistema ci sono diverse porte per l'interazione tramite HTTP. Ad esempio, utilizziamo le porte 80, 8890, 8080 per l'interazione tra servizi e richieste esterne. In tal caso, possono essere definite tramite una variabile d'ambiente. NETRA_HTTP_PORTS.

Se utilizzi Kubernetes come orchestratore e il suo meccanismo Service per l'interazione intra-cluster tra i servizi, il meccanismo rimane esattamente lo stesso. Inizialmente, il microservizio ottiene l'indirizzo IP del servizio tramite kube-dns e apre una nuova connessione ad esso. Questa connessione viene stabilita inizialmente con il netra-sidecar locale e tutti i pacchetti TCP arrivano inizialmente a netra. Successivamente, il netra-sidecar stabilisce la connessione con il punto di destinazione originale. NAT sull'IP del pod sul nodo rimane esattamente lo stesso di prima senza netra.

Tracciamento distribuito e propagazione del contesto

Netramesh offre la funzionalità necessaria per inviare span di tracciamento sulle interazioni HTTP. Il netra-sidecar analizza il protocollo HTTP, misura i ritardi delle richieste e estrae le informazioni necessarie dagli header HTTP. Alla fine, otteniamo tutti i trace in un'unica sistema Jaeger. Per una configurazione fine, si possono anche utilizzare le variabili d'ambiente fornite dalla biblioteca ufficiale. jaeger go library.

Netramesh – una soluzione service mesh leggera

Netramesh – una soluzione service mesh leggera

C'è un problema. Fintanto che i servizi non genereranno e inoltreranno un'intestazione uber speciale, non vedremo gli span di tracing connessi nel sistema. E questo è ciò di cui abbiamo bisogno per individuare rapidamente le cause dei problemi. Qui Netramesh ha di nuovo una soluzione. I proxy leggono le intestazioni HTTP e, se non contengono l'uber trace id, lo generano. Netramesh memorizza anche le informazioni sulle richieste in entrata e in uscita nel sidecar e le associa arricchendole con le intestazioni necessarie delle richieste in uscita. Tutto ciò che è necessario fare nei servizi è inoltrare soltanto un'intestazione X-Request-Id, che può essere configurato tramite una variabile d'ambiente NETRA_HTTP_REQUEST_ID_HEADER_NAME. Per gestire la dimensione del contesto in Netramesh, è possibile impostare le seguenti variabili d'ambiente: NETRA_TRACING_CONTEXT_EXPIRATION_MILLISECONDS (il tempo durante il quale verrà memorizzato il contesto) e NETRA_TRACING_CONTEXT_CLEANUP_INTERVAL (la frequenza di pulizia del contesto).

È anche possibile combinare più percorsi nel proprio sistema contrassegnandoli con un marcatore di sessione speciale. Netra consente di impostare HTTP_HEADER_TAG_MAP per trasformare le intestazioni HTTP in corrispondenti tag di tracing span. Questo può essere particolarmente utile per i test. Dopo aver completato un test funzionale, è possibile verificare quale parte del sistema è stata coinvolta filtrando in base alla chiave di sessione corrispondente.

Definizione della fonte della richiesta

Per determinare da dove proviene la richiesta, è possibile utilizzare la funzionalità di aggiunta automatica dell'intestazione con la fonte. Utilizzando la variabile d'ambiente NETRA_HTTP_X_SOURCE_HEADER_NAME è possibile impostare il nome dell'intestazione che verrà impostato automaticamente. Con NETRA_HTTP_X_SOURCE_VALUE è possibile specificare il valore da assegnare all'intestazione X-Source su tutte le richieste in uscita.

Questo consente di diffondere in modo uniforme questo utile intestazione su tutta la rete. Successivamente, può essere utilizzata nei servizi e aggiunta ai log, alle metriche.

Routing del traffico e dettagli interni di Netramesh

Netramesh è composto da due componenti principali. Il primo, netra-init, stabilisce le regole di rete per l'intercettazione del traffico. Utilizza regole di reindirizzamento iptables per intercettare tutto o parte del traffico su sidecar, che è il secondo componente principale di Netramesh. È possibile configurare quali porte intercettare per le sessioni TCP in entrata e in uscita: INBOUND_INTERCEPT_PORTS, OUTBOUND_INTERCEPT_PORTS.

C'è anche un'interessante funzionalità nello strumento: il routing probabilistico. Se si utilizza Netramesh esclusivamente per raccogliere tracing span, è possibile risparmiare risorse in un ambiente di produzione attivando il routing probabilistico tramite variabili NETRA_INBOUND_PROBABILITY e NETRA_OUTBOUND_PROBABILITY (da 0 a 1). Il valore predefinito è 1 (viene intercettato tutto il traffico).

Dopo un'intercettazione riuscita, il netra sidecar stabilisce una nuova connessione e utilizza SO_ORIGINAL_DST l'opzione del socket per ottenere l'originale punto di destinazione. Quindi Netra apre una nuova connessione all'indirizzo IP originale e stabilisce una comunicazione TCP bidirezionale tra le parti, ascoltando tutto il traffico in transito. Se la porta è definita come HTTP, Netra cerca di analizzarla e tracciarla. Se l'analisi HTTP fallisce, Netra effettua un fallback su TCP e proxy trasparentemente i byte.

Costruzione del grafo delle dipendenze

Dopo aver ottenuto una grande quantità di informazioni di tracing in Jaeger, si desidera visualizzare il grafico completo delle interazioni nel sistema. Tuttavia, se il vostro sistema è sufficientemente carico e si accumulano miliardi di tracing span in un giorno, aggregarli diventa una sfida non così semplice. Esiste un modo ufficiale per farlo: spark-dependencies. Tuttavia, ci vorranno ore per costruire il grafico completo e costringerà a scaricare da Jaeger l'intero dataset delle ultime 24 ore.

Se utilizzate Elasticsearch per memorizzare i tracing span, potete avvalervi di una semplice utilità in Golang, che costruirà un grafico simile in pochi minuti, sfruttando le caratteristiche e le capacità di Elasticsearch.

Netramesh – una soluzione service mesh leggera

Come utilizzare Netramesh

Netra può essere facilmente aggiunto a qualsiasi servizio che opera sotto un qualsiasi orchestratore. È possibile vedere un esempio qui.

Attualmente, Netra non ha la possibilità di implementare automaticamente il sidecar nei servizi, ma ci sono piani per farlo.

Futuro di Netramesh

L'obiettivo principale Netramesh è raggiungere il minimo consumo di risorse e un'alta prestazione, fornendo funzionalità fondamentali per l'osservabilità e il controllo delle interazioni tra i servizi.

In futuro, Netramesh supporterà altri protocolli di livello applicativo oltre all'HTTP. Presto sarà disponibile la possibilità di routing L7.

Utilizza Netramesh se ti trovi di fronte a problemi simili e scrivici per domande e suggerimenti.

Fonte: habr.com

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