Amazon Lambda per eseguire del codice dopo un’evento AWS
da Massimo Della Rovere · pubblicato il 28 dicembre, 2014 · modificato il 5 agosto, 2017

Il servizio di Amazon Lambda permette di eseguire del codice a seguito di un’evento che si verifica nei servizi di Amazon Web Services. La modalità del servizio è molto interessante, infatti la maniera classica per svolgere questa operazione era quella di depositare il codice su un’istanza di EC2 e leggere di continuo degli eventi, come ad esempio una coda su SQS o le notifiche di SNS, invece con Lambda si evita di creare risorse aggiuntive e si deve solo caricare il codice da eseguire con l’evento associato.

Questa tecnica non solo ci permette di risparmiare su risorse che vengono consumate in modo quasi inutile (in attesa che l’evento si verifichi), ma ci permettono di organizzare il nostro codice in maniera più ordinata e catalogata all’interno del servizio. Infatti potremo creare delle funzioni che memorizziamo in Lambda e una volta associate all’evento saranno richiamate dopo qualche millisecondo che questo si verifica.

Introduzione

Le possibilità legate a questo servizio sono molteplici, specialmente quando saranno disponibili anche tutti gli altri linguaggi di programmazione e saranno aggiunti dei nuovi eventi a quelli disponibili in questo momento. Nella documentazione ufficiale è riportato l’esempio classico per la creazione di miniature rispetto ad un’immagine che viene creata su S3, ma possono esistere molte possibilità diverse, ad esempio noi lo abbiamo utilizzato per creare un catalogo di backup settimanale e mensile partendo da un file di salvataggio giornaliero che viene memorizzato tutte le sere su S3.

Una volta che memorizziamo la funzione in Amazon Lambda questa sarà disponibile per qualsiasi evento che è possibile associare, ad esempio un’evento generato da S3, da una tabella su DynamoDB o da una trasmissione di Kinesis. Non ci dovremo preoccupare minimamente del problema legato alla scalabilità, infatti il nostro codice funzionerà bene sia se richiamato decine di volte al minuto che migliaia di volte. Tutta la complessità viene gestita da Amazon e noi paghiamo solo l’esecuzione del codice.

Configurazione

Per creare una funzione Lambda ed eseguire un Test, dobbiamo per prima cosa creare un programma funzionante, definire una funzione da associare al codice, impostare le giuste autorizzazioni, associare la funzione all’evento e provare il funzionamento. Per fare una prova possiamo pendere come esempio quello riportato dalla stessa Amazon sulla documentazione, che crea una miniatura partendo da un’immagine su S3.

Al momento il programma deve essere scritto in Node.js, non è sicuramente il linguaggio che preferisco e spero il prima possibile in una soluzione basata su Python o PHP. Per non perdere molto tempo vi ho preparato già il file .ZIP che contiene il programma di esempio e i moduli node.js che devono essere utilizzati. Quindi scaricate il file che verrà utilizzato come upload durante la creazione della funzione Lambda.

Download => CreateThumbnail.zip

// Moduli da caricare, escludendo il modulo SDK di Amazon
// gli altri vanno inseriti nel file .ZIP della funzione

var async = require('async');
var AWS   = require('aws-sdk');
var gm    = require('gm').subClass({ imageMagick: true });
var util  = require('util');

// Definizione delle costanti per il controllo
// delle immagini in miniatura tramite imageMagick

var MAX_WIDTH  = 100;
var MAX_HEIGHT = 100;

// Creazione oggetto S3 client senza chiavi di 
// accesso in quanto vengono utilizzati i Ruoli

var s3 = new AWS.S3();

// Funzione handler che viene richiamata dal 
// nostro evento con il passaggio delle informazioni

exports.handler = function(event, context)
{
  var srcBucket = event.Records[0].s3.bucket.name;
  var srcKey    = event.Records[0].s3.object.key;
  var dstBucket = srcBucket + ".resized";
  var dstKey    = "resized-" + srcKey;

  console.error("Source bucket " + srcBucket);
  console.error("Destination bucket " + dstBucket);

  // Il bucket di origine non può essere lo stesso di destinazione
  // altrimenti si crea un loop sugli eventi dopo la creazione file

  if (srcBucket == dstBucket) {
    console.error("Destination bucket must not match source bucket.");
    return;
  }

  // Espressione regolare per controllare se esiste una
  // estensione del file che può essere analizzata

  var typeMatch = srcKey.match(/\.([^.]*)$/);

  if (!typeMatch) {
    console.error('Unable to infer image type for key ' + srcKey);
    return;
  }

  // Il file immagine deve essere di tipo jpg o png
  // per essere trasformato con il modulo apposito

  var imageType = typeMatch[1];

  if (imageType != "jpg" && imageType != "png") {
    console.log('skipping non-image ' + srcKey);
    return;
  }

  // Funzioni per il download del file, la trasformazione e
  // la memorizzazione del risultato sul bucket di destinazione

  async.waterfall([

    // Caricamento in memoria del file immagine che è
    // stato indicato nella variabile passata dall'evento
    function download(next) {
      s3.getObject({
        Bucket: srcBucket,
        Key: srcKey
      },next);
    },

    // Funzione di trasformazione immagine tramite
    // la chiamata al modulo specifico di imageMagick

    function tranform(response, next) {
      gm(response.Body).size(function(err, size) {

        var scalingFactor = Math.min(
          MAX_WIDTH  / size.width,
          MAX_HEIGHT / size.height
        );

        var width  = scalingFactor * size.width;
        var height = scalingFactor * size.height;

        this.resize(width, height)
          .toBuffer(imageType, function(err, buffer) {
            if (err) next(err);
              else next(null, response.ContentType, buffer);
        });
      });
    },

    // Memorizzazione del file miniatura nel
    // bucket di destinazione su Amazon S3

    function upload(contentType, data, next) {
      s3.putObject({
        Bucket: dstBucket,
        Key: dstKey,
        Body: data,
        ContentType: contentType
      },next);
    }

  // Controllare lo stato di errore per la spedizione
  // di un messaggio informativo sulla console di log

  ],function(err) {
      if (err) {
        console.error(
          "Unable to resize " + srcBucket + "/" + srcKey +
          " and upload to "   + dstBucket + "/" + dstKey +
          " due to an error " + err);
      } else {
        console.log(
          "Successfully resized " + srcBucket + "/" + srcKey +
          " and uploaded to "     + dstBucket + "/" + dstKey);
      }
      context.done();
    }
  );
};

Il funzionamento del programma è molto semplice, ad ogni immagine di tipo (jpg/png) che viene aggiunta ad un bucket di origine verrà creata ed elaborata un’immagine ridotta, la quale sarà memorizzata in un bucket di destinazione che avrà il nome composto dal nome dell’origine e un prefisso (.resized). Quindi creiamo due bucket:

  • nomechevolete.images (dove farete gli upload delle immagini)
  • nomechevolete.images.resized (dove la funzione lambda memorizzerà il risultato)

Creazione della funzione

Andiamo nella management console e selezioniamo l’opzione per la creazione della nostra prima funzione Lambda. Come prima cosa dobbiamo specificare un nome di funzione che in questo caso possiamo indicare con il nome di (CreateThumbnail), indicare anche una descrizione, specificare come codice di upload il nostro file .ZIP e come file name indicare il valore di (CreateThumbnail.js). Creare un nuovo ruolo e dare le giuste autorizzazioni per lavorare con i nostri due bucket S3 creati precedentemente.

Amazon Lambda

Dopo la creazione della funzione dovremmo ottenere una schermata simile a questa, dove possiamo vedere la funzione, i grafici statistici delle chiamate e le azioni permesse. Adesso dobbiamo collegare la funzione ad un’evento, per fare questo usiamo il tasto che troviamo in basso a sinistra e “configure event source”. Ci verrà presentata una schermata in cui dovremmo specificare il nome del bucket di origine.

Amazon Lambda

A questo punto non ci rimane che provare la procedura, quindi andiamo a memorizzare delle immagini sul bucket di origine e vediamo se troviamo le miniature automatiche sul bucket di destinazione. La risposta all’evento e la elaborazione di Lambda è velocissima, quasi istantanea, quindi in mancanza di errori, il risultato è immediato.

Funzione di esempio

Vi riporto qui di seguito il risultato della mia prova, ho caricato una decina di copertine che uso per i video di youtube sul bucket di origine e questo è il risultato:

Amazon Lambda

È facile intuire che le applicazioni che possiamo sviluppare con questo metodo sono molte e tutte con dei riscontri interessanti. Infatti con questo meccanismo si evita di scrivere dei script su EC2 che non solo devono tenere conto delle notifiche che si generano nei vari componenti di AWS ma anche di un’eventuale scalabilità.

Guida completa su AWS

Questo articolo appartiene ad una serie di pubblicazioni che costituiscono una guida completa dedicata agli Amazon Web Services. Molti servizi che trattiamo in questo blog vengono anche spiegati con dei video che trovate nel nostro canale youtube. Se volete seguire questo percorso didattico iscrivetevi alla community Cloud AWS.

2 Commenti

  1. Ciao Massimo, è passato diverso tempo da questo articolo, adesso Lamba ha decine di eventi in più, supporta anche Python e Java. Manca solo il supporto per PHP (speriamo versione 7) e poi sono sicuro che stai contento :D

  2. Abbiamo pubblicato il tutorial anche in formato video
    https://www.youtube.com/watch?v=qfClT1-_K0Y

condividi