Netramesh – soluzione di service mesh leggera

Nel processo di transizione da un'applicazione monolitica a un'architettura a microservizi ci troviamo di fronte a nuove sfide.

In un'applicazione monolitica di solito è sufficiente identificare in quale parte del sistema si è verificato un errore. Probabilmente, il problema è nel codice stesso del monolite o nel database. Ma quando iniziamo a cercare problemi in un'architettura a microservizi, le cose non sono più così ovvie. È necessario risalire all'intero percorso del richiesto dall'inizio alla fine, isolandolo tra centinaia di microservizi. Inoltre, molti di essi hanno anche i propri archivi, nei quali possono sorgere errori logici, problemi di prestazioni e di tolleranza ai guasti.

Netramesh – soluzione di service mesh leggera

Ho cercato a lungo uno strumento che aiutasse a risolvere tali problemi (ne ho parlato su Habr: 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 tracing 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 un approccio di raccolta delle informazioni sulle interazioni di rete, o, peggio ancora, se in parte del sistema funziona già bene ma in altre no, perché non è stato aggiunto ai vecchi servizi? Per determinare la causa radice esatta di un problema, è necessario avere una visione completa di cosa sta accadendo nel sistema. È particolarmente importante capire quali microservizi sono coinvolti nei principali percorsi critici per il business.

Qui può aiutarci l'approccio service mesh, che gestisce tutta la meccanica della raccolta delle informazioni di rete a un livello inferiore rispetto a quello in cui operano i servizi stessi. Questo approccio ci consente di intercettare tutto il traffico e di analizzarlo in tempo reale. Inoltre, le applicazioni non devono nemmeno essere a conoscenza di questo.

Approccio service mesh

L'idea principale dell'approccio service mesh è quella di aggiungere un ulteriore livello infrastrutturale sopra la rete, che ci permetterà di gestire qualsiasi aspetto dell'interazione tra i servizi. La maggior parte delle implementazioni funziona nel seguente modo: a ogni microservizio viene aggiunto un contenitore sidecar aggiuntivo con un proxy trasparente, attraverso il quale passa tutto il traffico in entrata e in uscita del servizio. Ed è proprio in questo punto che possiamo effettuare bilanciamenti client, applicare politiche di sicurezza, introdurre limiti sul numero di richieste e raccogliere informazioni importanti relative all'interazione dei servizi in produzione.

Netramesh – soluzione di 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, maggiore è il cluster in cui opera un tale sistema, maggiore sarà la richiesta di risorse per mantenere la nuova infrastruttura. In Avito, sfruttiamo i cluster Kubernetes, in cui si trovano migliaia di istanze di servizi (e il loro numero continua a crescere rapidamente). Nella configurazione attuale, Istio consuma circa 300 Mb di memoria RAM per ogni istanza di servizio. A causa del gran numero di funzionalità, il bilanciamento trasparente influisce anche sul tempo di risposta totale dei servizi (fino a 10 ms).

Di conseguenza, abbiamo esaminato quali funzioni ci servono al momento e abbiamo deciso che la ragione principale per cui abbiamo iniziato a implementare soluzioni simili era la possibilità di raccogliere in modo trasparente le informazioni di tracciamento da tutto il sistema. Inoltre, desideravamo avere controllo sulle interazioni tra i servizi e fare varie manipolazioni con le intestazioni trasmesse tra di essi.

Alla fine siamo giunti alla nostra soluzione:  Netramesh.

Netramesh

Netramesh — è una soluzione service mesh leggera con capacità di scalabilità illimitata, indipendentemente dal numero di servizi nel sistema.

Le principali aspettative della nuova soluzione erano un basso overhead di risorse e alte prestazioni. Tra le funzionalità desiderate, volevamo sin da subito la possibilità di inviare in modo trasparente i tracing span al nostro sistema Jaeger.

Oggi, la maggior parte delle soluzioni cloud è realizzata in Golang. E, naturalmente, ci sono delle buone ragioni per questo. Scrivere applicazioni di rete in Golang, che operano in modo asincrono con input-output e che si scalano secondo necessità sui core, è comodo e abbastanza semplice. E, ciò che è molto importante, le prestazioni risultano adeguate per risolvere questo compito. Pertanto, abbiamo scelto anche noi Golang.

Prestazioni

Ci siamo concentrati sui nostri sforzi per raggiungere prestazioni massime. Per una soluzione che viene distribuita accanto a ogni istanza del servizio, è necessaria una bassa assunzione di memoria e tempo di CPU. E, naturalmente, il ritardo nella risposta deve essere anche molto ridotto.

Diamo un'occhiata ai risultati ottenuti.

RAM

Netramesh consuma circa 10Mb senza traffico e 50Mb al massimo con un carico fino a 10000 RPS su un'istanza.

Il proxy Istio envoy consuma sempre circa 300Mb nelle nostre aree clustering con migliaia di istanze. Questo non consente di scalare su tutto il cluster.

Netramesh – soluzione di service mesh leggera

Netramesh – soluzione di 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 al secondo sul sidecar. I valori a 3000 richieste al secondo in picco:

Netramesh – soluzione di service mesh leggera

Netramesh – soluzione di 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 vedere questa situazione senza carico:

Netramesh – soluzione di service mesh leggera

Utilizziamo HTTP/1 per l'interazione tra i servizi. L'aumento del tempo di risposta in Istio durante il proxying tramite envoy era fino a 5-10ms, il che è abbastanza per i servizi pronti a rispondere in un millisecondo. Con Netramesh questo tempo è diminuito a 0,5-2ms.

Scalabilità

La limitata quantità di risorse utilizzate da ogni proxy consente di posizionarlo vicino a ciascun servizio. Netramesh è stato volutamente creato senza un componente di control plane per mantenere leggeri ciascun sidecar. Spesso, in soluzioni di service mesh, il control plane diffonde informazioni di service discovery in ogni sidecar. Insieme a queste si trasmettono anche informazioni sui timeout e sulle impostazioni di bilanciamento. Tutto ciò rende possibile molte operazioni utili, ma, sfortunatamente, gonfia le dimensioni dei sidecar.

Service discovery

Netramesh – soluzione di service mesh leggera

Netramesh non aggiunge ulteriori meccanismi per la service discovery. Tutto il traffico viene proxyato in modo trasparente attraverso il sidecar netra.

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 attraverso le quali avviene l'interazione tramite HTTP. Ad esempio, per l'interazione tra servizi e richieste esterne utilizziamo le porte 80, 8890, 8080. In questo caso, possono essere definite tramite una variabile d'ambiente NETRA_HTTP_PORTS.

Se utilizzi Kubernetes come orchestratore e il suo meccanismo per le entità 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. Questa connessione viene stabilita inizialmente con il netra-sidecar locale e tutti i pacchetti TCP arrivano inizialmente a netra. Successivamente, netra-sidecar stabilisce la connessione con il punto di destinazione originale. Il NAT sull'indirizzo IP del pod nel nodo rimane esattamente lo stesso come senza netra.

Tracing distribuito e propagazione del contesto

Netramesh offre funzionalità necessarie per inviare tracing span riguardanti l'interazione HTTP. Netra-sidecar analizza il protocollo HTTP, misura i ritardi delle richieste ed estrae le informazioni necessarie dagli header HTTP. Alla fine, otteniamo tutti i trace all'interno di un'unica sistema Jaeger. Per una configurazione fine, è possibile utilizzare anche variabili d'ambiente fornite dalla libreria ufficiale jaeger go library.

Netramesh – soluzione di service mesh leggera

Netramesh – soluzione di service mesh leggera

Ma c'è un problema. Finché i servizi non generano e propagano un'intestazione uber speciale, non vedremo gli span di tracing connessi nel sistema. E questo è ciò di cui abbiamo bisogno per trovare rapidamente la causa dei problemi. Qui Netramesh ha di nuovo una soluzione. I proxy leggono gli header HTTP e, se non trovano l'uber trace id, lo generano. Netramesh memorizza anche informazioni su richieste in entrata e in uscita nel sidecar e le associa arricchendo le necessarie intestazioni delle richieste in uscita. Tutto ciò che è necessario fare nei servizi è propagare solo un'intestazione X-Request-Id, che può essere configurata 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 il contesto sarà conservato) e NETRA_TRACING_CONTEXT_CLEANUP_INTERVAL (la frequenza di pulizia del contesto).

È anche possibile combinare diversi percorsi nel sistema contrassegnandoli con un apposito marcatore di sessione. Netra consente di impostare HTTP_HEADER_TAG_MAP per trasformare le intestazioni HTTP nei corrispondenti tag tracing span. Questo può essere particolarmente utile per i test. Dopo aver eseguito un test funzionale, è possibile vedere quale parte del sistema è stata interessata filtrando in base al relativo chiave di sessione.

Identificazione della fonte della richiesta

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

Questo consente di diffondere uniformemente questa utile intestazione a tutta la rete. Si può quindi utilizzarla nei servizi e aggiungerla ai log e alle metriche.

Routing del traffico e interni di Netramesh

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

Inoltre, lo strumento offre un'interessante possibilità: il routing probabilistico. Se si utilizza Netramesh esclusivamente per raccogliere tracing span, si possono risparmiare risorse in ambiente di produzione e attivare il routing probabilistico utilizzando le 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 sidecar di netra accetta una nuova connessione e utilizza SO_ORIGINAL_DST l'opzione socket per ottenere il punto di destinazione originale. Successivamente, 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 tenta di analizzarla e tracciarla. Se l'analisi HTTP non ha successo, Netra effettua un fallback su TCP eproxy trasparentemente i byte.

Costruzione del grafo delle dipendenze

Dopo aver ricevuto una grande quantità di informazioni di tracing in Jaeger, si desidera ottenere un grafo completo delle interazioni nel sistema. Ma se il vostro sistema è abbastanza carico e nel corso di un giorno si accumulano miliardi di span di tracing, aggregarli diventa un compito piuttosto complesso. Esiste un modo ufficiale per farlo: spark-dependencies. Tuttavia, richiederà ore per costruire il grafo completo e costringerà a scaricare da Jaeger l'intero dataset degli ultimi giorni.

Se utilizzate Elasticsearch per archiviare gli span di tracing, potete sfruttare una semplice utility in Golang, che costruirà un grafo simile in pochi minuti, utilizzando le caratteristiche e le potenzialità di Elasticsearch.

Netramesh – soluzione di service mesh leggera

Come utilizzare Netramesh

Netra può essere semplicemente aggiunto a qualsiasi servizio gestito da qualsiasi orchestration tool. Potete vedere un esempio qui.

Attualmente, Netra non ha la possibilità di implementare automaticamente un sidecar ai servizi, ma ci sono piani per la sua realizzazione.

Futuro di Netramesh

L'obiettivo principale Netramesh è raggiungere costi minimi in termini di risorse e elevate prestazioni, fornendo funzionalità fondamentali per l'osservabilità e il controllo delle interazioni tra i servizi.

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

Utilizzate Netramesh se incontrate problemi simili e contattateci per domande e proposte.

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