From 88760a8a8f7088039854dd0497a255a386bc8992 Mon Sep 17 00:00:00 2001 From: Lorenzo Venerandi <68255980+Lore09@users.noreply.github.com> Date: Tue, 1 Apr 2025 19:54:09 +0200 Subject: [PATCH] updated readme --- readme.md | 962 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 859 insertions(+), 103 deletions(-) diff --git a/readme.md b/readme.md index 4cf39cd..939efd9 100644 --- a/readme.md +++ b/readme.md @@ -1,146 +1,637 @@ -# PELATO: Progettazione ed Esecuzione di Lifecycle Automatizzati per Tecnologie WebAssembly ed Orchestrazione +L'obiettivo di questo elaborato è progettare ed implementare una +soluzione che consenta la distribuzione di applicazioni nel panorama +dell'Edge-Cloud continuum. Le applicazioni devono essere sviluppate +seguendo il paradigma FaaS ed essere in grado di funzionare +indipendentemente dall'infrastruttura sottostante e senza essere +ri-compilate. Inoltre, la piattaforma deve essere in grado di effettuare +migrazioni live delle applicazioni e spostare il carico computazionale +in modo automatico in risposta a problemi infrastrutturali o a necessità +di bilanciamento del carico. La migrazione deve avvenire anche fra +ambienti eterogenei, per esempio deve essere possibile spostare +l'esecuzione di un'applicazione da un nodo Edge ad uno Cloud.\ +Le applicazioni in esecuzione devono poter interagire e scambiarsi +informazioni, quindi c'è necessità di utilizzare un sistema di +messaggistica distribuito che si sposi con il modello di esecuzione ad +eventi del paradigma FaaS.\ +Per facilitare l'adozione del paradigma FaaS è stato scelto un modello +applicativo basato sui microservizi, in +particolare facente uso della tecnologia Wasm. La scelta è stata fatta +in quanto riesce a superare una criticità associata alla tradizionale +soluzione del container. Essi infatti vengono eseguiti in runtime +strettamente collegati al kernel del sistema operativo in cui risiedono +ed esso cambia con l'architettura della macchina. Questo costringerebbe +ad avere una versione dell'applicazione per ogni architettura +supporata.\ +Le applicazioni compilate in Wasm invece possono operare in qualsiasi +host senza preoccuparsi di requisiti infrastrutturali, consentendo +l'utilizzo dello stesso artifact per ogni host. -# Infrastuttura +![Modello Edge Computing](res/img/schemi-Scenari.drawio.svg) -L'infrastuttura simula un flusso IoT ed è composta da: -- ambiente Cloud, deployato su kubernetes e dotato di cluster NATS, WADM e Wasmcloud Host -- ambiente Edge, semplice macchina Linux con docker compose, Wasmcloud e NATS Leaf come docker container -- dispositivi IoT collegati all'ambiente Edge +Per soddisfare i requisiti di migrazione e bilanciamento automatico fra +nodi si è scelto di adottare wasmCloud, principalmente per i seguenti +motivi: -![infra](/res/img/infra.png) +- Supporto all'esecuzione di componenti Wasm e specifica WASI Preview + 2. -# Pipeline +- Supporto sia di ambienti Cloud Native (come Kubernetes o Docker) che + tradizionali (VM con Linux). -Il processo si divide in due fasi principali, cioè quello di generazione del componente wasm e quella di deploy. +- Networking basato su rete mesh Lattice che consente di astrarre + l'infrastruttura sottostante e permette l'interazione dei + componenti. -## Generazione del componente WASM +- Cluster auto-rigenerante e facilmente scalabile all'interno del + Lattice. -Questa fase si occupa di trasformare una serie di files descrittivi in codice, selezionare il template corretto, buildare il progetto, generare il file di deploy e quindi pushare l'artifact OCI in un registry. +- Comunicazione efficiente grazie al backend basato su NATS. -### Parsing del codice +- Gestione delle applicazioni tramite OCI Registry. -Il progetto di partenza è composto da un file `workflow.yaml`, nel quale vengono descritti tutti i componenti, e da una cartella `tasks` contenente le porzioni di codice che devono essere eseguite dai vari componenti. +NATS è stata la soluzione selezionata anche per quanto riguarda la +comunicazione e l'interazione delle applicazioni stesse, principalmente +per il suo supporto alla clusterizzazione e alla distribuzione su +ambienti edge basata su nodi Leaf. + +![Infrastruttura +target](res/img/schemi-architettura-infra.drawio.svg) + +Il processo di trasformazione delle funzioni in componenti Wasm e la +loro distribuzione verrà approfondita nella prossima sezione. + +## PELATO Framework + +Lo scopo ultimo di questo elaborato è quindi trasformare una semplice +funzione in un codice compilabile in componente Wasm, configurarlo in +modo che possa essere deployato su ambienti wasmCloud e distribuirlo +sull'infrastruttura.\ +La soluzione proposta è un framework denominato PELATO, acronimo di +**Progettazione ed Esecuzione di Lifecycle Automatizzati e Tecnologie +d'Orchestrazione per moduli WebAssembly**, che ha lo scopo di: + +- Fornire un modo intuitivo per istanziare un modulo FaaS, quindi + ridurre al minimo le responsabilità dell'utilizzatore che dovrà + esclusivamente specificare il codice della funzione da eseguire + nella task remota. + +- Consentire all'utilizzatore di configurare in modo intuitivo i + metadati delle task, come il nome, il target di deployment, ma anche + l'origine e la destinazione dei dati. + +- Astrarre i più possibile il processo di generazione del codice e di + Build del componente Wasm. + +- Gestire il deployment dei componenti nell'infrastruttura. + +Nelle seguenti sezioni verranno approfondite le scelte tecnologiche ed +architetturali che sono state compiute durante la realizzazione di +questo progetto. + +### Tecnologie + +In questa sezione verranno elencate e spiegate le tecnologie utilizzate +per lo sviluppo di questa soluzione. Ci concentreremo principalmente +sui linguaggi e i tool utilizzati dal framework PELATO, come i linguaggi +Python e Go. + +#### Python + +Il framework è stato implementato utilizzando Python, un linguaggio di +programmazione ad alto livello interpretato ed orientato agli oggetti, +apprezzato per la sua sintassi chiara e la tipizzazione dinamica che ne +facilitano l'apprendimento e la manutenzione. Grazie a un vasto +ecosistema di librerie è ampiamente utilizzato in ambiti come data +science, sviluppo web e intelligenza artificiale. Supporta diversi +paradigmi di programmazione e, grazie alla gestione automatica della +memoria, riduce la complessità nello sviluppo. Inoltre, la sua +portabilità su Windows, macOS e Linux lo rende adatto a molteplici +contesti, dalla prototipazione rapida alle soluzioni enterprise.\ +È noto che Python, pur offrendo un ecosistema ricco e una sintassi +intuitiva, può presentare limitazioni in termini di performance, +soprattutto per operazioni CPU-intensive. Questo è in gran parte dovuto +al Global Interpreter Lock (GIL), che impedisce l'effettivo sfruttamento +del multi-threading in scenari di calcolo parallelo. Questi problemi +sono stati risolti spostando il carico computazionale ed il parallelismo +su strumenti esterni come Docker (la metodologia verrà spiegata in +seguito).\ +La scelta di impiegare questo linguaggio per sviluppare il framework è +stata motivata principalmente dalla vasta gamma di librerie disponibili. +In particolare, per lo sviluppo di PELATO ce ne sono due fondamentali: + +- **Jinja**: è un motore di template per Python che permette di + generare contenuti dinamici combinando testo statico e logica di + programmazione. Utilizzato in framework web come Flask e strumenti + di automazione come Ansible, trova applicazione anche nella + configurazione di orchestratori come Helm, dove consente di + parametrizzare file YAML. Attraverso la sintassi con doppie + parentesi graffe, Jinja sostituisce variabili con valori definiti, + rendendo la configurazione più flessibile e riutilizzabile. È stato + utilizzato in fase di generazione del codice per parametrizzare lo + stesso in base alla configurazione specificata dall'utente. + +- **docker-py**: questa libreria consente di interagire con le API di + Docker direttamente da Python, facilitando la gestione e + l'automazione dei container. Permette operazioni come la creazione, + l'avvio e l'eliminazione di container, oltre alla gestione di + immagini e volumi. Questa integrazione è stata fondamentale per + poter parallelizzare le operazioni di build e di deployment dei + moduli Wasm. + +#### Golang + +Un altro linguaggio utilizzato è Go, noto anche come Golang, un progetto +open source sviluppato da Google per offrire un equilibrio tra +efficienza, sicurezza e semplicità. Grazie alla tipizzazione statica, al +garbage collector e a un avanzato sistema di concorrenza basato sulle +goroutine, Go consente la creazione di applicazioni scalabili e +performanti. Pur supportando la programmazione orientata agli oggetti +attraverso interfacce e struct, mantiene una sintassi essenziale e +pragmatica. Inoltre, la sua ricca standard library fornisce strumenti +per la gestione della rete, il parsing di file e la sicurezza +crittografica.\ +Go è il linguaggio selezionato per la programmazione delle funzioni da +eseguire nei componenti Wasm: l'utente che utilizza il framework PELATO +dovrà programmare i propri flussi utilizzando Go.\ +La scelta di utilizzare questo linguaggio è stata effettuata +principalmente perché è uno dei due linguaggi (insieme a Rust) che +attualmente implementa la specifica WASI Preview 0.2, necessaria per la +composizione delle applicazioni tramite components e providers su +wasmCloud.\ +La scelta di Go invece di Rust è avvenuta perché la sintassi di +quest'ultimo risulta complessa e di difficile lettura, rendendolo meno +accessibile per utenti senza esperienza pregressa. Al contrario, Go +offre una sintassi più chiara e intuitiva, facilitando lo sviluppo e la +manutenzione del codice. + +### Struttura del framework + +In questa sezione verrà data una descrizione architetturale ad alto +livello di PELATO. Il framework è sviluppato secondo un'architettura che +adotta il pattern di progettazione `Facade` che fornisce un'interfaccia +semplificata e unificata per accedere alle varie funzionalità. In +pratica, una classe Facade funge da punto di accesso centrale, +nascondendo la complessità interna e offrendo metodi di alto livello per +interagire con il sistema.\ +La classe `Pelato` viene utilizzata come punto di accesso per la CLI +(Command Line Interface), memorizza le configurazioni iniziali e si +occupa di salvare le metriche. Inoltre è responsabile dell'esecuzione +della logica di business implementata nei tre package: + +- `code_generator` + +- `wasm_builder` + +- `component_deploy` + +La configurazione iniziale della classe avviene tramite variabili +d'ambiente (che possono essere caricate dal framework tramite un file +`.env` locale, verranno dati più dettagli in seguito); ciò consente una +facile implementazione del framework come applicazione containerizzata, +nel caso si volesse "trasformare\" in un servizio gestito in Cloud.\ +Questo pattern di progettazione aumenta la modularità dei componenti +facilitando eventuali estensioni dove si fa l'esempio di un API +Server affiancato alla CLI. + +![Architettura framework +PELATO](res/img/schemi-implementazione-struttura.drawio.svg) + +## Configurazione ed esecuzione framework + +In questa sezione verrà approfondita la configurazione del framework. +Come già detto in precedenza il codice è scritto in Python, quindi sarà +necessario avere un interprete di Python3 installato (consigliato +`Python 3.12`), inoltre sarà necessario installare le librerie +specificate nel `requirement.txt`. L'approccio consigliato è quello di +utilizzare un virtual environment, cioè un'istanza dell'interprete +Python che consente di installare le librerie localmente, senza toccare +l'interprete configurato nel sistema o preoccuparsi di eventuali +incompatibilità.\ +La configurazione dinamica avviene tramite variabili d'ambiente, +ottenute dal sistema all'inizio di ogni esecuzione. Per facilitare il +processo di configurazione è possibile creare un file `.env` situato +nella cartella del framework: all'inizio di ogni esecuzione il file .env +viene parsato e le variabili al suo interno vengono utilizzate per +impostare il servizio.\ +All'interno della repository è predisposto il file `.env.template`, nel +quale sono riportate le variabili d'ambienti impostabili e la +configurazione di default, che viene mostrata di seguito. + +```sh +REGISTRY_URL= +REGISTRY_USER= +REGISTRY_PASSWORD= +PARALLEL_BUILD=True +NATS_HOST=localhost +NATS_PORT=4222 +ENABLE_METRICS=True +``` + +Andiamo ad analizzarle: + +- `REGISTRY_URL`, `REGISTRY_USER`, `REGISTRY_PASSWORD` servono per + impostare le informazioni del registry in cui verranno caricate le + immagini OCI dei moduli Wasm. + +- `PARALLEL_BUILD` abilita l'esecuzione dei container in modalità + parallela. + +- `NATS_HOST` e `NATS_PORT` sono utilizzati in fase di deployment e + rappresentano l'istanza di NATS a cui il framework si collega per + deployare le applicazioni sul cluster wasmCloud. + +- `ENABLE_METRICS` abilita la memorizzazione delle metriche ad ogni + esecuzione del codice. + +### Setup progetto + +L'utente finale, una volta configurato l'ambiente di esecuzione e +impostate le variabili, per poter utilizzare il framework dovrà creare +un progetto contenente: + +- Cartella `task` contenente i file Go in cui vengono definite le + funzioni eseguite dai componenti Wasm. + +- File `workflow.yaml` in cui inserire la configurazioni dei vari Task + (per esempio nome, versione, template e nome del file Go) e sarà + utilizzato dal generatore per compilare i template Jinja. Un esempio + di file workflow viene mostrato di seguito. + +Le modalità di compilazione di questi file verranno approfondite nel +capitolo dedicato alla generazione del codice. + +```yaml +project_name: Test_project + tasks: + - name: Temp sensor read + type: producer_nats + code: sensor_read.go + targets: + - cloud + - edge + source_topic: test_source_data + dest_topic: test_dest_data + component_name: temp_sensor_data + version: 1.0.0 + ... +``` + +### PELATO CLI + +Come già anticipato in precedenza l'interfacciamento fra utente e +framework è realizzata tramite una CLI, cioè un'interfaccia a riga di +comando in grado di ricevere istruzioni e configurazioni.\ +Per invocare la CLI è sufficiente lanciare il comando +`python3 pelato.py`, che restituirà come output le varie opzioni e gli +argomenti necessari, come mostrato nel codice. + +``` {#code:pelato_cli caption="Output Pelato CLI" captionpos="b" label="code:pelato_cli"} +usage: pelato.py [-h] {gen,build,deploy,remove,brush} ... + Generate, build and deploy WASM components written in go + + Command list + gen Generate Go code + build Build WASM component + deploy Deploy WASM components + remove Remove deployed WASM components + brush Starts the pipeline: gen -> build -> deploy + + options: + -h, --help show this help message and exit +``` + +Ognuno di essi necessita come ulteriore argomento la path della cartella +in cui sono contenuti il file `workflow.yaml` e le `task`. Nel codice vengono riportati alcuni esempi di utilizzo. + +```sh +$ python3 pelato.py -h # help +$ python3 pelato.py gen /home/lore/documents/project/ # generazione +$ python3 pelato.py remove project/ # rimozione +$ python3 pelato.py brush project/ # esecuzione pipeline +``` + +## Pipeline di esecuzione + +In questa sezione verrà descritta l'intera pipeline di esecuzione del +framework, basandosi sui passaggi riportati seguente figura. + +![Pipeline di esecuzione +PELATO](res/img/schemi-arch-pipeline.drawio.svg) + +Adesso verranno analizzati i tre componenti di esecuzione di PELATO: + +1. **Generazione**: in questa fase viene parsato il file + `workflow.yaml` e poi utilizzato per generare i progetti Go + necessari per buildare i moduli Wasm. Le configurazioni del file + vengono utilizzate per selezionare il template di base, per + sostituire i valori del template tramite Jinja e per identificare il + file Go contenente la task.. + +2. **Build**: questa fase si occupa di utilizzare i progetti Go + generati in fase 1 e compilarli per ottenere un componente Wasm, + quindi pubblicarlo come OCI artifact sul registry configurato. + Queste operazioni avvengono all'interno di un container Docker nel + quale sono installati tutti i tool necessari per l'operazione, come + Go, TinyGo, Rust e wash. + +3. **Deploy**: è l'ultima fase della pipeline di PELATO, utilizza il + manifest `wadm.yaml` generato in fase 1 per creare l'applicazione + sulla piattaforma wasmCloud. Anche questa operazione avviene + all'interno di un container in quanto necessita del tool wash. + +Una volta ultimato il processo, le applicazioni saranno disponibili sul +wasmCloud, o lo diventeranno quando sarà presente un nodo healthy con +label `host-type` corrispondente a quella selezionata dalla task.\ +La pipeline del framework è suddivisa nelle tre operazioni di +generazione, build e deployment in modo che ognuna possa funzionare in +modo autonomo (ovviamente se provviste delle risorse necessarie +all'operazione). Inoltre, l'utilizzo di un container per le operazioni +di build e deployment mira ad aumentare la compatibilità e la +distribuzione del framework in modo che non sia dipendente dal sistema +in cui verrà implementato: in questo progetto l'interfaccia è stata +realizzata come CLI utilizzando Python, ma potrebbe essere anche esteso +ad una configurazione SaaS completamente in Cloud o con approccio GitOps +tramite actions e frameworks di CI/CD.\ +Tutte e tre le operazioni verranno approfondite nel dettaglio nei +prossimi capitoli. + +# Generazione + +In questo capitolo verrà approfondito il processo di generazione codice +del progetto Go e del manifest wadm, che verranno impiegati poi per le +fasi di Build e Deploy. Il primo passo è definire con precisione le +struttura e la composizione dei file che fanno parte del progetto +iniziale, quello che dovrà poi configurare l'utente finale che +utilizzerà il framework, strutturato nel modo seguente: ``` ├─ workflow.yaml -└─ tasks -   ├─ sensor_read.go -   ├─ aggregate.go -   └─ db_sync.go +├─ tasks/ +   ├─ task1.go + └─ ... +└─ gen/ ``` -#### Workflow -Nel file workflow vengono descritti i vari componenti, un esempio potrebbe essere +## Definizione Tasks -```yaml -tasks: - - name: Data Aggregation # Displayed name - type: processor_nats # Used to select template - code: aggregate.go # Go code file inside tasks/ dir - target: # Where the component will be deployed - - edge - - cloud - source_topic: temp_sensor # Source NATS topic - dest_topic: aggregated_data # Destination NATS topic - component_name: data_aggregation # Component name displayed in the OCI artifact - version: 1.0.0 # Component version -... +In questa sezione ci si concentrerà sulle specifiche dei file task, cioè +quelli situati nella cartella `tasks` del progetto e contenenti le +funzioni che verranno eseguite dai moduli Wasm. Iniziamo fornendo la +struttura base di ogni task file, mostrata nel codice. + +``` go +package main + import ( + ... + ) + ... + func exec_task(arg string) string{ + + return ... + } ``` -#### Task files -I file con il codice all'interno della cartella `task` devono seguire il formato +I requisiti da rispettare per la definizione della task sono: + +- Il file deve avere un'estensione `.go`. + +- Il package deve essere impostato su main (stesso package del main + fornito dal template). + +- Funzione di interfacciamento (cioè chiamata dal main) nominata + `exec_task`. + +- La funzione di interfacciamento deve accettare una stringa e + restituire una stringa. + +La funzione interfaccia accetta e restituisce una stringa per facilitare +il più possibile l'utilizzo da parte di un utente finale, che non dovrà +preoccuparsi di tradurre i tipi Go con i tipi Wasm specificati in WASI +(cosa che viene gestita in "background" sul file main del progetto Go).\ +Una volta soddisfatti questi requisiti è possibile eseguire innumerevoli +operazioni, come importare librerie, definire strutture e altre +funzioni. Un esempio è mostrato nel codice seguente, in cui viene importata la libreria `encoding/json` e definita una struct `Request`. ```go package main - -import ( - -) - -func exec_task(arg string) string{ - - ... your code ... - - return response // must me string -} + import ( + "encoding/json" + ) + type Request struct { + Data int + Name string + } + func exec_task(arg string) string{ + // unmarshal the data + req := Request{} + json.Unmarshal([]byte(arg), &req) + + // do some operations + ... + + // return the json string + json, _ := json.Marshal(req) + return string(json) + } ``` -### Generazione del codice +## Configurazione Workflow -Il parser utilizza il campo `type` del workflow per selezionare il corretto template fra quelli implementati (per ora) -- producer_nats -- produzione di dati e scrittura di essi in un topic NATS -- processor_nats -- lettura di dati da un topic, processamento dei dati e scrittura su un altro topic -- dbsync_nats -- lettura di dati da un topic e scrittura su un DB (Postgres) +Passiamo ora alla codifica del file `workflow.yaml`, nel quale verranno +effettivamente impostati i task e il loro comportamento. -Viene sostituito il file delle task con quello del template, i campi del file `workflow` vengono utilizzati per compilare il file `wadm.yaml`. +### Specifica file workflow -### Build componente WASM +Il file deve essere correttamente formattato in Yaml e contenere i +seguenti campi: -Viene utilizzato il Dockerfile del template per buildare il componente WASM e pusharlo al registry configurato utilizzando il nome e la versione specificati nel `workflow.yaml` +- `project_name`: stringa contenente il nome del progetto, può + contenere spazi e viene utilizzato principalmente nella CLI e nelle + metriche. +- `tasks`: lista contenente i vari componenti Wasm. -## Deploy componenti WASM su Wasmcloud +Andando nel dettaglio, ogni elemento della lista `tasks` deve contenere: -In fase di deployment vengono utilizzati i file `wadm.yaml` compilati in precedenza per effettuare il deployment dell'applicazione, eccone un esempio +- `name`: stringa contenente il nome del componente, può contenere + spazi e verrà impostato come nome dell'applicazione su wasmCloud. + +- `type`: stringa contenente il template da utilizzare come base. + +- `code`: stringa contenente il nome del file task da associare al + componente Wasm in fase di generazione. + +- `targets`: lista contenente delle stringhe che rappresentano la + label `host-type` associata ai nodi target di deployment dei + componenti Wasm. + +- `source_topic`: stringa contenente il topic da cui verranno ricevuti + i dati. + +- `dest_topic`: stringa contenente il topic a cui verranno inviati. + +- `component_name`: stringa contenente il nome sintetico del + componente, non può contenere spazi e funge da nome per l'OCI + artifact associato. + +- `version`: versione del componente, specificata nel formato `x.x.x`. + Insieme al component_name conferisce il nome all'OCI artifact. + +### Template + +I template sono dei progetti già sviluppati che forniscono certe +funzionalità e fungono da base per il codice fornito dall'utente. +Attualmente sono implementati due template: + +- `processor_nats`: comunicazione interamente basata su NATS: i dati + arrivano da un topic e vengono inviati ad un topic. + +- `http_producer_nats`: i dati possono provenire sia da un topic NATS + che da una richiesta POST effettuata al nodo in cui è deployata + l'applicazione. Viene utilizzato infatti l'http provider per esporre + un web server nella porta 8000. + +Questo approccio consente una facile estendibilità del progetto: infatti +potranno essere sviluppati ed aggiunti nuovi template per aumentare le +funzionalità disponibili sul framework. Un altro template attualmente in +sviluppo è quello per la scrittura dei dati su un DB relazionale.\ +Per comprendere meglio le modalità di utilizzo del file workflow +analizziamo un esempio. + +```yaml +project_name: Temperature data analysis +tasks: + - name: Temp data conversion + type: http_producer_nats + code: convert.go + targets: + - edge + source_topic: living_room_celsius_data + dest_topic: living_room_kelvin_data + component_name: celsius_to_kelvin_conversion + version: 1.0.0 + - name: Data filter + type: processor_nats + code: filter.go + targets: + - cloud + source_topic: living_room_kelvin_data + dest_topic: filtered_kelvin_data + component_name: temp_filter + version: 1.0.2 +``` + +Nel file workflow riportato nel precedente codice vengono definiti due componenti: + +- il primo si occupa di ricevere dati inviati da sensori ad un server + http, convertire le temperature e pubblicarli in un topic NATS + +- il secondo filtra i risultati, magari rimuovendo errori di + misurazione o aggregandoli + +Questo semplice esempio vuole mostrare come con una configurazione +ridotta sia possibile generare, compilare e distribuire un'applicazione +che supporta casi d'uso applicabili al mondo IoT. + +## Generazione codice + +Il codice che si occupa della generazione è situato sul package +`code_generator` ed è distribuito nei file `generator.py` e +`template_compiler.py`. All'interno del package sono presenti anche i +template utilizzati come base per i progetti Go, situati nella cartella +`templates`. `code_generator/` + +### Parsing Workflow + +Verrà ora analizzato nel dettaglio il processo di generazione del +codice, partendo dalla fase di analisi del progetto fornito dall'utente. +La prima operazione viene effettuata da `generator.py`, di cui +riportiamo un estratto contenente il codice più rilevante. + +```go +def generate(project_dir, registry_url, metrics, metrics_enabled): + ... + # Parsing del file workflow.yaml + config = __parse_yaml(f"{project_dir}/workflow.yaml") + + # Pulizia della cartella di output + __remove_dir_if_exists(output_dir) + os.makedirs(output_dir, exist_ok=True) + + # Per ogni task all'interno del file workflow + for task in config['tasks']: + ... + # Compilazione dei template + template_compiler.handle_task(task, output_dir) + + # Copia del file task all'interno della cartella di output + shutil.copy2(f"{project_dir}/tasks/{task['code']}", f"{output_dir}/{task['component_name']}/{task['code']}") + + if metrics_enabled: + gen_metrics['gen_time'] = '%.3f'%(end_time - start_time) + metrics['code_gen'] = gen_metrics +``` + +Come si può notare dal Listing +[\[code:code_gen\]](#code:code_gen){reference-type="ref" +reference="code:code_gen"} la funzione `generate` si occupa di parsare +il file workflow, controllarne la validità e preparare la cartella di +output. + +### Compilazione template + +La generazione vera e propria del codice viene affidata a +`template_compiler` tramite la funzione `handle_task`, la quale +seleziona il template corretto in base a quello riportato nella +configurazione, lo copia nella cartella di output e lo compila +utilizzando Jinja.\ +Nelle seguenti porzioni di codice viene riportato un esempio di file (in +questo caso una porzione di `wadm.yaml`) prima e dopo la compilazione +del template tramite Jinja. ```yaml -apiVersion: core.oam.dev/v1beta1 -kind: Application -metadata: - name: go-data-stream - annotations: - description: 'Data stream processing using NATS topic in Golang (TinyGo), using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)' - wasmcloud.dev/authors: Lorenzo Venerandi - wasmcloud.dev/source-url: https://gitea.rebus.ninja/lore/wasm-nats-stream-client.git/wadm.yaml - wasmcloud.dev/readme-md-url: https://gitea.rebus.ninja/lore/wasm-nats-stream-client.git/README.md - wasmcloud.dev/homepage: https://gitea.rebus.ninja/lore/wasm-nats-stream-client.git - wasmcloud.dev/categories: | - stream-processing,nats-client,tinygo,golang spec: components: - - name: go_stream_processor + - name: {{ component_name }} type: component properties: - image: gitea.rebus.ninja/lore/wasm-nats-stream-client:1.0.3 - id: stream - config: - - name: nats-topic - properties: - dest-topic: wasmcloud.echo.reply + image: "{{ registry_url }}/{{ component_name }}:{{ version }}" traits: - - type: spreadscaler - properties: - instances: 1 - spread: - - name: cloud - weight: 100 - requirements: - host-type: cloud - - name: edge - weight: 0 - requirements: - host-type: edge - type: link properties: - target: nats + target: nats-processor namespace: wasmcloud package: messaging interfaces: [consumer] - - name: nats - type: capability + - type: spreadscaler + properties: + instances: 1 + spread: + {%- set weight = (100 / (targets | length)) | int -%} + {% for target in targets %} + - name: {{ target }} + weight: {{ weight }} + requirements: + host-type: {{ target }} + {% endfor %} +``` + +$\downarrow$ + +```yaml +spec: + components: + - name: data_double_test1 + type: component properties: - image: ghcr.io/wasmcloud/messaging-nats:0.23.1 + image: "gitea.rebus.ninja/lore/data_double_test1:1.0.0" traits: - type: link properties: - target: go_stream_processor + target: nats-processor namespace: wasmcloud package: messaging - interfaces: [handler] - source_config: - - name: simple-subscription - properties: - subscriptions: streaming + interfaces: [consumer] - type: spreadscaler properties: instances: 1 @@ -149,12 +640,277 @@ spec: weight: 100 requirements: host-type: cloud - - name: edge - weight: 0 - requirements: - host-type: edge ``` -Il target del deployment viene selezionato tramite il campo `spreadscaler`, che indirizza i componenti nell'host con il tag corrispondente. -### Pipeline scheme -![pipeline](res/img/pipeline.png) \ No newline at end of file +A questo punto viene copiato il file specificato sulla configurazione +dalla cartella `task/` del progetto alla cartella di output. La funzione +viene automaticamente agganciata all'handler specifico del file main del +template (dato che appartengono allo stesso package).\ +L'intero processo di generazione è stato schematizzato nella seguente figura: + +![Processo generazione del +codice](res/img/schemi-implementazione-gen.drawio.svg) + +# Build + +In questo capitolo verrà mostrato il processo che porta alla +compilazione del processo Go in un componente Wasm.\ +Se la fase di generazione è avvenuta con successo, all'interno del +progetto dovrebbe essere presente una cartella chiamata `gen`, +contenente diverse sotto-cartelle con il codice necessario per compilare +i moduli Wasm. + +## Wasm Builder + +A questo punto può essere invocato il componente `build`, che +sostanzialmente esegue le seguenti operazioni: + +1. Istanzia il client Docker utilizzando l'apposito SDK[^2]. + +2. Controlla se l'immagine `wash-build-image:latest` è presente. Se non + lo è procede a buildarla utilizzando il dockerfile configurato. + +3. Per ogni cartella presente dentro `gen` istanzia un container con + `wash-build-image` come immagine e monta la cartella all'interno del + container. + +4. Attende la terminazione dei container. + +Si può notare come in questo caso le operazioni svolte dal framework +siano limitate: la logica di build del componente e la pubblicazione +dell'artifact OCI sono infatti delegate alle istanze in esecuzione su +Docker. + +### Wash build image + +Approfondiamo ora il meccanismo utilizzato per compilare i componenti +Wasm, iniziando dal Dockerfile che descrive l'immagine di base +utilizzata, mostrato nel seguente codice. + +```dockerfile +FROM ubuntu:24.04 AS wash-build-image + +# Install dependencies and tools +RUN apt-get update && apt-get install -y curl wget tar ... + +# ----------------- Install WasmCloud ----------------- +RUN curl -s "https://packagecloud.io/install/repositories/wasmcloud/core/script.deb.sh" | bash && \ + apt-get install -y wash + +# ----------------- Install Go 1.23 ----------------- +RUN wget https://go.dev/dl/go1.23.4.linux-amd64.tar.gz && \ + tar -C /usr/local -xzf go1.23.4.linux-amd64.tar.gz && \ + rm go1.23.4.linux-amd64.tar.gz + +# Set Go environment variables +ENV PATH="/usr/local/go/bin:${PATH}" +ENV GOPATH="/go" +ENV GOROOT="/usr/local/go" + +# ----------------- Install TinyGo 0.34.0 ----------------- +RUN wget https://github.com/tinygo-org/tinygo/releases/download/v0.34.0/tinygo_0.34.0_amd64.deb && \ + dpkg -i tinygo_0.34.0_amd64.deb && \ + rm tinygo_0.34.0_amd64.deb + +# ----------------- Install Rust ----------------- +# Install Rust +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y && \ + . "$HOME/.cargo/env" && \ + cargo install --locked wasm-tools + +# Set Rust environment variables +ENV PATH="/root/.cargo/bin:${PATH}" + +# Verify installations +RUN go version && tinygo version && cargo --version && wash --version && wasm-tools --version + +# ----------------- Build the WasmCloud module ----------------- +FROM wash-build-image + +RUN mkdir /app +WORKDIR /app + +# Install go dependencies, build the wasm module, push it to the registry +CMD ["sh", "-c", "go env -w GOFLAGS=-buildvcs=false && go mod download && go mod verify && wash build && wash push $REGISTRY build/*.wasm && chown -R ${HOST_UID}:${HOST_GID} ."] +``` + +Il Dockerfile è strutturato in due fasi: + +1. **Fase 1**: vengono installate le dipendenze di Go, TinyGo, Rust e + la shell di wasmCloud. + +2. **Fase 2**: viene predisposta l'immagine per la compilazione dei + moduli Wasm. + +La preparazione della compilazione avviene dentro l'istruzione CMD, che +infatti contiene: + +- Istruzioni per risolvere le dipendenze di go ed installarle nel + progetto. In questo modo l'utente può aggiungere librerie supportate + e il builder si occuperà di installarle nel progetto. + +- Comando `wash build` che esegue la compilazione del progetto e + genera il componente Wasm all'interno della cartella `gen`. + +- Comando `wash push` che pubblica il componente Wasm come artifact + OCI sul registry passato come configurazione. + +- Istruzione per impostare i permessi sui file generati, necessario + per poter gestire correttamente i files tramite Python. + +### Istanziamento container + +Questo approccio consente di utilizzare una sola immagine per tutte le +operazioni di build: le configurazioni dinamiche avvengono tramite +variabili d'ambiente e i file da buildare vengono montati come volume al +posto della cartella `/app`, come possiamo notare dal seguente codice contenente un estratto di codice del componente `build`: + +```go +def __build_wasm(task_dir, client, reg_user, reg_pass, detached, wait_list): + oci_url = wadm['spec']['components'][0]['properties']['image'] + name = wadm['spec']['components'][0]['name'] + '-build' + ... + # Build componente Wasm + print(f" - Building WASM module {oci_url}") + container = client.containers.run( + "wash-build-image:latest", + environment=[f'REGISTRY={oci_url}', + f'WASH_REG_USER={reg_user}', + f'WASH_REG_PASSWORD={reg_pass}', + f'HOST_UID={uid}', + f'HOST_GID={gid}'], + volumes={os.path.abspath(task_dir): {'bind': '/app', 'mode': 'rw'}}, + remove = True, + detach = True, + name = name + ) + + # Build sequenziale o parallela + if detached == 'False': + container.wait() + else: + wait_list.append(name) +``` + +### Esecuzione parallelizzata + +Il processo di build può essere eseguito in modalità sequenziale o +parallela, a seconda della configurazione delle variabili d'ambiente. +Questo comportamento è gestito tramite la flag `detach`, che permette di +avviare i container in modo asincrono. In questa modalità, viene +utilizzata una waiting list per istanziare tutti i container in +parallelo e attendere il completamento dell'operazione di build.\ + +## Processo completo + +L'intero processo di build viene mostrato nella seguente figura: + +![Processo di Build componente +Wasm](res/img/schemi-implementazione-build.drawio.svg) + +# Deployment + +In questo capitolo verrà descritta la procedura di deployment dei +componenti Wasm nella piattaforma wasmCloud. La comunicazione fra questa +ed il framework PELATO avviene tramite NATS: sarà sufficiente +configurare il framework con le credenziali di un client NATS collegato +al cluster per poter deployare le applicazioni.\ +Anche in questo caso il componente deploy si appoggia a Docker per +l'operazione, dato che l'applicazione del deployment tramite il manifest +`wadm.yaml` ottenuto in fase di Generazione deve essere effettuata +utilizzando `wash`. + +## Application Deployment + +La fase di deployment è strutturata in modo molto simile a quella di +build, infatti le operazioni svolte dal componente deploy sono: + +1. Istanziamento del client Docker. + +2. Controllo dell'immagine `wash-deploy-image:latest`, se non è + presente procede a buildarla utilizzando il dockerfile configurato. + +3. Per ogni cartella presente dentro `gen` istanzia un container con + `wash-deploy-image` come immagine e monta la cartella all'interno + del container. + +4. Attende la terminazione dei container. + +#### Wash deploy image + +Il Dockerfile utilizzato per buildare l'immagine `wash-deploy-image` è +più semplice, in quanto deve solamente installare la wasmCloud shell e +le sue dipendenze: + +```dockerfile +FROM ubuntu:24.04 AS wash-deploy-image + +# Install dependencies and tools +RUN apt-get update && apt-get install -y curl wget tar ... + +# ----------------- Install WasmCloud ----------------- +RUN curl -s "https://packagecloud.io/install/repositories/wasmcloud/core/script.deb.sh" | bash && \ + apt-get install -y wash + +# ----------------- Deploy the WasmCloud module ----------------- +FROM wash-deploy-image + +RUN mkdir /app +WORKDIR /app + +# Deploy the WasmCloud module +CMD ["sh", "-c", "wash app deploy wadm.yaml"] +``` + +### Deployer + +Il comando riportato nell'istruzione CMD in questo caso è +`wash app deploy wadm.yaml`, che utilizza il file wadm.yaml per creare +un'applicazione sul cluster wasmCloud specificato.\ +L'approccio utilizzato per eseguire i processi di deployment è analogo a +quello della fase Build, la differenza sta nelle variabili d'ambiente +necessarie all'operazione: in questo caso sarà necessario fornire +hostname e porta di un server NATS collegato al cluster wasmCloud. Di +seguito viene riportata la porzione di codice che si occupa di eseguire +il container.\ + +```go +def __deploy_wadm(task_dir, client, nats_host, nats_port, detached, wait_list): + path = os.path.abspath(task_dir) + '/wadm.yaml' + name = wadm['spec']['components'][0]['name'] + '-deploy' + ... + # Deploy wasmCloud app + print(f" - Deploying WASM module {name}") + container = client.containers.run( + "wash-deploy-image:latest", + environment=[f'WASMCLOUD_CTL_HOST={nats_host}', + f'WASMCLOUD_CTL_PORT={nats_port}'], + volumes={path: {'bind': '/app/wadm.yaml', 'mode': 'rw'}}, + remove=True, + detach=True, + name=name + ) + + if detached == 'False': + container.wait() + else: + wait_list.append(name) +``` + +### Remover + +Nel package `component_deploy` è presente anche la funzionalità +`remove`, con codice e comportamenti analoghi a quella di deploy. +L'unica differenza si presenta nel dockerfile, nel quale l'istruzione +specificata nel CMD è `wash app remove wadm.yaml` e permette di +rimuovere le applicazioni specificate sui file wadm dal cluster.\ + +## Processo completo + +Anche in questo caso è possibile parallelizzare l'esecuzione dei +container, sia in fase di deployment che di rimozione delle +applicazioni. L'intero processo di deployment viene mostrato nella +seguente figura. + +![Processo deployment applicazione su +wasmCloud](res/img/schemi-implementazione-deploy.drawio.svg) \ No newline at end of file