Pubblicato il 09/09/2023 da alnao nella categoria AWS

Per serverless si intende una architettura Cloud nella quale la gestione dei server viene delegata al gestore del Cloud e i programmatori possono concentrarsi al 100% sugli sviluppi. AWS ha investito molto sui servizi di questo tipo basando quasi interamente il Cloud su questo tipo di modello. Il più famoso servizio di questo tipo è Lambda che permette di creare applicazioni scrivendo piccole funzioni, altri servizi di tipo serverless saranno descritti in futuri articoli.

Nota: con il termine “serverless” non si intende l’assenza di server ma si intende che il server può essere configurato dall’utente ma non è possibile prenderne il controllo.

Il servizio Lambda prevede che venga indicata l’architettura (64 o 32 bit) e la quantità di memoria RAM, al resto di pensa AWS. Il servizio prevede un limite tecnologico sulle elaborazioni di ogni funzione: una esecuzione può durare al massimo 15 minuti, oltre questo limite il processo di interrompe per time-out. Per tutte le informazioni riguardo a questo servizio si rimanda alla documentazione ufficiale dove sono sono presenti anche esempi per tutti i linguaggi di programmazione supportati come Python, Java e Node.

Nella console web di AWS per ogni lambda si hanno a disposizione quattro tab per la gestione delle funzioni:

  • il codice, la configurazione dell’architettura e la dichiarazione del nome tecnico della funzione (handler)
  • il sistema di testing della funzione
  • il monitor della funzione con grafici e link per consultare i log in CloudWatch
  • le configurazioni avanzate della funzione.

Le principali configurazioni disponibili per ogni funzione:

  • il servizio chiamante (per esempio trigger EventBridge o coda SNS)
  • la execution rule (regola IAM di cosa può fare la lambda)
  • la resource-based policy statements (regola IAM di chi può invocare la funzione)
  • le Environment variables (le variabili d’ambiente)
  • la configurazione di rete (la VPC o i gateway)
  • le configurazioni dei sistemi di chiamata asincrona (per esempio SQS)

La console web mette a disposizione una procedura guidata per la creazione di una Lambda Function, nella procedura l’utente deve inserire i dati minimi: il nome, il runtime (Node, Python, Java, …). E’ possibile modificare alcune configurazione di default come la “execution role” che viene creata di default con solo la “permission” per permettere alla funzione di scrivere i log nella CloudWatch Logs, si rimanda alla documentazione ufficiale per maggior informazioni riguardo alle “execution role” e alle altre configurazioni delle lambda function da Console. Di default la Lambda creata in Console nel linguaggio Python viene creata con il codice di default molto semplice:

nel caso di linguaggio Java invece è necessario creare un Jar come spiegato nell’esempio disponibile nel repository:

https://github.com/alnao/AwsLambdaExamples/tree/master/java-maven-example01-console

In questo esempio sono introdotti anche i comandi maven per creare il progetto Java di base, è possibile creare lo scheletro di un progetto AWS-Lambda usando un archetipo specifico:

mvn archetype:generate -DarchetypeGroupId=xxxxx 
  -DarchetypeArtifactId=archetype-lambda -DarchetypeVersion=2.15.79

La CLI mette a disposizione una serie di comandi per creare e gestire le lambda function, la sintassi del comando base è

aws lambda create-function --function-name java-maven-example01-cli
--zip-file fileb://target/java-maven-example01-console-1.0-SNAPSHOT.jar
--runtime java8 --handler it.alnao.App::handleRequest
--role arn:aws:iam::xxxxxx:role/lambda-role

Tramite questo comando è possibile invocare, aggiornare e recuperare le informazioni riguardo alla lambda, una serie di esempi per la gestione di una lambda:

aws lambda invoke --function-name java-maven-example01-cli outputfile.txt
aws lambda update-function-code --function-name java-maven-example01-cli
  --zip-file fileb://target/java-maven-example01-console-1.0-SNAPSHOT.jar
aws lambda list-functions --max-items 10
aws lambda get-function --function-name java-maven-example01-cli
aws lambda delete-function --function-name java-maven-example01-cli
aws lambda get-function-configuration --function-name my-function

La CLI mette a disposizione un comando specifico per la gestione dei servizi serverless e in particolare le lambda function, chiamato Serverless Application Model abbreviato con la sigla SAM. Nella documentazione ufficiale sono descritti tutti i comandi messi a disposizione, è disponibile una guida per l’installazione. Il comando più usato è il:

sam deploy --guided

usato per eseguire il rilascio di una Lambda, il comando crea un template CloudFormation, per esempio:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  AppFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: java8
      Handler: it.alnao.App::handleRequest
      Timeout: 60
      MemorySize: 512
      CodeUri: ./target/java-maven-example01-console.jar

e successivamente esegue il rilascio di uno stack salvando un file di configurazione samconfig.toml dove sono indicati tutti i parametri per la creazione dello stack.


Con CloudFormation è possibile gestire Lambda, le “execution role” e tutti i servizi che possono interagire con le funzioni. L’esempio più semplice è creare una Lambda che legge un file da un Bucket S3, esempio classico consultabile nel solito repository. In questo esempio si vede la dichiarazione della Lambda con il codice Python in-line nel templare, questa tecnica è ovviamente da evitare e conviene sempre fare un file py separato, in questo esempio viene usata questa tecnica solo a titolo esplicativo:

FunzioneLambda:
  Type: 'AWS::Lambda::Function'
  Properties:
    Code:
      ZipFile: |
        import json
        def lambda_handler(event,context):
        for record in event['Records']:
          print("Esecuzione" + json.dumps(event) )
        bucket_name = record['s3']['bucket']['name']
        key_name = record['s3']['object']['key']
        print('Key found: ' + key_name + ' in Bucket: ' + bucket_name)
        return {'statusCode': 200 , 'body': 'OK'}
    Handler: index.lambda_handler
    Role: !GetAtt LambdaIAMRole.Arn
    Runtime: python3.9
    Timeout: 5

Come ripetuto più volte, la tecnica di inserire il codice Python all’interno dei template CloudFormation è una cosa da evitare. Esistono due alternative, prima è usare un file separato e caricarlo in un bucket S3, in questo caso nel template è necessario indicare bucket, key e versione del file, inoltre nella “execution rule” bisogna autorizzare la lambda ad accedere al bucket e al path. La seconda tecnica, consigliata e stra-usata, è usare un file esterno al template, richiamabile indicando il nome della sottoscartella nel parametro “CodiceUri”, mentre nel Handler si indica il nome del file e il nome del metodo (che tipicamente è lambda_handler), il caricamento su S3 viene delegato al comando della cli chiamato sam e al suo sotto-comando package, per usare questa tecnica è necessario usare il tipo di risorsa “Serverless::Function”, un esempio completo:

ExternalPyLambda:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: lambda
    Handler: esempio_file.lambda_handler
    Runtime: python3.8
    MemorySize: 512
    Timeout: 900

Oltre alla definizione della Lambda, all’interno del template CloudFormation, bisogna aggiungere due elementi:

la regola IAM detta execution role per gestire i permessi di cosa può fare la lambda. risorsa di tipo AWS::IAM::Role

la regola di invocazione detta resource base policy per permettere al chiamante di invocare la funzione Lambda, risorsa di tipo AWS::Lambda::Permission

in questo esempio la execution role permette al codice di accedere alla notifica da S3 e di scrivere i log, mentre la resource policy permette al bucket S3 di invocare la funzione lambda. Bisogna sempre ricordare che senza queste regole il sistema blocca le esecuzioni in quanto su AWS tutto è negato a meno che non sia specificato in una regola IAM, si rimanda alla documentazione ufficiale per maggior in formazioni.

LambdaInvokePermission:
  Type: 'AWS::Lambda::Permission'
  Properties:
    FunctionName: !GetAtt S3Notification.Arn
    Action: 'lambda:InvokeFunction'
    Principal: s3.amazonaws.com
    SourceArn: !Join
    - ''
    - - 'arn:aws:s3:::'
      - !Ref BucketName
LambdaIAMRole:
  Type: 'AWS::IAM::Role'
  Properties:
    AssumeRolePolicyDocument:
      Version: 2012-10-17
      Statement:
      - Effect: Allow
        Principal:
        Service:
        - lambda.amazonaws.com
        Action:
        - 'sts:AssumeRole'
    Path: /
    Policies:
    - PolicyName: root
      PolicyDocument:
      Version: 2012-10-17
      Statement:
      - Effect: Allow
        Action:
        - 's3:GetBucketNotification'
        - 's3:PutBucketNotification'
        Resource: !Join
        - ''
        - - 'arn:aws:s3:::'
          - !Ref BucketName
      - Effect: Allow
        Action:
        - 'logs:CreateLogGroup'
        - 'logs:CreateLogStream'
        - 'logs:PutLogEvents'
        Resource: 'arn:aws:logs:*:*:*'

Nelle funzioni lambda è possibile usare le librerie messe a disposizione dal SDK per interagire con altri gli altri servizi, per esempio nelle lambda function è possibile usare la libreria Boto3 per collegarsi ad altri servizi AWS, per esempio è possibile accedere ad un bucket con poche righe di codice:

import boto3
s3 = boto3.client('s3')
s3_resource = boto3.resource('s3')
...
def lambda_handler(event,context):
  print("Esecuzione" + json.dumps(event) )
  ...
  #esempio per leggere un oggetto da un bucket
  s3_object = s3.get_object(Bucket=bucket_name, Key=key_name)
  ...
  #esempio per copiare un file da un bucket ad un altro
  copy_source = {'Bucket': source_bucket,'Key': source_key }
  s3_resource.Bucket(destination_bucket_name).Object(target_key)
    .copy(copy_source, ExtraArgs={'ACL': 'bucket-owner-full-control'})

Per il linguaggio Java è sempre possibile usare la libreria SDK all’interno delle Funzioni tuttavia c’è un enorme problema: nel linguaggio Java le lambda function sono blocchi di codice senza nomee invocati con l’operatore freccia ->, per esempio:

numbers.forEach( (n) -> { System.out.println(n); } );

e purtroppo c’è un caso di omonimia con le Lambda Function del Cloud AWS, la cosa può creare confusione tra i non programmtori ma chi conosce il linguaggio Java comprende sempre la differenza tra i due concetti di funzione. Tipicamente in questo sito si usa sempre Python come linguaggio per le funzioni lambda e per gli esempi combinati con CloudFormation e gli altri servizi, tuttavia è possibile trovare esempi di AWS-Lambda Function scritti nel linguaggio Java nel repository:

https://github.com/alnao/AwsLambdaExamples

Con la libreria SDK è possibile gestire le lambda recuperando l’elenco delle Funzioni Lambda, invocare le funzioni da remoto e creare Lambda Application, concetti che saranno descritti in un prossimo articolo.

MENU