Cosa ho imparato dopo un paio di settimane di utilizzo di AWS Greengrass

Negli ultimi giorni abbiamo studiato AWS Greengrass, uno dei tanti servizi IoT offerti da Amazon Web Services, che è stato aggiornato alla versione v2 l'anno scorso. 

In questo articolo vorrei parlare di ciò che ho imparato e delle mie opinioni generali, sperando di aiutare chiunque si stia approcciando a questo servizio per la prima volta proprio come ho fatto io.

Allora, cominciamo con...

Che cos’è AWS Greengrass?

AWS Greengrass è un servizio che ci consente di portare l'esperienza di elaborazione a cui siamo abituati quando lavoriamo con i servizi AWS sui nostri dispositivi IoT. Consiste in un runtime open source che dobbiamo installare sui nostri dispositivi che offre diverse funzionalità, ad esempio:

  • Elaborazione locale con funzioni AWS Lambda
  • Supporto per container
  • Integrazione con AWS IoT Core (e gli shadow dei dispositivi)
  • Messaggistica locale

Come la maggior parte degli altri servizi AWS, Greengrass è stato creato per risolvere molteplici problemi in modo gestito senza costringere gli sviluppatori a reinventarsi la ruota ogni volta. Quando creiamo soluzioni IoT dobbiamo preoccuparci della scalabilità della nostra soluzione e di mantenere aggiornato il firmware dei nostri dispositivi. Il compito di Greengrass è proprio quello di aiutarci a gestire queste problematiche.

AWS IoT Greengrass semplifica la distribuzione e la gestione del software dei dispositivi su milioni di dispositivi remoti. Possiamo organizzare i nostri dispositivi in gruppi e distribuire e gestire il software e la configurazione in un sottoinsieme di dispositivi o in tutti i dispositivi. AWS IoT Greengrass ci offre la possibilità di inviare aggiornamenti relativi al nostro codice over-the-air sulle nostre macchine, senza preoccuparci di causare un disservizio dato da un errore sul layer di runtime e costringerci ad andare sul posto del dispositivo per ripristinarlo manualmente. Questo è possibile grazie a come è stata pensata l’architettura del software in esecuzione: il nostro software è suddiviso in diversi componenti disaccoppiati dal Core, che ha il compito di orchestrare i nostri componenti e gestire i job di aggiornamento del firmware.

Con Greengrass, possiamo eseguire inferenza Machine Learning, aggregazione di dati e streaming su più servizi cloud AWS (come S3 o Amazon Kinesis) direttamente dai nostri dispositivi, consentendoci quindi di decidere quali dati debbano essere inviati al cloud, in modo da poter ottimizzare i costi di analisi, elaborazione e archiviazione.

Pensiamo a un caso d'uso

Immagina di creare una soluzione IoT composta da più dispositivi installati in una fabbrica. Questi dispositivi raccolgono periodicamente i dati e li inviano direttamente a un back-end cloud. I dati vengono quindi elaborati e archiviati, consentendo ai tuoi clienti di sapere se le loro macchine stiano funzionando come dovrebbero, se alcune parti necessitano di manutenzione, e permettendo di configurare eventi personalizzati che devono essere notificati. 

Immagina ora questa soluzione distribuita in migliaia di fabbriche, quindi milioni di dispositivi che ogni pochi minuti inviano un messaggio al tuo back-end: i costi della tua applicazione aumenterebbero con il numero di dispositivi installati. Inoltre, i dati ricevuti da una fabbrica non hanno alcun collegamento con i dati ricevuti da un'altra perché ogni fabbrica è considerabile come un’entità a sé stante.

Quindi come possiamo ottimizzare questa infrastruttura? Con Greengrass potremmo configurare una macchina che funziona come un centralizzatore di elaborazione: raccoglie i dati, li aggrega e invia periodicamente un singolo messaggio contenente lo stato globale dell'impianto. Naturalmente i dispositivi potrebbero ancora dover inviare alcune informazioni (come allarmi o eventi) così come sono al nostro back-end, ma spostare parte dell'elaborazione dei dati dal cloud al luogo in cui si trovano i dispositivi migliorerebbe drasticamente i costi della nostra infrastruttura e ridurrebbe il throughput di rete. Avremmo quindi molte meno risorse da configurare e gestire lato cloud, e queste risorse non andrebbero a crescere con il numero di dispositivi ma con il numero di impianti.

Mettiamo le mani in pasta

Il mio approccio all'apprendimento di Greengrass è stato lo stesso che ho utilizzato per altri servizi AWS: solitamente inizio con una rapida panoramica della documentazione ufficiale per poi costruire qualcosa direttamente dalla console AWS, e quando mi trovo davanti ad un ostacolo eseguo ricerche puntuali sul web per capire come risolvere il problema.

La mia opinione sulla documentazione ufficiale di Greengrass è che è molto ampia ma a volte le nozioni sono sparse in sezioni diverse. Mi sono spesso trovato a cercare modi per risolvere un problema che pensavo non fosse documentato, e dopo minuti (se non ore) mi sono reso conto che la soluzione era proprio sotto i miei occhi ma in un’altra sezione della documentazione. Greengrass è composto da molti concetti strettamente legati tra di loro, quindi il mio consiglio è di studiare approfonditamente la documentazione ufficiale prima di iniziare a creare un progetto.

Configurazione del core device

Il core device è una macchina su cui è stato installato il runtime Greengrass. Ci sono molti modi per farlo; il percorso che ho scelto inizialmente è il provisioning automatico di Greengrass in un container Docker, poiché è il modo più semplice per eseguire questa attività e non richiede molta configurazione. Tutto quello che dobbiamo fare è creare un file ".env" con la configurazione del nostro dispositivo principale che assomiglia a questo:

GGC_ROOT_PATH=/greengrass/v2
AWS_REGION=eu-west-1
PROVISION=true
THING_NAME=P2BCGreengrassCore
THING_GROUP_NAME=P2BCGreengrassCoreGroup
TES_ROLE_NAME=P2BCGreengrassV2TokenExchangeRole
TES_ROLE_ALIAS_NAME=P2BCGreengrassCoreTokenExchangeRoleAlias
COMPONENT_DEFAULT_USER=ggc_user:ggc_group

Questo file ".env" può quindi essere passato nel file docker-compose.yaml come valore "env_file"; Se impostiamo la chiave PROVISION a true, Greengrass si occuperà di creare tutte le risorse necessarie su AWS IoT per configurare un nuovo dispositivo. Ma per fare ciò, dobbiamo fornire a Greengrass alcune credenziali AWS sotto forma di access key, secret access key e session token. Non ero entusiasta di questa soluzione in quanto non voglio eseguire questa operazione per ogni dispositivo core che installerò in futuro e, soprattutto, voglio essere incaricato di creare i ruoli e le autorizzazioni necessarie.

Quindi ho deciso di creare manualmente l'oggetto IoT, il ruolo, l'alias del ruolo, il gruppo e i certificati. I certificati verranno utilizzati per autenticare il dispositivo appena creato su AWS. In questa pagina puoi trovare una guida ben fatta che spiega come eseguire il provisioning di queste risorse direttamente dalla command line interface: . 

Il file ".env" ora ha il seguente contenuto:

GGC_ROOT_PATH=/greengrass/v2
AWS_REGION=eu-west-1
PROVISION=false
COMPONENT_DEFAULT_USER=ggc_user:ggc_group
INIT_CONFIG=/tmp/config/config.yaml

Il file config.yaml contiene invece tutti i riferimenti necessari al core device per trovare la posizione dei certificati e gli endpoint di IoT Core. Il mio file di configurazione è simile a questo:

system:
  certificateFilePath: "/tmp/certs/device.pem.crt"
  privateKeyPath: "/tmp/certs/private.pem.key"
  rootCaPath: "/tmp/certs/AmazonRootCA1.pem"
  rootpath: "/greengrass/v2"
  thingName: "P2BCGreengrassCore-1"
services:
  aws.greengrass.Nucleus:
    componentType: "NUCLEUS"
    version: "2.5.3"
    configuration:
      awsRegion: "eu-west-1"
      iotRoleAlias: "P2BCGreengrassCoreTokenExchangeRoleAlias"
      iotDataEndpoint: "xxxxxxxxxxxx-ats.iot.eu-west-1.amazonaws.com"
      iotCredEndpoint: "xxxxxxxxxxxx.credentials.iot.eu-west-1.amazonaws.com"

L'ultima cosa che rimane da fare è configurare il file docker-compose.yml con l'immagine Docker, i percorsi delle directory in cui sono stati salvati certificati e configurazioni, e il path dove vogliamo che questi volumi vengano montati. Ecco il mio file di composizione Docker:

version: '3.7'
 
services:
  greengrass:
    init: true
    cap_add:
      - ALL
    build:
      context: .
    container_name: aws-iot-greengrass
    image: amazon/aws-iot-greengrass:latest
    volumes:
      - ./greengrass-v2-config:/tmp/config/:ro
      - ./greengrass-v2-certs:/tmp/certs:ro 
    env_file: .env
    ports:
      - "8883:8883"

Ora possiamo eseguire Docker Compose e il nostro dispositivo principale dovrebbe avviarsi e connettersi ad AWS.

Greengrass core device

Deploy della nostra prima funzione Lambda

Il passaggio successivo consiste nell'aggiungere alcune funzionalità al nostro core device appena creato. Nel mio caso, volevo distribuire una funzione Lambda come componente Greengrass. Per fare ciò, dobbiamo creare una nuova funzione Lambda in uno dei runtime accettati da Greengrass, che sono:

  • Python 3.8 (la mia scelta)
  • Python 3.7
  • Python 2.7
  • Java 8
  • Node.js 12
  • Node.js 10

La funzione Lambda che volevo creare è una semplice funzione che interagisce con lo shadow MQTT del dispositivo, ovvero eseguire un'operazione di sottoscrizione a uno dei topic  dello shadow e pubblicare messaggi su un altro topic. A questo scopo, ho incluso nel codice della funzione la libreria ufficiale awsiotsdk.

Dopo aver creato la nostra funzione Lambda, dobbiamo racchiuderla in un componente Greengrass. La procedura è piuttosto semplice: selezioniamo la versione della funzione che vogliamo configurare e le diamo un nome. Altre configurazioni riguardano il tipo di componente e, facoltativamente, alcune origini eventi.

Esistono due tipi di componenti delle funzioni Lambda che possono essere distribuiti su un core device Greengrass:

  • Funzioni di lunga durata (chiamate anche pinned), più adatte per attività che devono essere costantemente attive.
  • Funzioni on-demand, che si avviano quando vengono richiamate e vengono terminate quando non sono rimaste attività da eseguire.

Possiamo configurare degli eventi che scatenino la nostra funzione Lambda, in maniera tale che venga eseguita quando avviene un'operazione di pubblicazione su un topic locale o un topic AWS IoT MQTT.

Possiamo sempre tornare alla configurazione del componente della funzione e modificarne le impostazioni in seguito se dobbiamo apportare alcune modifiche.

L'ultima cosa che dobbiamo fare è distribuire il nostro componente appena creato sul nostro core device. Per farlo, visita la pagina "Deployments" della console di Greengrass e fai clic sul pulsante "Create". Tutte le distribuzioni hanno un nome, che ha lo scopo di descrivere quali componenti sono raggruppati al loro interno, un tipo di destinazione che può essere un core device specifico o un gruppo di oggetti (quest'ultimo è utile se vogliamo creare un singolo deployment per più dispositivi) e un elenco di componenti custom e pubblici. Poiché avevo bisogno di interagire con l'ombra del dispositivo, ho dovuto aggiungere alla distribuzione il componente pubblico aws.greengrass.ShadowManager.

La funzione Lambda che abbiamo appena creato dovrebbe apparire nell'elenco dei componenti personalizzati. I componenti pubblici sono forniti da AWS e possono essere aggiunti alla nostra distribuzione se abbiamo la necessità di aggiungere funzionalità specifiche ai nostri core device. Possiamo configurare ciascuno di essi dopo aver selezionato i componenti che vogliamo includere nella nostra distribuzione. La configurazione dei componenti dipende dal tipo di componente:

  • La configurazione del componente pubblico ShadowManager deve contenere i nomi degli shadow che desideriamo sincronizzare localmente con AWS IoT. Di seguito puoi trovare la configurazione che ho creato:
{
	"strategy": {
		"type": "realTime"
	},
	"synchronize": {
		"shadowDocuments": [
			{
				"thingName": "P2BCGreengrassCore-1",
				"classic": false,
				"namedShadows": [
					"myShadow"
				]
			}
		]
	}
}
  • La configurazione del nostro componente Lambda contiene le autorizzazioni che dobbiamo aggiungere ad esso. Nel nostro caso, la funzione deve avere la capacità di sottoscriversi ad un topic shadow per ricevere notifiche ed eseguire operazioni di recupero, aggiornamento ed eliminazione.
{
	"accessControl": {
		"aws.greengrass.ShadowManager": {
			"P2BCGreenGrassLambda:shadow:1": {
				"policyDescription": "Allows access to shadows",
				"operations": [
					"aws.greengrass#GetThingShadow",
					"aws.greengrass#UpdateThingShadow",
					"aws.greengrass#DeleteThingShadow"
				],
				"resources": [
"$aws/things/P2BCGreengrassCore-1/shadow/name/myShadow"
				]
			}
		},
		"aws.greengrass.ipc.pubsub": {
			"P2BCGreenGrassLambda:pubsub:1": {
				"policyDescription": "Allows access to shadow pubsub topics",
				"operations": [
					"aws.greengrass#SubscribeToTopic"
				],
				"resources": [
"$aws/things/P2BCGreengrassCore-1/shadow/name/myShadow/update/delta"
				]
			}
		}
	}
}

L'ultima cosa che possiamo fare per la creazione del nostro deployment è decidere come il dispositivo debba applicare l'aggiornamento del software. Cosa deve fare il dispositivo se l'operazione di aggiornamento non va a buon fine? Quanto tempo ha il dispositivo per eseguire l'aggiornamento? Dovremmo interrompere il deployment se alcuni dei dispositivi interessati non si aggiornano?

Siamo finalmente pronti per distribuire i nostri componenti sui dispositivi core che abbiamo selezionato! Fare clic sul pulsante "Deploy" per avviare l'aggiornamento!

Risolvere problemi

Quando inizialmente mi sono approcciato a Greengrass, ho riscontrato alcuni problemi nella configurazione corretta del deployment (per lo più la configurazione dei componenti) e sono riuscito a eseguire il debug dello stato del mio dispositivo core entrando nel container Docker e cercando i log. Ecco come l'ho fatto.

Cerca l'ID del container eseguendo questo comando:

docker ps

Copia l'ID del container ed esegui questo comando:

docker exec -it  /bin/bash

Con questi due comandi è possibile accedere al container Docker. I file di log che mi hanno aiutato di più nel debug della mia applicazione sono stati il file greengrass.log e il file con lo stesso nome del componente della mia funzione Lambda (nel mio caso P2BCGreenGrassLambda.log) che si trovano nella cartella /greengrass/v2/logs. Il primo contiene lo stato generale del runtime Greengrass e lo stato del deployment; l’ultimo contiene ciò che viene stampato sullo stdout nel codice della funzione Lambda.

Considerazioni finali

AWS Greengrass è un potente strumento che ci solleva dall'onere di gestire le distribuzioni di nuove versioni del nostro software sui dispositivi. Non è intuitivo e facile da usare come molti altri servizi poiché sono coinvolte molte funzionalità, tutte necessarie per far funzionare correttamente i nostri dispositivi. È stato necessario spendere del tempo per realizzare un proof of concept funzionante, ma alla fine ne posso vedere il potenziale: il caso d'uso perfetto per Greengrass è quando abbiamo molti dispositivi geograficamente vicini l'uno all'altro che sono strettamente correlati tra loro e possono essere visti dal cloud come un unico gruppo di dispositivi. In questo caso, Greengrass potrebbe aiutarci a ridurre i costi della nostra infrastruttura (che è un punto critico nelle soluzioni IoT), il throughput della rete e la complessità del nostro codice. Greengrass è ancora relativamente giovane rispetto ad altri servizi AWS e sono sicuro che con il tempo verranno aggiunte più funzionalità e integrazioni!

Mattia Costamagna
Ingegnere DevOps e sviluppatore cloud-native @ beSharp. Adoro passare il mio tempo libero a leggere romanzi e ascoltare musica rock e blues degli anni '70. Sempre alla ricerca di nuove tecnologie e framework da testare e utilizzare. La birra artigianale è il mio carburante!

Lascia un commento

Ti potrebbero interessare

Integration testing con Postman e Newman