AWS Lambda in Docker

Sviluppo offline di funzioni AWS Lambda

3 min

I progetti a cui lavoro sono sempre più indirizzati verso il paradigma serverless e sempre più spesso implementati su piattaforma AWS Lambda. Poter sviluppare una funzione AWS Lambda offline, cioè rimanendo comodamente nel proprio IDE preferito senza dover effettuare l’upload del codice per poterlo testare, consente di velocizzare notevolmente le attività ed essere più efficienti.

AWS Lambda environment in docker

Esatto! La soluzione che ci consente di sviluppare codice AWS Lambda in modalità offline è utilizzare un’immagine docker che replica in maniera pressoché identica l’ambiente live di AWS. Le immagini docker disponibili presso DockerHub costituiscono una sandbox all’interno della quale eseguire la propria funzione, sicuri di trovare le medesime librerie, struttura file e relativi permessi, variabili d’ambiente e contesto di produzione. Fantastico!

Raramente una Lambda function è “indipendente” da altre risorse: spesso ha la necessità di accedere a degli oggetti memorizzati in un bucket S3, accodare messaggi su SQS o accedere ad una tabella DynamoDB. L’aspetto interessante di questa soluzione è la possibilità di sviluppare e testare il proprio codice offline, interagendo comunque con servizi e risorse reali di AWS, semplicemente andando a specificare una coppia di chiavi di accesso AWS nelle variabili di ambiente AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY.

Il progetto LambdaCI è aggiornato frequentemente e ben documentato: prevede diversi ambienti di runtime tra quali Python, che andremo ad utilizzare nei prossimi paragrafi. L’ambiente che ho utilizzato per lo sviluppo è disponibile in questo repository.

Funzione di esempio

Supponiamo di lavorare ad una semplice funzione Python che si occupi di processare dei messaggi SQS e che utilizzi un package normalmente non installato nell’ambiente AWS Lambda Python. ll codice di esempio è il seguente.

Per prima cosa viene istanziato l’oggetto Logger che andremo ad utilizzare per tracciare l’evento SQS. Nel body del messaggio sono previsti degli addendi che andremo a sommare ed il risultato verrà riportato nei log. Andremo inoltre a tracciare la versione del package PILLOW, normalmente non previsto di default nell’ambiente AWS Lambda, per verificare che l’installazione dei pacchetti aggiuntivi avvenga correttamente. Per finire restituiremo un messaggio testuale (All the best for you) al termine dell’esecuzione della funzione.

Vediamo ora come eseguire la Lambda function in Docker.

Dockerfile e Docker-Compose

Per prima cosa dobbiamo preoccuparci di come installare i pacchetti Python aggiuntivi, nel nostro esempio PILLOW. Andremo quindi a creare una nuova immagine Docker grazie ad un dockerfile che parta dall’immagine lambci/lambda:python3.6 e che si occupi di installare tutti i pacchetti aggiuntivi specificati nel file requirements.txt

Per finire, con un file docker-compose.yml andremo a definire un servizio lambda da utilizzare per il debugging offline. Lo scopo è mappare la directory host src per i sorgenti e impostare PYTHONPATH per l’utilizzo dei pacchetti aggiuntivi in /var/task/lib

Come primo test basterà avviare docker-compose per eseguire la nostra funzione Lambda.

docker-compose run lambda src.lambda_function.lambda_handler

Eventi

La nostra funzione si aspetta un evento SQS da processare. Come inviarlo? Per prima cosa dobbiamo procurarci un JSON di test e salvarlo in un file (ad esempio event.json). Andremo poi a specificarlo nella chiamata a docker-compose.

docker-compose run lambda src.lambda_function.lambda_handler "$(cat event.json)"

Vediamo il risultato dell’esecuzione.

Perfetto! La nostra funzione viene eseguita correttamente ed il risultato corrisponde a quanto atteso. L’avvio del container Docker corrisponde ad un “cold start” di AWS Lambda. Vediamo come sia possibile mantenere attivo il container per richiamare più volte la funzione.

Mantenere in esecuzione

In alternativa è possibile avviare e mantenere in esecuzione il container della nostra funzione Lambda in modo da poter effettuare velocemente diverse chiamate consecutive senza attendere i tempi di “cold start”. In questa modalità viene avviato un server API che risponde di default alla porta 9001.

docker-compose run -e DOCKER_LAMBDA_STAY_OPEN=1 -p 9001:9001 lambda src.lambda_function.lambda_handler

Andremo a richiamare la nostra funzione usando, ad esempio, curl.

curl --data-binary "@event.json" http://localhost:9001/2015-03-31/functions/myfunction/invocations

All’endpoint indicato risponde l’handler di default della nostra Lambda function. Il parametro data-binary consente di inviare il contenuto del file JSON relativo all’evento SQS di esempio.

Conclusioni

Ho raccolto in questo repository GitHub i file necessari a ricreare l’ambiente Docker che utilizzo per lo sviluppo ed il debugging offline di funzioni AWS Lambda in Python. Per comodità ho raccolto in un Makefile le operazioni più frequenti.

Il comando make lambda-build realizza il pacchetto di deployment della funzione, comprensivo dei pacchetti aggiuntivi.

Di seguito un esempio di deployment della nostra Lambda function con CloudFormation.

Altri comandi disponibili nel Makefile sono:

## create Docker image with requirements
make docker-build

## run "src.lambda_function.lambda_handler" with docker-compose
## mapping "./tmp" and "./src" folders. 
## "event.json" file is loaded and provided to function  
make lambda-run

## run API server on port 9001 with "src.lambda_function.lambda_handler" 	 
make lambda-stay-open

Ci siamo divertiti? Ci piacerebbe utilizzare questo ambiente per realizzate una pipeline di CI che si occupi di testare la nostra funzione? Nel prossimo post!

Leave a Comment