Azure Functions in Python

Alla vasta e rapida diffusione di modelli di programmazione serverless per applicazioni event-driven, Microsoft ha risposto con le Azure Functions. Grazie a questi prodotti, anche Microsoft è approdata nel mondo dei FaaS (Function as a Service), fornendo direttamente un ambiente dove memorizzare ed eseguire il proprio applicativo.

Fin da subito, l’integrazione con Python è stata una delle funzionalità più richieste. Tale connubio ha aumentato le possibilità di integrazione del servizio offrendo un ampio ventaglio di soluzioni basate su machine learning, data manipulation, scripting, automazione e molto altro.

Perché scegliere Azure Functions con Python?

L’obiettivo principale del computing Serverlsess/FaaS è quello di semplificare progettazione, sviluppo e deploy delle proprie applicazioni. Per garantire questo risultato le Azure Function con Python offrono i seguenti vantaggi:

  • Remote Build: alcune librerie Python potrebbero basarsi su dipendenze native. Questo significa, ad esempio, che se si sta sviluppando la propria applicazione su ambiente Windows e si effettua il deploy su un ambiente Linux, potrebbero generarsi dei comportamenti inaspettati o non supportati. Per risolvere questa problematica, le Azure Function ottengono e compilano le librerie richieste direttamente sull’ambiente server di esecuzione dell’applicativo. Grazie a questa funzionalità, non è necessario specificare localmente le librerie richieste; basterà referenziarle all’interno del file dei requisiti di una funzione (requirements.txt).
    Inoltre, viene garantito il supporto out of the box a framework anche di grandi dimensioni.
  • Mount di file esterni tramite Azure files: alcuni progetti, come ad esempio quelli basati su machine learning, possono basarsi su modelli di training forniti esternamente. Le Azure function permettono un montaggio automatico di questi file tramite Azure files.
  • Durable Functions for Python: nel caso in cui il progetto richieda elevati workflow, iterazioni tra più funzioni e input multipli nel batch di esecuzione è necessario ricorrere ad una orchestration. Questa soluzione si basa su tre modelli principali (Activity, Orchestrator e Client) nei quali possono essere implementate più Azure Functions. Per implementare la propria orchestration si può ricorrere a questo package.
  • Facilità di creazione: grazie a strumenti come Azure Function Core Tools e Azure Function extensions for Visual Studio Code, è possibile creare la propria function pronta per l’uso con pochi comandi. Sarà possibile scegliere il template adatto alle proprie esigenze e cominciare lo sviluppo senza preoccuparsi della creazione di un virtualenv e di fornirsi delle librerie necessarie.
  • Scelta del piano tariffario a seconda delle proprie esigenze. Se è necessario evitare il cold-start oppure è necessaria una quantità maggiore di risorse, è possibile scegliere tra vari pacchetti Azure Functions Premium SKU. Inoltre, se sottoscritto un piano premium, sarà possibile integrare il proprio progetto machine learning con Azure machine learning service.

In questo articolo troveremo un esempio di Azure Function basata su Python, che mostrerà il potenziale in rapporto alla semplicità di sviluppo ed integrazione. In particolare, sarà mostrato come da un trigger HTTP vi è la possibilità di importare moduli esterni contenenti l’integrazione di librerie e pacchetti. È possibile trovare il progetto usato come esempio su GitHub.

Struttura di una Azure Function

Le Azure Functions seguono un modello standard per l’impostazione del progetto che indica gli elementi che verranno pubblicati su Azure e la loro funzione.

app
 | - function1
 | | - __init__.py
 | | - function.json
 | | - module.py
 | - function2
 | | - __init__.py
 | | - function.json
 | - shared
 | | - helper_function1.py
 | | - helper_function2.py
 | - host.json
 | - requirements.txt
 | - Dockerfile
 | - local.settings.json
 tests

Dove “app” è la cartella principale del progetto che al suo interno conterrà:

  • function1: una cartella contenente i file necessari per la registrazione e l’esecuzione della funzione Python nominata “function1”.
  • function2: una cartella contenente i file necessari per la registrazione e l’esecuzione della funzione Python nominata “function2”.
  • shared: una cartella contenente script Python di utility disponibili a tutte le funzioni che li importano.
  • hosts.json: contiene i valori comuni ad ogni funzione dell’applicazione come, ad esempio, la versione di runtime delle Azure Functions. Alcune opzioni potrebbero non essere supportate nell’esecuzione locale.
  • requirements.txt: contiene la lista di pacchetti che il sistema deve installare al momento della pubblicazione su Azure. Di default, sarà presente il pacchetto “azure-functions”. Il contenuto di questo file, al momento dell’esecuzione, verrà scaricato e referenziato all’interno del virtualenv Python creato per il progetto.
  • Dockerfile: utilizzato se l’applicazione dovesse essere pubblicata su un container specifico.
  • local.settings.json: utilizzato per contenere gli app settings (connection strings, etc…) solamente per l’esecuzione locale. Questo file non sarà pubblicato su Azure.

Inoltre, potranno essere presenti i file “.gitignore” per i file esclusi dal repository git e “.funcignore” per dichiarare i file che non dovranno essere pubblicati su Azure. È consigliabile predisporre i test in una cartella separata al progetto – come nel modello fornito – per evitare il deploy su Azure di file non utilizzati.

Definire una Azure Function

Il fulcro di una Azure Function risiede nell’implementazione di minimo due file fondamentali: function.json ed il relativo file contenente il codice. Opzionalmente, è possibile aggiungere altri file di codice alla directory della funzione integrandoli come moduli all’interno del file utilizzato dalla chiamata della function.

Configurare una Function

Il file function.json descrive le associazioni ed i comportamenti della function definendo elementi come il file di scripting, il metodo da sfruttare come entry point all’interno del file di scripting, i relativi parametri di input al metodo ed il suo output. Tramite di esso il runtime di Azure determina gli eventi da monitorare e come vengono passati e ritornati i dati. Ogni funzione possiede il suo unico e solo file function.json.

In questo caso, function.json descrive la function getCoordinates che si basa sulla chiamata al metodo localize all’interno del file __init__.py. Si eseguirà il tutto tramite una richiesta HTTP denominata req. Il metodo fornirà poi come output una risposta HTTP.

Nell’esempio è stato specificato un metodo da utilizzare come entry point, di default verrà utilizzato il metodo main presente all’interno del file python indicato.

Codice della Function

Una volta configurata la funzione, è possibile passare all’implementazione del codice della stessa.

Per sfruttare l’integrazione con le Azure Functions vi è il bisogno di importare le librerie necessarie alla prima esecuzione: azure.functions e logging. La prima offre gli strumenti necessari per poter agire durante i trigger descritti nel file function.json, la seconda permette di loggare messaggi utili durante l’analisi della function tramite la dashboard di Application Insight sul portale Azure.

Il modulo azure-functions è specificato all’interno del file dei requisiti: requirements.txt e verrà scaricato ed installato durante l’esecuzione del virtual environment della function.

Possiamo quindi procedere nel definire la funzione localize con il parametro in ingresso req.

In questo caso, req è specificato come HttpRequest ed il tipo di ritorno della function come HttpResponse. Entrambi i tipi sono forniti dal modulo azure.function.

Adesso possiamo aggiungere le iterazioni principali, tra di esse l’interpretazione della richiesta HTTP di input e l’iterazione con eventuali moduli aggiuntivi.

L’applicativo utilizzato come esempio da una richiesta HTTP in input, estrapola il parametro spot, contenente il nome del luogo del quale si vuole conoscere le coordinate geografiche. Per fare questo, la function localize chiama il metodo getCoordinates definito nel modulo esterno geo. Un risultato valido mostra il nome del luogo scelto e le sue coordinate, altrimenti verrà visualizzato un messaggio di errore.

Questo modulo esterno utilizza il framework geopy per ottenere le coordinate geografiche richieste. Per poterlo includere e permettere la corretta esecuzione in runtime è necessario aggiungere questa libreria al file requirements.txt.

Testing

Una volta implementato il codice e descritto il comportamento della function all’interno del relativo file di configurazione, sarà possibile eseguire localmente il proprio progetto per verificare lo stato e la corretta esecuzione, prima del deploy su Azure.

Collegandosi quindi all’indirizzo di pubblicazione locale della function, fornendo il parametro descritto, attraverso un web browser farà partire la chiamata alla funzione e, dopo le eventuali logiche, sarà possibile consultare il risultato. In questo caso, una risposta HTTP.

Oltre ad i test funzionali, il pacchetto azure.functions consente di creare istanze delle classi utilizzate da utilizzare come mock in input per eseguire unit tests. In questo caso vedremo un esempio di unit test utilizzando il framework Python unittest.

Monitoraggio su Azure

Una volta eseguito il deploy della propria function su Azure, selezionando il piano di offerta adatto alle proprie esigenze, sarà possibile accedere alla dashboard di monitoraggio e configurazione. Tramite questa sarà possibile verificare e modificare lo stato di esecuzione della propria function, visualizzare storico di eventi e chiamate ricevute e tempo impiegato nell’esecuzione e risposta.

Un tool di utilità è senz’altro l’Application Insight che abilita la scrittura e la visualizzazione di messaggi di log delle proprie function (utilizzati tramite l’implementazione del modulo logging) catalogati per ogni singola chiamata ricevuta.

In conclusione

Il matrimonio tra Azure Functions e Python è una bella storia d’amore basata sullo sviluppo power accelerated di Python ed il modello di programmazione basato su eventi e triggers delle functions. La facilità e velocità di sviluppo sono senz’altro uno dei fattori di scelta principali ma non devono e possono offuscare i vantaggi dovuti alle ampie possibilità di sviluppo che questa coppia offre.