Come eseguire qualsiasi linguaggio di programmazione su AWS Lambda: i Custom Runtimes.

Le Funzioni AWS Lambda (FaaS) sono diventate rapidamente un tool estremamente versatile del cloud AWS poiché è possibile utilizzarle per una moltitudine di compiti: dal backend di un'applicazione Web all'ingestion di un'applicazione AWS IoT, dalla semplice automazione dell'infrastruttura all'analisi in tempo reale dei messaggi inviati tramite AWS SQS o AWS Kinesis. Inoltre, sono economiche, funzionali, scalabili e molto semplici da installare e mantenere. 

A renderle ancor più attraenti agli occhi di sviluppatori e devops, è il numero sempre crescente di runtime Lambda offerti da AWS, che consentono di scrivere il codice in svariati linguaggi di programmazione. Al momento della stesura di questo articolo AWS Lambda supporta nativamente Java, Go, PowerShell, Node.js, C #, Python e Ruby. 

Tuttavia ci sono molti altri linguaggi di programmazione che potremmo voler usare in una funzione Lambda, per esempio per migrare alle AWS Lambda Functions applicazioni già esistenti ed attualmente deployate in locale o su istanze EC2. Poiché la riscrittura di codice esistente è spesso impossibile a causa della mancanza di tempo o della mancanza di librerie e funzionalità, AWS ha recentemente fornito una nuova possibilità: i Runtime personalizzati per Lambda che permettono di utilizzare qualsiasi linguaggio di programmazione!

Una funzione Lambda con un ambiente di runtime personalizzato differisce da una normale funzione lambda perché contiene non solo il codice che eseguirà quando verrà invocata la funzione, ma anche tutte le librerie compilate necessarie per eseguire il codice e, se il linguaggio scelto è interpretato come PHP o compilato just in time come Julia, è necessario includere anche il binario dell'interprete. Per la maggior parte dei linguaggi di programmazione, un runtime personalizzato preparato da terze parti è generalmente disponibile su github ed è spesso utilizzabile direttamente oppure può fornire una buona base per una soluzione personalizzata.

Nella sezione seguente descriveremo in dettaglio come creare un runtime generico e presenteremo due esempi creati da AWS: bash e C ++. Infine, confronteremo il tempo di risposta delle lambda dei runtime personalizzati con quello di un runtime nativo (Python 3.8). La creazione di un runtime personalizzato ci dà anche l'opportunità di capire come funziona davvero il servizio lambda.

Come funziona Lambda?

AWS Lambda è costituito da due parti principali: il servizio Lambda che gestisce le richieste di esecuzione e le micro virtual machines Amazon Linux deployate tramite AWS Firecracker che eseguono effettivamente il codice. Una VM Firecracker viene avviata la prima volta che una determinata funzione Lambda riceve una richiesta di esecuzione (il cosiddetto "Cold Start") e non appena la VM completa il boot, inizia a eseguire il polling del servizio Lambda per ricevere i messaggi. Quando un messaggio viene ricevuto dalla VM, essa esegue il codice della funzione handler passandogli il messaggio JSON ricevuto nell’invocazione. 

Pertanto, ogni volta che il servizio Lambda riceve una richiesta di esecuzione, verifica se è disponibile una microVM Firecracker per gestire la richiesta di esecuzione e, in tal caso, recapita il messaggio alla VM da eseguire. Al contrario, se non viene trovata nessuna VM disponibile, Firecracker avvia una nuova macchina virtuale per gestire il messaggio. 

Ogni VM esegue un messaggio alla volta, quindi se molte richieste simultanee vengono inviate al servizio Lambda, ad esempio a causa di un picco di traffico ricevuto da un gateway API, verranno accese diverse nuove VM Firecracker per gestire le richieste. Per questo motivo, la latenza media delle richieste sarà maggiore poiché ogni VM impiega all'incirca un secondo per avviarsi (lambda Cold Start). 

In una funzione lambda che utilizza un runtime nativo non è necessario preoccuparsi di come la funzione eseguirà il polling dei messaggi dal servizio lambda e l’invio dei rapporti di esecuzione, il runtime nativo si occuperà di tutto ciò senza alcuna modifica necessaria da parte dello sviluppatore. Tuttavia, questo non avviene nel caso di un runtime personalizzato. Infatti, quando viene creata una funzione Lambda con un runtime personalizzato, AWS Lambda Service avvia una VM AmazonLinux di base senza librerie e pacchetti installati ad eccezione di bash e alcuni comandi unix di base (ad esempio ls, curl) . A differenza di una normale Lambda, oltre al codice e alle librerie esterne, è necessario includere nel pacchetto di distribuzione anche uno script o un eseguibile chiamato "bootstrap" che gestirà l'interazione tra la VM della funzione e il servizio Lambda. AWS Lambda Service espone una semplice interfaccia HTTP affinchè i runtime possano usarla per ricevere gli eventi di invocazione e inviare l’esito delle esecuzioni.

Il programma bootstrap deve perciò implementare le seguenti funzioni:

  1. Ottieni un evento: Invocare l'API di invocazione per ottenere l'evento successivo. Il corpo della risposta contiene i dati dell'evento. Le intestazioni di risposta contengono l'ID richiesta e altre informazioni.
  1. Propagare l’header di xray: Ottenere l'header della trace di X-Ray dall'header Lambda-Runtime-Trace-Id nella risposta API. Impostare la variabile di ambiente _X_AMZN_TRACE_ID localmente con lo stesso valore. X-Ray SDK utilizza questo valore per connettere i dati di tracing tra servizi.
  1. Creare un oggetto di contesto: crea un oggetto con informazioni di contesto da variabili di ambiente e intestazioni nella risposta API.
  1. Richiamare il gestore funzioni: passare l'evento e l'oggetto contestuale all’handler.
  1. Gestire la risposta: chiamare l'API di risposta dell'invocazione per pubblicare la risposta dell’handler.
  1. Gestisci errori: se si verifica un errore, chiamare l'API di errore.
  1. Pulizia: eliminare le risorse non utilizzate, inviare dati ad altri servizi o eseguire attività aggiuntive prima di ottenere il prossimo evento.

Lo script / eseguibile bootstrap e altre librerie ed interpreti a livello di linguaggio (ad es. Interprete PHP) possono essere incluse in un layer lambda dedicato al fine di generare un runtime personalizzato generico e portatile che può essere utilizzato con diverse funzioni Lambda.

Come creare una Lambda Bash con un runtime personalizzato

Il modo più semplice per iniziare a lavorare con i runtime personalizzati è direttamente tramite la Console di AWS: dalla dashboard del servizio Lambda basta selezionare “Crea Lambda” e nella sezione runtime selezionare Runtime personalizzato con Usa bootstrap predefinito e fare clic su Crea funzione

create lambda function

Usando queste impostazioni predefinite il servizio Lamba creerà una Lambda Bash di base con uno script bootstrap predefinito. Diamo un'occhiata allo script bootstrap pregenerato:

 bootstrap script

#!/bin/sh
set -euo pipefail

# Handler format: .## The script file .sh must be located at the root of your# function's deployment package, alongside this bootstrap executable.source $(dirname "$0")/"$(echo $_HANDLER | cut -d. -f1).sh"while truedo # Request the next event from the Lambda runtime HEADERS="$(mktemp)" EVENT_DATA=$(curl -v -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next") INVOCATION_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) # Execute the handler function from the script RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA") # Send the response to Lambda runtime curl -v -sS -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$INVOCATION_ID/response" -d "$RESPONSE"done<\/script_name><\/bash_function_name><\/script_name><\/pre>

Esaminiamo rapidamente questo script: <\/p>