Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Calcolo scientifico nel cloud su scala globale con Kubernetes e Terraform (1/2)

By Matthew PorterSep 19, 202112 min read

Questa pagina è disponibile anche in English, Deutsch, Español, Français, 日本語 e Português.

La sua organizzazione si è già trovata in una situazione come questa?

Uno scienziato ha terminato lo sviluppo di uno strumento o di una pipeline analitica, ma nessuno nel team sa con certezza come portarli in produzione: come semplificare il deployment automatizzato, fare in modo che le risorse di calcolo dietro la pipeline scalino (verso l'alto e verso il basso) con un utilizzo ottimizzato sui costi, garantire la resilienza a guasti hardware/software imprevisti e, al tempo stesso, agevolare il rilascio degli aggiornamenti e la dismissione delle versioni precedenti.

Con così tante domande critiche di natura ingegneristica ancora aperte, come può il suo team scientifico passare dalla logica di test e sviluppo in un ambiente accademico da playground alla progettazione di strumenti scientifici davvero affidabili e pronti per la produzione su larga scala, riducendo al minimo le ore-uomo necessarie a realizzare e mantenere un sistema simile?

Vorrebbe eseguire workloads di questo tipo negli ambienti cloud GCP o AWS, per sfruttare la scala globale, l'elevatissima affidabilità infrastrutturale e il rapporto costi/benefici che queste piattaforme offrono rispetto ai cluster di calcolo on-prem?

Le interesserebbe magari anche una spiegazione dei principi DevOps fondamentali alla base dell'esecuzione di workloads di questo tipo nel cloud?

Userò una demo dettagliata per illustrare come sfruttare un esempio funzionante, sia per Google Cloud Platform (GCP) sia per Amazon Web Services (AWS), che mostra l'esecuzione di un workload reale di calcolo scientifico (bioinformatica) applicando diversi principi DevOps moderni. Senza ulteriori indugi, entriamo nel vivo.

Ad alto livello, il codice di esempio:

  • Mostra come impacchettare strumenti bioinformatici diffusi (FastQC e BWA) in container image e caricarle nel registry delle immagini di ciascun cloud
  • Crea (e consente di smantellare rapidamente) tutta l'infrastruttura cloud necessaria a eseguire i workloads tramite Terraform, uno strumento di Infrastructure as Code
  • Distribuisce una pipeline di workflow basata su queste immagini e sull'infrastruttura cloud creata, sul servizio Kubernetes completamente gestito di ciascun cloud, ovvero un sistema di gestione e orchestrazione dell'esecuzione dei container. Per orchestrare un workflow / pipeline di task su un cluster Kubernetes verrà utilizzato Argo

Utilizzando Docker, Terraform, Kubernetes e Argo, imparerà a:

  • Creare, aggiornare e smantellare l'infrastruttura necessaria a eseguire i suoi workloads con comandi semplici
  • Eseguire workloads analitici end-to-end basati su DAG con retry automatici dei singoli step in caso di errori imprevisti o guasti dell'infrastruttura
  • Centralizzare il logging dei workloads e il monitoraggio delle metriche in strumenti cloud-native dedicati all'esplorazione di log e metriche
  • Eseguire workloads su un'infrastruttura che scala automaticamente verso l'alto (fino a una capacità su scala globale) e verso il basso (fino a poche o nessuna risorsa di calcolo) in base alle esigenze, così da pagare solo per le risorse di CPU/RAM/storage effettivamente necessarie a portare a termine i task. Sono finiti i tempi dei server lasciati accesi a vuoto, che facevano lievitare la bolletta inutilmente
  • Distribuire in modo fluido nuove versioni del software ed eliminare gradualmente quelle precedenti

Il codice che illustra questi principi è disponibile qui, mentre la Parte 2 spiega come utilizzarlo. Se preferisce saltare la panoramica sui principi DevOps e andare dritto al codice, può fermarsi qui con la lettura di questa Parte 1.

Se invece vuole una visita guidata alle tecnologie alla base di questi obiettivi DevOps, resti con me. Le consiglio vivamente di prepararsi un buon caffè forte: le servirà per arrivare in fondo a questo articolo.

Quello che segue è un crash course su DevOps, container, Kubernetes, Terraform e sul perché il loro impiego sia decisivo per l'utilizzo reale del software, in ambito bioinformatico e non solo.

Container: deployment scalabile degli strumenti

Il problema centrale che questo articolo e il relativo codice affrontano è quello del DevOps, ovvero del Development Operations. Dockerizzazione, Kubernetes e Terraform convergono tutti verso lo stesso obiettivo: semplificare il deployment affidabile dei programmi su larga scala. Per comprendere i fondamenti del DevOps moderno serve una conoscenza di base dei container. Partiamo proprio da qui.

La maggior parte dei bioinformatici conosce bene questo problema: vuole utilizzare un programma open-source, probabilmente nato in ambito accademico, con dipendenze ormai obsolete. Installarlo si trasforma in un incubo di gestione dei pacchetti, perché i suoi requisiti di versione per Python, Perl e altre dipendenze entrano in conflitto con le versioni aggiornate già installate localmente.

Probabilmente ha aggirato il problema creando ambienti virtuali con Anaconda o con il comando venv di Python. Purtroppo, però, è un approccio fallace. Prima o poi questo modo di lavorare diventa insostenibile e più oneroso da mantenere di quanto valga la pena. Gli ambienti virtuali possono andare bene in fase di sviluppo e di test, ma su larga scala semplicemente non reggono.

Ho visto molte aziende, piccole e grandi, scoprirlo a proprie spese: hanno insistito troppo a lungo prima di gettare la spugna.

Arriva la containerizzazione

Un container è un processo software isolato che racchiude tutto il codice e tutte le dipendenze necessarie a eseguire l'applicazione rapidamente, con prestazioni vicine a quelle bare-metal, in qualsiasi ambiente di calcolo.

Una container/Docker image è la definizione di come un container debba essere costruito ed eseguito. I container sono i processi che eseguono le istruzioni impacchettate all'interno delle container image.

Una container image viene creata con un semplice Dockerfile che definisce:

  • Il sistema operativo di base su cui si fonda l'immagine (questo non impedisce a un container basato su tale immagine di essere eseguito su altri OS host)
  • I pacchetti da installare
  • I comandi shell da eseguire all'avvio di un container basato sull'immagine

Questo Dockerfile viene poi utilizzato da docker build per creare un'immagine immutabile.

Come si garantiscono ridondanza e scalabilità orizzontale al servizio impacchettato nell'immagine?

Distribuendo istanze eseguibili di una container image attraverso più container in esecuzione su più host, dislocati in più data center distinti, e bilanciando poi il carico tra questi container. Vista la scala globale dei cloud provider, il potenziale di scalabilità orizzontale e la resilienza ai guasti dei container sono di fatto illimitati.

Potrebbe, ad esempio, creare una container image che impacchetti uno strumento come FastQC insieme a tutte le sue dipendenze. I container avviati a partire da questa immagine FastQC possono essere distribuiti praticamente in qualsiasi ambiente di calcolo, a qualsiasi livello di scala, indipendentemente da hardware, software o sistema operativo della macchina host. Una vera manna.

L'esecuzione di questo container è simile all'avvio diretto di FastQC. È semplicissimo, ad esempio, passare file di input a un container FastQC e ottenerne in restituzione i file di output.

Ogni cloud provider mette a disposizione un proprio container registry su cui caricare e da cui scaricare le immagini. Funziona in modo analogo al push e al pull dei branch di un repository git. Per esempio, eseguendo docker pull <image_name> si scarica un'immagine FastQC ospitata nel container repository del proprio cloud. In questo modo l'immagine può essere portata su una varietà di risorse di calcolo nel proprio ambiente cloud, rendendo FastQC immediatamente pronto all'uso con docker run <image_name>, senza ricorrere a metodi arcani per gestire l'installazione delle dipendenze.

L'analogia con i repository git non si esaurisce qui. Anche ai container possono essere applicati tag, come latest o v1.0.1, in modo da tenere traccia di quale container image corrisponda a una specifica versione del codice. docker run <image_name>:<tag_name> permette di scaricare ed eseguire un container basato su una versione precisa dell'immagine.

Container e macchine virtuali (VM) a confronto

Se le sembra che i container assomiglino molto alle macchine virtuali, anch'esse pensate per impacchettare il software e renderlo eseguibile in un'ampia varietà di ambienti di calcolo, si starà chiedendo quali siano le differenze tra le due tecnologie e perché i container siano la scelta privilegiata per il DevOps scalabile.

  • Sia i container sia le VM possono essere eseguiti su un singolo host, ma i container vi riescono senza il pesante impatto sulle prestazioni delle VM, dovuto al fatto di eseguire un proprio sistema operativo oltre al software impacchettato. I container, invece, condividono l'OS host e risultano quindi leggeri: di norma si osserva un degrado delle prestazioni intorno allo 0,5%.

Con una macchina virtuale in esecuzione su un hypervisor moderno, è già fortuna riscontrare un degrado delle prestazioni di appena l'1-3%. Su scale più ampie, queste differenze hanno un impatto significativo e tangibile sui costi.

  • I container sono molto più rapidi da costruire e distribuire. Abilitano un processo di sviluppo Agile che con le VM non è praticabile, dal momento che queste ultime possono richiedere minuti o addirittura ore per essere costruite.
  • I container sono immutabili e definiti da un singolo file, il che rende più affidabile la continuous integration del software. Per esempio, può tornare a una versione precedente e funzionante quando necessario, certo che non possa essere stata modificata dall'ultimo deployment.
  • I container incoraggiano la pratica architetturale ormai consolidata di privilegiare microservizi distribuiti e debolmente accoppiati rispetto alle applicazioni monolitiche. Una scelta che permette deployment più rapidi quando occorre rilasciare una correzione o una nuova funzionalità ed evita che un singolo problema in un'applicazione monolitica trascini con sé anche tutte quelle componenti che non avrebbero dovuto essere strettamente accoppiate.

Idealmente, ogni programma distribuito su larga scala dovrebbe essere realizzato in una propria container image, in modo che il numero di container distribuiti per quel programma possa scalare verso l'alto e verso il basso in autonomia, in funzione della domanda di risorse.

  • I container facilitano notevolmente il logging a livello di OS e di applicazione, oltre al monitoraggio delle metriche, perché condividono le risorse del sistema operativo host e sono progettati pensando proprio alla raccolta dettagliata di log e metriche.

Gestione dell'esecuzione dei container

Supponiamo che abbia containerizzato un programma come FastQC, marcandolo con tag adeguati come latest e v0.11.9. Cosa farne, non solo per abilitare ma per semplificare drasticamente l'esecuzione fault-tolerant e scalabile di FastQC?

Arriva Kubernetes

Kubernetes (spesso abbreviato in K8s) è una piattaforma open-source creata da Google nel 2014 come job scheduler e sistema di cluster management open-source, pensata per favorire l'adozione, da parte della community, della gestione automatizzata dei workloads containerizzati. Ha riscosso enorme popolarità grazie alla relativa facilità con cui consente operazioni su scala globale.

Kubernetes è oggi il secondo repository GitHub più popolare per numero di autori e issue, secondo solo al kernel Linux.

La documentazione K8s offre una spiegazione eccellente, ma piuttosto lunga, del perché Kubernetes sia così utile. In sintesi, K8s abilita:

  • Bin packing automatico: fornisce a Kubernetes un cluster di nodi (server cloud) che la piattaforma può utilizzare per eseguire i task containerizzati. Indica a Kubernetes quanta CPU, memoria e storage richiede ciascun container e Kubernetes è in grado di disporre i container sui nodi in modo da ottimizzarne l'utilizzo delle risorse.
  • Self-healing: Kubernetes riavvia i container in errore (per problemi software o guasti hardware), li sostituisce, termina quelli che non rispondono all'health check definito dall'utente e non li espone ai client finché non sono pronti a fornire il servizio.
  • Service discovery e load balancing: Kubernetes può esporre un container tramite un nome DNS o tramite il suo indirizzo IP. Se il carico di traffico è elevato, Kubernetes può fare load balancing e distribuire il traffico di rete tra più istanze dello stesso container, in modo che il deployment resti stabile anche su larga scala.
  • Rollout e rollback automatizzati: può descrivere lo stato desiderato dei container distribuiti utilizzando Kubernetes, che si occuperà di portare lo stato attuale verso quello desiderato a un ritmo controllato. Per esempio, può automatizzare Kubernetes affinché crei nuovi container per il suo deployment (es. container del software taggati 'v2'), rimuova quelli esistenti (es. taggati 'v1') e trasferisca al nuovo container tutte le loro risorse di calcolo. Questa migrazione verso un container più recente può avvenire tutta in una volta o secondo regole personalizzate, ad esempio sostituendo il 5% dei container esistenti ogni cinque minuti. Se durante il rollout osserva un aumento del tasso di errori, riportare indietro il deployment richiede l'esecuzione di un singolo comando.

Kubernetes completamente gestito

Sarà lieto di sapere che oggi Kubernetes è offerto come servizio completamente gestito dai principali cloud provider. Tra questi:

  • GKE (Google Kubernetes Engine) su Google Cloud
  • EKS (Elastic Kubernetes Service) su Amazon Web Services

Queste offerte semplificano l'installazione di Kubernetes e il provisioning dell'hardware, astraendo la creazione del cluster Kubernetes. Le permettono di concentrarsi sull'avvio di workloads scalabili su un cluster K8s che richiede solo pochi minuti e qualche click in console per essere creato.

Sia GKE sia EKS prevedono una macchina di cluster control plane serverless. Funge da master node, per così dire, con più macchine worker chiamate nodi. I container vengono inviati al control plane per l'esecuzione e quest'ultimo ne pianifica l'avvio sui nodi. Quando vengono eseguiti sui nodi, questi task prendono il nome di pod.

Riepilogando: i pod (in genere costituiti da un singolo container) vengono eseguiti sui nodi e il numero di pod scala verso l'alto e verso il basso sotto il controllo della macchina del control plane. Le offerte K8s completamente gestite automatizzano anche la configurazione dell'auto-scaling dei nodi (su GKE è integrato; su AWS, EKS crea template di EC2 Auto Scaling). Con GKE ed EKS si ottiene quindi sia l'auto-scaling dei pod sia quello dei nodi con il minimo sforzo.

Quando configura un cluster K8s, è opportuno definire anche dei node group opzionali, ovvero la famiglia di risorse hardware da utilizzare nel cluster. I node group vengono spesso specificati quando si inviano workloads di calcolo scientifico.

Per esempio, potrebbe creare un node group 'high cpu' che utilizza famiglie di macchine ricche di CPU e ottimizzate sui costi della CPU, come la famiglia c2 su GCP o la c5 su AWS, e assegnargli i workloads CPU-intensive. Potrebbe avere poi un node group 'GPU' separato, basato sulla famiglia a2-highgpu su GCP o p3 su AWS, a cui inviare i workloads che sfruttano le GPU. Lavorando in questo modo, tipi di macchina distinti possono scalare verso l'alto e verso il basso in autonomia, seguendo le esigenze dei workloads che devono essere eseguiti su quei tipi di macchina.

Tuttavia, se utilizza la modalità Autopilot di GKE invece della Standard, non dovrà specificare alcun node group per il cluster. Il provisioning delle risorse hardware viene astratto: GKE Autopilot guida il settore DevOps spostando l'ecosistema K8s verso un approccio più serverless al calcolo su scala globale. Con GKE Autopilot le basta inviare il job K8s, al cui interno sono definiti i requisiti di CPU/RAM/storage del container, e GKE scalerà le risorse di calcolo verso l'alto e verso il basso dietro le quinte, secondo necessità, per eseguire quel task in base alle sue specifiche. ECS Fargate è l'equivalente AWS di GKE Autopilot.

Infrastruttura riproducibile e automatizzata

A legare insieme containerizzazione ed esecuzione robusta dei container su Kubernetes c'è Terraform, uno strumento open-source di infrastructure as code che consente di definire, con testo YAML facile da leggere, lo stato desiderato della propria infrastruttura cloud. Con un singolo comando può creare, aggiornare o smantellare l'intera infrastruttura cloud.

Se i container rendono le versioni del software immutabili, facili da riprodurre e da scalare, Terraform rende l'infrastruttura cloud su cui quei container girano semplice da replicare, aggiornare o eliminare e, al tempo stesso, pienamente versionabile.

Spostando il software nei container, il sistema di gestione dei workloads su Kubernetes e codificando l'infrastruttura cloud con Terraform, getterà le basi per un'azienda capace di scalare dalla modalità stealth fino alle operazioni globali e, soprattutto, di riprendersi rapidamente dalle cause più comuni di guasto, come release problematiche e problemi hardware, con pochissime modifiche di fondo nel proprio ecosistema DevOps.

Mettere in pratica le nuove competenze

Grazie per il suo tempo: spero davvero che questo articolo l'abbia aiutata a comprendere meglio il DevOps. Se vuole iniziare a mettere in pratica le competenze di cui abbiamo parlato seguendo una demo funzionante, prosegua con l'ultima parte di questa serie in due episodi.

Grazie per la lettura! Per restare in contatto, ci segua sul DoiT Engineering Blog , sul canale LinkedIn di DoiT e sul canale Twitter di DoiT . Per scoprire le opportunità di carriera, visiti https://careers.doit-intl.com .