
In questo articolo si discuteranno due tecnologie ampiamente utilizzate nel mondo dello sviluppo software, in particolare nell’ambito del data engineering: Apache Airflow e la containerizzazione sul cloud Azure, e nello specifico, tramite il servizio App Service e Container App.
Il motivo che mi spinge a scrivere questo articolo è che oggi, la data engineering, è una disciplina fondamentale per ogni azienda che voglia ottenere valore dai propri dati. Si pensi ad esempio al valore dei sistemi di IA basati sui dati aziendali, di cui ho parlato in questo articolo.
Inoltre, nella mia carriera mi sono sempre occupato (e continuo a farlo tutt’ora) di architetture software. E questo fa sì che dal mio punto di vista, la data engineering non si esaurisca solo nello sviluppo di “data pipelines” (ovvero sistemi di integrazione e arricchimento dati) e gestione dello storage.
I sistemi di data engineering, perché siano efficienti, devono essere progettati anche con un approccio all’architettura software che permetta di realizzare applicazioni correttamente stratificate, scalabili e manutenibili / evolvibili.
Fondamentale per raggiungere questo obiettivo è la scelta degli strumenti corretti e delle tecnologie più… (si legga scalabili, portabili, standardizzate, ecc…).
E qui arriviamo appunto ad Apache Airflow, Microsoft Azure, ed i suoi servizi di virtualizzazione e gestione dei container.
Ma andiamo con ordine.
Cos’è Apache Airflow
Apache Airflow è uno strumento potente per la gestione e l’automazione dei flussi di lavoro dei dati. Consente di orchestrare una serie di compiti che devono essere eseguiti in una sequenza specifica, come l’estrazione di dati da una fonte, la loro trasformazione e il caricamento in un database, assicurandosi che ogni fase sia completata con successo prima di passare alla successiva.
Per riuscire in questo, Airflow fa utilizzo di “DAGs” (Directed Acyclic Graphs) per definire i flussi di lavoro. I DAG sono “grafi” di processo dove ogni nodo rappresenta un task specifico. Airflow gestisce l’esecuzione, la pianificazione e il monitoraggio di questi task, risultando altamente scalabile e adatto per flussi di lavoro complessi.
Va detto innanzitutto che airflow è un pacchetto python da installare in un ambiente python. tramite il comando:
pip install apache-airflow
A questo poi si aggiungono molti altri pacchetti, ma la base è appunto quella scritta sopra.
Airflow è diviso in alcuni componenti principali che insieme portano a termine le operazioni richieste:
- Operatori: Un operatore definisce una singola unità di lavoro all’interno di un DAG. Esistono vari tipi di operatori, come BashOperator per eseguire script Bash, PythonOperator per eseguire codice Python, e operatori specifici per l’integrazione con altri servizi.
- Task Instances: Esecuzioni specifiche di un task all’interno di un DAG. Un task instance è una combinazione di un task, un DAG, e un timestamp che rappresenta l’istante di esecuzione.
- Scheduler: Il componente che pianifica e mette in coda i task per l’esecuzione. Il scheduler monitora i DAG e le loro dipendenze, determinando quali task devono essere eseguiti e quando.
- Executor: Gestisce l’esecuzione dei task. Può essere configurato in vari modi, ad esempio con esecuzioni locali, su un cluster Celery, o su Kubernetes.
- Web Server: Fornisce un’interfaccia utente per monitorare e gestire i DAG e i task. Permette di visualizzare lo stato dei flussi di lavoro, i log dei task, e di avviare o fermare manualmente i DAG.
- Database: Un database relazionale che conserva lo stato dei DAG, dei task, delle esecuzioni, e altre informazioni necessarie per il funzionamento di Airflow. Solitamente si utilizza PostgreSQL o MySQL.

Fonte: https://learnwithaakash.com/2023/06/26/a-beginner-guide-to-airflow-components/
Questa divisione ci tornerà utile successivamente, perchè solitamente l’ “installazione” di airflow prevede il deploy di almeno due componenti (oltre al database): il webserver e lo scheduler.
Comunque, una volta installato il pacchetto airflow è possibile lanciare lo scheduler con il comando “airflow scheduler” ed il webserver con il comando “airflow webserver”.
Apache Airflow è stato sviluppato inizialmente da Airbnb nel 2014 per gestire i propri flussi di lavoro complessi. È diventato un progetto open source nel 2015 ed è cresciuto rapidamente in popolarità grazie alla sua flessibilità e scalabilità. Nel 2019, Apache Airflow è diventato un progetto Apache di alto livello.
La sua bontà è dimostrata dal fatto che oggi, Airflow, è utilizzato da una vasta gamma di aziende di diversi settori e dimensioni, dimostrando la sua versatilità e potenza come strumento di orchestrazione dei flussi di lavoro. Ad esempio Airbnb, Twitter, e Lyft utilizzano Airflow per gestire i loro flussi di dati e pipeline di machine learning (ML) e ETL (Extract, Transform, Load) (Airflow Summit). Ma anche Infosys e Fujitsu sono altre grandi aziende che hanno adottato Airflow per le loro esigenze di orchestrazione dei dati, così come tanti altri nomi “di un certo peso”.
Comunque, una volta avviato lo scheduler, questo andrà a cercare nella cartella dei dags tutti i file python che li rappresentano e li metterà a disposizione per poter essere eseguiti.
Tipicamente un DAG ha questo aspetto:
from datetime import datetime, timedelta
from airflow import DAG
from airflow.operators.bash_operator import BashOperator
import pendulum
default_args = {
'owner': 'airflow',
'depends_on_past': False,
'email': ['airflow@example.com'],
'email_on_failure': False,
'email_on_retry': False,
'retries': 1,
'retry_delay': timedelta(minutes=5),
}
with DAG(dag_id='ingest_sap_data_without_download',
default_args=default_args,
start_date=pendulum.datetime(2022, 5, 1, tz="UTC"),
catchup=False,
schedule_interval='0 7 * * *',
tags=['sap', 'data_integration'],
) as dag:
root_folder = "/usr/local/airflow/app"
py_x_path = "python3"
script_folder = f"{root_folder}"
stage_pricelist = BashOperator(
bash_command=f"{py_x_path} {script_folder}/stage_sap_pricelist.py",
task_id="stage_price_list")
ingest_pricelist = BashOperator(
bash_command=f"{py_x_path} {script_folder}/ingest_sap_pricelist.py",
task_id="ingest_price_list")
ingest_translations = BashOperator(
bash_command=f"{py_x_path} {script_folder}/ingest_sap_translations.py",
task_id="ingest_translations")
ingest_future_prices = BashOperator(
bash_command=f"{py_x_path} {script_folder}/ingest_sap_future_prices.py",
task_id="ingest_future_prices")
stage_sap_contact_registry = BashOperator(
bash_command=f"{py_x_path} {script_folder}/stage_sap_contact_registry.py",
task_id="stage_sap_contact_registry")
ingest_sap_contact_registry = BashOperator(
bash_command=f"{py_x_path} {script_folder}/ingest_sap_contact_registry.py",
task_id="ingest_sap_contact_registry")
ingest_customer_economics = BashOperator(
bash_command=f"{py_x_path} {script_folder}/ingest_customer_economics.py",
task_id="ingest_customer_economics")
ingest_product_cost = BashOperator(
bash_command=f"{py_x_path} {script_folder}/ingest_product_cost.py",
task_id="ingest_product_cost")
stage_pricelist >> ingest_pricelist >> ingest_translations >> ingest_future_prices >> ingest_product_cost
stage_sap_contact_registry >> ingest_sap_contact_registry >> ingest_customer_economics
File come questo servono ad orchestrare i singoli task eseguendo il codice python (ma possono fare anche molto altro).
Quando si avvia invece il componente web server, una console fruibile da browser permette di gestire i DAG. E’ possibile ad esempio avviarli, analizzarne l’esecuzione, fermarli, ecc…
Di seguito gli screenshot della console di amministrazione web dove è possibile vedere la lista dei processi utilizzabili (la prima immagine), ed il dettaglio di esecuzione di un processo (seconda immagine).


Detto quindi COSA è Airflow, la domanda successiva è COME eseguirlo. Sicuramente una strada percorribile è quella di installarlo su un server on-premise per avere il massimo controllo.
Ma se si vuole scegliere un approccio “cloud native”, abbiamo la possibile di passare all’esecuzione su container.
Cosa si intende per containerizzazione
La containerizzazione è una tecnologia che consente di impacchettare un’applicazione e le sue dipendenze in un’unità portatile chiamata “container”. Questo garantisce che l’applicazione funzioni in modo coerente, indipendentemente dall’ambiente in cui viene eseguita.
Quindi per farla semplice, ogni applicazione “gira” in un container dedicato che contiene oltre al codice sorgente, le librerie necessarie. Tutti i container vengono eseguiti a loro volta in un ambiente che condivide il kernel del sistema operativo con ciascun container. Questo li rende leggeri e veloci da avviare.
Lo stesso risultato si otterrebbe eseguendo ciascuna applicazione in una virtual machine dedicata, ma come si intuisce questo porterebbe alcune complicazioni.
Ogni VM infatti deve eseguire un sistema operativo completo, il che significa che richiede più CPU, memoria e spazio di archiviazione. Questo può ridurre il numero di VM che possono essere eseguite contemporaneamente su un singolo server. Inoltre, le VM sono meno portatili poiché dipendono dall’hypervisor e dalle configurazioni hardware sottostanti.

Fonte: https://https://www.researchgate.net/figure/Schematic-comparison-of-virtual-machines-and-containers-Virtual-machines-run-on-a_fig5_338889305. Differenza tra container e virtual machines.
Per lavorare con i container esistono diverse piattaforme, e forse quella più conosciuta è Docker. Docker è una piattaforma open source che consente agli sviluppatori di automatizzare la distribuzione di applicazioni all’interno di container software.
Tornando ad Airflow, la containerizzazione con Docker permette di creare un’immagine dell’ambiente di esecuzione di Airflow, comprensiva di tutte le dipendenze necessarie.
I vantaggi della containerizzazione
Dopo aver visto a grandi linee in cosa differisce un approccio classico basato su VM rispetto ad un approccio basato sul deploy di container, quali sono i vantaggi della containerizzazione rispetto alle più tradizionali macchine virtuali?
Eccone alcuni:
- Portabilità: I container sono altamente portatili perché includono tutto ciò di cui un’applicazione ha bisogno per funzionare, come codice, runtime, librerie e impostazioni di sistema. Questo garantisce che i flussi di lavoro dei dati possano essere eseguiti in modo coerente su qualsiasi ambiente, sia esso un laptop di sviluppo, un server on-premise o una piattaforma cloud. La portabilità elimina i problemi di compatibilità e facilita il passaggio dei flussi di lavoro tra diversi ambienti.
- Isolamento: I container offrono un ambiente isolato per ciascuna applicazione, il che significa che i processi e le dipendenze di un container non interferiranno con quelli di un altro. L’isolamento inoltre aiuta a prevenire conflitti e garantisce che i processi critici funzionino senza interruzioni.
- Efficienza e prestazioni: A differenza delle macchine virtuali, i container condividono il kernel del sistema operativo host, riducendo il sovraccarico delle risorse. Questo permette di eseguire un numero maggiore di container sullo stesso hardware rispetto alle VM e di avere prestazioni migliori.
- Scalabilità e Gestione Automatizzata: La containerizzazione facilita la scalabilità automatizzata attraverso l’uso di orchestratori come Kubernetes. Gli orchestratori possono gestire automaticamente l’avvio e l’arresto dei container in base alla domanda, bilanciando il carico e garantendo la continuità dei servizi.
- Coerenza tra Ambienti di Sviluppo e Produzione: Grazie alla containerizzazione, è possibile garantire che il codice funzionante nell’ambiente di sviluppo si comporti esattamente allo stesso modo in produzione. Questo riduce i bug e i problemi derivanti da differenze ambientali, migliorando l’affidabilità dei flussi di lavoro dei dati e accelerando il ciclo di sviluppo.
Se è chiaro che i container abbiano degli indiscussi vantaggi, come è possibile gestire i container tramite Docker?
Come lavorare con i container utilizzando Docker
Docker è una piattaforma open source che consente di creare, implementare, eseguire, aggiornare e gestire container. E’ diffuso al punto che container e docker vengono spesso utilizzati in modo intercambiabile, anche se docker è solo uno degli strumenti disponibili per lavorare con i container.
Docker è composto da alcuni strumenti su cui è necessario fare chiarezza fin da subito.
DockerFile: è un file di testo che contiene istruzioni su come creare un’immagine Docker. Automatizza la creazione delle immagini utilizzando comandi CLI che vengono eseguiti dal motore Docker per assemblare l’immagine.
Immagini Docker: contengono il codice sorgente delle applicazioni e tutte le dipendenze necessarie per l’esecuzione come container. Ogni modifica apportata crea un nuovo livello nell’immagine, facilitando il rollback e il riutilizzo.
Container Docker: sono istanze eseguibili delle immagini Docker. Mentre le immagini sono di sola lettura, i container sono modificabili durante l’esecuzione.
Docker Hub: è un repository pubblico di immagini Docker, con oltre 100.000 immagini disponibili da fornitori commerciali, progetti open-source e singoli sviluppatori. Consente la condivisione e il download di immagini Docker.
Docker Desktop: è un’applicazione per Mac e Windows che include Docker Engine, Docker CLI, Docker Compose e Kubernetes, offrendo accesso a Docker Hub.
Daemon Docker: gestisce la creazione e l’esecuzione delle immagini Docker, fungendo da centro di controllo per l’implementazione di Docker sull’host.
Registro Docker: è un sistema di archiviazione e distribuzione delle immagini Docker. Permette di tracciare le versioni delle immagini nei repository tramite tag, simile al controllo delle versioni con git.
Spesso si può partire da un dockerfile per partire da un’immagine di un prodotto open source e modificarla (come faremo appunto per airflow).
Ad esempio, il seguente dockerfile prende un’immagine del runtime python, vi copia il codice sorgente, installa i pacchetti nell’ambiente ed esegue il codice:
# Usa una base image ufficiale di Python
FROM python:3.9-slim
# Imposta la directory di lavoro nel container
WORKDIR /app
# Copia i file requirements.txt nella directory di lavoro
COPY requirements.txt .
# Installa le dipendenze specificate in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Copia tutto il codice dell'applicazione nella directory di lavoro
COPY . .
# Esponi la porta su cui l'applicazione sarà eseguita
EXPOSE 5000
# Definisci il comando per eseguire l'applicazione
CMD ["python", "app.py"]
Cosa fanno questi comandi?
- FROM python:3.9-slim: Usa una versione leggera di Python 3.9 come base.
- WORKDIR /app: Imposta la directory di lavoro nel container a /app.
- COPY requirements.txt .: Copia il file requirements.txt nella directory di lavoro.
- RUN pip install –no-cache-dir -r requirements.txt: Installa le dipendenze.
- COPY . .: Copia il contenuto della directory corrente nella directory di lavoro del container.
- EXPOSE 5000: Espone la porta 5000 per l’applicazione web.
- CMD [“python”, “app.py”]: Esegue il file app.py quando il container viene avviato.
Per avere una panoramica più completa dei comandi disponibili vedi questo link: https://docs.docker.com/reference/dockerfile/
Una volta scritto il dockerfile, è necessario creare l’immagine a partire dal dockerfile stesso. Come abbiamo detto prima l’immagine è necessaria per istanziare a partire da essa i container eseguibili. L’immagine infatti è come se fosse uno “stampo” su cui vengono creati i container.
docker build -t nome-immagine:tag .
In questo comando:
- “-t specifica” il nome e il tag dell’immagine (nome-immagine:tag).
- “.” indica che il Dockerfile si trova nella directory corrente.
- in presenza di più dockerfile è possibile scegliere quale utilizzare con l’attributo “-f”
Questo comando permetterà di generare l’immagine sul docker locale, ma come detto prima, non realizzerà un container eseguibile. Per farlo, sarà necessario eseguire il comando “run”.
docker run -d -p 5000:5000 --name nome-container my-python-app:latest
In questo comando:
- “-d” esegue il container in modalità “detached” (in background).
- “-p 5000:5000” mappa la porta 5000 del container alla porta 5000 dell’host.
- “–name nome-container” assegna un nome al container.
- “my-python-app:latest” è il nome e il tag dell’immagine da eseguire
Fatto questo, sarà possibile collegarsi all’applicazione ad esempio tramite browser navigando all’indirizzo localhost:5000.
E’ possibile gestire i container anche dall’applicazione docker desktop, ad esempio per avviarli, fermarli, eliminarli od ispezionarli.

Infine per poter pubblicare l’immagine in un registro come il registro docker o quello di Azure, è necessario un ultimo comando (solo dopo aver eseguito il tag corretto ed il login al registro).
docker push myusername/my-python-app:latest
Questo comando eseguirà l’upload dell’immagine appena generata su un registro pubblico o privato, e questo sarà necessario per distribuirla ad esempio sui servizi app di Azure.
Adesso che abbiamo visto le basi di airflow e della tecnologia che sta dietro ai container c’è da chiedersi come Azure supporti tutto questo. Nel prossimo articolo parleremo proprio di questo.
Alla prossima informazione!