Usare correttamente i layers di AWS Lambda
Cosa sono i livelli di AWS Lambda? Come sappiamo le funzioni AWS Lambda ci consentono di eseguire codice nel cloud secondo il paradigma serverless. Ciascuna applicazione cloud serverless è normalmente caratterizzata da molteplici funzioni Lambda indipendenti in grado di risponde ad eventi specifici (rest API, scheduled, triggers). Ogni funzione Lambda è definita dal proprio pacchetto di deployment che contiene il codice sorgente della stessa e gli eventuali requisiti, come librerie aggiuntive, dipendenze e middleware.
In questa tipologia di architettura, i livelli di AWS Lambda di consentono di introdurre il concetto di riusabilità del codice o delle dipendenze, in modo da condividere moduli tra diverse funzioni: i layers sono infatti semplici pacchetti riutilizzabili nella definizione delle proprie Lambda che di fatto estendono il runtime base. Vediamo come si utilizzano e la mia esperienza a riguardo.
AWS Lambda layers 101
Come si prepara un livello di AWS Lambda? Per mostrarlo consideriamo questo esempio: una Lambda realizzata in Python che richiede di eseguire un applicativo binario non compreso nel runtime AWS standard. Nel nostro esempio l’applicativo è un semplice script bash.
#!/bin/bash
# This is version.sh script
echo "Hello from layer!"
Per creare il Layer dobbiamo preparare un archivio ZIP con la seguente struttura:
layer.zip
└ bin/version.sh
Dalla console di AWS Lambda ci basta procedere alla creazione del livello specificando l’archivio ZIP sorgente e gli eventuali runtime compatibili.
Supponiamo ora di creare una Lambda function che utilizzi tale layer. Il codice potrebbe essere simile a questo:
import json
def lambda_handler(event, context):
import os
stream = os.popen('version.sh')
output = stream.read()
return {
'statusCode': 200,
'body': json.dumps('Message from script: {}'.format(output))
}
Il suo output sarà:
{
"statusCode": 200,
"body": "\"Message from script: Hello from layer!\\n\""
}
La nostra funzione AWS Lambda esegue correttamente lo script bash incluso nel layer. Questo accade perché il contenuto del Layer viene estratto nel folder /opt. Poiché abbiamo utilizzato la struttura prevista da AWS per la realizzazione dell’archivio ZIP di deployment, il nostro script bash è già incluso nel PATH (/opt/bin). Ottimo!
Considerando un esempio più completo, un progetto in Python di cui ho parlato in un altro post: l’utilizzo di Chromium e Selenium in una funzione AWS Lambda.
Per utilizzare Chromium in una funzione Lambda è necessario includere i binaries e le relative librerie nel pacchetto di deployment, in quanto, ovviamente, AWS non li prevede nel runtime Python standard. Il mio primo approccio è stato quello di non utilizzare alcun layer ottenendo un unico pacchetto ZIP da più di 80MB. Ogni qualvolta volevo aggiornare il codice della mia funzione Lambda, ero costretto a effettuare l’upload dell’intero pacchetto, con conseguente lunga attesa. Considerando il numero di volte che ho ripetuto l’operazione durante la fase di sviluppo del progetto e che il sorgente della funzione era una piccolissima parte dell’intero pacchetto (poche righe di codice), mi rendo conto di quanto tempo ho sprecato!
Il secondo approccio, decisamente più smart, è stato di utilizzare un layer di AWS Lambda per includere i binaries di Chromium e tutti i packages Python richiesti in maniera analoga a quanto visto in precedenza. La struttura è questa:
layer.zip
└ bin
└ chromium
chromedriver
fonts.conf
lib
└ ...
└ python
└ selenium
selenium-3.14.0.dist-info
...
Per installare i packages Python ho utilizzato il solito PIP:
pip3 install -r requirements.txt -t python
Una volta creato il layer, i tempi necessari per il deployment della funzione si sono ridotti notevolmente, il tutto a favore della produttività.
Qualche informazione in più sui Layer di AWS Lambda:
- Sono utilizzabili da più Lambda
- Si possono aggiornare e ogni volta viene creata una nuova versione
- Le versioni sono numerate automaticamente da 1 a salire
- Le versioni precedenti non vengono eliminate
- Possono essere condivisi con altri Account AWS e resi pubblici
- Sono specifici di una Region AWS
- In presenza di più layer in una Lambda, questi vengono “fusi” insieme secondo l’ordine specificato, sovrascrivendo eventuali file già presenti
- Una funzione può utilizzare fino a 5 livelli alla volta
- Non consentono di superare il limite della dimensione del pacchetto di distribuzione di AWS
Quando utilizzare i layers di AWS Lambda
Nel mio caso specifico, l’utilizzo di un layer ha portato grandi benefici riducendo i tempi di deployment. Mi sono chiesto se quindi è sempre una buona idea utilizzare i livelli di AWS Lambda. Spoiler alert: la risposta è no!
Esistono due ragioni principali per utilizzare i layers:
- la riduzione della dimensione dei pacchetti di deployment delle AWS Lambda
- la riusabilità di codice, middleware e binaries
Questo ultimo punto è proprio il più critico: cosa accade alle funzioni Lambda durante il ciclo di vita dei layers da cui dipendono?
I layer possono essere eliminati: la rimozione di un layer non comporta problemi alle funzioni che già lo utilizzano. E’ possibile modificare il (solo) codice della funzione ma, se è necessario modificare i livelli dai quali dipende, andrà rimossa la dipendenza al layer non più disponibile.
I layer possono essere aggiornati: la creazione di una nuova versione di un layer non comporta problemi alle funzioni che utilizzano le versioni precedenti. Il processo di aggiornamento delle lambda non è però automatico: se necessario dovrà essere specificata la nuova versione di layer nella definizione della lambda, rimovendo prima la precedente. Sebbene l’utilizzo dei layer possa quindi consentire la distribuzione di fix e patch di sicurezza relativi ai componenti comuni alle nostre lambda, si deve tener conto che tale processo non è completamente automatizzato.
AWS Lambda layers: test più complesso?
Oltre a quanto già evidenziato nel paragrafo precedente, l’utilizzo di layer comporta la necessità di affrontare nuove sfide, in particolare nell’ambito dei test.
Il primo aspetto da tenere in considerazione è che un layer determina l’introduzione di dipendenze che sono disponibili solo a runtime, rendendo più complessa la possibilità di debuggare il proprio codice localmente. La soluzione è effettuare il download da AWS del contenuto dei layer da cui dipende la funzione da testare ed includerlo durante il processo di build. Non molto pratico, comunque.
Analogamente l’esecuzione di unit tests e integration tests subisce un aumento di complessità: come per il debugging locale, è necessario che il contenuto dei layer sia disponibile durante l’esecuzione.
Il secondo aspetto riguarda invece linguaggi statici come Java o C#, per i quali è richiesto che tutte le dipendenze siano disponibili per compilare DLL o JAR. Ovviamente anche in questo caso esistono soluzioni più o meno eleganti, come il caricamento a runtime.
Security & Performance
In generale l’introduzione di livelli di AWS Lambda non comporta svantaggi in termini di sicurezza: al contrario è possibile effettuare il deployment di nuove versioni di layer esistenti per rilasciare security patch. Come visto in precedenza è bene ricordare che il processo di aggiornamento non è automatico.
Particolare attenzione va invece posta nei confronti di layer di terze parti: esistono diversi livelli resi disponibili pubblicamente e dedicati a vari ambiti. Sebbene sia effettivamente comodo poter utilizzare un layer già configurato per uno scopo ben specifico, è ovviamente meglio realizzare direttamente i propri layer in modo da non finire vittima di codice malevolo. In alternativa è sempre consigliato verificare prima il repository del layer che si intende utilizzare.
Performance: l’utilizzo dei layer in alternativa ad un package all-in-one, non comporta alcun effetto anche in caso di cold start.
CloudFormation
La creazione di Layer di AWS Lambda in CloudFormation è molto semplice. I Layer sono risorse di tipologia AWS::Lambda::LayerVersion. Nella definizione delle funzioni Lambda, il parametro Layers consente di specificare una lista di (massimo 5) dipendenze.
Ecco un esempio:
Conclusioni
My two cents: l’utilizzo dei layer di AWS Lambda comporta sicuramente dei benefici in presenza di dipendenze di notevoli dimensioni che non è necessario aggiornare molto di frequente. Spostare queste dipendenze in un layer riduce notevolmente i tempi di deployment della propria Lambda function.
E per condividere codice sorgente? In questo caso è bene fare delle valutazioni che tengano conto della complessità che si introduce nei processi di debug e test dell’applicazione: è probabile che l’effort richiesto non sia giustificabile dai benefici ottenibili con l’introduzione dei layer.
Ci siamo divertiti? Alla prossima!