Envoy è un proxy L3/L4 e L7 programmabile ad alte prestazioni su cui si basano molte implementazioni di service mesh come Istio. Il cuore della gestione delle connessioni e del traffico di Envoy è costituito dai filtri di rete che, una volta combinati in catene di filtri, consentono l‘implementazione di funzionalità di ordine superiore per il controllo degli accessi, la trasformazione, l’arricchimento dei dati, l’auditing e così via. È possibile aggiungere nuovi filtri per estendere l’attuale set di funzioni di Envoy con nuove funzionalità.

Ci sono due modi per farlo:

  • Integrare i filtri aggiuntivi nel codice sorgente di Envoy e compilare una nuova versione di Envoy. Lo svantaggio di questo approccio è che è necessario mantenere la propria versione di Envoy e mantenerla costantemente sincronizzata con la distribuzione ufficiale. Inoltre, poiché Envoy è implementato in C++, anche il filtro deve essere implementato in C++;
  • Caricare dinamicamente nuovi filtri nel Proxy Envoy in fase di esecuzione;

La seconda opzione è estremamente interessante per noi, in quanto semplifica notevolmente il processo di estensione di Envoy con nuove funzionalità. Questa soluzione si basa su WebAssembly (WASM), un efficiente formato di istruzioni binarie portatili che fornisce un ambiente di esecuzione incorporabile e isolato.

Perché i filtri WASM

Con le implementazioni dei filtri WASM otteniamo:

  • Agilità – i filtri possono essere caricati dinamicamente nel processo Envoy in esecuzione, senza la necessità di interrompere o ricompilare;
  • Manutenibilità non è necessario modificare la base di codice di Envoy per estendere le sue funzionalità;
  • Diversità – i linguaggi di programmazione più diffusi, come C/C++ e Rust, possono essere compilati in WASM in modo che gli sviluppatori possano implementare i filtri utilizzando il loro linguaggio di programmazione preferito;
  • Affidabilità ed isolamento – i filtri vengono distribuiti in una macchina virtuale (sandbox), quindi sono isolati dal processo Envoy che li ospita (ad esempio, se il filtro WASM si arresta in modo anomalo, non influirà sul processo Envoy);
  • Sicurezza: poiché i filtri comunicano con l’host (Envoy Proxy) attraverso un’API ben definita, hanno accesso e possono modificare solo un numero limitato di proprietà di connessione o richiesta;

Ha anche alcuni aspetti che devono essere presi in considerazione:

  • Le prestazioni sono circa il 70% più veloci del C++ nativo;
  • Maggiore utilizzo della memoria dovuto alla necessità di avviare una o più macchine virtuali WASM;

SDK WASM di Envoy Proxy

Envoy Proxy esegue i filtri WASM all’interno di una macchina virtuale, in modo che la memoria del filtro sia isolata dall’ambiente host. Tutte le interazioni tra l’host incorporato (Envoy Proxy) e il filtro WASM sono realizzate attraverso funzioni e callback forniti da Envoy Proxy WASM SDK. L’SDK WASM di Envoy Proxy dispone di implementazioni in vari linguaggi di programmazione come:

  • C++
  • Rust
  • AssemblyScript
  • Go (ancora sperimentale)

In questo articolo, discuteremo come scrivere filtri WASM per Envoy utilizzando l’SDK in C++. Non discuteremo in dettaglio delle sue API, poiché non rientrano nello scopo dell’articolo. Tuttavia, accenneremo alcuni aspetti necessari per comprendere le basi della scrittura di filtri WASM per Envoy.

L’implementazione del filtro deve derivare dalle due classi seguenti:

Quando il plugin WASM (il binario che contiene il filtro) viene caricato, viene creato un contesto root. Il contesto ha lo stesso ciclo di vita dell’istanza della macchina virtuale che esegue il filtro e viene utilizzato per:

  • Interazioni al momento della configurazione iniziale tra il codice dell’utente e il proxy Envoy;
  • Interazioni che durano più di una richiesta;

onConfigure(size_t) viene richiamato solo nel RootContext da Envoy Proxy per passare le configurazioni di VM e plugin. Se il plug-in contenente uno o più filtri prevede che una configurazione venga passata da Envoy Proxy, è possibile sovrascrivere questa funzione e ottenere la configurazione usando la funzione helper getBufferBytes tramite WasmBufferType::VmConfiguration e WasmBufferType::PluginConfiguration rispettivamente.

Il traffico di rete gestito da Envoy Proxy passa attraverso la catena di filtri associata al listener che riceve il traffico. Per ogni nuovo flusso che attraversa una catena di filtri, Envoy Proxy crea un nuovo contesto che dura fino al termine del flusso.

La classe base Context fornisce hook (callback) sotto forma di funzioni virtuali onXXXX(…) per il traffico HTTP e TCP, che vengono invocate quando Envoy Proxy scorre la catena di filtri. Si noti che le callback invocate su Context dipendono dal livello della catena di filtri in cui il filtro è inserito. Ad esempio, FilterHeadersStatus onRequestHeaders(uint32_t) viene invocato solo sui filtri WASM che fanno parte di una catena di filtri a livello HTTP e non sui filtri a livello TCP.

L’implementazione della classe base Context viene utilizzata da Envoy Proxy per interagire con il codice durante l’intero ciclo di vita del flusso. È possibile manipolare/mutare il traffico dall’interno di queste funzioni di callback.

L’SDK fornisce funzioni specifiche per la manipolazione delle intestazioni delle richieste/risposte HTTP (ad es. getRequestHeader, addRequestHeader, ecc.), del corpo HTTP, dei flussi TCP (ad es. getBufferBytes, setBufferBytes), ecc.

Ogni funzione di callback restituisce uno stato attraverso il quale è possibile indicare a Envoy Proxy se passare o meno l’elaborazione del flusso al filtro successivo della catena.

Il prossimo pezzo riguarda la registrazione delle istanze del factory per la creazione delle implementazioni di RootContext e Context, dichiarando una variabile statica di tipo:

La variabile si aspetterà il factory context root e il factory context sotto forma di argomenti del costruttore.

Esempio di filtro 

Di seguito è riportato un esempio molto semplice che mostra lo scheletro di un filtro WASM utilizzando il CPP Envoy Proxy WASM SDK: example-filter.cc:

Creare il filtro

Il modo più semplice per costruire un filtro è usare Docker poiché non richiede la conservazione di varie librerie sul computer locale.

  • Per prima cosa, creare un’immagine Docker con l’SDK WASM di Envoy Proxy C++.
  • Creare il Makefile per il filtro WASM. Makefile:
  • Costruire il filtro WASM:

Distribuzione del filtro WASM con Istio

Vediamo come distribuire il nostro filtro Envoy WASM all’interno di una rete di servizi Istio su Kubernetes.

È possibile creare rapidamente una mesh Istio, che comprende un’applicazione demo su Kubernetes con Backyards (ora Cisco Service Mesh Manager), la distribuzione Istio di Banzai Cloud.

Con questo singolo comando, si ottiene una mesh di servizi Istio pronta per la produzione e completamente operativa e un’applicazione demo composta da più microservizi in esecuzione all’interno della mesh.

Creare una mappa di configurazione per contenere il binario wasm

Creare una mappa di configurazione per contenere il binario WASM del filtro nello spazio dei nomi backyards-demo dove è in esecuzione l’applicazione demo.

Iniettare il binario wasm nell’applicazione demo utilizzando Istio

Iniettare il binario wasm nel servizio frontpage della nostra applicazione demo, utilizzando le due annotazioni seguenti:

Eseguire quanto segue:

Il binario del filtro WASM dovrebbe ora essere disponibile in /var/local/lib/wasm-filters nel contenitore istio-proxy:

Per consentire ai filtri WASM di registrarsi a livello di log DEBUG durante l’elaborazione del traffico destinato al servizio frontpage:

Inserire il nostro filtro WASM nella catena di filtri a livello HTTP agganciati alla porta HTTP 8080:

Inviare del traffico alla porta HTTP 8080 del servizio frontpage:

Nella risposta, ci aspettiamo di vedere l’intestazione del nostro filtro aggiunta all’intestazione della risposta:

Se si vuole registrare il filtro WASM in una catena di filtri TCP per il servizio frontpage che accetta connessioni TCP sulla porta 8083, la risorsa personalizzata EnvoyFilter avrà questo aspetto:

Quando il filtro viene aggiunto a una catena di filtri a livello TCP, solo gli hook specifici per il traffico TCP saranno rispettati dall’SDK e invocati sul filtro.

Il diagramma seguente illustra ad alto livello il flusso di distribuzione dei filtri con Istio:

Scrivere filtri WASM per Envoy con WASME

solo.io ha fornito una soluzione per lo sviluppo di filtri WASM per Envoy: un hub WebAssembly in cui le persone possono caricare/scaricare i file binari dei filtri WASM. Forniscono uno strumento chiamato WASME che ti aiuta a impalcare i filtri WASM, creando e spingendo i filtri su WebAssembly Hub .

Quando viene distribuito un filtro WASM, wasme estrae l’immagine che contiene il plugin del filtro WASM da WebAssembly Hub, avvia un daemonset per estrarre il binario del plugin WASM dall’immagine estratta e renderlo disponibile ai proxy Envoy su ogni nodo attraverso i volumi hostPath.

Nota: le immagini estratte da WebAssembly Hub non vengono normalmente visualizzate come immagini Docker standard.

Poiché questa soluzione prevede la pubblicazione e l’archiviazione dei filtri WASM in una posizione centrale esterna (WebAssembly Hub), potrebbe non essere un’opzione per quelle aziende che, a causa di rigide politiche di sicurezza, (o per qualsiasi altro motivo) non sono disposte a pubblicare la logica aziendale proprietaria, anche in formato binario, al di fuori dei confini della rete aziendale.

Con i filtri WASM per Envoy, gli sviluppatori possono scrivere il loro codice personalizzato, compilarlo in plugin WASM e configurare Envoy per eseguirlo. Questi plugin possono contenere una logica arbitraria, quindi sono utili per tutti i tipi di integrazioni e mutazioni di messaggi, il che rende i filtri WASM per Envoy Proxy il modo perfetto per integrare Kafka su Kubernetes con Istio.

Non perderti, ogni mese, gli approfondimenti sulle ultime novità in campo digital! Se vuoi sapere di più, visita la sezione “Blog“ sulla nostra pagina!