In questa pagina sono raggruppati tutti gli articoli riguardo all'argomento AWS. Non si tratta di un manuale o un corso dove sono descritti i servizi e il loro funzionamento ma si tratta solo di un gruppo di esempi di codice o mini-guide per gestire le risorse nel cloud. La documentazione ufficiale è disponibile al sito docs.aws.amazon.com, è consigliato tenere sempre a portata di click la pagina per visionare le pagine ufficiali con particolare attenzione alle sezioni che descrivono i servizi e i relativi costi di uso.

AWS

 

Per poter leggere e seguire questi articoli è necessario aver presenti alcuni concetti base:

  • all'interno di un cloud AWS tutto ha un costo calcolato in base all'utilizzo, bisogna sempre prestare attenzione ai costi nelle pagine dei vari servizi per evitare brutte sorprese e addebiti indesierati
  • chi gestisce e amministra un cloud AWS deve costantemente prestare attenzione alla sicurezza e bisogna sempre usare i servizi seguendo le linee guida per evitare buchi di sicurezza e brutte sorprese
  • ogni risorsa possiede un ARN (Amazon Resource Name) che identifica in maniera univoca la risorsa, concetto molto utile quando bisogna fare iteragire più servizi tra loro
  • tutti gli esempi e i servizi presentati in questo sito sono stati provati per quanto possibile nella regione irlandese ma possono essere usati in tutte le region disponibili nel cloud AWS che prevedono i servizi usati, non è stata scelta la region italiana in quanto non tutti i servizi sono disponibili e i costi in Irlanda sono a volte minori

In molti di questi articoli vengono fatti riferimenti a codice sorgente scritto in Java o in Python, vengono usati spesso comandi Linux e spesso sono usati file in formato Json e Yaml, inevitabilmente questi concetti devono essere conosciuti per poter comprendere al meglio l'uso dei servizi AWS e in particolar modo CloudFormation usato in quasi tutti gli articoli e il AWS SDK specifico per Java.

Per accedere alla console amministrativa via web, viene fornita un link di accesso che tipicamente è del tipo:

https://999999999999.signin.aws.amazon.com/console

dove il numero iniziale indica il "numero" di account aws. Il primo accesso deve essere effettuato tramite la mail di registrazione che viene chiamata "root-user", questa utenza è abilitata a fare qualunque attività ed operazione nel Cloud, per ragioni di sicurezza e di buona gestione è sconsigliato usare questa utenza per le operazioni quotidiane di sviluppo e monitoraggio, nei prossimi articoli verrà spiegato come blindare l'utenza "root" e come attivare le utenze con un livello normale di operatività.

All'accesso, la console web compare in questo aspetto:

La barra in alto alla console è sempre visibile e presenta questi link rapidi:

  1. informazioni sull'account e possibilità di accedere ad alcune opzioni del profilo
  2. selettore della regione attiva (region)
  3. selettore di servizi
  4. barra di ricerca dei servizi
  5. link per accedere velocemente a AWS CloudShell

Nella pagina di ingresso della console sono presenti i widget personalizzabili, la videata è presa dalla guida ufficiale dove sono disponibili tutti i dettagli riguardo ai componenti della console web.

AWS free tierIl piano gratuito di AWS (free tier in lingua inglese) è un programma che offre ai nuovi utenti la possibilità di provare servizi di AWS senza dover sostenere alcun costo. Il piano comprende alcuni servizi entro determinati limiti temporali e dimensioni di storage, questi limiti sono dovuto alla volontà del gestore di permettere agli utenti neofiti di eseguire prove senza costi ed evitare che il piano gratuito possa essere usato a sproposito dagli utenti esperti. Bisogna ricordare che il piano gratuito ha la durata di un anno dalla data di attivazione dell'account, al termine del quale tutti i servizi saranno a pagamento secondo le tariffe standard previste quindi bisogna prestare attenzione a come gestire il proprio account.

I principali servizi previsti dal free tier e i rispettivi limiti di utilizzi sono:

  • Storage S3: 5 Gb di spazio di archiviazione sommando tutti i bucket di un account
  • Compute Ec2: 750 ore mensili di esecuzione di istanze di tipo "t2.micro", queste ore sono calcolate per poter eseguire 24  ore per 31 giorni al mese una istanza ma è possibile eseguire più istanze in parallelo (per esempio 12 ore con due istanze per 31 giorni)
  • Database RDS: 750 ore mensili di esecuzione di istanze di tipo "db.t2.micro" con uno storage massimo di 20 Gb, anche in questo caso le ore di esecuzione bastano per avere in piccolo database attivo per tutto un mese ma può essere anche diviso in altre maniere, lo storage è calcolato sommando tutti i componenti di un database, è possibile scegliere un DBSM di tipo MySQL, PostgreSQL, MariaDB o SQL Server.
  • Database Dynamo: 25 Gb di storage per il database not-SQL nativo di AWS
  • Esecuzioni di funzioni Lambda: 1 milione di esecuzioni al mese
  • Esecuzioni di step function: 4.000 cambi di stato al mese
  • Sistema di notifiche SNS : 1 milione di notifiche inviate al mese
  • Monitoraggio CloudWatch: 10 allarmi massimi impostati attivi nello stesso momento
  • Chiamate API-gateway: 1 milione di chiamate mensili indipendentemente dal back-end richiamato
  • LightSail: 750 ore mensili di attivazione di una istanza, anche in questo caso è calcolato in base all'esecuzione per un mese.
  • Storage condiviso EFS/FSx: 5 Gb di storage totale sommando tutti i dischi di un account

l'elenco completo di tutti i servizi e limiti di utilizzo è disponibile nella pagina ufficiale che è sempre bene tenere sotto mano se si vuole usare il piano gratuito in maniera intelligente senza avere brutte sorprese e addebiti indesiderati.

Per iscriverti al piano gratuito di AWS, è necessario creare un account AWS dal sito ufficiale e seguire i passi della procedura guidata presentata, la guida alla procedura spiega dettagliatamente i passi da seguire che possono essere riassunti:

  • indicazione della mail dell'utente di registrazione con successiva verifica dell'indirizzo
  • scelta della password dell'utente di registrazione
  • configurazione del metodo di pagamento obbligatorio, previsto anche in caso di utilizzo del piano gratuito infatti bisogna sempre ricordare che superare i limiti previsti dal piano gratuito causeranno l'addebito di tutti i costi con il metodo di pagamento indicato nell'account
  • inserimento del numero di cellulare necessario per la verifica a più fattori della prima login

Una volta effettuata la registrazione il piano gratuito si attiva in pochi minuti e sarà possibile iniziare ad usare tutti i servizi. Il piano gratuito include una varietà di servizi, ma non tutti sono uguali: alcuni servizi sono più costosi di altri, quindi è importante scegliere i servizi in base ai limiti gratuiti o quelli con il massimo rapporto qualità-prezzo. In successivi articoli sarà descritto come monitorare i costi, i servizi gratuiti e l'impostazione di budget specifici con l'invio di alert per evitare il superamento di certe soglie prefissate.

Per eseguire la prima login viene fornito via mail un link di accesso del tipo:

https://999999999999.signin.aws.amazon.com/console

dove il numero iniziale indica il "numero" di account aws. Il primo accesso deve essere effettuato tramite la mail di registrazione che viene chiamata "root-user", questa utenza è abilitata a fare qualunque attività ed operazione nel Cloud, per ragioni di sicurezza e di buona gestione è sconsigliato usare questa utenza per le operazioni quotidiane di sviluppo e monitoraggio, nei prossimi articoli verrà spiegato come blindare l'utenza "root" e come attivare le utenze con un livello normale di operatività.

Il piano gratuito di AWS è un ottimo modo per iniziare a utilizzare i servizi AWS senza spendere nulla o quasi, scegliendo i servizi giusti e impostando limiti di monitoraggio.

E' possibile chiudere un account AWS con una procedura molto semplice, la chiusura è consigliata se un profilo non viene usato, visto che alcuni servizi sono a pagamento e si rischia di ricevere addebiti indesiderati. Per chiudere l'account basta aprire la console con l'account root e aprire la pagina dell'account nel link disponibile nel menù in alto a destra. Nella pagina aperta compaiono tutte le informazioni dettagliate e alla fine della pagina è presente la sezione per richiedere la chiusura. Si consiglia di leggere le condizioni prima di selezionare i checkbox perché alcune informazioni possono essere utili per evitare addebiti non previsti. Se si chiude l'account per sbaglio oppure se si cambia idea dopo la chiusura, l'account rimane sospeso per 90 giorni prima della cancellazione definitiva, in quel periodo è possibile richiedere la ri-apertura tramite una specifica funzionalità del supporto tecnico di AWS. Dopo aver chiuso un account, non è più possibile utilizzarlo per accedere ai Servizi AWS ma nel periodo post-chiusura è possibile accedere alle informazioni riguardo alla fatturazione dei periodi precedenti alla chiusura ed è possibile accedere al supporto tecnico. Se l'utente root aveva abilitato l'autenticazione a più fattori (MFA) per acceso, conviene mantenere il dispositivo MFA attivo fino alla scadenza del periodo successivo alla chiusura di 90 giorni. La chiusura viene confermata tramite una mail all'indirizzo dell'account root. La guida completa di questa procedura è disponibile nel sito ufficiale di AWS anche se ovviamente è nascosta e quasi introvabile.

AWSIdentity and Access Management (spesso abbreviato con IAM) è uno dei servizi più importanti di tutto il Cloud AWS e deve essere sempre a portata di mano nell'uso e nella gestione di un servizio cloud complesso come quello di AWS. La regola principale del cloud è che, di default, tutto è bloccato e non è possibile accedere da una risorsa ad un altro, rispecchiando il paradigma dei "privilegi minimi", cioè assegnare autorizzazioni specifiche a ciascun utente o gruppo, assicurando che abbiano accesso solo alle risorse di cui hanno veramente bisogno. Questo per ridurre al minimo il rischio di accesso non autorizzato o uso improprio accidentale di dati sensibili.

Il servizio IAM consente di creare e gestire più utenti all'interno del tuo account AWS, funzionalità indispensabile per le organizzazioni con un numero elevato di persone che devono accedere allo stesso Cloud, con questo servizio si può gestire l'autenticazione e le autorizzazioni specifiche per ogni utente per ogni servizio, tutto da una dashboard centralizzata. Un altro vantaggio significativo di IAM è la sua capacità di fornire registri di controllo dettagliati e capacità di monitoraggio. Puoi monitorare l'attività degli utenti, visualizzare le chiamate API e persino impostare avvisi per comportamenti sospetti.

IAM supporta anche l'autenticazione a più fattori (MFA), un ulteriore livello di sicurezza che rafforza i controlli di accesso, è possibile richiedere agli utenti di fornire una seconda forma di autenticazione, come un codice univoco generato da un'app mobile, riducendo in modo significativo il rischio di accesso non autorizzato. E' consigliato, quasi obbligatorio, attivare l'autenticazione MFA all'utente root. L'attivazione si esegue manualmente dalla console web di AWS, cliccando sulla voce di menù "Security credentials" e poi assegnando un dispositivo con una applicazione compatibile. Si consiglia la lettura della mini-guida completa su come attivare la strong-autentication che è disponibile nel sito ufficiale.

Tramite IAM è possibile creare account e gruppo di account, è fortemente sconsigliato usare l'utenza principale root nella normale operatività di tutti i giorni ma è vivamente consigliato la creazione e l'uso di utenze personali senza i "poteri" di super-administrator. Un'altra funzionalità molto importante di IAM è la possibilità di definire le regole di accesso (dette policies), questo strumento serve ai gestori del Cloud per definire le regole di "comunicazione" tra due o più risorse in quanto, per default, tutto è bloccato.

Le regole (dette policies) vengono rappresentate in formato Json e devono essere della struttura:

{
"Version": "2012-10-17",
"Statement": [{
"Sid": "Sid1",
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances"
],
"Resource": "arn:aws:ec2:*:*:instance/i-123456789"
},{
"Sid": "Sid2",
"Effect": "Allow",
"Action": [
"cloudwatch:ListMetrics",
"cloudwatch:GetMetricStatistics",
"cloudwatch:Describe*"
],
"Resource": "*"
}]
}

In questo semplice esempio sono state definite due politiche "permissive" a due diversi servizi (ec2 e cloudwatch) con le corrispettive azioni per servizio, da prestare particolare attenzione che le regole possono essere limitate a specifiche risorse, come nel caso del servizio EC2 dove è indicata la istanza rappresentata con il rispettivo codice ARN, oppure è possibile indicare asterisco per non limitare la regola ad una singola risorsa, questa tecnica viene spesso usata ma è fortemente sconsigliata.

Da ricordare sempre che una regola/policy può essere agganciata ad un singolo utente, un gruppo e/o ad una risorsa, per esempio la regola di esempio può può essere assegnata ad un utente per permettergli di gestire una istanza EC2 ma può essere assegnata anche ad una funzione lambda per assegnare gli stessi poteri.

Grazie al servizio IAM e alla somma di tutte le sue funzionalità è possibile costruire una infrastruttura sicura, nei prossimi articoli sarà indicato ogni volta sarà necessario creare regole/policies specifiche, si può fare riferimento alla documentazione ufficiale per maggiori approfondimenti e dettagli. Usare questo servizio è fondamentale anche per il rispetto della "responsabilità condivisa" compreso nelle indicazioni del contratto tra il customer e AWS al momento dell'apertura di un account, secondo questo modello

  • è responsabilità di AWS la "sicurezza del cloud": AWS è responsabile della protezione dell'infrastruttura in cui vengono eseguiti tutti i servizi offerti nel cloud AWS. Questa infrastruttura è composta da hardware, software, reti e strutture che eseguono i servizi Cloud AWS.
  • è responsabilità del cliente (customer) la "sicurezza nel Cloud": la responsabilità del cliente è determinata dai servizi Cloud AWS selezionati dal cliente. Ciò determina la quantità di lavoro di configurazione che il cliente deve eseguire nell'ambito delle proprie responsabilità di sicurezza.

Per maggiori informazioni si rimanda alla documentazione ufficiale "AWS Well-Architected" e si rimanda alla documentazione ufficiale del servizio IAM.

La Command Line Interface (abbreviato con CLI) è un tool che consente di interagire con i servizi AWS da riga di comando in maniera veloce e diretta, senza l'uso della console e senza dover scrivere codice con un linguaggio specifico. Per poterla lanciare i comandi della CLI è possibile usare la Shell di GNU Linux, la riga di comando di windows, da remoto via ssh su istanze EC2 oppure è possibile usare la CLI tramite il servizio AWS CloudShell e il bottone presente nella barra principale della console web.

In questo sito si fa sempre riferimento alla versione 2, per chi ha usato in passato la versione 1 deve aggiornarsi perchè le due versioni non sono compatibili. L'installazione della CLI in qualsiasi sistema è molto semplice e si può fare sempre riferimento alla documentazione ufficiale dove sono descritti i passi per l'installazione e l'aggiornamento nei più comuni sistemi operativi, per ora non è previsto una versione nativa per smartphone.

Per poter usare la CLI è necessario usare uno dei sistemi di autenticazione abilitati dal sistema IAM, il modo più semplice è quello descritto nella guida ufficiale, questa tecnica preve di creare o usare un utente specifico creando le chiavi di accesso nel dettaglio di un utente e selezionado "Crea chiavi di accesso" nel menù "Credenziali di sicurezza", con questa semplice procedura viene generata una coppia di chiavi (KeyId e AccessKey) che poi deve essere salvata al momento della creazione, successivamente non sarà più possibile recuperare la coppia in nessun modo.

Per configurare la cli bisogna lanciare il comando

$ aws configure

e in questa procedura guidata nella CLI vengono chieste quattro informazioni:

AWS Access Key ID [None]: xxxx
AWS Secret Access Key [None]: xxxx
Default region name [None]: us-west-2
Default output format [None]: json

le prime due sono le credenziali ritornate dal servizio IAM, la terza rappresenta la region di default usata dal profilo, è comunque possibile usare altre region aggiungendo un parametro al comando eseguito, il quarto valore richiesto è il formato dell'output dei comandi. Come alternativa al comando di configurazione manuale, è possibile usare un file csv scaricabile dalla console e caricare le informazioni con il comando:

aws configure import --csv file://credentials.csv

Una volta creato il profilo è possibile verificare il file di configurazione presente nella cartella di sistema:

~/.aws/credentials

oppure: 

%USERPROFILE%\.aws\credentials

a seconda del sistema operativo usato (rispettivamente GNU Linux o Ms Windows). Se si dispone di più account è possibile impostare diversi profili configurando un default e poi gli altri profili con un nome personalizzato:

$ aws configure --profile <nomeProfilo>

Per vedere l'elenco dei profili è possibile lanciare il comando:

$ aws configure list-profiles

Quasi tutti i comandi della CLI sono del tipo:

$ aws <servizio> <comando> <parametri> --profile <nomeProfilo>

il profilo è un parametro facoltativo, se non indicato viene usato il profilo di default configurato. Per semplice esempio per visualizzare l'elenco di tutti i bucket in un account:

$ aws s3 ls

L'elenco completo di tutti i servizi e i comandi disponibili è consultabile con il comando:

$ aws help

oppure è sempre possibile controllare la documentazione ufficiale. In tutti i futuri articoli verranno descritti i comandi se il servizio descritto prevederà operazioni di amministrazione con la CLI.

La fatturazione e la gestione degli addebiti di AWS viene gestita dalla console web tramite il servizio AWS Billing dove si possono scaricare le fatture, i dettagli dei costi e impostare i sistemi di pagamento. Quando si utilizza e amministrano sistemi Cloud bisogna sempre ricordare che ogni risorsa ha un costo, anche se molto basso, questo è calcolato spesso in base all'uso in testo e in dimensioni, per esempio le istenze EC2 sono calcolate in base al numero di ore di utilizzo e al numero di CPU mentre i costi dei servizi di storage tipicamente sono calcolati in base alla dimensione in Gb dello storage.

Il servizio di fatturazione (billing) e tutti i suoi sotto-servizi sono limitati all'account root ma è possibile abilitare l'accesso anche agli altri utenti dalla dashboard del servizio billing dell'acount root nella sezione "IAM user and rule access to billing information", da questa sezione è possibile attivare o disattivare l'accesso degli utenti non root alle informazione delle fatturazione, cost explorer e i budget. Questo è un tema molto delicato e bisogna prestare sempre molta attenzione, AWS ha dedicato una pagina specifica che tratta di questo argomento. 

In questo servizio è possibile anche analizzare i pagamenti passati e impostare ordini e monitorare l'utilizzo del "free tier" e monitorare la situazione per cercare di evitare di superare i limiti del piano gratuito e avere addebiti non previsti.


Il servizio principale di monitoraggio dei costi è Cost Explorer che include report dinamici per visualizzare i costi e l'uso dei servizi con un'ananlisi dettaglia di tutti i servizi una semplice e comoda tabella. I report permettono di regolare l'intervallo di tempo fino a 12 mesi così da poter verere anche una tendenza dei costi. Inoltre è possibile creare report specifici per alcuni servizi così da monitorare i tipi di risorse usati in un servizio, per esempio per il servizio EC2 è possibile creare un report diviso per ogni tipo di istanza come si può vedere da questa immagine di esempio:

E' possibile anche aumentare i dettagli dei report aggiungendo informazioni orarie e usare i "tags" per creare report che visualizzano e analizzano di dati dei costi solo delle risorse taggate con specifici tag, ovviamente il report analizza i costi delle risorse dopo l'inserimento dei tag; fornisce anche report per analizzare gli acquisti e le opportunità di risparmio con il famoso "Savings Plans".

Il servizio AWS Cost Explorer ti consente di accedere direttamente al motore di query interattivo che alimenta AWS Cost Explorer. Ogni richiesta comporterà un costo di 0,01 dollari, anche in questo caso si deve sempre prestare attenzione a come si usa questo servizio. La documentazione ufficiale del servizio è molto ricca di informazioni ed è disponibile alla pagina ufficiale.

Di default solo l'amministratore root può accedere a questo servizio ma, come indicato dal servizio stesso, è possibile fornire a specifici utenti l'autorizzazione creando policy IAM, per maggiori informazione è disponibile una pagina dedicata alle politiche IAM per i servizi di fatturazione.

La policy generica per abilitare un utente al servizio di Cost Explorer (ce) è del tipo:

{
"Version": "2012-10-17",
"Statement": [{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "ce:*",
"Resource": "*"
}]
}

Un altro sotto-servizio molto utile è AWS Budget, si trova sempre all'interno della sezione billing della console web di AWS, grazie a questo è possibile impostare degli alert in base a delle soglie limite in dollari, al superamento di questo limite viene inviata una mail ad un gruppo di destinatari prefissato. Visto che la fatturazione è sempre mensile anche i Budget fanno sempre riferimento ai costi mensili ma è possibile creare report con periodi di riferimento diversi dalla frequenza mensile.

Un semplice esempio è impostare un budget di un dollaro mensile, è possibile essere avvisati quando si supera la soglia ed è possibile avere uno storico di quando questo budget è stato superato, nel mio esempio nel febbraio 2023 la soglia è stata superata ed è arrivata la mail, negli altri mesi il budget non è mai stato superato, per fortuna.

La creazione di un budget è molto semplice e richiede solo un nome simbolico, l'importo soglia in dollari e l'elenco degli indirizzi mail ai quali AWS deve inviare le notifiche. Da notare che, per default, un primo alert viene inviato al 85% della soglia di spesa, un secondo alert viene inviato al arrivo della soglia e un terzo alert viene inviato quando la soglia viene superata.

Anche in questo caso il costo del servizio è di 0.01 dollari per ogni budget impostato ma i primi due budget di un account sono gratuiti, mentre il servizio di "Budgets reports" ha una tariffa di 0.01 dollari per ogni report creato. Maggior informazioni sono disponibili nella pagina ufficiale.

AWS mette a disposizione tantissimi percorsi per imparare ad usare i servizi e rimanere aggiornati visto che il mondo cloud è in continua evoluzione. La principale risorsa è il sito pubblico ufficiale docs.aws.amazon.com che spesso viene citato in questo sito come riferimento attendibile, è consigliato usare sempre la versione in inglese in quanto la versione in Italiano a volte non è accurata e spesso tradotta in maniera automatica. I percorsi formativi ufficiali rilasciano dei badge ufficiali verificati tramite il servizio credly.com.

CloudQuest è un "gioco" on-line disponibile su sito ufficiale, permette di eseguire alcuni task via via sempre più complessi con suggerimenti e consigli passo dopo passo. Il programma formativo contiene diversi ruoli per aiutare gli utenti e le organizzazioni a sviluppare le competenze ricercate studiando casi specifici per i vari ruoti ruolo e raggiungere obiettivi di apprendimento. Con questo programma è possibile ottenere alcuni badge, il più semplice "Cloud Practitioner" è gratuito mentre per ottenere i successivi è necessario sottoscrivere un abbonamento a pagamento, questi badge non sono molto famosi ma sono comunque ufficiali e sono consigliati ad utenti poco esperti per fare esperienza pratica. 

I percorsi formativi con corsi e aule studio nel senso classico del termine è Skill-Builder, questi sono raggiungibili nel sito ufficiale e nel sito specifico AWS-skillbuilder dove sono disponibili guide specifiche per vari sotto-servizi ma anche percorsi formativi a 360 gradi su tutto il cloud di AWS. L'obbiettivo di questi piani formativi è mettere a disposizione corsi per gli utenti e i customer, alcuni di questi prevedono un esame online per la verifica delle competenze e vengono rilasciati anche alcuni badge ufficiali chiamati "Knoledge bedge". 

Le certificazioni ufficiali permettono ad una persona di convalidare le competenze tecniche tramite degli esami ufficiali che, se superati, rilasciano un badge prestigioso riconosciuto a livello mondiale. Gli esami si possono effettuare da alcuni centri "test center" oppure da casa tramite la piattaforma "pearsonVue" che prevede delle regole stringenti, per maggiori informazioni riguardo alla modalità di esame si può visitare il sito ufficiale. Gli esami sono dei test online a risposta multipla con 65 o più domande in lingua inglese (o in altre lingue se richiesto), le domande sono casuali scelte diverse migliaia di domande possibili con una risposta corretta e 4 o 5 risposte errate casuali. Esistono tre livelli di certificazione ufficiali AWS:

  • practitioner: è la certificazione base che tutti devono avere per poter sostenere gli esami successivi, riguarda tutti i servizi di AWS ad alto livello e la maggior parte di domande riguarda la teoria e i paradigmi del Cloud System di AWS.
  • associate: si tratta del livello intermedio con una verifica specifica delle conoscenze teoriche ma anche pratiche di determinati servizi di tre categorie: developer (sviluppo), architect (architettura), sysOps (operazioni sistemistiche).
  • professional & specialty: si tratta del livello più specifico con le categorie: solution architect, devops, data analytics, database, machine laerning, security e il prodotto SAP.

Dopo aver sostenuto l'esame, in pochi giorni giorni, si riceve una mail di comunicazione del risultato dell'esame che può essere passato (pass) o non passato. Se passato viene rilasciato un badge digitale verificato che viene caricato in automatico nel servizio credly.com dove chiunque può verificare che l'esame è stato sostenuto e la data di scadenza visto che i certificati AWS durano 3 anni! Oltre alla soddisfazione di avere un badge di questo tipo, AWS mette a disposizione alcune benefici per chi ha sostenuto almeno un esame, l'elenco completo può essere trovato al sito ufficiale. Per maggiori dettagli tutte le informazioni si può visionare il sito ufficiale nella sezione faqs.

CloudFormation è un servizio che permette di creare e gestire risorse AWS, si tratta del principale servizio AWS di tipo Infrastructure as Code (IaC) di AWS, questo paradigma prevede che i componenti nel Cloud siano create da codice programmato, nello specifico questo servizio prevede un file che definisce una lista di risorse AWS che vengono create, gestite dal servizio CloudFormation in maniera programmatica.

Il servizio prevede due concetti chiave che devono essere ben compresi: 

  • template: il file che elenca un gruppo di risorse con specificate tutte le caratteristiche per ogni risorsa, sono previsti due possibili formati: Json o Yaml, per poter usare questo servizio è necessario conoscere almane uno dei due formati, esistono dei tool grafici per la creazione di questi template e il convertitore da un formato all'altro
  • stack: è la realizzazione di un template con tutte le risorse create a partire da un template. CloudFormation garantisce che tutte le risorse all'interno di uno stack vengano create, modificate ed eliminate come una unica "unità". Se non è possibile creare una risorsa, AWS CloudFormation esegue il rollback dell'intero stack ed elimina automaticamente tutte le altre risorse create. Se una risorsa non può essere eliminata, tutte le risorse rimanenti vengono mantenute fino a quando lo stack non può essere eliminato correttamente

Per usare CloudFormation non bisogna distinguere i due concetti, paragonandoli ai concetti culinari di stampo e biscotto: il file template è lo stampo mentre gli stack sono i biscotti, ogni biscotto è creato da uno stampo e ci possono essere più biscotti creati dallo stesso stapo, in questo caso ogni stack ha un nome che lo identifica e le risorse create saranno cloni nati dallo stesso template. Maggiori dettagli su questi concessi si possono trovare nella pagina ufficiale. I template prevedono la presenza di queste sezioni:

  • Intestazione: versione di CloudFormation e descrizione del template, questa sezione è sempre fissa ed unica per ogni template.
  • Parameters: elenco dei parametri del template, per ogni parametro è necessario impostare il nome e il tipo (come String). Per ogni parametro è possibile impostare un valore di default, un elenco di possibili valori e alcune regole specifiche (come MinLen e Constraint)
  • Resources: elenco delle risorse, ogni risorsa deve avere un nome univoco, il tipo e l'elenco dei parametri, alcuni obbligatori altri facoltativi a seconda delle regole di ogni tipo di risorsa
  • Outputs: elenco dei valori che lo stack deve ritornare e visualizzare in Console o nella Cli al momento di creazione o aggiornamento

Uno degli esempi di template più semplici su AWS è descrivere la creazione di un storage S3 senza nessuna configurazione particolare, il template CloudFormation è:

#Intestazione con la descrizione
AWSTemplateFormatVersion: 2010-09-09
Description: CloudFormation di esempio s3
#blocco Parametri
Parameters:
NomeBucket:
  Type: String
  Default: esempio01buckets3
  Description: Nome del bucket
  MinLength: 9
#blocco Risorse
Resources:
S3Bucket:
  Type: 'AWS::S3::Bucket'
  Properties:
    BucketName: !Ref NomeBucket
#blocco Outputs
Outputs:
S3Bucket:
  Value: !GetAtt S3Bucket.Arn
  Description: S3 bucket ARN

Per caricare un template e creare uno stack ci sono due possibilità: Console web o CLI-SAM. Nella console web, nella pagina principale del servizio c'è la lista di tutti gli stack con un dettaglio di tutte le risorse appartamenti al gruppo e il template originario, è presente anche il bottone "create stack", questa procedura guidata prevede quattro passi:

  • selezionare se il template è già pronto (template is ready) oppure la creazione tramite designer web (sconsigliato in quanto non è molto efficiente come metodo)
  • selezionare un file dal proprio pc oppure un file già depositato in un bucket S3, nel primo caso l'upload del file verso S3 viene eseguito dalla console
  • inserimento dei parametri dello stack come il nome univoco e inserire i valori di tutti i parametri previsti dal template, la pagina web visualizza anche il valore di default se previsto o la lista di valori possibili
  • inserimento dei parametri avanzati come tag, la regola IAM, le impostazioni di rollback ed altre impostazioni che possono essere ignorate da un utente poco esperto

All'avvio del processo di creazione, lo stack compare nello stato "CREATE_IN_PROGRESS", se la creazione genera degli errori lo stato diventa "ROLLBACK_COMPLETE" ed è possibile controllare i log nel dettaglio. Se lo stack viene creato con successo viene visualizzato con lo stato "CREATE_COMPLETE", i parametri di Output di un template possono essere visualizzati nel dettaglio dello stack stesso, tramite console è possibile anche aggiornare uno stack ricaricando un file template. La rimozione di uno Stack può essere eseguita da console con l'accorgimento che, di default, tutte le risorse descritte nel template e create in AWS vengono eliminate quando viene rimosso uno stack, è possibile modificare questo comportamento impostando che le risorse rimangano quando lo stack viene rimosso con la proprietà:

DeletionPolicy: Retain

che deve essere inserita nel blocco Resources del template.

Come si può notare dal semplice esempio, nei template è possibile usare alcuni metodi come Ref e GetAtt per far riferimento a parametri e ad risorse, questa tecnica è molto usata nei template complessi dove ci sono molte risorse legate tra loro. La AWS-CLI mette a disposizione una serie di comandi per la gestione di template e stack, l'elenco completo può essere trovato nel sito ufficiale, il più interessante è il comando per recuperare la lista di tutti gli stack con i relativi stati e le informazioni basiche, la sintassi è:

aws cloudformation list-stacks

ed è possibile recuperare il dettaglio di ogni stack con i comandi

aws cloudformation describe-stacks --stack-name CIT
aws cloudformation describe-stack-events --stack-name CIT --max-items 2
aws cloudformation describe-stack-resources --stack-name CIT

i comandi visualizzano rispettivamente la descrizione generica di uno stack, gli eventi e le risorse appartenenti a quello specifico stack. Tramite i comandi CLI è possibile creare e distruggere stack ma è consigliato l'uso della CLI-SAM specifica per questo tipo di operazioni. Per usare la CLI-SAM che è necessario installare il pacchetto specifico dopo aver configurato la CLI di AWS, la guida per l'installazione si trova nel sito ufficiale, questa è un gruppo di comandi specifici per la gestione dei servizi serverless come CloudFormation. I principali comandi messi a disposizione dalla CLI-SAM per la gestione di template e stack sono:

  • sam validate: esegue la validazione del template e visualizzare messaggi di errori se presenti, non è obbligatorio ma è sempre buona prassi validare il template prima di eseguirlo
  • sam build: crea una sottocartella ".aws-sam" con i file necessari per il rilascio
  • sam pacakge: se si tratta di template composti da più file questo passo è necessario per creare il file unico da rilasciare. Se il template è composto da un solo file, il passo non è da eseguire
  • sam deploy: esegue il rilascio e crea lo stack con tutte le sue risorse

L'elenco completo di tutti i comandi previsti è disponibile nella documentazione ufficiale. Usando il template di esempio di creazione del bucket S3, per eseguire la creazione di uno stack, i comandi da eseguire nella cartella dove è salvato il template in un file con il nome "template.yaml":

sam validate
sam build
sam deploy --stack-name esempio1buckets3 --capabilities CAPABILITY_IAM

Nel deploy è sempre indispensabile impostare il nome dello stack e la regola base IAM di tipo "CAPABILITY_IAM", senza questa regola CloudFormation non disporrà delle necessarie autorizzazioni e restituirà un errore. Quando il comando deploy viene seguito, vengono visualizzate le informazioni dei passi eseguiti e, se lo stack viene creato senza errori, vengono visualizzati gli Output indicati nel template:

Initiating deployment
=====================
Waiting for changeset to be created..
CloudFormation stack changeset
-------------------------------------------------------------------------
Operation           LogicalResourceId                   ResourceType   
------------------------------------------------------------------------- 
+ Add               S3Bucket                            AWS::S3::Bucket 
-------------------------------------------------------------------------
2042-04-02 00:00:00 - Waiting for stack create/update to complete
CloudFormation events from stack operations (refresh every 0.5 seconds)
------------------------------------------------------------------------- 
ResourceStatus      ResourceType                        LogicalResourceId
------------------------------------------------------------------------- 
CREATE_IN_PROGRESS  AWS::CloudFormation::Stack          esempio1buckets3
CREATE_IN_PROGRESS  AWS::S3::Bucket                     S3Bucket       
CREATE_IN_PROGRESS  AWS::S3::Bucket                     S3Bucket       
CREATE_COMPLETE     AWS::S3::Bucket                     S3Bucket       
CREATE_COMPLETE     AWS::CloudFormation::Stack          esempio1buckets3
-------------------------------------------------------------------------
CloudFormation outputs from deployed stack
-------------------------------------------------------------------------
Outputs
------------------------------------------------------------------------- 
Key                 S3Bucket
Description         S3
Value               arn:aws:s3:::esempio01buckets3
------------------------------------------------------------------------- 
Successfully created/updated stack - esempio1buckets3 in None

I comandi della CLI-SAM mettono a disposizione alcuni comandi specifici per le operazioni negli stack: 

  • per rimuovere uno stack e di tutte le sue risorse con il comando:
sam delete --stack-name esempio1buckets3
  • per valorizzare i parametri obbligatori o modificare i valori di default dei parametri di un template con il comando "parameter-overrides":
sam deploy --stack-name esempio1buckets3 --capabilities CAPABILITY_IAM 
--parameter-overrides NomeBucket=nome-bucket-personalizzato
  • se un template è formato da più file, è necessario usare il comando pacakge prima del deploy, il comando necessita di tre parametri: il nome del file che verrà creato, un bucket e un path:
sam package --output-template-file <packagedV1.yaml> --s3-bucket <bucket-name> --s3-prefix <path>
  • come per la CLI, anche la CLI-SAM usa il profilo di default impostato, se si vuole personalizzare profilo e zona di rilascio bisogna aggiungere il parametro "profile" quando deploy e delete vengono usati:
sam deploy --stack-name esempio1buckets3 --profile <nomeProfilo>
sam delete --stack-name esempio1buckets3 --profile <nomeProfilo>

Nei successivi articoli in questo sito che trattano i servizi AWS, se il servizio è compatibile con con CloudFormation, verrà introdotto un template specifico per la gestione di risorse con template, per temi avanzati come la dipendenza tra template e i template annicati si rimanda ad articoli futuri. Tutti i dettagli e le caratteristiche del servizio si possono trovare nel sito ufficiale, questa fonte è molto utile perchè ricca di esempi e sono elencati tutte le proprietà possibili per ogni tipo di risorsa supportata dal servizio CloudFormation. Bisogna ricordare anche che il servizio è gratuito ma ogni risorsa creata negli stack è a pagamento quindi bisogna sempre prestare alle risorse create nei template.

Tutti gli esempi di template di CloudFormation presentati in questo sito sono disponibili in un repository pubblico:

https://github.com/alnao/AWSCloudFormationExamples

AWS ha realizzato due specifiche librerie per la gestione delle risorse in Cloud usando linguaggi di programmazione: Cloud Development Kit, spesso abbreviato con la sigla CDK, e SDK, la prima mette a disposizione librerie per creare e gestire risorse mentre la seconda mette a disposizione librerie per usare/interagire le risorse nel Cloud. Comprenderne la differenza è fondamentale: in parole semplici la libreria CDK gestisce risorse del Cloud mentre la libreria SDK gestisce risorse nel Cloud, sottile differenza ma fondamentale.

Quando si usa la CDK bisogna sempre tenere presente che si tratta di un servizio di tipo "infrastructure as code", concetto già presentato con il servizio di CloudFormation, secondo la documentazione ufficiale:

the output of an AWS CDK program is an AWS CloudFormation template

cioè tutti i programmi, spesso chiamati "App", creati con la libreria CDK corrispondono al concetto di stack di CloudFormation, con la differenza che non c'è un template YAML/Json ma c'è il codice sorgente del programma per la definizione delle risorse.

Per entrambe le librerie AWS mette a disposizione librerie per i maggiori linguaggi di programmazione: Python, Java, .Net, JavaScript e altri, disponibili nlelle documentazioni ufficiali di CDK e SDK. Purtroppo è facile confondere i nomi delle due librerie vista l'assonanza, spesso si fa riferimento solo al SDK anche per componenti comprese nella CDK. In questo sito si presterà sempre attenzione e verrà indicato sempre quale delle due librerie è usata negli esempi. Sono messe a disposizione anche Toolkit specifici per i principali ambienti di sviluppo come Eclipse e Visual Studio Code, per maggior informazioni si può consultare la documentazione ufficiale.


Per l'installazione della CDK di Python bisogna avere a disposizione il compilatore python e il programma npm installati, poi si deve lanciare i comandi di installazione:
python -m pip install aws-cdk-lib
npm install -g aws-cdknpm uninstall aws-cdk
Un nuovo progetto può essere creato con i comandi:
cdk init app --language python
python -m pip install -r requirements.txt
e si può consultare la documentazione ufficiale per maggiori dettagli. La libreria può essere importata come previsto dallo standard python e usata per definire una "app" che crea un oggetto di una classe:
#!/usr/bin/env python3
import os
import aws_cdk as cdk
from prova1.prova1_stack import Prova1Stack
app = cdk.App()
Prova1Stack(app, "Prova1CDKStack",    )
app.synth()
all'interno della definizione della classe si definiscono le risorse da creare nello stack:
from aws_cdk import (    Stack,)
from constructs import Construct
import aws_cdk.aws_s3 as s3
class Prova1Stack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        # definizione di un bucket
        bucket = s3.Bucket("MyBucket", bucket_name="my-bucket", versioned=True,)

I principali comandi per gestire le applicazioni con la libreria CDK:
  • cdk deploy: esegue il deploy dell'applicazione
  • cdk synth: crea un template CloudFormation per la app
  • cdk diff: confronta il codice sorgnete con l'eventuale stack già presente nel cloud
  • cdk list: visualizza la lista di tutti gli stack rilasciati
  • cdk destroy <nome>: distrugge uno stack nel cloud
Tutti i comandi e i parametri messi a disposizione dalla libreria Cdk sono disponibili alla pagina della documentazione ufficiale dove è possibile trovare la descrizione del file cdk.json, file di configurazione di tutti i progetti costruiti con le librerie CDK di AWS.  Quando si usano le librerie di AWS (CDK e SDK) bisogna sempre ricordare che queste usano i profili configurati con la AWS-CLI: esattamente come per il comando CLI-SAM anche per la CDK, se non specificato come parametro, viene usato il parametro di default, se non c'è nessun profilo configurato queste librerie non sono utilizzabili a meno che non vengano indicate specificatamente come parametro in creazione dell'oggetto App nel codice.

Il pacchetto standard npm è ben documentato nella pagina ufficiale, la libreria è gratuita ed open-source, il repository pubblico del progetto è disponibile su GitHub:

https://github.com/aws/aws-cdk
Esattamente come per il servizio CloudFormation, bisogna ricordare che la CDK è gratuita ma tutte le risorse create e gestite possono essere a pagamento e bisogna sempre prestare attenzione a quello che viene creato nel Cloud per evitare addebiti indesiderati.

Una Software Development Kit, spesso abbreviato con la sigla SDK, è una libreria che permette ad un programmatore di interfacciarsi con i servizi AWS tramite codice scritto in un linguaggio utilizzando delle specifiche API messe a disposizione. A differenza della CDK di AWS, che può essere usata solo per creare e configurare risorse, la SDK permette anche di interagire con i servizi, per esempio con le librerie SDK è possibile recuperare la lista di un bucket mentre con la CDK è possibile solo creare e distruggere uno storage S3. La libreria è documentata molto bene nel sito ufficiale e sono disponibili moltissimi esempi gratuiti che è possibile studiare nel repository ufficiale gratuito, gli usi più comuni sono lo sviluppo di applicazioni che interagiscono con i servizi nel Cloud sia come codice serverless tramite il servizio AWS Lambda Function sia come di applicazioni client.

Quando si usano le librerie di AWS (CDK e SDK) bisogna sempre ricordare che queste usano i profili configurati con la AWS-CLI: esattamente come per il comando CLI-SAM anche per la SDK viene usato il parametro di default se non specificato diversamente, se non c'è nessun profilo configurato queste librerie non sono utilizzabili, tuttavia è possibile impostare il profilo direttamente nel codice senza dover usare i profili CLI del sistema.


Per il linguaggio di programmazione Python la libreria SDK messa a disposizione da AWS è boto3, installabile grazie al gestore dei pacchetti pip:

pip install boto3

oppure nei sistemi Debian 12 è installabile con il pacchetto ufficiale:

apt-get install python3-boto3
Con questa libreria è possibile recuperare l'elenco dei profili e l'elendo dei bucket di un account con pochissime righe di codice:
#Elenco profili disponibili
import boto3
lista_profili=boto3.session.Session().available_profiles
for profilo in lista_profili:
    print(profilo)
#Elenco bucket
import boto3
s3 = boto3.resource('s3')
for bucket in s3.buckets.all():
    print(bucket.name)
Tutta la libreria boto3 per python è open-source e la documentazione ufficiale è la fonte principale di informazioni in caso di necessità.

Per il linguaggio Java la libreria è disponibile in un respository maven di amazon, purtroppo esistono due versioni della libreria non compatibili tra loro come indicato nella documentazione ufficiale:
Package names begin with software.amazon.awssdk in SDK 2.x, 
  whereas the SDK 1.x uses com.amazonaws
Nota: In questo sito sono disponibili esempi solo della seconda versione, nella precedente versione del sito sono disponibili esempi della prima prima versione considerata deprecata.

Per iniziare a lavorare con SDK in Java è necessario configurare la libreria nel file di configurazione, aggiungendo i riferimenti al repository maven nel pom.xml con il tag:

<dependencyManagement>
    <dependencies>
        <dependency>
          <groupId>software.amazon.awssdk</groupId>
          <artifactId>bom</artifactId>
          <version>2.16.1</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
e poi è necessario aggiungere la dipendenza delle singole librerie in base ai singoli servizi usati, per esempio per usare le librerie di autenticazione e profili:
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>auth</artifactId>
</dependency>
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>profiles</artifactId>
</dependency>
Per recuperare i profili di un sistema è possibile usare i metodi base della libreria SDK
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.profiles.ProfileFile;
import software.amazon.awssdk.profiles.ProfileFile.Type;
...
    public static ProfileFile loadCredentialsFromFile() 
                throws FileNotFoundException {
        File configfile = new File(System.getProperty("user.home"),
           ".aws/credentials");
        ProfileFile profileFile = ProfileFile.builder()
           .content(new FileInputStream(configfile))
           .type(Type.CREDENTIALS).build();
        return profileFile;
    }
    public static Set<String> loadProfilesFromFile() 
                throws FileNotFoundException{
        ProfileFile p=loadCredentialsFromFile();
        return p.profiles().keySet();
    }
    public static AwsCredentialsProvider loadCredentialFromFile() 
                throws FileNotFoundException {
        return loadCredentialFromFile(DEFAULT_PROFILE);
    }
    public static AwsCredentialsProvider loadCredentialFromFile(
                String profileName) throws FileNotFoundException {
        ProfileFile p=loadCredentialsFromFile();
        AwsCredentialsProvider profileProvider = 
           ProfileCredentialsProvider.builder()
           .profileFile(p)
           .profileName(profileName)
           .build();
        return profileProvider;
    }
...
Tutti i dettagli riguardo alla libreria per la gestione di profili e credenziali nella SDK v2 di Java è disponibile nella solita documentazione ufficiale. Per tutti i linguaggi di programmazione sono disponibili esempi di codice con progetti completi nella documentazione ufficiale.

Esattamente come per i servizi CloudFormation e CDK bisogna ricordare che la SDK è gratuita ma tutte le risorse gestite possono essere a pagamento e bisogna sempre prestare attenzione a quello che viene creato nel Cloud per evitare addebiti indesiderati.

CloudWatch è il principale servizio di monitoraggio degli eventi nel Cloud AWS, con il quale è possibile raccogliere log e dati in tempo reale, questi dati vengono poi resi disponibili in un pannello di controllo nella console web, studiata per la manutenzione dell'infrastruttura e delle applicazioni in esecuzione. Con CloudWatch e le sue funzionalità di Log e Alarm, è possibile ridurre al minimo la logica da programmare per il monitoraggio, avendo a disposizione un sistema molto robusto e flessibilo.

Il servizio si basa su alcuni concetti:

  • Log: un registro di log proveniente da tutti gli altri servizi, i dati vengono strutturati in Namespace e metriche
  • Namespace: è un contenitore di metriche che risultano separate
  • Metric: rappresenta un insieme di dati in ordine temporale pubblicati su CloudWatch, vengno rappresetnate come variabili da monitorare (a volte il termine Metric viene tradotto in italiano con la parola Parametro)
  • Dashboard: nella console web è disponibile un pannello di controllo personalizzabile con il quale è possibile monitorare il sistema e gli eventi passati
  • Alarm: dalle metriche si possono creare allarmi che generano eventi in base ad eventi. L'allarme più semplice consiste nell'avvisare persone inviando un messaggio quando si verifica un evento registrato in una metrica
  • CloudWatch agent: sistema per inserire log in Metric provenienti da sistemi esterni al sistema Cloud AWS.
Nella documentazione ufficiale è presente uno schema che descrive il funzionamento del servizio:

Bisogna prestare attenzione quando viene usato questo servizio in quanto non è gratuito visto che alcune funzionalità sono a pagamento in base all'uso e in base a quante query vengono eseguite nel motore che gestire log ed eventi, è consigliato leggere attentamente la pagina ufficiale.


La CLI mette a disposizione una serie di comandi per la gestione di comandi, la documentazione ricca di esempi è sempre il punto di riferimento princpale. I comandi principali per gestire le metriche si basano sul comando:

aws cloudwatch list-metrics --namespace "AWS/Lambda"

Per gestire gli allarmi è possibile recupeare la lista con i comandi:

aws cloudwatch describe-alarms
aws cloudwatch describe-alarms --alarm-names "nome-alarm"

Per scatenare un allarme è possibile usare lo stesso comando con dei parametri specifici:

aws cloudwatch set-alarm-state --alarm-name "nome-alarm" 
  --state-value ALARM --state-reason "testing purposes"

La gestione dei log da riga di comando può essere scomoda a causa della numerosità dei log comunque esiste il comando per recuperare l'elenco dei gruppi di log e i singoli eventi salvati nel registro con la possibilità di filtrare per pattern e orario del log:

aws logs describe-log-groups
aws logs filter-log-events --log-group-name /aws/lambda/nome-lambda
aws logs filter-log-events --log-group-name /aws/lambda/nome-lambda 
  --filter-pattern ERROR
aws logs filter-log-events --log-group-name /aws/lambda/nome-lambda 
  --start-time 1688977737000 --end-time 1688977738000
aws logs filter-log-events --log-group-name /aws/lambda/nome-lambda 
  --output text

Con CloudFormation è possibile gestire i log e i permessi che le varie risorse devono avere per scrivere nei log, per esempio in un template per la creazione di una lambda, oltre alla creazione della Lambda, nella sua execution role creata come regola IAM è necessario aggiungere la policy per permettere alla funzione di creare il gruppo di log e a scriverci, un semplice e classico esempio è:

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:
          - 'logs:CreateLogGroup'
          - 'logs:CreateLogStream'
          - 'logs:PutLogEvents'
          Resource: 'arn:aws:logs:*:*:*'

Tramite CloudFormation è possibile anche creare Allarmi, Dashboard e Metriche, la documentazione ufficiale è completa e ricca di esempi, per la gestione dei log è disponibile una pagina dedicata.


La libreria SDK mette a disposizione una libreria per la gestione del servizio CloudWatch, dei log, degli alert e delle metriche tramite la solita libreria boto3. Le principali funzionalità sono recuperare la lista delle metriche disponibili ed eseguire una query su una metrica per recuperare la lista dei log degli eventi:

def get_metrics(profile_name):
    boto3.setup_default_session(profile_name=profile_name)
    cloudwatch = boto3.client('cloudwatch')
    lista=[]
    paginator = cloudwatch.get_paginator('list_metrics')
    for response in paginator.paginate(
        Dimensions=[{'Name': 'LogGroupName'}],
            MetricName='IncomingLogEvents',
            Namespace='AWS/Logs'):
        lista=lista+response['Metrics']
    return lista

def get_metric_group_log(profile_name,group_name):
    # Create CloudWatchLogs client
    boto3.setup_default_session(profile_name=profile_name)
    cloudwatch_logs = boto3.client('logs')
    # List subscription filters through the pagination interface
    paginator = cloudwatch_logs.get_paginator('describe_log_groups')
    lista=[]
    for response in paginator.paginate(logGroupNamePrefix=group_name):
        lista=lista+response['logGroups']
        #for i in response['logGroups']:
            #print(i['logGroupName'])
    return lista

def get_metric_log(profile_name,group_name,hours):
    boto3.setup_default_session(profile_name=profile_name)
    cloudwatch_logs = boto3.client('logs')
    query = "fields @timestamp, @message"
    start_query_response = cloudwatch_logs.start_query(
        logGroupName=group_name,
        startTime=int((datetime.today() - timedelta(hours=hours)).timestamp()),
        endTime=int(datetime.now().timestamp()),
        queryString=query,
    )
    query_id = start_query_response['queryId']
    response = None
    while response == None or response['status'] == 'Running':
        #print('Waiting for query to complete ...')
        time.sleep(1)
        response = cloudwatch_logs.get_query_results(
            queryId=query_id
        )
    return response["results"]

L'icona del servizio è proprio il logo del sito

EventBridge è il servizio AWS per la schedulazione di eventi nel Cloud, come un registro tecnico di eventi programmati prevede due tipi di eventi a frequenza oppure ad evento. Questo servizio è stato creato da pochi anni, precedentemente si trovata come funzionalità all'interno di CloudWatch infatti nella console web di è ancora presente il link "Events roles" che rimanda dal vecchio servizio alla nuova versione. Nella descrizione principale del servizio si indica che EventBridge è un servizi di tipo serverless che collega altri servizi AWS tramite l'esecuzione di eventi, in questo caso il termine serverless indica che non è necessario preoccuparsi del server dove è eseguito l'evento, a quello ci pensa AWS, l'unico onere dell'utente è creare la regola indicando il tipo di evento e la sua destinazione.

Semplificando al massimo il funzionamento del servizio, nel tipo di eventi ad evento si può impostare che venga eseguito ad orario prefissato con una certa frequenza attraverso una regola chiamata Cron (simile alle regole CronTab dei sistemi UniX/Linux). La descrizione completa del funzionamento del servizio è spiegato nell'immagine presa dal sito ufficiale dove è possibile identificare che gli eventi sono generati da una sorgente (crontab o da altri servizi), questi eventi vengono elaborati da un bus e inviati ad un servizio AWS di destinazione come funzioni Lambda, SNS o altri servizi AWS.

Tutte le esecuzioni degli eventi regolati con EventBridge vengono salvate come log nel servizio dedicato CloudWatch, attraverso la console web è possible recuperare l'elenco di tutti gli eventi e i dettagli di ogni esecuzione se il servizio è attivo. Due classici casi d'uso di uso di questo servizio che verranno esposti nei prossimi esempi di codice: eseguire una Lambda Function ogni giorno ad una certa ora e eseguire la Lambda Function ogni volta che viene caricato un file in un bucket S3. Se si vuole usare il servizio EventBridge per la gestione degli eventi di bucket S3 bisogna ricordare che c'è una configurazione da attivare nelle proprietà del singolo bucket, senza questa configurazione gli eventi non saranno trasmessi ad EventBridge e non ci sarà nessun log di errore (chi scrive ha perso ore per capire il motivo di eventi non scatenati senza errori), si rimanda alla documentazione ufficiale per maggiori informazioni.

Per gli eventi di tipo schedulato è necessario configurare la frequenza di lancio dell'evento, sono previsti due tipi: ogni X minuti oppure una regola specifica Cron:


Se si vuole gestire con EventBridge gli eventi generati dal servizio S3 è necessario abilitare il singolo bucket a inviare gli eventi a EventBridge con una opzione specifica, nella documentazione ufficiale sono spiegati i passi da eseguire: nella console web, nella pagina di dettaglio di un bucket è presente un opzione per abilitare o disabilitare il collegamento tra i due servizi.

Senza questa configurazione il bucket non invia ad EventBridge gli eventi e non ci sarà nessun log o errore a segnalare la cosa, chi scrive questi articoli ha perso molto tempo a causa di questo piccolo dettaglio, il consiglio è di attivare sempre questa opzione che purtroppo di default è disattivata quando un bucket viene creato.


Attraverso la CLI è possibile utilizzare la riga di comando per gestire gli eventi con il comando events. Il principale comando recupera l'elenco di tutte le regole create nel servizio

aws events list-rules --name-prefix "Alberto"
{
  "Name": "Alberto-regola-esempio",
  "Arn": "arn:aws:events:eu-west-1:xxx:rule/Alberto-regola-esempio",
  "EventPattern": "{\"detail-type\":[\"Object Created\"],\"source\":
    [\"aws.s3\"],\"detail\":{
      \"bucket\":{\"name\":[\"bucket-name\"]},
      \"object\":{\"key\":[{\"prefix\":\"path-base\"}]}}}",
  "State": "ENABLED",
  "EventBusName": "default"
},
{
  "Name": "Alberto-regola-esempio2",
  "Arn": "arn:aws:events:eu-west-1:xxx:rule/Alberto-regola-esempio2",
  "State": "ENABLED",
  "ScheduleExpression": "cron(10 06 ? * 1 *)",
  "EventBusName": "default"
},

Dalla risposta è possibile determinare il tipo (Trigger o Cron) e lo stato ma nella risposta non è disponibile l'informazione del target dell'evento recuperabile con una seconda chiamata:

aws events list-targets-by-rule --rule "Alberto-regola-esempio"
{ "Targets": [
  {
    "Id": "id1",
    "Arn": "arn:aws:lambda:eu-west-1:xxx:function:nome-lambda"
  }
]}

Sempre da riga di comando è possibile abilitare e disabilitare una regola con un semplice esempio:

aws events disable-rule --name "Alberto-regola-esempio"
aws events enable-rule --name "Alberto-regola-esempio"

Come per tutti i servizi, grazie alla libreria SDK boto3 è possibile gestire le regole degli eventi, i principali metodi sono:

def get_lista_regole(profile_name,prefix):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('events')
  response = client.list_rules(
    NamePrefix=prefix,
    EventBusName='default',
    #NextToken='string',
    Limit=100
  )
  if "Rules" in response:
    return response['Rules']
  return []
def disable_role(profile_name,name):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('events')
  response = client.disable_rule(
    Name=name,
    EventBusName='default'
  )
  return response
def enable_role(profile_name,name):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('events')
  response = client.enable_rule(
    Name=name,
    EventBusName='default'
  )
  return response
def describe_rule(profile_name,name):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('events')
  response = client.describe_rule(
    Name=name,
    EventBusName='default'
  )
  return response

Con CloudFormation è possibile creare regole, sempre molto utile in applicazioni serverless e event-driven, un semplice esempio per eseguire una lambda all'evento di carico di un oggetto in un bucket o eseguire la lambda ogni domenica:

EventBridgeTriggerRole:
  Type: AWS::Events::Rule
  Properties:
    EventBusName: default
    EventPattern: 
      source: 
      - "aws.s3"
      detail-type:
      - "Object Created"
      detail: 
        bucket:
          name: 
            - !Ref SourceBucket
        object:
          key:
            - prefix: !Ref SourcePath
    Targets:
      - Id: id1
        Arn: !GetAtt StartLambdaNewFile.Arn
EventBridgeCronRole:
  Type: AWS::Events::Rule
  Properties:
    EventBusName: default
    State: !RefStateTrigger
    ScheduleExpression: cron(00 09 ? * 1 *)
    #ogni domenica alle 9 di mattina (orario UDC)
    Targets:
      - Id: id1
        Arn: !GetAttLambdaDeleteFunction.Arn

Nel caso di evento che esegue una funzione lambda bisogna sempre ricordarsi di creare una regola specifica per permettere al servizio EventBridge di invocare funzioni Lambda, le famose "resource-based policy statements" del servizio Lambda, su CloudFormation il blocco da aggiungere è del tipo:

PermissionForEventsToInvokeLambda: 
  Type: AWS::Lambda::Permission
  Properties: 
    FunctionName: !Ref ExternalPyLambda
    Action: lambda:InvokeFunction
    Principal: events.amazonaws.com
    SourceArn: !GetAtt TriggerExternalPy.Arn

L'esempio completo con un tutti i dettagli del template è disponibile al solito repository:

github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio06eventBridge

Il servizio CloudTrail è studiato per monitorare gli eventi eseguiti nel Cloud non con lo scopo di monitoraggio ma di Audit e gestione della sicurezza, infatti il servizio registra tutte le attività degli utenti e le chiamate alle API nel servizio, in particolare vengono registrati tutti i servizi dei tipi.

A titolo di esempio per il servizio S3 vengono registrati gli eventi:

  • relativi alla gestione: azioni del piano di controllo (control-plane) sulle risorse come la creazione o l'eliminazione di bucket
  • relativi ai dati: azioni del piano dati all'interno di una risorsa come la lettura o la scrittura di un oggetto

CloudTrail utilizza queste fonti di eventi in tre funzionalità: cronologia degli eventi, CloudTrail Lake e Percorsi e la Cronologia degli eventi fornisce un registro visualizzabile, ricercabile, scaricabile e immutabile degli ultimi 90 giorni di eventi di gestione in una Regione AWS. La visualizzazione della Cronologia degli eventi non prevede costi di CloudTrail.

CloudTrail Lake è un data lake gestito per l'acquisizione, l'archiviazione, l'accesso e l'analisi delle attività di utenti e API su AWS a scopo di audit e sicurezza. È possibile aggregare, visualizzare, interrogare e archiviare immutabilmente i log delle attività da fonti AWS e non AWS. I Percorsi acquisiscono un registro delle attività degli account AWS e procedono a distribuire e archiviare tali eventi in S3, con possibilità di distribuzione anche a File di log Amazon CloudWatch e ad Amazon EventBridge, è possibile esportare questi eventi nelle soluzioni di monitoraggio della sicurezza. Per maggiori informazioni su cronologia degli eventi Data Lake AWS CloudTrail e sui percorsi, è possibile consultare il sito ufficiale di CloudTrail.

In particolare bisogna ricordare che il servizio non è gratuito, il piano gratuito prevede la possiblità di usare le funzionalità con alcune limitazioni, si rimanda alla documentazione ufficiale, inoltre bisogna sempre ricordare che è studiato per uno scopo diversi rispetto a CloudWatch: CloudTrail si concentra principalmente sull’auditing e sulla conformità registrando le chiamate API mentre CloudWatch è un servizio di monitoraggio che fornisce dati e informazioni utili per la gestione delle applicazioni


La CLI mette a disposizione il comando lookup-events per il recuperi di informazioni da questo servizio:

aws cloudtrail lookup-events help
aws cloudtrail lookup-events --max-items 10

che permette di recupeare i dati salvati. In particolare esistono alcuni comandi specifici per filtrare i dati, per esempio per filtrare i dati tra due date:

aws cloudtrail lookup-events --start-time <timestamp> --end-time <timestamp>

Si rimanda alla documentazione ufficiale per maggiori dettagli, da notare che esistono comandi specifi anche per il data lake e il viewer.


La libreria SDK mette a disposizione un client specifico per l'uso del servizio Cloudtrail, in particolare il comando lookup_events ha la sintassi del tipo:

response = client.lookup_events(
  LookupAttributes=[{
    'AttributeKey': 'EventId'|'EventName'|'ReadOnly'|'Username'|'ResourceType'|'ResourceName'|'EventSource'|'AccessKeyId',
    'AttributeValue': 'string'
  },],
  StartTime=datetime(2015, 1, 1),
  EndTime=datetime(2024, 1, 1),
  EventCategory='insight',
  MaxResults=123,
  NextToken='string'
)

Dalla libreria sono messe a disposizione anche i comandi per la gestione dei trails, per esempio:

client = boto3.client('cloudtrail')
def list_trail(profile_name):
  response = client.list_trails(
    NextToken='string'
  )
  if 'Trails' in response:
    return response['Trails']
  return []
def get_trail(profile_name, trail_name):
  response = client.get_trail( Name=trail_name )
  if 'Trail' in response:
    return response['Trail']
  return {}
def describe_trails(profile_name, trail_name):
  response = client.describe_trails( trailNameList=[trail_name] )
  if 'trailList' in response:
    return response['trailList']
  return []

Il servizio AWS Config è stato studiato per permettere ai customer di valutare, registrare e monitorare tutte le modifiche alle configurazioni delle risorse per semplificare la gestione del Cloud. Da notare che come servizio non permette di modificare le configurazione ma è studiato solo per essere uno strumento di monitoraggio con un registro unificato per tutte le risorse del Cloud. Bisogna sempre ricordare che per risorsa AWS si intende qualsiasi entità in AWS e comprende qualsiasi tipo di entità creata, l'elenco completo di tutti i tipi di risorse è disponibile nella documentazione ufficiale:

Da console web è possibile attivare il servizio con una procedura chiamata 1-click, inoltre è possibile definire regole usando quelle definite di default (Managed) e personalizzate (Custom), si rimanda sempre alla documentazione ufficiale per tutti i dettagli.

La documentazione ufficiale descrive molto bene il funzionamento del sistema e presenta diversi esempi di utilizzo con casi d'uso reali , per esempio è possibile contare il numero di istanze EC2 attive aggregando il dato per tipo con una semplice query:

SELECT configuration.instanceType, COUNT(*)
 WHERE resourceType = 'AWS::EC2::Instance'
 GROUP BY configuration.instanceType

La CLI mette a disposizione una serie di comandi per la gestione del servizio. Per evitare problemi con doppi nomi, è stato creato il tipo config-service per far riferimento ai comandi del servizio AWS Config per evitare fraintendimenti con il comando configuration utilizzato per definire i profili della AWS CLI. I principali comandi di questo servizio sono

  • - recupero dello stato dei sistemi di registro di AWS Config
    aws configservice describe-delivery-channels
    aws configservice describe-configuration-recorders
    aws configservice describe-configuration-recorder-status
  • elenco di tutte le regole
    aws configservice describe-config-rules 
    aws configservice describe-config-rules | grep ConfigRuleName
  • creazione ed eliminazione di una regola
    aws configservice put-config-rule --generate-cli-skeleton > putConfi gRule.json
    aws configservice put-config-rule --cli-input-json file://putConfigRule.json
    aws configservice delete-config-rule --config-rule-name ConfigRuleName
  • elenco di tutte le regole compliance
    aws configservice describe-compliance-by-config-rule
    aws configservice describe-compliance-by-resource --resource-type AWS::EC2::Instance
    aws configservice get-compliance-details-by-resource --resource-type AWS::EC2::Instance --resource-id i-nnnnnnnn
  • lista delle risorse
    aws configservice list-discovered-resources
    aws configservice list-discovered-resources --resource-type "AWS::EC2::Instance"
    aws configservice list-discovered-resources --resource-type "AWS::CloudFormation::Stack"
  • storico di eventi di una risorsa
    aws configservice get-resource-config-history --resource-type AWS::EC2::Instance --resource-id i-0117ee5195da9794b

Oltre alla documentazione ufficiale, è consigliata anche la lettura di un libro disponibile nel sito ufficiale che descrive in maniera dettagliata tutti i comandi


Anche la libreria SDK mette a disposizione i metodi per gestire il servizio da codice: la libreria boto3 mette a disposizione metodi per i servizio config, i principali metodi messi a disposizione sono:

def resources_list(profile_name,resource_type):
  boto3.setup_default_session(profile_name=profile_name)
  config = boto3.client('config')
  lista = config.list_discovered_resources(
    resourceType=resource_type
    #limit=100,
    #nextToken='string'
  )
  if 'resourceIdentifiers' in lista:
    return lista['resourceIdentifiers']
  return [] 
def resource_detail(profile_name, resource_type, resource_id):
  boto3.setup_default_session(profile_name=profile_name)
  config = boto3.client('config')
  lista = config.get_resource_config_history(
    resourceType=resource_type,
    resourceId=resource_id,
    limit=100,
    #nextToken='string'
  )
  if 'configurationItems' in lista:
    return lista['configurationItems']
  return []

Per la gestione di AWS Config in CloudFormation sono previsti tutti i tipi di risorse, si rimanda alla documentazione ufficiale per tutti i dettagli. Nell'esempio proposto in AwsLabs viene definita la regola per la registrazione degli evento sui volumi delle EC2:

ConfigRecorder:
  Type: AWS::Config::ConfigurationRecorder
  Properties:
    Name: default
    RecordingGroup:
    ResourceTypes: ['AWS::EC2::Volume']
    RoleARN: !GetAtt [ConfigRole, Arn]
  DeliveryChannel:
    Condition: CreateDeliveryChannel
    Type: AWS::Config::DeliveryChannel
    Properties:
      ConfigSnapshotDeliveryProperties:
        DeliveryFrequency: Six_Hours
      S3BucketName: !Ref 'ConfigBucket'
      SnsTopicARN: !Ref 'ConfigTopic'
  ConfigRuleForVolumeTags:
    Type: AWS::Config::ConfigRule
    Properties:
      InputParameters:
        tag1Key: CostCenter
      Scope:
        ComplianceResourceTypes: ['AWS::EC2::Volume']
      Source:
        Owner: AWS
        SourceIdentifier: REQUIRED_TAGS
    DependsOn: ConfigRecorder

Per ulteriori informazioni ed esempi si rimanda al repository ufficiale di AwsLabs.

Elastic Compute Cloud, abbreviato con la sigla EC2, è servizio principale di servizio di calcolo (compute in inglese) nel Cloud di Amazon Web Services (AWS). Si tratta di un servizio che permette di avviare istanze remote per virtualizzare server o desktop con la posssilità di configurare la sicurezza, networking, storage, capacity e molto altro. Il servizio permette di gestire processi molto pesanti ma anche molti piccoli grazie alla scalabilità che permette di ridimensionare le istanze in base alle necessità di calcolo e al carico di lavoro. Questo non è un articolo che tratta di questo servizio nello specifico, si rimanda alla documentazione ufficiale per tutti i dettagli a riguardo, si da per scontato che un utente abbia almeno le conoscenze basi del servizio EC2 come i SecurityGroup, la gestione delle chiavi KeyPair, gli storage EBS e il bilanciamento del traffico ELB.

Questo articolo ha lo scopo di descrivere i principali comandi per la gestione delle istanze EC2 nel cloud usando gli strumenti messi a disposizione da AWS come la CLI, il servizio CloudFormation e le librerie CDK/SDK, lo scopo finale è automatizzare la gestione del servizio di virtualizzazione EC2.


La CLI mette a disposizione un unico comando con alcuni sottocomandi, tutte le informazioni sono disponibili nel solito sito ufficiale. I principali comandi sono:

  • recuperare l'elenco di tutte le istanze:
    aws ec2 describe-instances
  • recuperare il dettaglio di una istanza partendo dal nome dell'instanza (come tag name):
    aws ec2 describe-instances --filters "Name=tag:Name,Values=testAlberto"
  • recuperare il dettaglio di una istanza partendo dal id istanza:
    aws ec2 describe-instances --instance-ids i-094d7505c54b0xxxx
  • filtrare i dati ritornati dai precedenti comandi per ottenere solo alcuni dati delle istanze (come id, stato e indirizzi ip):
    aws ec2 describe-instances --instance-ids i-094d7505c54b0xxxx 
       --query "Reservations[*].Instances[*].[InstanceId, ImageId, 
         State, PrivateIpAddress, PublicIpAddress]"
  • impostare un tag di una istanza per assegnare un nome alla istanza:
    aws ec2 create-tags --resources i-094d7505c54b0xxxx 
       --tags Key=Name,Value=testAlbertoLinux3b
  • eseguire lo stop di una istanza:
    aws ec2 stop-instances --instance-ids i-094d7505c54b0xxxx
  • eseguire il comando di start di una istanza:
    aws ec2 start-instances --instance-ids i-094d7505c54b0xxxx

Quasi tutti i comandi ritornano i dati secondo il fomato specifico al momento della configurazione della AWS-CLI, se si è impostato il tipo Json le risposte saranno del tipo:

{"StartingInstances": [{
  "CurrentState": {
    "Code": 0,
    "Name": "pending"
  },
  "InstanceId": "i-094d7505c54b0e029",
  "PreviousState": {
    "Code": 80,
    "Name": "stopped"
  }
} ] }

L'elenco completo di tutti i parametri e i possibili utilizzi è disponibile nelle pagine dedicate nella pagina ufficiale: ec2 e describe-instances. Per creare/avviare una nuova istanza è disponibile il comando run-instances, documentato nella pagina ufficiale, il comando ha alcuni parametri obbligatori che corrispono ai dati obbligatori per l'avvio di una istanze EC2 da console:

  • immagine ami
  • numero di istanze da avviare
  • tipo (consigliato t2.micro)
  • chiavi di accesso (se si tratta di istanza Linux)
  • security group
  • subnet-id

Un semplice esempio di avvio nella AZ Irlandese è:

aws ec2 run-instances --image-id ami-06e0ce9d3339xxxx --count 1 
  --instance-type t2.micro --key-name Keeeey 
  --security-group-ids sg-0f2d1afe5832e7xxxx 
  --subnet-id subnet-0b6f53c0291c1xxxx

La risposta di questo comando è la conferma che una nuova istanza è stata avviata con tutti i dati, compresi quelli di default, alcuni dei principali dati ritornati dal json sono:

"ImageId": "ami-06e0ce9d3339cb039",
"InstanceId": "i-02849762f5cf6d1e1",
"Placement": {
  "AvailabilityZone": "eu-west-1b",
  "GroupName": "",
  "Tenancy": "default"
},
"PrivateDnsName": "ip-10-199-12-207.eu-west-1.compute.internal",
"PrivateIpAddress": "10.199.12.207",
"PublicDnsName": "",
"State": {
  "Code": 0,
  "Name": "running"
},

Per terminare l'istanza esiste un comando specifico, ben documentato nella pagina ufficiale, il comando ha bisogno in input solo dell'istanza, per esempio:

aws ec2 terminate-instances --instance-ids i-02849762f5cf6xxxx

Con CloudFormation è possibile creare e gestire istanze EC2 in template con il tipo base "AWS::EC2::Instance", ben documentato nella pagina ufficiale, i parametri minimi per avviare una istanza sono:

EC2Instance:
  Type: AWS::EC2::Instance
  Properties:
    InstanceType: !Ref 'InstanceType' # t2.micro free tier
    SecurityGroups: [!Ref 'InstanceSecurityGroup']
    KeyName: !Ref 'KeyName'
    ImageId: !Ref 'LatestAmiId'

L'esempio più semplice con solo i quattro parametri fondamentali è disponibile nel repository pubblico ufficiale di AWS:

github.com/awslabs/aws-cloudformation-templates/blob/master/aws/services/EC2/EC2InstanceWithSecurityGroupSample.yaml

Un esempio leggermente più complesso con la creazione di un web-server esposto in internet con uno script user-data e una configurazione più avanzata della rete è disponibile nel repository:

github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio02istanzeEC2

Da notare che, per creare uno stack da questo template, è necessario inserire tre parametri obbligatori: KeyName, VpcId e SubnetId mentre AMI, security group sono parametri con un valore di default.


La libreria CDK permette la creazione e l'avvio di istanze EC2 da codice con la libreria ufficiale, un semplice esempio di creazione con la libreria Java:

final Instance engineEC2Instance = Instance.Builder.create(this, id + "-ec2")
.instanceName(id + "-ec2")
.machineImage(armUbuntuMachineImage)
.securityGroup(securityGroup)
.instanceType(InstanceType.of(InstanceClass.BURSTABLE4_GRAVITON,InstanceSize.SMALL))
.vpcSubnets(SubnetSelection.builder()
.subnetType(SubnetType.PUBLIC).build())
.vpc(vpc)
.build();

maggiori informazioni sono disponibili nella documentazione ufficiale.


La libreria SDK mette a disposizioni tool per gestire le Istanze, la documentazione ufficiale descrive metodi e procedure disponibili per la gestione del servizio Ec2. Per creare istanze è disponibile metodo per Java specifico:

RunInstancesRequest runRequest = RunInstancesRequest.builder()
.imageId(amiId)
.instanceType(InstanceType.T1_MICRO)
.maxCount(1)
.minCount(1)
.build();
RunInstancesResponse response = ec2.runInstances(runRequest);
String instanceId = response.instances().get(0).instanceId();
Tag tag = Tag.builder().key("Name").value(name).build();
CreateTagsRequest tagRequest = CreateTagsRequest.builder()
.resources(instanceId).tags(tag).build();
ec2.createTags(tagRequest);

Per ottenere la lista delle istanze di un account è disponibile un metodo nella libreria specifica, si riporta un esempio dove si può notare che il metodo ritorna al massimo 100 (o 1000) elementi quindi si usa deve usare il sistema a token per ottenerere l'elenco completo:

Region region = Region.EU_WEST_1; //default irland
Ec2Client ec2 = Ec2Client.builder().region(region)
  .credentialsProvider( Profiles.loadCredentialFromFile() ).build();
do {
  DescribeInstancesRequest request = DescribeInstancesRequest.builder()
    .maxResults(6).nextToken(nextToken).build();
  DescribeInstancesResponse response = ec2.describeInstances(request);
  for (Reservation reservation : response.reservations()) {
    for (Instance instance : reservation.instances()) {
      list.add(instance);
    }
   }
   nextToken = response.nextToken();
} while (nextToken != null);
for (Instance instance : list) {
  System.out.println("Instance:" + instance.instanceId() 
  + " " + instance.tags().get(0).value()
  + " " + instance.instanceType() + " " + instance.state().name());
}

Per il linguaggio Python, la libreria SDK-Boto3 mette a disposizione metodi specifici simili a quelli disponibili per la libreria in linguaggio Java, per esempio i 4 metodi principali per gestire le istanze sono: import boto3

def get_lista_istanze(profile_name):
  boto3.setup_default_session(profile_name=profile_name)
  ec2 = boto3.client('ec2')
  response = ec2.describe_instances()
  return response
def set_tag(instance_id, tag_key, tag_value):
  if tag_key=='':
    return 
  ec2 = boto3.resource('ec2')# region_name=AWS_REGION)
  tags=[{'Key': tag_key,'Value': tag_value}]
  instances = ec2.instances.filter(InstanceIds=[instance_id,],)
  for instance in instances:
    instance.create_tags(Tags=tags)
    print("set_tag " + instance_id)
  return tags
def stop_instance(instance_id):
  ec2_client = boto3.client('ec2')#, region_name=”us-west-2"
  print ("stop_instance " + instance_id)
  response = ec2_client.stop_instances(InstanceIds=[instance_id])
  return response
def start_instance(instance_id):
  ec2_client = boto3.client('ec2')
  print ("start_instance " + instance_id)
  response = ec2_client.start_instances(InstanceIds=[instance_id])
  return response

In questo gruppo di funzioni sono esposti i metodi per eseguire start e stop di una istanza, esiste anche il metodo "terminate" per terminare una istanza ma è sconsigliato prevedere questo tipo di operazioni da un programma se non strettamente necessario.

All'avvio di istanze EC2 è possibile indicare quale istruzioni eseguire, questo gruppo di istruzioni è chiamato User data, nonostante il nome possa essere forviante si tratta di un sistema di configurazione e controllo dell'avvio delle istanze, per ovvi motivi questo tipo di script può essere eseguito solo in istanze con sistema operativo GNU Linux ma non può essere usata con istanze con sistema operative MsWindows. Di default queste istruzioni vengono eseguite solo al primo avvio ma è possibile modificare la configurazione da console per eseguire lo script anche ogni riavvio come suggerito da una pagina del sito ufficiale. Ci sono due tipi di script: shell o cloud-init, il primo tipo viene usato per installare software o eseguire istruzione mentre il secondo viene usato quando c'è la necessità di gestire il collegamento tra l'istanza EC2 e altri servizi come CloudFormation, RDS. Un semplice esempio di uso di user-data è l'installazione di un web server LAMP con una piccola infrastuttura di webserver, database MySql e il motore Php. Nel caso di sistema operativo Linux con distruzione Amazon con il gestore di pacchetti yum:

#!/bin/bash
yum update -y
amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
yum install -y httpd mariadb-server
systemctl start httpd
systemctl enable httpd
usermod -a -G apache ec2-user
chown -R ec2-user:apache /var/www
chmod 2775 /var/www
find /var/www -type d -exec chmod 2775 {} \;
find /var/www -type f -exec chmod 0664 {} \;
echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php

Per verificare il corretto funzionamento degli script è possibile verificare i log scritti dal sistema nei due file dedicati:

/var/log/cloud-init.log
/var/log/cloud-init-output.log

e alcuni sotto-servizi del Cloud scrivono i corrispettivi files di log nella cartella specifica:

/var/log/amazon/

Il tipo cloud-init configura aspetti specifici di una nuova istanza Amazon Linux al momento del lancio come la configurazione del file delle chiavi private .ssh/authorized_keys. Le direttive utente cloud-init possono essere passate a un'istanza all'avvio nello stesso modo in cui viene passato uno script ma con una sintassi molto diversa: la prima riga deve iniziare con

#cloud-config

così da indicare il tipo di script, un esempio completo corrispondente allo script per l'installazione di un server LAMP:

#cloud-config
repo_update: true
repo_upgrade: all
packages:
- httpd
- mariadb-server
runcmd:
- [ sh, -c, "amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2" ]
- systemctl start httpd
- sudo systemctl enable httpd
- [ sh, -c, "usermod -a -G apache ec2-user" ]
- [ sh, -c, "chown -R ec2-user:apache /var/www" ]
- chmod 2775 /var/www
- [ find, /var/www, -type, d, -exec, chmod, 2775, {}, \; ]
- [ find, /var/www, -type, f, -exec, chmod, 0664, {}, \; ]
- [ sh, -c, 'echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php' ]
in questo esempio è possibile notare la distinzione tra la sezione "packages" dove sono elencati tutti i pacchetti da installare e la sezione "runcmd" con l'elenco dei comandi da eseguire.
Attraverso la CLI è possibile avviare istanze con uno specifico script salvato in un file
aws ec2 run-instances --image-id ami-XXXXX --count 1 
--instance-type m3.medium --key-name my-key-pair 
--subnet-id subnet-abcd1234 --security-group-ids sg-abcd1234 
--user-data file://my_script.txt
E' possibile anche recuperare l'informazione dell'user data con il comando
aws ec2 describe-instance-attribute --instance-id i-1234567890abcdef0 --attribute userData

Con la libreria boto3 di SDK è possibile indicare lo script user-data come parametro del metodo per avviare una istanza:

import boto3
ec2 = boto3.resource('ec2')
user_data_script = """#!/bin/bash
   echo "Hello, World!" > /home/ec2-user/hello.txt
"""
instance = ec2.create_instances(
  ImageId='<your-ami-id>',
  MinCount=1,
  MaxCount=1,
  InstanceType='t2.micro',
  UserData=user_data_script
)

Nei template CloudFormation è possibile indicare l'user data di una istanza EC2 come è possibile notare nell'esempio:

Resources:
  WebServerInstance:
    Type: 'AWS::EC2::Instance'
    Metadata:
      Comment1: >-
        Configure to install the Apache Web Server and PHP
      Comment2: Save website content to /var/www/html/index.php
      'AWS::CloudFormation::Init':
        configSets:
          InstallAndRun:
          - Install
          - Configure
        Install:
          packages:
            yum:
              mysql: []
              mysql-server: []
              mysql-libs: []
              httpd: []
              php: []
              php-mysql: []
          files:
            /var/www/html/index.php:
              content:
                ...: null
              mode: '000600'
              owner: apache
              group: apache
            /tmp/setup.mysql:
              content: !Join 
              - ''
              - - 'CREATE DATABASE '
                - !Ref DBName
                - |
                  ;
                - 'GRANT ALL ON '
                - !Ref DBName
                - .* TO '
                - !Ref DBUsername
                - '''@localhost IDENTIFIED BY '''
                - !Ref DBPassword
                - |
                ';
              mode: '000400'
              owner: root
              group: root
            /etc/cfn/cfn-hup.conf:
              content: !Join 
              - ''
              - - |
                  [main]
                - stack=
                - !Ref 'AWS::StackId'
                - |+
                  #riga vuota
                - region=
                - !Ref 'AWS::Region'
                - |+
                  #riga vuota
              mode: '000400'
              owner: root
              group: root
            /etc/cfn/hooks.d/cfn-auto-reloader.conf:
              content: !Join 
              - ''
              - - |
                  [cfn-auto-reloader-hook]
                - |
                  triggers=post.update
                - > path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init
                - 'action=/opt/aws/bin/cfn-init -v '
                - ' --stack '
                - !Ref 'AWS::StackName'
                - ' --resource WebServerInstance '
                - ' --configsets InstallAndRun '
                - ' --region '
                - !Ref 'AWS::Region'
                - |+
                  #riga vuota
                - |
                  runas=root
          services:
            sysvinit:
              mysqld:
                enabled: 'true'
                ensureRunning: 'true'
              httpd:
                enabled: 'true'
                ensureRunning: 'true'
              cfn-hup:
                enabled: 'true'
                ensureRunning: 'true'
                files:
                - /etc/cfn/cfn-hup.conf
                - /etc/cfn/hooks.d/cfn-auto-reloader.conf
        Configure:
          commands:
            01_set_mysql_root_password:
              command: !Join 
              - ''
              - - mysqladmin -u root password '
                - !Ref DBRootPassword
                - ''''
                  test: !Join 
                - ''
                - - '$(mysql '
                - !Ref DBUsername
                - ' -u root --password='''
                - !Ref DBRootPassword
                - ''' >/dev/null 2>&1 </dev/null); (( $? != 0 ))'
            02_create_database:
              command: !Join 
                - ''
                - - mysql -u root --password='
                  - !Ref DBRootPassword
                  - ''' < /tmp/setup.mysql'
              test: !Join 
                - ''
                - - '$(mysql '
                  - !Ref DBUsername
                  - ' -u root --password='''
                  - !Ref DBRootPassword
                  - ''' >/dev/null 2>&1 </dev/null); (( $? != 0 ))'
  Properties:
    ImageId: !FindInMap 
      - AWSRegionArch2AMI
      - !Ref 'AWS::Region'
      - !FindInMap 
        - AWSInstanceType2Arch
        - !Ref InstanceType
        - Arch
    InstanceType: !Ref InstanceType
    SecurityGroups:
    - !Ref WebServerSecurityGroup
    KeyName: !Ref KeyName
    UserData: !Base64 
      'Fn::Join':
      - ''
      - - |
          #!/bin/bash -xe
        - |
          yum install -y aws-cfn-bootstrap
        - |
          # Install the files and packages from the metadata
        - '/opt/aws/bin/cfn-init '
        - ' --stack '
        - !Ref 'AWS::StackName'
        - ' --resource WebServerInstance '
        - ' --configsets InstallAndRun '
        - ' --region '
        - !Ref 'AWS::Region'
        - |+

In questo semplice esempio è possibile notare che sono presenti script di entrambi i tipi di user data separati:

  • cloud-init: in una sezione Metadata vengono divisi i comandi tra package, files, service e command, si rimanda alla documentazione ufficiale per la descrizione approfondita della sintassi di queste sezioni.
  • script: nello script viene installato il pacchetto "aws-cfn-bootstrap" e successivamente viene eseguito un comando "cfn-init" per avviare lo script indicato nel cloud-init.

Questa tecnica è necessaria per permettere a CloudFormation di avviare i vari script nella istanza in fase di creazione, si rimanda alla documentazione ufficiale. Molti esempi di user data sono consultabili nel sito ufficiale.

Con il servizio ElasticIP è possibile creare e gestire indirizzi IPv4 di tipo pubblico e statico per le risorse presenti nel Cloud che dispongono di indirizzi dinamici come le istanze EC2 eseguite nelle subnet pubbliche. Gli indirizzi ip configurati con questo servizio hanno alcune caratteristiche: sono statici e non possono cambiare nel tempo, vengono assegnati ad una regione e non possono essere spostati, assegnando un indirizzo IP elastico ad una istanze EC2 vengono rilasciati tutti gli IP pubblici dinamici se presenti come specificato nella guida ufficiale ma se si associa un IP elastico ad un'istanza che aveva un nome DNS pubblico, questo cambierà per corrispondere al nuovo indirizzo.

Un indirizzo IP elastico dissociato rimane assegnato all'account finché non viene rilasciato esplicitamente ed è prevista una piccola tariffa oraria per gli indirizzi IP elastici che non sono associati a un'istanza in esecuzione. Un indirizzo IP viene assegnato ad un account AWS e rimane assegnato finchè non è rilasciato, grazie a questo servizio è possibile mascherare il guasto di un'istanza o di un software rimappando rapidamente l'indirizzo su un'altra istanza, in alternativa, è possibile specificare l'indirizzo IP in un record DNS per un dominio, in modo che il dominio punti alla tua istanza.

Per ulteriori informazioni, è possibile consultare la documentazione ufficiale per l'intagazione di ElasticIP con i DNS. ElasticIP è un servizio di rete ma è studiato per essere integrato con EC2 quindi si trova nella console e nelle varie configurazioni dentro al servizio di Elastic Cloud Conputing, spesso viene raffigurato con l'icona di una freccia anche se questo non è un logo ufficiale.


Con i comandi da riga di comando CLI è possibile gestire tutti gli IP con il comando specifico il cui comando principale permette di allocale un IP:

aws ec2 allocate-address

ed ovviamente è disposnibile il comando per consultare la lista degli IP gestiti tramite questo servizio:

aws ec2 describe-addresses

dove è possibile vedere anche in quale VPC e quale IP privato corrisponde l'indirizzo pubblico gestito.

Sono disponibili anche i comandi per disassociare e rilasciare un IP:

aws ec2 disassociate-address --public-ip 198.X.Y.Z
aws ec2 disassociate-address --association-id eipassoc-xxxxx
aws ec2 release-address --public-ip 198.X.Y.Z

Con la libreria SDK è possibile gestire gli indirizzi gestiti con il servizio ElasticIP, in particolare i metodi principali sono tre: elenco, impostazione e rilascio

def get_elastic_addresses(profile_name):
  boto3.setup_default_session(profile_name=profile_name)
  ec2 = boto3.client('ec2')
  filters = [] #[{'Name': 'domain', 'Values': ['vpc']}]
  response = ec2.describe_addresses(Filters=filters)
  if 'Addresses' in response:
    return response['Addresses']
  return []
def allocate_address(profile_name,vpc,instance):
  boto3.setup_default_session(profile_name=profile_name)
  ec2 = boto3.client('ec2')
  try:
    allocation = ec2.allocate_address(Domain=vpc)
    response = ec2.associate_address(AllocationId=allocation['AllocationId'],InstanceId=instance)
    return response
  except ClientError as e:
    print(e)
    return e
def release_address(profile_name,allocationId):
  boto3.setup_default_session(profile_name=profile_name)
  ec2 = boto3.client('ec2')
  try:
    response = ec2.release_address(AllocationId='ALLOCATION_ID')
    return response
  except ClientError as e:
    print(e)
    return e

maggiori dettagli e la descrizione di tutti i parametri di questi comandi possono essere trovati nella pagina ufficiale.


Con CloudFormation è possibile gestire gli indirizzi con il servizio ElasticIP, come indicato nell'esempio disponibile nel sito ufficiale, il tipo di risorsa dedicato è di tipo EIP, per esempio:

Resources:
  EC2Instance:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: "../Esempio02istanzeEC2/template.yaml"
      Parameters:
      SubnetId: !Ref SubnetId
      VpcId: !Ref VpcId
      KeyName: !Ref KeyName
      InstanceType: !Ref InstanceType
      SSHLocation: !Ref SSHLocation
  EIP:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !GetAtt EC2Instance.Outputs.InstanceId
Outputs:
  InstanceEIPAddress:
    Description: IP address from Elastic IP
    Value: !Ref EIP
  InstanceId:
    Description: InstanceId of the newly created EC2 instance
    Value: !GetAtt EC2Instance.Outputs.InstanceId

L'esempio completo funzionante può essere trovato al solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio17elasticIP

Il tema della sicurezza non deve mai essere sottovalutato, in particolare quando si lavora in un Cloud AWS è indispensabile prestare attenzione alle regole di accesso alle istanze Ec2 per evitare che qualcuno di non autorizzato acceda alle istanze e faccia danni o rubi informazioni. In particolare, in AWS, per Security Group si intende il sotto-servizio per la definizione delle regole di accesso alle macchine Ec2: di default tutte le interfacce di rete sono bloccate sia in ingresso sia in uscita ma grazie ai Security Group (spesso indicati con la sigla SG) è possibile abilitare porte di rete in ingresso e in uscita. Si rimanda sempre alla documentazione ufficiale per tutti i dettagli riguardo a questo servizio. Il servizio era inizialmente studiato solo per le istanze E22 ma oggi è usato per gestire le regole di rete anche di altri servizi come RDS e ALB, si tuttavia il SG è un sottoservizio del servio Ec2. Purtroppo spesso in lingua italiana il nome di questo servizio viene tradotto con gruppo di sicurezza, questa traduzione spesso può creare confusione quindi è consigliato usare sempre la lingua inglese per evitare fraintendimenti.

Le regole definite con SG sono di due tipi: in ingresso (incoming) e in uscita (outgoing), tipicamente si autorizza tutto il traffico in uscite mentre è importate limitare le regole in ingresso solo ai protocolli, alle porte e agli IP indispensabili. Si rimanda alla documentazione ufficiale per maggior informazioni riguardo alla procedura guidata via web.


La CLI mette a disposizione una serie di comandi per la gestione dei SecurityGroup, si rimanda alla documentazione ufficiale e alla guida per tutti i dettagli. I principali comandi sono:

  • elenco delle regole e il dettaglio di uno specifico
    aws ec2 describe-security-groups 
    aws ec2 describe-security-groups --group-ids sg-903004f8
  • creazione di un nuovo security group
    aws ec2 create-security-group --group-name my-sg --description "My security group" --vpc-id vpc-xxxx
  • aggiunta, rimozione emodifica di una regola all'interno del security group
    aws ec2 authorize-security-group-ingress --group-id sg-903004f8 --protocol tcp --port 3389 --cidr x.x.x.x/x
    aws ec2 authorize-security-group-ingress --group-id sg-903004f8 --protocol tcp --port 22 --cidr x.x.x.x/x
    aws ec2 modify-security-group-rules ...
    aws ec2 revoke-security-group-ingress ..
  • modifica di una reola con tutti i dettagli
    aws ec2 modify-security-group-rules --group-id sg-1234567890abcdef0 --security-group-rules SecurityGroupRuleId=sgr-abcdef01234567890,SecurityGroupRule='{Description=test,IpProtocol=-1,CidrIpv4=0.0.0.0/0}'
  • cancellazione di un security group
    aws ec2 delete-security-group --group-id sg-903004f8

La libreria boto3 del SDK mette a disposizione molti metodi per la gestione dei security group, si rimanda alla documentazione ufficiale per tutti i dettagli, il principale metodo è quello per recuperare l'elenco completo delle regole:

ec2_client = boto3.client('ec2')
response = ec2_client.describe_security_groups()

Si rimanda alla documentazione ufficiale per esempi in altri linguaggi di programmazione come java.


Con CloudFormation è possibile definire regole con il tipo specifico dedicato:

InstanceSecurityGroup:
  Type: AWS::EC2::SecurityGroup
  Properties:
    GroupDescription: Enable SSH access via port 22
    VpcId: !Ref 'VpcId'
    SecurityGroupIngress:
    - IpProtocol: TCP
      FromPort: 22
      ToPort: 22
      CidrIp: !Ref 'SSHLocation'
    - CidrIp: '0.0.0.0/0'
      IpProtocol: TCP
      FromPort: 80
      ToPort: 80

E nella definizione delle istanze è possibile collegare una EC2 ad uno o più gruppi con la definizione delle istanze:

EC2Instance:
  Type: AWS::EC2::Instance
  Properties:
    InstanceType: !Ref 'InstanceType'
    KeyName: !Ref 'KeyName'
    ImageId: !Ref 'LatestAmiId'
    NetworkInterfaces: 
    - GroupSet: [!Ref 'InstanceSecurityGroup']
      SubnetId: !Ref SubnetId
      AssociatePublicIpAddress: true
    DeviceIndex: '0'
    DeleteOnTermination: true

Nella definizione di un security group è possibile definire nell'input come sorgente l'output di un altro security group, per esempio il traffico di un ALB verso un WebServer può essere regolato con la definizione:

ALBSecurityGroup:
  Type: AWS::EC2::SecurityGroup
  Properties:
    GroupDescription: Load balancer traffic
    SecurityGroupIngress:
    - IpProtocol: TCP
      FromPort: '80'
      ToPort: '80'
      CidrIp: 0.0.0.0/0
    VpcId: !Ref VpcId 
WebServerSecurityGroup:
  Type: 'AWS::EC2::SecurityGroup'
  Properties:
    GroupDescription: Enable HTTP access via port 80 locked down to the load balancer + SSH access
    SecurityGroupIngress:
    - IpProtocol: TCP
      From Port: '80'
      ToPort: '80'
      SourceSecurityGroupId: !Ref ALBSecurityGroup

Da notare che in tutti i punti è indispensabile indicare sempre la VPC dove la regola deve essere definita. Si rimanda i vari esempi presenti al solito repository

https://github.com/alnao/AWSCloudFormationExamples

Per il collegamento dei Security Group alle istanze EC2 esistono diversi modi ma il principale per le istanze GNU Linux rimane il protocollo SSH (secure shell h), questo viene configurato di default nelle istanze AMI standard di tipo GNU Linux, per il collegamento è sempre necessario usare una chiave privata in formato PEM configurabile nel sotto-servizio Key Pairs previsto dal servizio Ec2, si rimanda alla documentazione ufficiale per i dettagli di questo servizio. La o le chiavi devono essere agganciate alle istanze Ec2, questa associazione può essere effettuata da console, da SDK o anche da CloudFormation con la proprietà specifica

KeyName: !Ref 'KeyName'

Nella regola del security group è necessario configurare la porta 22, senza questa regola la porta di rete rimarrebbe chiusa e il collegamento non potrebbe avvenire

SecurityGroupIngress:
- IpProtocol: tcp
  FromPort: 22
  ToPort: 22
  CidrIp: !Ref 'SSHLocation'

Per il collegamento alla istanza è possibile un qualunque programma shell e/o bash, come un terminale GNU Linux o Putty per Windows.

ssh utente@indirizzoip -i key_privata.pem

Da notare che le chiavi pubbliche abilitate al collegamento vengono salvate nelle istanze nel file:

.ssh/authorized_keys

della utenza principale (bitnami nel caso di AMI di quel tipo o ec2_user nel caso di istanze di tipo Amazon Linux).


Per le istanze Windows, è possibile abilitare la connessione tramite desktop remoto abilitando il protocollo RDP (remote desktop protocol), per le istanze con questo sistema operativo non è possibile usare una chiave privata ma bisogna usare le credenziali con username e password. L'abilitazione del protocollo RDP è molto semplice: basta infatti abilitare la porta 3389 nel protocollo TCP, si rimanda alla documentazione ufficiale per maggiori informazioni.

Nota: è sempre sconsigliato utilizzare la Cidr di tipo 0.0.0.0/0 tranne per quelle connessioni aperte verso tutta la rete internet, in particolare per l'accesso con i protocolli SSH e/o RDP è consigliato impostare un Ip fisso o un range molto più ristretto.

Simple Storage Service, abbreviato con la sigla S3, è il principale servizio di archiviazione del servizio Cloud di AWS, studiato per memorizzare e recuperare qualsiasi volume di dati, in qualunque momento e da qualunque luogo. Le caratteristiche principali del servizio sono: gli oggetti sono salvati in contenitori chiamati bucket (non a caso il logo di questo servizio è proprio un secchio), ogni elemento ha un nome (key), gli oggetti sono organizzati in strutture (path), l'accesso agli oggetti può essere personalizzato grazie a regole IAM, gli accessi possono essere salvati in registri specifici nel servizio CloudWatch. E' possibile cancellare o spostare oggetti e il servizio permette di attivare il sistema di versioning per avere la storia di tutti gli oggetti. Per qualsiasi informazione e approfondimenti si può fare riferimento alla pagina dedicata al servizio S3 nel sito ufficiale.


La AWS-CLI mette a disposizione tutta una serie di comandi per la gestione dei bucket e degli oggetti, in particolare "mb" può essere usato per creare bucket:

$ aws s3 mb s3://bucket-name

ricordandosi che il nome deve essere globalmente unico (globally unique unique across all of Amazon S3) e deve rispettare le regole imposte da AWS. La lista dei bucket disponibili si recupera con il comando:

$ aws s3 ls

e per cancellare un bucket (che non contiene nessun oggetto) si usa il comando rb:

$ aws s3 rb s3://bucket-name

Per ottenere la lista di tutti gli oggetti contenuti in un bucket:

$ aws s3 ls bucket-name

E per muovere oggetti tra bucket o scaricare un oggetto cancellandolo si usa la sintassi:

$ aws s3 mv s3://bucket-name/example.txt s3://bucket-name2/
$ aws s3 mv s3://bucket-name/filename.txt ./

Mentre per copiare tra bucket o dal sistema locale da/per un bucket si usa la sintassi:

$ aws s3 cp s3://bucket-name/example.txt s3://my-bucket/
$ aws s3 cp ./filename.txt s3://bucket-name
$ aws s3 cp s3://bucket-name/filename.txt ./

I comandi possono essere combinati con i comandi Linux, per esempio per scrivere o leggere un oggetto:

$ echo "hello world" | aws s3 cp - s3://bucket-name/filename.txt
$ aws s3 cp s3://bucket-name/filename.txt -

Il comando Sync permette di tenere sincronizzato una cartella locale con un bucket, quindi eventuali orfani vengono cancellati e nuovi file vengono copiati, la sintassi preve solo la cartella sorgente e il bucket di destinazione:

$ aws s3 sync . s3://my-bucket/path

Per svuotare un bucket da tutti gli oggetti contenuti si può usare il comando rm con il parametro ricorsivo:

$ aws s3 rm s3://my-bucket/path --recursive

è ovvio che bisogna sempre prestate attenzione a questo ultimo comandi in quanto rimuove tutti gli oggetti e, senza sistema di versionamento attivo, gli oggetti vengono cancellati in maniera permanente. Tutti i parametri e le caratteristiche di questo comando sono disponibili alla pagina della documentazione ufficiale.


I template CloudFormation spesso hanno al proprio interno definizione o riferimenti a bucket S3, il tipo specifico AWS::S3::Bucket che ha come unico parametro obbligatorio il nome (che deve sempre rispettare le regole previste da AWS)

Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Ref NomeBucket

La documentazione ufficiale è ricca di esempi e può essere sempre consultata come fonte principale di informazioni. Il più semplice esempio è la gestione di siti web esposti dallo stesso servizio S3 senza l'uso di CloudFront o altri servizi dedicati, ispirato alla documentazione ufficiale con in aggiunta le regole ACL, nel template è indispensabile aggiungere la regola di accesso al bucket tramite un oggetto di tipo BucketPolicy:

S3BucketPolicy:
  Type: AWS::S3::BucketPolicy
  Properties:
    Bucket: !Ref NomeBucket
    PolicyDocument:
    Version: 2012-10-17
    Statement:
     - Sid: AllowSSLRequestsOnly
       Action: 's3:GetObject'
       Effect: Allow
       Resource: !Join
         - ''
         - - 'arn:aws:s3:::'
           - !Ref NomeBucket
           - /*
       Principal: '*'

L'esempio completo funzionante può essere trovato al solito repository

github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio03bucketS3sito

La libreria CDK mette a disposizione classi per la gestione dei bucket, la documentazione ufficiale è ricca di esempi pratici. Per il linguaggio Java la principale risorsa è proprio il builder di Bucket:

Bucket bucket = Bucket.Builder.create(this, "MyBucket")
  .versioned(true)
  .encryption(BucketEncryption.KMS_MANAGED)
  .build();

Per il linguaggio Python la creazione di bucket è ancora più semplice, un esempio ripreso dalla guida ufficiale:

from aws_cdk import (aws_s3 as s3,core)
class MyStack(core.Stack):
  def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
  super().__init__(scope, id, **kwargs)
  # Create an S3 bucket
  bucket = s3.Bucket(self, "MyBucket",
    bucket_name="my-unique-bucket-name",#unique bucket name
    versioned=True, # enable versioning for the bucket
    encryption=s3.BucketEncryption.S3_MANAGED
  )
app = core.App()
MyStack(app, "MyStack")
app.synth()

La libreria SDK è ricca di classi e metodi per la gestione dei bucket e dei contenuti, per il linguaggio di programmazione Python il sito ufficiale mette a disposizione diversi esempi i principali metodi messi a disposizione del servizio S3 sono:

def bucket_list(profile_name):
  boto3.setup_default_session(profile_name=profile_name)
  s3_client = boto3.client("s3")
  return s3_client.list_buckets()["Buckets"]
def object_list(bucket_name, path):
  s3_client = boto3.client("s3")
  response={"objects":[],"folders":[]}
  response["objects"]=[]
  response["folders"]=[]
  if not path: #objects = list(bucket.objects.all())
    response = s3_client.list_objects_v2(
      Bucket=bucket_name, Delimiter="/",
    )
    if "Contents" in response:
      response["objects"]=response["Contents"]
    if "CommonPrefixes" in response:
      response["folders"]=response["CommonPrefixes"]
  else: #objects = list(bucket.objects.filter(Prefix=path))
    response = s3_client.list_objects_v2(
      Bucket=bucket_name, Delimiter="/",
      Prefix=path,
    )
    if "Contents" in response:
      response["objects"]=response["Contents"]
    if "CommonPrefixes" in response:
      response["folders"]=response["CommonPrefixes"]
  return rimuovi_folder_padre(path,response)
def object_list_paginator(bucket_name, path):
  s3_client = boto3.client("s3")
  response={"objects":[],"folders":[]}
  response["objects"]=[]
  response["folders"]=[]
  s3_paginator = s3_client.get_paginator('list_objects_v2')
  for page in s3_paginator.paginate(Bucket=bucket_name, Prefix=path,
        Delimiter='/'): #, StartAfter=start_after):
    if "Contents" in page:
      response["objects"]=response["objects"] + page["Contents"] 
    if "CommonPrefixes" in page:
     response["folders"]=response["folders"] + page["CommonPrefixes"]
  return rimuovi_folder_padre(path,response)
def rimuovi_folder_padre(folder_name, list):
  s3_client = boto3.client("s3")
  el={}
  to_remove=False
  if "objects" in list:
    for o in list["objects"]:
      if o["Key"]==folder_name or o["Key"]==folder_name+"/" :
        el=o
        to_remove=True
  if to_remove:
    list["objects"].remove(el)
  return list
def content_object_text(bucket_name, key):
  s3_client = boto3.client("s3")
  content=s3_client.get_object(Bucket=bucket_name,Key=key)["Body"].iter_lines()
  lista=[]
  for line in content:
    lista.append(str(line.decode("utf-8")))
  return lista
def content_object_presigned(bucket_name, key):
  s3_client_p = boto3.client('s3',region_name="eu-west-1",
     config=boto3.session.Config(signature_version='s3v4',))
  response = s3_client_p.generate_presigned_url('get_object', 
     Params={'Bucket': bucket_name, 'Key':key},ExpiresIn=3600)
  return response
def write_test_file(bucket_name, key, body):
  s3_client = boto3.client("s3")
  OUT_string_encoded = body.encode("utf-8")
  s3_client.put_object(Bucket=bucket_name, Key=key, Body=OUT_string_encoded)
  return True
def delete_all_content_folder(bucket_name,path): 
  s3_client = boto3.client("s3")
  objects = s3_client.list_objects(Bucket=bucket_name, Prefix=path)
  i=0
  for object in objects['Contents']:
    s3_client.delete_object(Bucket=bucket_name, Key=object['Key'])
    i=i+1
  s3_client.delete_object(Bucket=bucket_name, Key=path)
  return i

Nel la libreria per il linguaggio Java le classi disponibili sono nel package:

software.amazon.awssdk.services.s3

Per esempio per ottenere la lista completa di tutti i bucket si può usare il semplice metodo:

S3Client client = S3Client.builder().build();
...
public static List<Bucket> getBucketList(S3Client s3Client){
  ArrayList<Bucket> l=new ArrayList<Bucket>();
  ListBucketsRequest listBucketsRequest = ListBucketsRequest.builder().build();
  ListBucketsResponse listBuckets = s3Client.listBuckets(listBucketsRequest);
  listBuckets.buckets().stream().forEach(x -> l.add(x));
  return l;
}

Un semplice esempio dei vari metodi messi a disposizione della libreria può essere trovato al solito repository oppure mella documentazione ufficiale sono disponibili molti esempi di gestione dei bucket

Il servizio CloudFront è studiato per distribuire contenuti in modo sicuro ad alta velocità, i suoi caso d'uso più comuni sono l'esposizione di un sito web statico, trasmissione di video in streaming e distribuzione di software e aggiornamenti a grandi dimensioni di client. Il servizio è proprio pensato e studiato per condividere dal Cloud oggetti in tutto il mondo attraverso le zone geografica (AZ) e gli Edge di distribuzione risultando integrato con il servizio di firewall (WAF) che garantisce la sicurezza e la gestione della cache ottimizzata proprio per migliorare ulteriormente le performance e diminuire il traffico tra CloudFormation e la sorgente.

Per Edge si intendono i punti di connessione gestiti da AWS:

Il suo funzionamento è descritto dall'immagine del sito ufficiale:

Il servizio non è gratuito ma prevede un piano gratuito senza limiti di tempo con 1 TB di trasferimento dati su Internet al mese, 10.000.000 richieste HTTP/HTTPS al mese e 2.000.000 chiamate di CloudFront Function al mese. Nel sito ufficiale è disponibile una guida che spiega i passi per condividere un sito web statico salvati in bucket S3 e esposto con CloudFront, i passi descritti in questo esempio sono:

  • creazione di un bucket contenente il sito statico
  • creazione della distrubuzione CloudFront configurando il "Origin Domain Name" cioè il bucket S3 sorgente dei contenuti da distribuire
  • creazione dalla regola "Origin Access Control" indispensabile per permettere alla distribuzione di accedere al contenuto nel Bucket, senza questa regola la distribuzione otterrà un brutto errore HTTP di tipo 403 (Forbiden - accesso negato)
  • configurazione della cache "default cache behavior"
  • configurazione delle regole di rete come la gestione del HTTP/HTTPS, il certificato TLS/TTL, un certificato SSL e la configurazione di un firewall tramite il servizio WAF

La distribuzione creata ha sempre come endpoint il tipo xxxxxx.cloudfront.net e da Route53 è possibile creare la regola DNS per puntare a questa distribuzione con un dominio come record A oppure con una regola CNAME. La gestione delle cache è molto evoluta ed ottimizzata in questo servizio, l'invalidazione della cache per il refresh dei contenuti dalla sorgente è chiamata invalidazione della distribuzione ed è un'operazione che può essere eseguita da console o via CLI, si rimanda alla documentazione ufficiale per approfondimenti. Nel blog ufficiale è disponibile una guida per accelerare le prestazioni di un sito WordPress, configurando CloudFront per esporre i media statici del sito in maniera veloce e sicura.


La CLI mette a disposizione una serie di comandi per la gestione delle distribuzioni, si rimanda alla documentazione o ai tanti siti di esempi. I principali comandi sono:

elenco delle distribuzioni:
aws cloudfront list-distributions
elenco delle distribuzioni con solo id e nome:
aws cloudfront list-distributions --output table 
  --query 'DistributionList.Items[*].[Id,Origins.Items[0].DomainName]'
dettaglio di una singola distribuzione:
aws cloudfront get-distribution --id xxxxxxxxxxxx
creazione e modifica di una distribuzione inserendo i parametri in un file di configurazione:
aws cloudfront create-distribution --distribution-config file://distribution.json 
aws cloudfront update-distribution --id xxxx 
  --distribution-config file://distribution.json
invalidazione di una distribuzione e dettaglio dell'operazione:
aws cloudfront create-invalidation --distribution-id xxxx --paths "/*" aws cloudfront get-invalidation --distribution-id xxxx --id yyyy

La libreria SDK per Java e Boto3 per python mettono a disposizione metodi per la gestione delle distribuzioni, i principali metodi sono:

def list_distributions(profile_name):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('cloudfront')
  response = client.list_distributions(MaxItems='200')
  if 'DistributionList' in response:
    if 'Items' in response['DistributionList']:
      return response['DistributionList']['Items'] #['ResponseMetadata']
  return []
def get_distribution(profile_name, distribution_id):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('cloudfront')
  response = client.get_distribution(Id=distribution_id)
  return response['Distribution'] #['ResponseMetadata']

Con CloudFormation è possibile creare le risorse legando il bucket S3 e la distribuzione CloudFormation, ricordandosi che è necessario creare anche la regola OriginAccessControl e il BucketPolicy, risorse necessarie per evitare di avere un "Access denied". Un esempio completo di creazione di una infrastuttura con i 4 componenti:

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: Private
      BucketName: !Ref NomeBucket
  CloudFrontOriginAccessControl:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Description: "OAC for allowing cloudfront to access S3 bucket"
        Name: !Join ['', ['static-hosting-OAC-',!Ref NomeBucket]]
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4
  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
        - DomainName: !Join ['', [!Ref S3Bucket, '.s3.amazonaws.com']]
          Id: static-hosting
          S3OriginConfig:
            OriginAccessIdentity: ""
          OriginAccessControlId:
             Ref: CloudFrontOriginAccessControl
      Enabled: "true"
      DefaultRootObject: index.html
      ...
  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref NomeBucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Principal:
            Service: "cloudfront.amazonaws.com"
          Action: "s3:GetObject"
          Resource: !Join ['', ['arn:aws:s3:::', !Ref NomeBucket, '/*']]

L'esempio completo fuzionante può essere trovaro al solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio07cloudFront

Il servizio AWS Key Management Service, spesso abbreviato con la sigla KMS, è studiato per gestire le fasi entyption di oggetti e della gestione delle chiavi di sicurezza usate in fase di cifratura e decifratura. Essendo un servizio unico e centralizzato garantisce la sicurezza ed è studiato per lavorare con tutti gli altri servizi e a tutte le applicazioni AWS. Visto che si tratta di un argomento molto importante e delicato, si rimanda alla guida ufficiale e alla pagina ufficiale per i dettagli, questo vuole essere solo un riassunto dei comandi principali per poter usare questo servizio. Si rimanda alla documentazione ufficiale anche per i dettagli sulla rotazione delle chiavi e la gestione delle policy di accesso, temi molto importanti che è fondamentale conoscere per garantire la sicurezza nel Cloud, è fondamentale anche ricordare che il servizio presenta dei costi d'uso: per ogni chiave c'è un costo di manutenzione mensile e sono previsti dei costi in base alle richieste di operazioni eseguite. Per usare questo servizio bisogna aver presente la differenza tra chiavi simmetriche e asimmetteriche e bisogna conoscere bene la differenza tra i tipi di chiave: owned (SSE), managed oppure custom.

La procedura di creazione di una chiave da console è molto semplice, richiede queste informazioni:

  • tipo: simmetrico o asimmetrico
  • uso: semplice o hMAC
  • origine: KMS, CloudHSM oppure esterna
  • region: semplice o multi AZ
  • nome, descrizione e tags
  • regole IAM di amministratore e l'elenco degli utenti abilitati all'uso delle chiavi

Con la CLI è possibile gestire il servizio KMS con un gruppo di comandi dedicato, i principali comandi sono:

  • la lista di tutte le chiavi:
    $ aws kms list-keys
  • il dettaglio di una chiave:
    $ aws kms describe-key --key-id <key_id>
  • la chiave pubblica di una chiave di tipo asimmetrico:
    $ aws kms get-public-key --key-id <key_id>
  • comando per la cifratura di un file:
    $ aws kms encrypt --key-id <key_id> --plaintext fileb://in.txt --output text
    $ aws kms encrypt --key-id <key_id> --plaintext fileb://in.txt --output text --encryption-algorithm RSAES_OAEP_SHA_256 --query CiphertextBlob | base64 --decode > out.txt
  • comando la la decifratura di un file:
    $ aws kms decrypt --key-id <key_id> --ciphertext-blob fileb://out.txt --encryption-algorithm RSAES_OAEP_SHA_256 --output text --query Plaintext | base64 --decode
  • comando per creare una chive, per questo comando specifico si rimanda alla documentazione ufficiale per tutti i dettagli, un semplice esempio:
    $ aws kms create-key --key-spec RSA_4096 --key-usage ENCRYPT_DECRYPT

Anche la libreria SDK mette a disposizione molte funzioni per la gestione delle chiavi gestite con il servizio KMS, i principali sono:

def get_keys(profile_name):
  boto3.setup_default_session(profile_name=profile_name)
  kms = boto3.client('kms')
  response = kms.list_keys( Limit=100, )#Marker='string'
  if 'Keys' in response:
    return response['Keys']
  return []
def get_key_detail(profile_name,key_id):
  boto3.setup_default_session(profile_name=profile_name)
  kms = boto3.client('kms')
  response = kms.describe_key(KeyId=key_id)
  if 'KeyMetadata' in response:
    return response['KeyMetadata']
  return {}
def get_keys_detail(profile_name):
  l=[]
  lista=get_keys(profile_name)
  for k in lista:
    l.append ( get_key_detail(profile_name,k['KeyId']) )
  return l
def get_public_key(profile_name,key_id):
  boto3.setup_default_session(profile_name=profile_name)
  kms = boto3.client('kms')
  response = kms.get_public_key(KeyId=key_id)
  return response
def entrypt_file(profile_name,key_id,source_file_path,dest_file_path,algoritm):
  boto3.setup_default_session(profile_name=profile_name)
  kms = boto3.client('kms')
  with open(source_file_path, 'rb') as infile :
    with open(dest_file_path, 'wb') as outfile :
      while True:
        chunk = infile.read(1000)
        if not chunk :
          break
        resp = kms.encrypt(KeyId=key_id, Plaintext=chunk,EncryptionAlgorithm=algoritm)['CiphertextBlob']
        outfile.write(resp)
def decrypt_file(profile_name,key_id,source_file_path,dest_file_path,algoritm):
  boto3.setup_default_session(profile_name=profile_name)
  kms = boto3.client('kms')
  with open(source_file_path, 'rb') as infile :
    with open(dest_file_path, 'wb') as outfile :
      while True:
        chunk = infile.read(1000)
        if not chunk :
          break
        resp = kms.decrypt(KeyId=key_id,CiphertextBlob=chunk,EncryptionAlgorithm=algoritm)['Plaintext']
        outfile.write(resp)
  return None

Con CloudFormation non solo è possibile gestire la creazione delle chiavi ma è possibile anche gestire il modo in cui queste sono usate nelle risorse, questo servizi infatti può essere usato assieme ad altri per cifrare cose che devono esserlo, come per esempio le password di un database o la chiave di accesso di un server SFTP. La sintassi CloudFormation per creare chiavi è molto semplice e necessita solo della lista dei permessi, inoltre è possibile creare da template degli alias da usare come referenze nel codice delle applicazioni:

RdsKey:
  Type: AWS::KMS::Key
  Properties:
    KeyPolicy:
      Version: 2012-10-17
      Id: key-rds
      Statement:
      - Sid: Enable IAM User Permissions
        Effect: Allow
        Principal:
          AWS: !Join
          - ''
          - - 'arn:aws:iam::'
            - !Ref 'AWS::AccountId'
            - ':root'
        Action: 'kms:*'
        Resource: '*'
RdsAlias:
  Type: AWS::KMS::Alias
  Properties:
  AliasName: alias/rds
  TargetKeyId:
    Ref: RdsKey
Outputs:
  RdsAlias:
    Description: 'RDS KMS Encryption Key Alias'
    Value:
      Ref: 'RdsAlias'

Le chiavi possono essere usate per impostare la cifrature di un sistema, per esempio nel caso di database RDS:

MySQLDatabase:
  Type: 'AWS::RDS::DBInstance'
  Properties:
    Engine: MySQL
    DBName: "RDS"
    StorageEncrypted: true
    KmsKeyId: !Ref rdsKey
    MasterUsername: 'dbuser'
    MasterUserPassword: 'Passw0rd!'
    DBInstanceClass: db.t2.small
    AllocatedStorage: 5
    DBSubnetGroupName: !Ref RDSDBSubnetGroup
    VPCSecurityGroups:
      - Ref: RDSSecurityGroup

Il servizio KMS si integra perfettamente anche con Secret Manager, infatti con KMS è possibile gestire il sistema di cifratura delle password salvate,si rimanda alla documentazione ufficiale.

Il recupero dei valori segreti è possibile sempre grazie alle librerie SDK, per esempio in python con la libreria Boto3:

import json
import boto3
import json
secrets = boto3.client("secretsmanager")
rds = json.dumps(secrets.get_secret_value(SecretId="prod/app/Database")['SecretString'])
print(rds)

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.

Uno dei servizi serverless più famosi di AWS è Step Functions, questo servizio permette la definizione di flussi di lavoro con macchine a stati, pensato per lavorare con la maggior parte degli altri servizi AWS e permette di costruire applicazioni distribuite con processi automatizzate. Spesso le singole definizione vengono chiamate State machine (in italiano macchine a stati), spesso abbreviato con la sigla sm. Ad oggi è studiato per poter lavorare con oltre 200 servizi AWS e oltre 1000 API, con la possibilità di creare strutture logiche condizionali e cicli di esecuzioni. Il servizio è studiato per non avere limiti di tempo (il timeout di default per una step function è un mese), prevede 4.000 transazioni di stato gratuite al mese e il costo di 0,025 $ per ogni mille transizioni di stato successive.

Spesso una macchina a stati viene rappresentata nella console web in maniera grafica con un grafico:

I casi d'uso più semplici sono l'invocazione di funzioni Lambda e integrazione con gli altri servizi serverless come Dynamo, SNS e SQS; per esempio si può pensare di voler definire una macchina a stati per salvare un ordine di un e-business, i passi da definire sono il salvataggio dell'ordine in una tabella (Dynamo), l'inserimento dell'ordine in una coda per il magazzino (SQS) e l'invio di una notifica al cliente (SNS).

Tuttavia anche questo servizio ha alcune caratteristiche che lo limitano:

  • ogni SM è sequenzale e, a meno di struttute molto complesse, non si può tornare indietro
  • ogni SM ha un unico inizio e, per terminare correttamente, deve avere un solo punto di fine
  • le esecuzioni hanno dei limiti di esecuzione descritti nella pagina ufficiale
  • ogni esecuzione ha un nome e una arn che identifica l'esecuzione
  • ogni esecuzione può avere in input ma deve essere in formato json
  • una esecuzione non può essere messa in pausa, può solo essere terminata

Tecnicamente i passi da eseguire in una macchina a stati sono rappresentati in un documenti json che prevede un unizio (startAt), una serie di passi che terminano con un End finale.

{
  "StartAt":"CopyInputFile",
  "States":{
    "CopyInputFile":{
      "Type":"Task",
      "Resource":"arn:aws:states:::aws-sdk:s3:copyObject",
      "Parameters":{
         "Bucket":"bucket1",
         "CopySource.$":'States.Format("bucket1/path/{}', $.filename)",
         "Key.$":"States.Format('bucket2/path2/{}-{}', $.filename, $$.Execution.StartTime)"
      },
      "ResultPath":null,
      "Next":"DeleteInputFile"
    },
    "DeleteInputFile":{
      "Type":"Task",
      "Resource":"arn:aws:states:::aws-sdk:s3:deleteObject",
      "Parameters":{
         "Bucket":"bucket1",
         "Key.$":"States.Format(\"path/{}\", $.filename)"
      },
      "ResultPath":null,
      "Next":"End"
    },
    "End":{
     "Type":"Pass",
     "End":true
    }
  }
}

AWS ha studiato un ambiente grafico nella console grafica dove è possibile creare una macchina a stati e definire tutte le configurazioni degli stati:

La documentazione ufficiale e il blog sono ricchi di esempi di questo tipo ovviamente è possibile gestire questo servizio con Cli e in maniera programmatica con SDK e CloudFormation.


La CLI un un numeroso elenco di comandi per la gestione delle state machine, i comandi più usati sono:

  • lista delle macchine a state definite
aws stepfunctions list-state-machines
aws stepfunctions list-state-machines --output table --query 'stateMachines[*].[name,stateMachineArn]'
  • dettaglio di una macchina a stati
aws stepfunctions describe-state-machine --state-machine-arn arn:aws:states:eu-west-1:740456629644:stateMachine:sfBonificiWaitingDaCedacri --output text
  • elenco di esecuzioni
aws stepfunctions list-executions --state-machine-arn arn:aws:states:eu-west-1:740456629644:stateMachine:sfBonificiWaitingDaCedacri --output table --query 'executions[*].[name,status,startDate,stopDate,executionArn]'
  • elenco di esecuzioni
aws stepfunctions list-executions --state-machine-arn arn:aws:states:eu-west-1:740456629644:stateMachine:sfBonificiWaitingDaCedacri --output table --query 'executions[*].[name,status,startDate,executionArn]' --status-filter FAILED
  • dettaglio di una esecuzione
aws stepfunctions describe-execution --execution-arn arn:aws:states:eu-west-1:740456629644:execution:sfBonificiWaitingDaCedacri:testDaCli
--output text
  • avvio di una macchina a stati
aws stepfunctions start-execution --state-machine-arn arn:aws:states:eu-west-1:740456629644:stateMachine:sfBonificiWaitingDaCedacri
  • avvio di una macchina a stati con un parametro
aws stepfunctions start-execution --state-machine-arn arn:aws:states:eu-west-1:740456629644:stateMachine:sfBonificiWaitingDaCedacri --name testDaCli --input "{ \"filename\": \"prova.csv\" , \"SkipDeleteSourceFile\" : false }"

Con SDK è possibile gestire le macchine a stati definire nel servizio Step Function le funzioni base:

def state_machine_list(profile_name):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('stepfunctions')
  response = client.list_state_machines(maxResults=100)
  if 'stateMachines' in response:
    return response['stateMachines']
  return[]
def state_machine_detail(profile_name,smArn):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('stepfunctions')
  response = client.describe_state_machine( stateMachineArn=smArn)
  return response
def state_machine_execution(profile_name,smArn):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('stepfunctions')
  response = client.list_executions(
    stateMachineArn=smArn,
    #statusFilter='RUNNING'|'SUCCEEDED'|'FAILED'|'TIMED_OUT'|'ABORTED',
    maxResults=100,
  )
  if 'executions' in response:
    return response['executions']
  return []
def state_machine_execution_detail(profile_name,esecutionArn):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('stepfunctions')
  response = client.describe_execution(executionArn=esecutionArn)
  return response
def state_machine_start(profile_name,stateMachineArn,name,input):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('stepfunctions')
  response = client.start_execution( stateMachineArn=stateMachineArn, name=name, input=input )
  return response

Con CloudFormation è possibile definire macchine a stati e lavorare con altri servizi in maniera "automatica", la miglior strada è creare un file con la definizione della macchina e richiamare il file da un template, per esempio la definizione della risorsa:

StateMachine:
  Type: AWS::Serverless::StateMachine
  Properties:
    Name: !Ref SFName
    Type: STANDARD
    DefinitionUri: statemachine.yaml
    Role: !GetAtt StateMachineRole.Arn
    DefinitionSubstitutions:
      SourcePath: !Ref SourcePath
      SourceBucket: !Ref SourceBucket
      DestBucket: !Ref DestBucket
      DestFilePath: !Ref DestFilePath
    Logging:
      Destinations:
      - CloudWatchLogsLogGroup:
        LogGroupArn: !GetAtt StateMachineLogGroup.Arn
        Level: ALL
        IncludeExecutionData: True

Nell'esempio si può intuire che c'è da definire una regola IAM per impostare i permessi della macchina (che di default non può fare nulla se non specificato chiaramente in una una regola) e il gruppo di log su CloudWatch. La definizione della macchina può essere descritta in json o un yaml, un semplice esempio di step function che sposta un file è:

StartAt: CopyInputFile
States:
CopyInputFile:
  Type: Task
  Resource: arn:aws:states:::aws-sdk:s3:copyObject
  Parameters:
    Bucket: ${DestBucket}
    CopySource.$: States.Format('${SourceBucket}/${SourcePath}/{}', $.filename)
    Key.$:States.Format('${DestFilePath}/{}-{}',$.filename,$$.Execution.StartTime)
  ResultPath: null
  Next: DeleteInputFile
DeleteInputFile:
  Type: Task
  Resource: arn:aws:states:::aws-sdk:s3:deleteObject
  Parameters:
    Bucket: ${SourceBucket}
    Key.$: States.Format('${SourcePath}/{}', $.filename)
  ResultPath: null
  Next: End
End:
  Type: Pass
  End: true

Come è ovvio questo servizio non lavora mai da solo ma è studiato per essere integrato con altri servizi, nell'esempio completo si può vedere come EventBridge avvia una funzione lambda che poi esegue la macchina a stati che copia il file e poi lo elabora con un altra funzione lambda:

L'esempio completo funzionante può essere trovato al solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio08stepFunction

Amazon API Gateway è un servizio serverless che permette di creare, pubblicare e proteggere le API su qualsiasi tipo di tecnologia e qualsiasi dimensione di traffico, la sicurezza e il monitoraggio viene delegato al servizio di tpo serverless che permette di fare tutto in maniera agile e semplice. Le API fungono da "porta d'ingresso" (detto proprio EndPoint) per consentire alle applicazioni di accedere a dati e ai servizi di backend. La applicazione più usata è l'esposizione di servizi API RESTful oppure API WebSocket che abilitano applicazioni di comunicazione bidirezionale in tempo reale.

Inoltre il servizio è studiato per collegarsi e lavorare con tutti gli altri servizi AWS come Lambda, SQS, SNS e supporta nativamente la gestione di CORS e WAF in modo da rendere sicure le API con controlli di autenticazione e autorizzazione native. Il servizio API Gateway non prevede tariffe minime o costi di avvio ma il costo è calcolato in base al numero di chiamate API e per la quantità di dati trasferiti e, con il modello di prezzi a scaglioni di API Gateway.

La documentazione ufficiale descrive il funzionamento del servizio con uno schema, anche se può sembrare complicato in realtà si può vedere come il Gatewey si interfaccia con i vari servizi AWS come Lambda, può esporre servizi esterni "on-premises" e gestisce una cache interna.

Per ogni API configurata con questo servizio, è possibile impostare le seguenti configurazioni:

  • Resources: per ogni risorsa è necessario impotare il tipo di metodo HTTP (GET, POST, PUT, ec...) e la destinazione (come una funzione Lambda). La console web permette anche di eseguire un test della risorsa e permette di configurare il CORS.
  • Stages: per ogni risorsa è possibile esporre i servizi REST in stage separati che saranno raggiungibili con l'EndPoint:
https://{restapi-id}.execute-api.{region}.amazonaws.com/{stageName}
  • Authorizers: per ogni risorsa è possibile creare uno "strato di sicurezza", per esempio è possibile creare e configurare una lambda function che effettui una verifica di autorizzazione/autenticazione prima della chiamata alla destinazione della risorsa.
  • Gateway responses e Resource policy: per ogni risorsa è possibile aggiungere delle configurazioni particolari, per esempio per un errore 500 è possibile aggiungere header alla risposta
  • Documentation: per ogni risorsa esposta dal servizio è possibile configurare la documentazione secondo gli standard RESTfull come Swagger o altre librerie standard
  • Dashboard: per ogni risorsa è possibile accedere ad una dashboard di monitoraggio del traffico, degli errori e dello stato degli stage
  • API settings: per ogni risorsa è possibile visualizzare le impostazioni base come l'EndPoint e i Timeout (TTL).

Per maggior in formazioni si rimanda alla documentazione ufficiale anche se è sempre conveniente usare CloudFormation o altri sistemi per creare API e risorse.


La CLI mette a disposizione tutta una serie di metodi per la gestione delle API, per la creazione di una risorsa esposta con l'API Gateway per chiamare una funzione lambda si può seguire la procedura descritta nella documentazione ufficiale. I cui principali passi sono:

  • Creazione delle regola IAM che permetterà alla API di eseguire la funzione Lambda (ricordandosi che per default su AWS tutto è negato a meno che non sia strettamente indicato con una regola IAM)
echo "{\"Version\": \"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" > role.json
aws iam create-role --role-name lambda-api-role --assume-role-policy-document file://role.json
aws iam attach-role-policy --role-name lambda-api-role --policy-arn "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
aws iam attach-role-policy --role-name lambda-api-role --policy-arn "arn:aws:iam::aws:policy/service-role/AWSLambdaRole"
  • Creazione della risorsa
aws apigateway create-rest-api --name 'ApiGatewayEsCLI'
{ "id": "r43b0zk1lg", ... }
aws apigateway get-resources --rest-api-id r43b0zk1lg
{ "items": [{"id": "yd6srqipx0",
aws apigateway create-resource --rest-api-id r43b0zk1lg --parent-id yd6srqipx0 --path-part list
{ "id": "w1iqv9",
aws apigateway put-method --rest-api-id r43b0zk1lg --resource-id w1iqv9 --http-method GET --authorization-type "NONE" 
{"httpMethod": "GET","authorizationType": "NONE","apiKeyRequired": false}
aws apigateway put-method-response --rest-api-id r43b0zk1lg --resource-id w1iqv9 --http-method GET --status-code 200 
{ "statusCode": "200" }
  • Destinazione della risorsa: la funzione lambda con l'indicazione della lambda nel parametro uri (con /invocation finale altrimenti non funziona) e la regola iam nel parametro credentials:
aws apigateway put-integration --rest-api-id r43b0zk1lg --resource-id w1iqv9 
--type AWS_PROXY --http-method GET --integration-http-method POST 
--uri arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:xxxx:function:yyyyyy/invocations 
--credentials arn:aws:iam::740456629644:role/lambda-api-role
  • Deploy della risorsa in uno stage
aws apigateway create-deployment --rest-api-id r43b0zk1lg 
--stage-name dev --stage-description 'DEV stage' --description 'First deployment'
  • Test di invocazione
curl https://xxxx.execute-api.eu-west-1.amazonaws.com/dev/list
Per quanto riguarda la gestione delle API la CLI mette a disposizione i comandi:
  • recuperare l'elenco delle API
aws apigateway get-rest-apis --output table --query 'items[*].[name,id,createdDate]'
  • recuperare l'elenco delle risorse di una API
aws apigateway get-resources --rest-api-id r43b0zk1lg --query 'items[*].[id,path,resourceMethods]'
  • recuperare il dettaglio di un metodo
aws apigateway get-method --rest-api-id r43b0zk1lg --resource-id w1iqv9 --http-method GET
  • recuperare l'elenco degli stage di una API
aws apigateway get-deployments --rest-api-id r43b0zk1lg --output table --query 'items[*].[description,id,createdDate]'
  • recuperare l'elenco degli stage
aws apigateway get-stages --rest-api-id r43b0zk1lg --output table --query 'items[*].[deploymentId,stageName,description]'
  • eliminare una api
aws apigateway delete-rest-api --rest-api-id r43b0zk1lg

Per quanto riguarda la libreria SDK, sono stati studiati metodi specifici per la gestione delle API esposte con questo servizio. I principali metodi per recuperare le informazioni di API, risorse, metodi e stages sono:

def api_list(profile_name):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('apigateway')
  response = client.get_rest_apis( limit=100 )
    if 'items' in response:
      return response['items']
  return []
def resouce_list(profile_name,api_ip):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('apigateway')
  response = client.get_resources( restApiId=api_ip, limit=100)
  if 'items' in response:
    return response['items']
  return []
def method_detail(profile_name,api_ip,resouce_id,method):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('apigateway')
  response = client.get_method(restApiId=api_ip,resourceId=resouce_id,httpMethod=method)
  return response
def stage_list(profile_name,api_ip):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('apigateway')
  response = client.get_stages( restApiId=api_ip)
  if 'item' in response:
    return response['item']
  return response

Con il servizio CloudFormation è possibile creare infrastrutture programmandone i componenti, nella versione più strong e pura il servizio permette di creare i componenti previsti dal API Gateway di tipo:

AWS::ApiGateway::RestApi
AWS::ApiGateway::Resource
AWS::ApiGateway::Method
AWS::ApiGateway::Model
AWS::ApiGateway::Stage
AWS::ApiGateway::Deployment

La documentazione ufficiale è ricca di esempi completi per l'utilizzo di questo tipo. Un esempio completo è disponibile al solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio10apiGateway

Tuttavia il servizio CloudFormation mette a disposizione una via molto più veloce e semplice per costruire API di tipo RESTful, usando un unico componente di tipo:

AWS::Serverless::Api

questa tecnica è descritta in un articolo dedicato nella sezione dei template evoluti di CloudFromation.

Il servizio API-Gateway di AWS è stato studiato per permettere ai customer di non doversi preoccupare del processo di autenticazione e autorizzazione: il servizio è studiato per gestire l'accesso alle API esposte, questa tecnica è utile per implementare sistemi di autorizzazioni personalizzate che usano tecnologie come token di connessione OAuth o SAML o che utilizza parametri di richiesta per determinare l'identità del chiamante.

Esistono tre tipi di sistemi di autenticazione

  • IAM Permission: l'accesso alle risorse viene regolato da una policy IAM che gestisce l'autenticazione con la tecnologia "sign v4", con questo è possibile gestire limitazioni tramite IP, VPC o altro
  • Cognito user pool: l'integrazione permette la gestione di una lista di utenti, la lunghezza di vita di un token e non necessità implementazioni ulteriori, l'autorizzazione può essere a livello di metodo
  • Lambda authorizer: si bassa sul concetto di token-bearer come JWT o Oauth  e permette di invocare una funzione lambda che regola l'accesso alla risorse tramite una IAM policy ritornata dalla funzione stessa

Questo articolo vuole descrivere il terzo caso: quando un client effettua una richiesta a uno dei metodi di una API, il servizio API Gateway effettua una chiamata ad una Lambda per verificare l'identità e il livello di autorizzazione e, in base alla policy IAM ritornata dalla lambda, il Gateway permette o meno l'esecuzione del backend della API.

Esistono due tipi di validazioni implementabili con questa tecnica:

  • token che verifica l'identità grazie ad un bearer token (come JWT o OAuth)
  • request che verifica l'identità usando una combinazione dei dati in request come l'header, la query string e le variabili d'ambiente (per i WebSocket si può usare solo questa seconda tecnica)

Vista la delicatezza e l'importanza del tema, la configurazione non è banale e si rimanda alla documentazione ufficiale per tutti i dettagli.


Con CloudFormation è possibile creare una API con questo tipo sistema di autenticazione usando il tipo serverless API che aggiunge una seziona dedicata di Auth dove si possono definire una lista di sistemi di autenticazione, il tipo e la lambda corrispondete

ApiGateway:
  Type: AWS::Serverless::Api
  Properties:
    StageName: !Ref Stage
    OpenApiVersion: 3.0.2
    CacheClusterEnabled: false
    CacheClusterSize: '0.5'
    MethodSettings:
      - ResourcePath: /
        HttpMethod: GET
        CachingEnabled: false
        CacheTtlInSeconds: 300
    Auth:
      DefaultAuthorizer: MyLambdaRequestAuthorizer
      AddApiKeyRequiredToCorsPreflight : false
      AddDefaultAuthorizerToCorsPreflight: false
      Authorizers:
        MyLambdaRequestAuthorizer:
          FunctionPayloadType: REQUEST
          FunctionArn: !GetAtt LambdaAuthorizer.Arn
          Identity:
            Headers:
              - Authorization

L'esempio completo e funzionante può essere trovato al solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio12lambdaAuthorizer

Un esempio di sistema di autenticazione di questo tipo integrato con il servizio Cognito, può essere trovato in questo tutorial e, in futuro, sarà realizzato anche su questo sito in un articolo dedicato al servizio Cognito.

Il processo di estrazione, trasformazione e caricamento di dati, molto spesso nominato con la sigla ETL, consiste nella combinazione di dati provenienti da più fonti in un grande archivio centrale spesso chiamato data warehouse ma si può fare riferimento ad ETL a qualsiasi processo di trasformazione di dati da una sorgente ad una destinazione, si rimanda alla documentazione ufficiale per maggiori dettagli. AWS ha studiato una serie di servizi per la gestione di grandi dimensioni di dati (big-data), la documentazione ufficiale descrive i principali servizi di gestione dei dati:

  • Estrazione, trasformazione e caricamento: Glue
  • Analisi interattiva: Athena
  • Elaborazione di Big Data: EMR
  • Data warehousing: Redshift
  • Analisi in tempo reale: Flink
  • Analisi operative: OpenSearch
  • Governance dei dati: DataZone
  • Archiviazione di oggetti: S3
  • Backup e archiviazione: S3 Glacier e AWS Backup

In questo articolo verrà introdotto il servizio Glue che permette di definire all'interno di un cloud AWS, processi ETL in linguaggio Python o un suo dialetto Spank, questo servizio facilita la scoperta, la preparazione, lo spostamento e l'integrazione dei dati da più origini per l'analisi, il machine learning (ML) e lo sviluppo di applicazioni.

Il funzionamento del servizio è di tipo serverless, quindi si definisce il codice da eseguire e le caratteristiche iii minime iii che eseguono il codice, per questo servizio la configurazione prevede la definizione del numero di DPU, una DPU corrisponde a 4 vCPU e 16 GB di memori. Esistono quattro tipi di processi: Spark, Spark Streaming, Ray e Python Shell, a seconda del tipo ci sono delle configurazioni minime come Python che prevede un minimo di 0,0625 DPU per un minimo di un minuto di esecuzione.

Il dettaglio di quali tipi vengono usati influisce molto sul costo del servizio: il costo è calcolato in base all'utilizzo, al tipo e al numero di DPU, per esempio il costo è di 0,44 dollari per DPU all'ora per ogni processo Spark, fatturato al secondo con un minimo di 1 minuto nella regione irlandese, bisogna sempre ricordare che nel totale bisogna aggiungere anche i costi di traffico o di uso degli altri servizi come i bucket S3 hanno il costo di traffico dati transitati. Per maggiori dettagli si rimanda sempre alla pagina ufficiale del servizio.

Il servizio Glue mette a disposizione un ETL completo: tramite questo servizio è possibile definire crawlers, tabelle e workflow, questi argomenti saranno trattati in un articolo separato, in questo viene descritta solo la funzionalità di definizione dei job.


Per utilizzare al meglio questo servizio bisogna tenere conto che non si tratta di soli processi eseguiti come le Lambda Function, Glue è studiato per integrarsi con un data Catalog e crawlers, per il servizio è stato studiata un AWS Glue Studio: un ambiente web con il quale è possibile gestire i vari job definendo non solo il codice ma anche l'elenco delle esecuzioni e il monitoraggio dei log

La via più semplice per creare e gestire un processo è utilizzare la console Web che mette a disposizione una procedura guidata per la creazione di un processo, nel sito ufficiale è disponibile una guida con dei laboratori pratici per apprendere il funzionamento del servizio e vedere le varie possibilità di utilizzo dei sistemi. La via più semplice per creare un job è utilizzare la console web e AWS Glue Studio che permette di creare processi in pochi passi:

, si rimanda alla documentazione ufficiale per tutti i dettagli che descrivono in maniera semplice ma completa tutte le funzionalità disponibili.


La CLI mette a disposizione una serie di comandi per la definizione e la gestione dei processi, il comando principale per avviare un processo è

aws glue start-job-run --job-name <nome>

comando corrispondente alla procedura da console per eseguire i Job. Si rimanda alla documentazione ufficiale per maggiori dettagli su tutti i comandi disponibili. I principali comandi per il monitoraggio dei job:

  • elenco semplice di tutti i job definiti
    $ aws glue list-jobs
  • elenco di tutti i job con i dettagli
    $ aws glue get-jobs
  • dettaglio di un job
    $ aws glue get-job --job-name <value>
  • elenco e dettaglio delle esecuzioni di un job
    $ aws glue get-job-runs --job-name <value>
    $ aws glue get-job-runs --job-name <value> --run-id <value>

Con la libreria SDK è possibile gestire i job definiti con dei metodi, si rimanda sempre alla documentazione ufficiale, in particolare i principali metodi per la gestione dei job sono:

  • elenco semplice di tutti i job definiti
    response = client.get_jobs(MaxResults=123)
  • dettaglio di un job
    response = client.get_job(JobName=job_name)
  • elenco delle esecuzioni di un job
    response = client.get_job_runs(JobName=job_name,MaxResults=123)
  • dettaglio dell'esecuzione di un job
    response = client.get_job_run(JobName=job_name,RunId=run_id)
  • metodo per avviare un job
    client.start_job_run( ... )

con quest'ultimo comando è possibile eseguire un job, il nome del job è l'unico parametro obbligatorio ma è possibile lanciare l'esecuzione con diversi parametri facoltativi. Si rimanda sempre alla documentazione ufficiale per maggiori dettagli.


Con CloudFormation è possibile creare e gestire processi JOB, si fa sempre riferimento alla documentazione ufficiale che descrive molto chiaramente le proprietà del tipo specifico

Type: AWS::Glue::Job

studiato per la creazione e definizione dei Job di questo tipo. In particolare bisogna sempre ricordare che è necessario indicare la posizione del codice sorgente del job:

Command: 
  # PythonVersion: "3.9"
  Name: glueetl 
  ScriptLocation: !Sub "s3://${Bucket}/${CodePosition}/etl_code.py"py

come indicato nella documentazione ufficiale. Nell'esempio disponibile al solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio16glueJobETL

è possible vedere la definizione di un job tramite template e una piccola infrastruttura con una step function invocata da una regola EventBridge, questa macchina a stati esegue una lambda function e una logica prima di eseguire l'esecuzione del job descritta con Glue.

Da notare che il codice del job è quasi privo di ogni logica ma nell'esempio è possibile vedere come i parametri vengono passaggi al job: alcuni sono costanti indicati direttamente nel template di CloudFormation altri sono indicati nella step function indicando che due parametri di input del job devono contenere i valori ritornati dalla funzione lambda eseguita poco prima nella step function. Da notare anche che nei template di esempio sono sempre create le regola IAM associate anche al Job di tipo Glue in quanto, come sempre, in AWS tutti gli accessi sono negati a meno che non vengano specificati da una regola IAM.

Bisogna ricordare che, quando si lavora con i Job di Glue, il codice è un file separato dai template CloudFormation e che il file deve essere indicato nel parametro ScriptLocation e deve essere caricato manualmente o in maniera parallela rispetto alla gestione dello Stack.

Il Cloud AWS mette a disposizione una serie di servizi per la gestione di base di dati di tipo Serverless, quindi il programmatore non deve preoccuparsi di gestire il server fisico/virtuale dove sono gestiti i dati ma deve solo preoccuparsi dei dati stessi. Il principale servizio di base dati DynamoDB è un servizio proprietario di AWS di tipo noSQL e schema-less quindi non si tratta di un database relazione e non si possono eseguire query nel linguaggio SQL ma sono presenti delle API che permettono di scrivere e leggere i dati. Si tratta di un database di tipo chiave-valore quindi bisogna sempre ricordare che ogni elemento deve sempre avere una chiave univoca che identifica un elemento, i dati contenuti negli elementi sono sempre rappresentati nel formato json standard e le chiavi univoche possono gestire il tipo HASH nativamente.

Il servizio è studiato per gestire carichi di lavoro di qualsiasi tipo, può gestire da poche richieste a più di 10 trilioni di richieste al giorno; infatti la disponibilità, la durabilità e tolleranza ai guasti sono funzionalità integrate che eliminano la necessità di progettare le applicazioni per queste funzionalità. Il costo del servizio è calcolato in base alla quantità di dati e al numero di richieste con 25 GB di archiviazione e fino a 200 milioni di richieste di lettura/scrittura al mese con il Piano gratuito AWS. Si rimanda sempre alla documentazione ufficiale per verificare costi e caratteristiche di DynamoDB.

Il servizio prevede molti sotto-servizi come DynamoDB stream, editor PartiQL, gestione di Clusters e molto altro ma bisogna sempre ricordare che l'entità base di questo servizio sono le tabelle che necessitano "sempre" una partition-key e una sort-key, campi necessari per permettere al servizio di gestire correttamente ai dati.

Nella console è possibile le tabella con una intuitiva console amministrativa:

Dove è possibile anche visualizzare e modificare gli item manualmente:


Con la CLI è possibile gestire tabelle in maniera molto semplice, infatti sono disponibili comandi per tutte le operazioni necessarie sulla base di dati ben descritti nella documentazione ufficiali, i principali comandi sono:

  • elenco delle tabelle:
    aws dynamodb list-tables
  • creazione di una tabella:
    aws dynamodb create-table --table-name nome-tabella --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --table-class STANDARD --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5
  • scrittura di un elemento in una tabella:
    aws dynamodb put-item --table-name nome-tabella --item '{"id": {"S": "1"}, "Name": {"S": "Alberto"}}'
  • recupero di un elemento da una tabella:
    aws dynamodb get-item --consistent-read --table-name nome-tabella --key '{ "id": {"S": "1"}}'
  • operazione di lettura di tutti i dati di una tabella:
    aws dynamodb scan --table-name nome-tabella
  • operazione di lettura di tutti i dati di una tabella filtrando i campi recuperati:
    aws dynamodb scan --table-name nome-tabella --query "Items[*].[id.S,Name.S]"
  • scarico di tutti gli elementi di una tabella in formato json in un file:
    aws dynamodb scan --table-name nome-tabella --select ALL_ATTRIBUTES --output json > a.json
  • eliminazione di una tabella:
    aws dynamodb delete-table --table-name nome-tabella

Bisogna sempre ricordare che il comando che cancella una tabella esegue anche la cancellazione dei dati contenuti, non essendoci nessun controllo sulla presenta di dati in una tabella, bisogna sempre prestare attenzione quando si lancia questo comando per evitare che i dati vengano cancellati per sbaglio.


La libreria SDK boto3 permette di gestire da codice le tabelle in maniera veloce e semplice, la documentazione ufficiale è veramente ricca di esempi, i principali metodi sono:

def table_list(profile_name):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('dynamodb')
  response = client.list_tables( Limit=100)
  tables = []
  for table in response['TableNames']:
    tables.append(table)
  return tables
def write_element_with_id(profile_name, table,element):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('dynamodb')
  if 'id' not in element:
    element['id'] = str(uuid.uuid4()) #calcolo id univoco
  ts= TypeSerializer()
  serialized_post= ts.serialize(element)["M"]
  res = client.put_item(TableName=table,Item=serialized_post)
  res['id']=element['id']
  return res
def get_element_by_id(profile_name,table,id):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('dynamodb')
  response = client.get_item( Key={ 'id': {'S': id, } } , TableName=table,)
  return response['Item']
def delete_element_by_id(profile_name,table,id):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('dynamodb')
  response = client.delete_item( Key={ 'id': {'S': id, } } , TableName=table,)
  return response
def scan_table(profile_name,table):
  boto3.setup_default_session(profile_name=profile_name)
  client = boto3.client('dynamodb')
  response = client.scan(TableName=table,Limit=123)
  return response['Items']
def full_scan_table(profile_name,table):
  boto3.setup_default_session(profile_name=profile_name)
  dynamodb = boto3.resource('dynamodb')#, region_name="eu-west-1"
  table = dynamodb.Table(table)
  today = datetime.datetime.now()
  response = table.scan( ) #FilterExpression=Attr('country').eq('US') & Attr('city').eq('NYC')
  data = response['Items']
  while 'LastEvaluatedKey' in response:
    response = table.scan(ExclusiveStartKey=response['LastEvaluatedKey'], ) #FilterExpression
    data.extend(response['Items'])
  return data

Come si può notare per eseguire la ricerca si usa il metodo scan che prevede un sistema paginante con il parametro LastEvaluatedKey che permette le ricerche multiple, inoltre nelle esecuzioni del comando scan è possibile aggiungere il parametro FilterExpression se necessario aggiungere filtri su attributi non chiave, nell'esempio qui presente si usa un attributi id come chiave primaria ma è possibile costruire chiavi con chiavi primarie più complesse.


Con CloudFormation è possibile creare tabelle in maniera veloce e velocissima, il tipo specifico è veramente basico e prevede come parametri il nome, la definizione della chiave primaria e i parametri del "Throughput".

Dynamo:
  Type: AWS::DynamoDB::Table
  Properties:
    TableName: !Ref DynamoName
    AttributeDefinitions:
      -
        AttributeName: "id"
        AttributeType: "S"
    KeySchema:
      -
        AttributeName: "id"
        KeyType: "HASH"
    ProvisionedThroughput:
      ReadCapacityUnits: "5"
      WriteCapacityUnits: "5"

L'esempio completo e funzionante è disponibile al solito repository

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio11dynamoApiCrud

Ovviamente questo servizio è studiato per interagire con altri servizi di tipo Serverless di AWS come Lambda, SQS, Step function e Glue.

In questo esempio è presente una lambda che scrive e legge i valori dalla tabella, in successivi esempi saranno introdotte anche altre operazioni con altri servizi.

Il Cloud mette a disposizione diversi servizi di tipo DBMS cioè basi di dati (database) di tipo relazionali, il più usato è RDS (abbreviazione proprio di Relational Database Service). Si tratta di un servizio di tipo serverless: usando questo servizio è possibile concentrarsi sulla gestione delle tabelle e tutte le altre strutture del database delegando ad AWS l'onere di gestire il server e il sistema operativo dove viene eseguito il database. RDS è compatibile con i più comuni database: offre la scelta tra sette motori di database popolari tra cui Aurora-MySQL, Aurora-PostgreSQL, MySQL, MariaDB, PostgreSQL, Oracle e SQL Server, per quanto riguarda i database Aurora sarà introdotto un articolo dedicato e in questo articolo saranno descritti solo gli altri tipi.

Il servizio è studiato per la costruzioni di applicazioni di vario tipo: le varie features permettono di scalare i database (con le varie grandezze di engine) e creare strutture sicure con le tecniche di backup, snapshot, read-replica e multiAZ, si rimanda alla documentazione ufficiale per maggiori informazioni e dettagli, questo articolo non vuole essere un manuale d'uso ma solo una introduzione ai comandi per la gestione del servizio. Per usare e configurare al meglio questo servizio bisogna sempre tener presente che

  • non è possibile collegarsi al server fisico dove è in esecuzione il server e si possono usare i vari tool di accesso da remoto, la password di amministrazione viene creata alla creazione e non può essere modificata
  • i database RDS vengono sempre "posizionati" all'interno di una VPC e di un SubnetGroup, inteso come gruppo di subnet, gli viene assegnato un endpoint di accesso non modificabile
  • con un security group è possibile definire la lista delle regole di accesso al database, è sconsigliata la configurazione di apertura delle porte a tutto internet con il CIDR 0.0.0.0/0
  • non esiste una console web con la quale accedere ai dati, la funzionalità di "Query Editor" è disponibile solo per i database Aurora. Anche in maniera programmatica con l'SDK non è possibile eseguire query da RDS non di tipo Aurora
  • da console è possibile configurare tutte le caratteristiche del DB come la gestione dei backup, entryption, storage, allarmi CloudWatch e monitoraggio

Quando si usa questo servizio bisogna sempre ricordare che non è gratuito ma il costo è calcolato in base al tipo di istanza e al tempo di utilizzo, si consiglia di controllare sempre la pagina ufficiale dei prezzi del servizio RDS, è possibile ridurre i costi usando le istanze riservate.


Come per tutti i servizi, la CLI di AWS mette a disposizione una lunga lista di comandi per la gestione dei Database gestiti tramite il servizio RDS. Il più rilevante comando è messo a disposizione per la creazione di istanze dove bisogna inserire le impostazioni base come la classe, subnet e il tipo di engine:

aws rds create-db-instance 
  --db-name database 
  --db-instance-identifier databaseid 
  --db-instance-class db.t3.small 
  --db-subnet-group-name subnet-xxxxxxxxxxxx 
  --engine aurora-mysql 
  --engine-version 5.7.23 
  --master-username username 
  --master-user-password 123password

Per ottenere l'elenco di tutte le istanze e di tutte le proprietà per ogni stanza si può usare il comando:

aws rds describe-db-instances

il cui output può essere semplicifico filtrando i dati di risposta:

aws rds describe-db-instances --query "DBInstances[*].[DBInstanceIdentifier.S,DBInstanceClass.S,Engine.S,DBInstanceStatus.S]"

La libreria SDK mette a disposizione una libreria dedicata alla gestione dei database RDS da non confondere con la libreria per gestire i dati che funziona solo con i database aurora e non descritti in questo articolo. Il principale metodo DescribeDBInstances permette di recuperare da un ambiente cloud tutte le informazioni riguardo ai database RDS disponibli con ilmetodo, un semplice esempio di utilizzo è:

response = rds_client.describe_db_instances(
  #DBInstanceIdentifier=instance_id
)

Esempi di utilizzo di questa libreria può essere trovato al repository ufficiale.


Con CloudFormation è possibile creare e gestire database con RDS, la documentazione ufficiale descrive tutte le proprietà e le possibilità di configurazione, il tipo principale è DBInstance che necessita di alcuni parametri obbligatori, si riporta un semplice esempio funzionante:

RDS:
  Type: 'AWS::RDS::DBInstance'
  Properties:
    Engine: !Ref EngineRDS # MySQL
    DBName: !Ref DBName
    MultiAZ: !Ref MultiAZDatabase
    MasterUsername: !Ref DBUser
    MasterUserPassword: !Ref DBPassword
    DBInstanceClass: !Ref DBInstanceClass
    AllocatedStorage: !Ref DBAllocatedStorage
    VPCSecurityGroups: !GetAtt RDSSecurityGroup.GroupId
    DBSubnetGroupName: !Ref RDSSubnetGroup
    Tags:
      - Key: "Name"
        Value: !Ref AWS::StackName
        Publicly Accessible: !Ref DBPubliclyAccessible #True
    #StorageType: gp2

Il tipo di engine supportati sono ben descritti nella documentazione ufficiale.

L'esempio completo con la configurazione base di un SecurityGroup e la configurazione del SubnetGroup è disponibile al solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio14rds

AWS ha studiato un servizio di base dati specifica per essere usata nelle applicazioni distrubuite e/o ServerLess chiamato Aurora, una database può essere impostato in modalità compatibilità con i DBMS più comuni come MySql oppure PostgreSQL. E' stato studiato per integrarsi maggiormente con gli altri servizi come le Lambda Function e ha dei costi molto più bassi rispetto alle basi dati create con RDS o altre servizi. La documentazione ufficiale descrive il suo funzionamento principale:

Da notare che il servizio prevede due versioni di Aurora, si rimanda alla documentazione ufficiale, è possibile gestire anche la migrazione dalla prima alla seconda versione.


La architettura di AWS mette a disposizione i comandi RDS per la gestione dei database già introdotti nell'articolo dedicato ad RDS e un comando specifico della CLI rds-data studiato appositamente per l'esecuzione di query scritte in linguaggio SQL che funziona con i database RDS di tipo Aurora solo alla prima versione, questo comando non funziona con i database RDS non Aurora o la seconda versione dei DB.

La sintassi di esecuzione non è delle più semplici in quanto il comando necessita diversi parametri, per esempio:

aws rds-data execute-sql 
--db-cluster-or-instance-arn "arn:aws:rds:us-east-1:xx:cluster:my-cluster" 
--schema "" 
--database "MarketPlace" 
--aws-secret-store-arn "arn:aws:secretsmanager:us-east-1:xx:secret:xx-xx"
--endpoint-url https://rds-data.us-east-1.amazonaws.com 
--sql-statements "select * from Table"

Da notare che questo comando, oltre ha bisogno di una secret di collegamento configurata nel servizio SSM, senza questa non è possibile collegarsi al servizio. Esistono comandi più evoluti come begin-transaction e execute-statement che permettono di eseguire gruppi di istruzioni.


Per creare e gestire le risorse con la libreria SDK è possibile usare il client già introdotto nell'articolo sul servizio RDS. Per poter eseguire una query è disponibile un client specifico rds-data simile alla riga di comando, infatti i parametri necessari sono gli stessi: il cluster, il secret, il nome del database e la query sql:

import boto3
rdsData = boto3.client('rds-data')
cluster_arn = 'arn:aws:rds:us-east-1:xxxx:cluster:xxxx'
secret_arn = 'arn:aws:secretsmanager:us-east-1:xxxx:secret:xxxxx'
response1 = rdsData.execute_statement(
  resourceArn = cluster_arn,
  secretArn = secret_arn,
  database = 'mydb',
  sql = 'select * from employees limit 3'
)

Questo metodo funziona solo con la prima versione di Aurora ma per gli altri database di RDS e la seconda versione di Aurora è necessario usare altri sistemi per la connessione ai DB. Una possibilità di connessione programmatica ad un database RDS è usare una libreria esterna come pymysql per python in una funzione lambda:

import pymysql
try:
  conn = pymysql.connect(host=C_rds_host, user=C_user_name, passwd=C_password, db=C_db_name, connect_timeout=5)
except pymysql.MySQLError as e:
  print("ERROR: Unexpected error: Could not connect to MySQL instance. GENERIC")
  print(e)
  return {'statusCode': 500 }
cursor1 = conn.cursor()
cursor2.execute(sql )
conn.commit()
cursor2.close()

da ricordare che la lambda deve essere posizionata nella stessa VPC, per esempio con CloudFormation è possibile definire la funzione con la proprietà dedicata alla configurazione di rete:

Lambda:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: lambda
    Handler: execute_sql.entrypoint
    Runtime: python3.11
    MemorySize: 10240
    Timeout: 900
    Environment: 
      Variables:
        DbUserName: !Ref DbUserName
        DbPassword: !Ref DbPassword
        RdsHost: !Ref RdsHost
        DbName: !Ref DbName
    Role: !GetAtt LambdaExecutionRole.Arn
    VpcConfig:
      SubnetIds: #devono essere le private?
        - !Ref Subnet1
        - !Ref Subnet2
      SecurityGroupIds:
        - !Ref SecurityGroupIds

Come si può vedere dall'esempio, oltre la configurazione della VPC e delle Subnet, è presente anche la configurazione di un security group, questo è necessario per permettere la connessione dalle lambda al database RDS, per maggiori dettagli in una pagina nel sito ufficiale.

Il servizio SNS è studiato appositamente per gestire sistemi di notifiche dove una o più sorgenti abbiano la necessità di inviare notifiche ad uno o più destinatari, questo servizio non è da confondere con il servizio di code SQS che si occupa solo di smistamento di dati e non di notifiche con code come le FIFO. Esistono due tipi di notifiche: A2A e A2P:

  • A2S permette di notificare in maniera asincrona notifiche push anche molti-a-molti come Kinesis, Lambda e API Gateway
  • A2P consente l'invio di messaggi tramite SMS, notiche push ad aplicazioni e/o tramite e-mail.

I casi d'uso più usati sono invio da e per applicazioni per smartphone di messaggi, invio di SMS a clienti, invio di notifiche mail da una applicazione o da altri servizi AWS, procedure AWS Step Function che inviano notifiche nei flusso operativo.

Come ogni servizio AWS prevede un costo calcolato in base ad una tariffa ad uso, si rimanda alla documentazione ufficiale per maggiori dettagli riguardo alle tariffe e tutte le caratteristiche del servizio.


La CLI mette a disposizione una serie di comandi per il controllo del servizio SNS, si rimanda alla documentazione ufficiale. I principali comandi sono:

  • creazione di un topic
    aws sns create-topic --name ProvaAlbertoCLI
  • lista dei topic
    aws sns list-topics
  • lista delle sottoscrizioni
    aws sns list-subscriptions
  • creazione di una sottoscrizione
    aws sns subscribe --topic-arn arn:aws:sns:eu-west-1:xx:ProvaAlbertoCLI --protocol email --notification-endpoint test@email.com
  • pubblicazione di un contenuto in un topic
    aws sns publish --topic-arn arn:aws:sns:eu-west-1:xx:ProvaAlbertoCLI --message "Hello World!"
  • eliminazione di un topic
    aws sns delete-topic --topic-arn arn:aws:sns:eu-west-1:xxx:ProvaAlbertoCLI

Si rimanda sempre alla pagina specifica dove sono elencati tutti i comandi e i parametri previsti.


La libreria SDK mette a disposizione una serie di metodi per la gestione dei topic SNS, per tutti i dettagli si rimanda alla documentazione ufficiale, i principali metodi sono:

def get_sns_list(profile_name):
  boto3.setup_default_session(profile_name=profile_name)
  sns_client = boto3.client('sns')
  topics_iter = sns_client.list_topics()
  if 'Topics' in topics_iter :
    return topics_iter['Topics']
  return []
def create_topic(profile_name, topic_name):
  boto3.setup_default_session(profile_name=profile_name)
  sns_client = boto3.client('sns')
  topic = sns_client.create_topic(Name=topic_name)
  return topic
def delete_topic(profile_name, topic_arn):
  boto3.setup_default_session(profile_name=profile_name)
  sns_client = boto3.client('sns')
  topic = sns_client.delete_topic(TopicArn=topic_arn)
  return topic
def subscribe_topic(profile_name, topic_arn,email):
  boto3.setup_default_session(profile_name=profile_name)
  sns = boto3.client('sns')
  sns_resource = boto3.resource('sns')
  topics_iter = sns.list_topics()
  if 'Topics' in topics_iter :
    for element in topics_iter['Topics']:
      if topic_arn==element['TopicArn']:
        topic_arn = element['TopicArn']
        topic = sns_resource.Topic(arn=topic_arn)
        subscription = topic.subscribe(Protocol='email', Endpoint=email, ReturnSubscriptionArn=True)
        return subscription
  return {}
def get_subscriptions(profile_name,topic_arn):
  boto3.setup_default_session(profile_name=profile_name)
  sns_client = boto3.client('sns')
  subscriptions = sns_client.list_subscriptions()
  if 'Subscriptions' in subscriptions:
    list=[]
    for el in subscriptions['Subscriptions']:
      if topic_arn == el['TopicArn']:
        list.append(el)
      return list
  return []
def publish(profile_name,topic_arn,post):
  boto3.setup_default_session(profile_name=profile_name)
  #sns_client = boto3.client('sns')
  sns_resource = boto3.resource('sns')
  topic = sns_resource.Topic(arn=topic_arn)
  result = topic.publish(Message=post)
  return result

Il servizio CloudFormation permette la definizione e la gestione di SNS, si rimanda alla documentazione ufficiale per tutti i dettagli e la descrizione di tutte le proprietà. Il tipo base permette di definire un sistema di notifiche con poche proprietà:

GenericSnsTopic:
  Type: AWS::SNS::Topic
  Properties:
    DisplayName: uploadGenericSns
    TopicName: uploadGenericSns

Inoltre in un template è possibile anche impostare le sottoscrizioni con una risorsa specifica:

AlarmTopic:
  Type: AWS::SNS::Topic
  Properties:
    DisplayName: uploadGenericSns
    TopicName: uploadGenericSns
    Subscription:
    - Protocol: email
      Endpoint: !Ref NotificationEmail
  NotificationEmail:
    Type: AWS::SNS::Subscription
    Properties:
      Endpoint: example@mail.com
      Protocol: email
      TopicArn: !GetAtt AlarmTopic.Arn

Per tutti i dettagli si rimanda alla documentazione ufficiale, si possono trovare alcuni esempi nel solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio13lambdaApplicationS3Utils

Il servizio SQS di AWS permette di creare code di dati, il nome deriva dall'inglese Simple Queue System. In Infromatica per coda si intende una struttura dati costituita come raccolta di entità gestite in una sequenza che può essere modificata aggiungendo entità a un estremo e rimuovendole dall'altro estremo, si rimanda alla pagina wiki per maggiori dettagli sui vari tipi di code e il loro funzionamento.

Su AWS il servizio SQS ha determinate caratteristiche che lo distinguono da sistemi simili, le principali caratteristiche sono:

  • è di tipo Serverless quindi risulta completamente gestito
  • non ha un limite di scrittura/lettura (throughput) e non ha limiti di numero di entità gestite
  • il limite massimo di retenzione di una entità è di 4 giorni come default che può essere modificato fino a 14 giorni massimo
  • ogni entità ha un limite massimo di 256 Kb
  • è possibile avere entità duplicate e possono essere ordinate, di default infatti le code non sono di tipo FIFO ma può essere attivata come opzione avanzata
  • i servizi che scrivono nella coda sono detti Producer
  • i servizi che leggono dalla coda sono Consumer ed è loro compito eliminare l'elemento letto
  • è possibile gestire la scalabilità dei Consumer tramite AutoScaling grazie ad allarmi di CloudWatch
  • è possibile gestire la sicurezza tramite sistema di entryption con KMS (client side) ed è possibile definire regole di acesso (access policy) trmiate il servizio IAM
  • il servizio prevede molti parametri e configurazioni avanziate per la gestione di un DLQ (dead letter queue), un DQ (delay queue) e il polling (long polling)

Il costo del servizio è calcolato in base all'uso con un milione di richieste al mese gratuite, si rimanda al sito ufficiale per tutti i dettagli sul servizio.


La CLI mette a disposizione una serie di comandi per la gestione delle code SQS, i principali comandi sono:

  • lista delle code:
    aws sqs list-queues
    aws sqs list-queues --queue-name-prefix "rahul"
  • creazione di una nuova coda:
    aws sqs create-queue --queue-name sqs-formaz-with-tag --tags "env"="formazione" --attributes DelaySeconds=10
  • gestione delle entità nella coda:
    aws sqs get-queue-attributes --queue-url https://sqs.us-east-1.amazonaws.com/<aws-account-number>/sqs-formaz-with-tag --attribute-names All
    
    aws sqs send-message --queue-url https://sqs.us-east-1.amazonaws.com/<aws-account-number>/sqs-formaz-with-tag --message-body "Test message to my-sqs-using-cli sqs"
    
    aws sqs receive-message --queue-url https://sqs.us-east-1.amazonaws.com/<aws-account-number>/sqs-formaz-with-tag
    
    aws sqs purge-queue --queue-url https://sqs.us-east-1.amazonaws.com/<aws-account-number>/sqs-formaz-with-tag
  • eliminazione di una coda:
    aws sqs delete-queue --queue-url https://sqs.us-east-1.amazonaws.com/<aws-account-number>/sqs-formaz-with-tag

La libreria SDK mette a disposizione una serie di funzioni per la gestione delle code SQS, nel sito ufficiale possono essere trovati esempi dei principali linguaggi di programmazione come Java , Javascript e Python tramite la libreria boto3, i metodi principali sono:

def get_sns_list(profile_name):
  boto3.setup_default_session(profile_name=profile_name)
  sqs_client = boto3.client("sqs") #, region_name=AWS_REGION
  topics_iter = sqs_client.list_queues(
    MaxResults=100
  )
  if 'QueueUrls' in topics_iter :
    return topics_iter['QueueUrls']
  return []
def create_queue(profile_name, queue_name,delay_seconds,visiblity_timeout):
  boto3.setup_default_session(profile_name=profile_name)
  sqs_client = boto3.client("sqs") #, region_name=AWS_REGION
  response = sqs_client.create_queue(QueueName=queue_name,Attributes={
    'DelaySeconds': str(delay_seconds),
    'VisibilityTimeout': str(visiblity_timeout)
     # 'FifoQueue': 'true'
  })
  return response
def delete_queue(profile_name, queue_name):
  boto3.setup_default_session(profile_name=profile_name)
  sqs_client = boto3.client("sqs") #, region_name=AWS_REGION
  response = sqs_client.delete_queue(QueueUrl=queue_name)
  return response
def get_queue(profile_name, queue_url):
  boto3.setup_default_session(profile_name=profile_name)
  sqs_client = boto3.client("sqs") #, region_name=AWS_REGION
  response = sqs_client.get_queue_attributes( QueueUrl=queue_url, AttributeNames=['All'])
  if 'Attributes' in response:
    return response['Attributes']
  return {}
def send_queue_message(profile_name,queue_url,msg_attributes,msg_body):
  boto3.setup_default_session(profile_name=profile_name)
  sqs_client = boto3.client("sqs") #, region_name=AWS_REGION
  response = sqs_client.send_message(QueueUrl=queue_url,
    MessageAttributes=msg_attributes,
    MessageBody=msg_body)
  return response
def receive_queue_messages(profile_name,queue_url):
  boto3.setup_default_session(profile_name=profile_name)
  sqs_client = boto3.client("sqs") #, region_name=AWS_REGION
  response = sqs_client.receive_message(QueueUrl=queue_url,MaxNumberOfMessages=10)
  if 'Messages' in response:
    return response['Messages']
  return []
def delete_queue_message(profile_name,queue_url, receipt_handle):
  boto3.setup_default_session(profile_name=profile_name)
  sqs_client = boto3.client("sqs") #, region_name=AWS_REGION
  response = sqs_client.delete_message(QueueUrl=queue_url,ReceiptHandle=receipt_handle)
  return response

Con CloudFormation è possibile gestire le risorse di SQS con il tipo specifico

SqsQueue:
  Type: AWS::SQS::Queue
  Properties:
    QueueName: !Ref QueueName
    VisibilityTimeout: 180
    Tags:
    -
      Key: StackId
      Value: !Ref AWS::StackId

Inoltre bisogna ricordarsi che, in presenza di producer/consumer, è sempre necessario gestire le regole di accesso tramite la definizione di una regola IAM, per esempio:

SqsQueueLambdaConsumer:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: lambda
    Handler: consumer.entrypoint
    Runtime: python3.8
    MemorySize: 1280
    Timeout: 900
    Role: !GetAtt SqsQueueLambdaIamRole.Arn
SqsQueueLambdaIamRole:
  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:
            - sqs:GetQueueUrl 
            - sqs:SendMessage
            - sqs:ReceiveMessage
            - sqs:DeleteMessage
            Resource:
            - !GetAtt SqsQueue.Arn

L'esempio completo di coda SQS con un funzioni Lambda producer/consumer è disponibile nel solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio18sqs

Questo articolo non vuole essere una lista la lista dei servizi disponibili, ben descritti nel sito ufficiale, ma questo gruppo di articoli vuole essere una esposizione di esempi pratici di combinazione dei vari servizi. I siti di riferimento sono il centro per sviluppatori e le sezioni dedicate alla IA generativa e al concetto di Machine laerning. Fortunatamente la maggior parte dei linguaggi di programmazione è supportata dal cloud e la lista completa con esempi pratici può essere trovata nel sito ufficiale.

Gli strumenti di sviluppo AWS sono creati per lavorare con AWS e per facilitare al tuo team l'avvio di configurazione e produttività e si basano sul concetto principale di DevOps, si rimanda alla sito ufficiale per maggior dettagli riguardo a questo paradigma della programmazione e alla pagina introduttiva dei servizi dedicati allo sviluppo integrato.

Sono disponibili i vari Tool di AWS come CLI, SDK, CDK e CloudFormation (Infrastructure as Code), questi argomenti sono introdotti in altri articoli e in questa sezione non saranno descritti. Oltre ai servizi messi a disposizione esistono molti Toolkit e IDE specifici per lavorare su AWS come l'estensione ufficiale per Microsoft Visual Studio Code e/o Eclipse, tale toolkit è introdotto nell'articolo sullo sviluppo delle Lambda ed è consigliato agli utenti anche poco esperti, Cloud9 è il tool IDE studiato integrato con la console web di AWS e studiato per integrarsi con tutti gli altri servizi DevOps.


Il servizio Cloud9 è un ambiente di sviluppo integrato (IDE) che permette scrittura di codice, la sua esecuzione anche tramite debugging attraverso browser collegato alla console web di AWS. Include un editor di codice, un dispositivo di esecuzione di debug e di un terminale integrato con tutti gli altri servizi del Cloud. La sua principale potenza è che è studiato per poter essere usato con la maggior parte dei linguaggi di programmazione, tra cui JavaScript, Python, Java e altri. Questo servizio è studiato per permettere ai customer di non dover installare file o configurare il computer di sviluppo ogni volta che inizia un nuovo progetto.

La procedura guidata da console web è molto semplice e necessita pochissime informazioni: un nome simbolico, il tipo di istanza (t2.micro è più che sufficiente e consigliata per evitare costi troppo elevati), il tipo di immagine (Linux 2023), la region, il tipo di connessione e la subnet di destinazione, infatti oltre alle informazioni base bisogna ricordarsi che, trattandosi di sistema virtualizzato, bisogna prestare attenzione agli aspetti di sicurezza, si rimanda alla documentazione ufficiale per maggiori dettagli.

La creazione di ambienti (detto environment) è possibile tramite un comando specifico della CLI che necessita tutti i parametri necessari nella procedura guidata:

aws cloud9 create-environment-ec2
--name my-demo-environment
--description "This environment is for the AWS Cloud9 tutorial."
--instance-type t2.micro
--image-id resolve:ssm:/aws/service/cloud9/amis/amazonlinux-1-x86_64
--region MY-REGION
--connection-type CONNECT_SSM
--subnet-id subnet-1234xxxxx

Questo servizio è stato studiato per essere multi-utente: più utenti possono essere collegati allo stesso ambiente di sviluppo con la possibilità.

Trattandosi di un servizio di sviluppo, è previsto i repository GIT e in particolare è studiato per integrarsi perfettamente con CodeCommit, si rimanda alla documentazione ufficiale dove sono descritti i passi da eseguire per eseguire l'operazione di clonaggio di un repository.

Quando si usa questo servizio bisogna sempre ricordare che l'uso è gratuito ma sono a pagamento tutte le risorse usate, soprattutto l'istanza EC2 usata come base del servizio, si rimanda alla documentazione ufficiale per maggiori dettagli.

La relazione tra AWS è il linguaggio di programmazione Java è sempre stato molto travagliato visto che si tratta di un linguaggio di proprietà della concorrenza ma visto che è uno dei linguaggi più usati al mondo è stato inevitabile al gestore del Cloud permettere l'uso del linguaggio nei vari servizi. In questo articolo vengono riassunti alcune modalità di sviluppo delle lambda function in linguaggio Java. Il titolo di questo articolo fa sorgente immediata un grosso problema problema: le Java Lambda Function (o lambda expression) sono una tecnica per scrivere blocchi di codice anonimi del tipo:

(parameter1, parameter2) -> { codeBlock(); }

questa tecnica non è da confondere con le AWS Lambda Function, servizio specifico per definire blocchi di codice eseguibile in modalità serverless. In questo sito si cerca sempre di distinguere le due cose anche se, avendo lo stesso nome, è impossibile non fare confusione.

Ad oggi, le versioni supportate sono le versioni stabili dalla 8 alla recente 21, in questo articolo si fa riferimento sempre alla versione 8 per retro-compatibilità, per maggior informazioni si rimanda sempre alla documentazione ufficiale dove sono indicati anche i passi per la creazione di AWS Lambda function da console web. Inoltre è disponibile una bellissima libreria SDK per permettere l'integrazione con altri servizi del Cloud, si rimanda sempre alla documentazione ufficiale per maggiori dettagli.

Come per le AWS Lammbda Function scritte negli altri linguaggi, è necessario definire un punto di ingresso detto Handler che quasi sempre viene indicato con un metodo chiamato handleRequest, questo metodo poi definisce la logica di implementazione:

package example;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class HandlerIntegerJava implements RequestHandler<String, String>{
  @Override
  public String handleRequest(String event, Context context){
    LambdaLogger logger = context.getLogger();
    logger.log("EVENT TYPE: " + event.getClass().toString());
    return event.toLowerCase();
  }
}

Nella definizione da console o in maniera programmatica è necessario poi indicare il pacakge e il Handler:


Attraverso l'ambiente di sviluppo MS Visual studio è possibile creare e gestire le AWS Lambda function in maniera veloce ed efficace, è consigliato l'utilizzo dell'estensione ufficiale AWS Toolkit:

che permette di gestire, creare e invocare le AWS Lambda function in combinazione alla CLI e alla CLI-SAM indispensabili per l'utilizzo di questi sistemi.

Per creare una lambda da zero è possibile usare il comando:

> Create lambda SAM application

dalla riga di comando dell'applicazione (Ctrl+Maiusc+P), in questa piccola procedura guidata è possibile selezionare il tipo di linguaggio (java e maven), il modello (consigliato il base HelloWorld) e la cartella dove salvare il progetto. Il generato è un piccolo progetto con i componenti:

  • cartella event con un file json di esempio che permetterà di eseguire i test da locale
  • il file template.yaml con il template completo di cloudformation con la definizione della lambda e la definizione della API tramite gateway
  • il progetto java-maven in una sottocartella con la classe App già pronta per essere eseguita

Da notare che trattandosi di un piccolo template CloudFormation è possibile eseguire il rilascio in AWS con i comandi CLI-SAM:

sam build 
sam deploy --guided 
sam delete --stack-name es03j

Ma è possibile eseguire il rilascio anche con il plugin del programma con il comando:

> AWS deploy sam application

La guida completa del plugin e di tutte le funzionalità disponibili è presente nel sito ufficiale. Bisogna sempre ricordare che è possibile invocare le AWS Lambda function da CLI con il comando:

aws lambda invoke --function-name function_name --payload file://event.json out.json

da console oppure anche dal programma Visual Studio è possibile eseguirle con una semplice interfaccia. Il progetto di esempio creato dal plugin è più complesso rispetto al metodo semplice descritto sopra in quanto questo prevede in request un oggetto proveniente dal ApiGateway e in response un oggetto secondo il formato http standard per il ApiGateway, l'esempio base prevede questa forma:

public APIGatewayProxyResponseEvent handleRequest(
    final APIGatewayProxyRequestEvent input, final Context context) {
  Map<String, String> headers = new HashMap<>();
  headers.put("Content-Type", "application/json");
  headers.put("X-Custom-Header", "application/json");
  APIGatewayProxyResponseEvent response = 
    new APIGatewayProxyResponseEvent().withHeaders(headers);
  try {
    final String pageContents = "hello world";
    String output = String.format("{ \"message\": \"%s\" }", pageContents);
    return response.withStatusCode(200).withBody(output);
  } catch (IOException e) {
    return response.withBody("{}").withStatusCode(500);
  }
}

Il codice di queste semplici AWS Lambda Function scritte in linguaggio Java sono disponibili in un repository specifico:

https://github.com/alnao/AwsLambdaExamples

Negli esempi creati da Visual Studio Code, il plugin genera un template CloudFormation per la gestione delle risorse AWS, in particolare la CLI-SAM provvede anche alla compilazione della classe java e al rilascio delle classi, bisogna ricordare che il file jar deve essere rilasciato in un bucket S3, anche usando la funzionalità del plugin:

> AWS deploy sam application

bisogna indicare il nome del bucket di appoggio. Nel output del comando viene visualizzato lo stato di avanzamento del rilascio e l'esito finale del caricamento.

Con CloudFormation è possibile creare una funzione AWS-Lambda in maniera molto veloce usando maven e i vari modelli messi a disposizione dal framework serverless, in particolare la classe java deve essere definita in un progetto maven con le dipendenze già viste in precedenza. Nel template di definizione delle risorse è possibile definire le varie caratteristiche della funzione e la definizione di un metodo/stage con API-Gateway per esporre la funzione come servizio HTTP-Rest:

HelloWorldFunction:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: HelloWorldFunction
    Handler: helloworld.App::handleRequest
    Runtime: java8
    Environment:
      Variables:
         PARAM1: VALUE
    Events:
      HelloWorld:
        Type: HttpApi
        Properties:
          ApiId: !Ref ES15HttpApi
          Path: /hello
          Method: GET
ES15HttpApi:
  Type: AWS::Serverless::HttpApi
  Properties:
    StageName: !Ref StageName
    RouteSettings:
      "GET /hello":
        ThrottlingBurstLimit: 500

L'esempio completo è disponibile al solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio15lambdaJava
CloudFormation è uno dei servizi più complessi di AWS ma allo stesso tempo è uno dei più potenti. In questa serie di articoli vengono esposti alcuni esempi completi di template avanzati con lo scopo di costruire infrastrutture usabili in ambienti reali. Si rimanda sempre alla documentazione ufficiale per tutti i dettagli del servizio, bisogna sempre ricordare che l'uso di questo è gratuito ma tutte le risorse create e gestite sono a pagamento quindi bisogna sempre prestare attenzione alla creazione di stack con risorse a pagamento. In fase di realizzazione di template è possibile usare la tecnica Nested stacks per creare template riusabili e innestabili in altri template evitando di scrivere file yaml (o json) lunghi e molto complessi, nella documentazione ufficiale è specificata la semplice definizione:
Nested stacks are stacks created as part of other stacks
Con questa tecnica infatti è possibile usare il tipo  AWS::CloudFormation::Stack per eseguire l'innesto di un template dentro ad un altro, l'uso di questa tecnica è frequente quando si vogliono usare template già pronti dal sito ufficiale o personalizzati integrandoli in un template principale (spesso chiamato root nei vari documenti o siti di riferimento).
Un semplice esempio è creare un template con innestato un template ufficiale:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Template che crea una VPC richiamando il template ufficiale
Parameters:
  CidrBlockVPC:
    Description: 'The IP address range to VPC'
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 10.184.0.0/16
    AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x
  CidrBlockSubnetA:
    Description: 'The IP address range to Subnet A'
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 10.184.1.0/24
    AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
  CidrBlockSubnetB:
    Description: 'The IP address range to Subnet B'
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 10.184.2.0/24
    AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
  CidrBlockSubnetC:
    Description: 'The IP address range to Subnet A'
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 10.184.3.0/24
    AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
  CidrBlockSubnetD:
    Description: 'The IP address range to Subnet B'
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 10.184.4.0/24
    AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
Resources:
  VPC:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/ecs-refarch-cloudformation/infrastructure/vpc.yaml
      Parameters:
        EnvironmentName: !Ref AWS::StackName #usato come tag per le risorse
        VpcCIDR: !Ref CidrBlockVPC # "10.84.0.0/16"
        PublicSubnet1CIDR: !Ref CidrBlockSubnetA # "10.84.1.0/24"
        PublicSubnet2CIDR: !Ref CidrBlockSubnetB # "10.84.2.0/24"
        PrivateSubnet1CIDR: !Ref CidrBlockSubnetC # "10.84.3.0/24"
        PrivateSubnet2CIDR: !Ref CidrBlockSubnetD # "10.84.4.0/24"
Un occhio attento avrò notato che in questo esempio viene usato un template da un bucket S3, infatti esiste un repository ufficiale dove ci sono moltissimi template già pronti all'uso:
https://github.com/aws-samples
Nella maggior parte degli articoli che seguiranno verranno usati alcuni template pubblici ufficiali ma sarà sempre chiara la sorgente a la tecnica di import, in alcuni esempi i template ufficiali vengono scaricati e modificati ma è sempre indicata la sorgente originale e viene sempre indicata la risorsa aggiunta al template.

All'interno dei template CloudFormation è possibile creare delle condizioni in una sezione Conditions, queste possono gestire la creazione di alcune risorse in maniera semplice in modo da poter avere un unico template con delle condizioni al suo interno. Il caso d'uso più semplice è creare lo stesso templare per l'ambiente di produzione e l'ambiente di test ma creare risorse diverse a seconda del tipo di ambiente, spesso questa tecnica viene chiamata come enviroments-template. La base è dichiarare la condizione e il suo valore che può dipendere da parametri o mappings, per esempio:
Conditions:
  CreateVolume: !Equals [!Ref EnvName, prod]
All'interno di queste condizioni possono essere usate tutte queste istruzioni logiche:
  • Fn::And
  • Fn::Equals
  • Fn::If
  • Fn::Not
  • Fn::Or
All'interno delle risorse è possibile aggiungere una proprietà specifica con il riferimento al nome della condizione dichiarata nella sezione Condition: CreateVolume. Esaminando un esempio di creazione di un disco solo se si tratta di un ambiente di produzione mentre il disco non verrà creato se si tratta di altro ambiente:
Esempio20conditionsVolumeAttachment:
  Type: AWS::EC2::VolumeAttachment
  Condition: CreateVolume
  Properties:
    InstanceId: !Ref Esempio20conditionsInstance
    VolumeId: !Ref Esempio20conditionsVolume
    Device: /dev/sdh
Esempio20conditionsVolume:
  Type: AWS::EC2::Volume
  Condition: CreateVolume
  Properties:
    Size: 10
    AvailabilityZone: !Ref RegionAZ
All'interno della sezione di Outputs è possibile fare rifermenti a valori condizionali, per esempio è possibile ritornare il riferimento al Volume creato, anche in questo caso il messaggio di output sarà creato a seconda della condizione dichiarata all'inizio del template.
Outputs:
  StackName:
    Description: Deployed StackName for update
    Value: !Ref AWS::StackName
  VolumeId:
    Description: Volume ID if created
    Condition: CreateVolume
    Value: !Ref Esempio20conditionsVolume

All'interno dei template CloudFormation è possibile usare una sezione Mappings per la definizione di coppie chiave-valori come struttura dati utilizzabili nei template con l'operatore Fn::FindInMap, la documentazione ufficiale del comando è ricca di esempi. Un esempio di utilizzo di questa tenica può essere la definizione di un blocco per descrivere la dimensione di istanze a seconda di diversi ambienti:
Mappings:
  EnvInstance:
    dev:
      EC2Type: t2.micro
    prod:
      EC2Type: t2.small
Il valore è recuperabile all'interno dei template nel blocco di una risorsa usando FindInMap con i tre parametri: il nome definito nel mapping, il valore recuperato da parametri o costanti e il nome della proprietà:
InstanceType: !FindInMap [EnvInstance, !Ref 'EnvName', EC2Type]
Un esempio dove sono utilizzate le tecniche di condition e mapping combinate per la creazione di istanze EC2 è disponibile al solito repository:
https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio05conditions

Il servizio Systems Manager, spesso abbreviato con la sigla SSM, mette a disposizione un Parameter Store per la gestione di valori dinamici che possono essere usati come parametri nei template CloudFormation. Questa tecnica viene molto usata per usare lo stesso template in account diversi per creare livelli di ambienti diversi (dev/test/produzione) oppure replicare più volte la stessa infrastruttura in più istanze (per esempio per clienti diversi in account diversi). Secondo la documentazione ufficiale l'utilizzo dei parametri con SSM non prevede nessun costo aggiuntivo a meno che non si decida di usare parametri di tipo avanzato, in questo articolo saranno usati solo i tipi standard.

Da console è possibile vedere l'elenco di tutti i parametri, il tipo e la data di modifica, ovviamente è possibile creare e modificare i parametri esistenti:

Nella guida ufficiale sono descritti tutti i passi e le modalità per la creazione dei parametri, in particolare come standard è possibile vedere che i nomi dei parametri sono impostati con

/livello1/livello2/.../nome-parametro

in questo modo è possibile creare una sorta di gerarchia dove ogni livello raggruppa i successivi. Per esempio tutte i parametri di una applicazione possono essere creati con:

/nome-applicazione/ambito1/nome-parametro1
/nome-applicazione/ambito1/nome-parametro2
/nome-applicazione/ambito2/nome-parametro3

Questo standard è suggerito anche per somigliare ad alcuni parametri che sono messi a disposizione da AWS come parametri di architettura, l'elenco dei parametri ufficiali è disponibile nel sito ufficiale e può essere usata in CloudFormation ma in qualsiasi altro punto del Cloud.


La CLI mette a disposizione una serie di comodi comandi per la gestione di questi parametri, l'elenco completo dei parametri

aws ssm describe-parameters
aws ssm describe-parameters --output table --query 'Parameters[*].[Name,Type,Description]'

I parametri comuni messi a disposizione da AWS possono essere recuperati con lo stesso comando

aws ssm get-parameters-by-path --path "/aws/service/ami-amazon-linux-latest"
  --query "Parameters[].Name"

Per creare un nuovo parametro è possibile eseguire

aws ssm put-parameter --overwrite --profile default --name "/ambito/nome-parametro" --type String 
  --value "valore del parametro"

Con le librerie SDK messe a disposizione di AWS è possibile gestire questo servizio, i principali metodi per leggere e scrivere nella tabella dei parametri sono:

def get_parameters_by_path(profile_name,path):
  boto3.setup_default_session(profile_name=profile_name)
  ssm = boto3.client('ssm') #, 'us-east-2'
  paginator = ssm.get_paginator('get_parameters_by_path')
  response_iterator = paginator.paginate(
    Path=path,Recursive=True,WithDecryption=True#,MaxResults=123,
  )
  parameters=[]
  for page in response_iterator:
    for entry in page['Parameters']:
      parameters.append(entry)
  return parameters
def put_parameter(profile_name, name, value, type, description):
  boto3.setup_default_session(profile_name=profile_name)
  ssm = boto3.client('ssm') #, 'us-east-2'
  response = ssm.put_parameter(
    Name=name,
    Description=description,
    Value=value,
    Type=type, #'String'|'StringList'|'SecureString',
    Overwrite=True, #|False,
    Tier='Standard',#|'Advanced'|'Intelligent-Tiering',
  )
  return response

L'uso di questo servizio è molto potente quando si vuole recuperare da CloudFormation dei parametri dinamici da SSM, per fare esiste proprio un tipo di parametro specifico:

Parameters:
  InstanceType:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /nao/ec2/instenceType
  ImageId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

grazie a questa tecnica è possibile recuperare un valore di un parametro personalizzato ma anche da un parametro pubblici di AWS, per esempio recuperare l'ultima immagine ami disponibile di un determinato tipo.  Questi parametri possono poi essere usati nei template:

Resources:
  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: !Ref ImageId
      NetworkInterfaces:
      - GroupSet:
      - Ref: SecurityGroup
      AssociatePublicIpAddress: true
      DeviceIndex: '0'
      DeleteOnTermination: true

Nel template di esempio:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio09parametriSSM

è possibile notare come è possibile recuperare valori da SSM e passarli ad un template con la tecnica del "nested stacks" già presentata nei precedenti articoli. Tramite template CloudFormation è possibile anche creare parametri come risorsa di uno stack, nei template è possibile inserire le risorse:

Parametro:
  Type: AWS::SSM::Parameter
  Properties:
    Description: 'Descrizione CF'
    Name: '/dev/provaCF'
    Tier: 'Standard'
    Type: 'String' # SecureString | String | StringList
    Value: 'valore che arriva da cloudFormation'

Questa tecnica è usata anche per salvare in uno store centrale e unico le password e le chiavi chiamate "SecureString", questo tipo di parametri sono salvati nel servizio in maniera cifrata, tramite questa tecnica è possibile salvare chiavi private pem o password in modo che altri servizi possano recuperarle senza problemi di sicurezza. Per esempio una lambda che vuole collegarsi ad un server SFTP può recuperare password o chiave da SSM in maniera sicura.

AWS permette di creare template complessi con una serie di tipi non atomici, cioè non corrispondenti ad un singolo servizio, ma che permettono di costruire applicazioni o infrastrutture che comprendono più risorse, questo tipo di tecnica è stata chiamata Serverless Application Model abbreviato con la sigla SAM. Un classico esempio è la gestione dell'API Gateway che necessità di diversi elementi (stage, risorse, deploy) ma che può essere raggruppato in un unica risorsa nel template di CloudFormation.

I tipo messi a disposizione sono:

  • AWS::Serverless::Api
  • AWS::Serverless::Application
  • AWS::Serverless::Connector
  • AWS::Serverless::Function
  • AWS::Serverless::GraphQLApi
  • AWS::Serverless::HttpApi
  • AWS::Serverless::LayerVersion
  • AWS::Serverless::SimpleTable
  • AWS::Serverless::StateMachine

ben descritti nella documentazione ufficiale. In realtà questi tipo sono stati usato anche in precedenti esempi senza dar particolare attenzione alla differenza tra i tipi atomici e i tipi "serverless", questo perché spesso risultano più semplici da imparare ed usare questi tipi rispetto ai tipi elementari.

Per poter usare questa tecnica è necessario aver installato la SAM CLI, una estensione della CLI, si rimanda alla documentazione per maggior informazioni e i link dai quali scaricare il programma di installazione. Da notare che tutti i template scritti con questi modelli devono avere sempre l'indicazione

Transform: AWS::Serverless-2016-10-31

Questa proprietà indicherà al comando SAM di trasformare i tipi serverless in tipi atomici per creare un template completo che il servizio CloudFormation potrà elaborare.


Il primo tipo messo a disposizione dal framework è studiato per creare API di tipo RESTful, questo permette di creare tutta l'infrastuttura (risorse, metodi e stage) in un unico componente indicando lo stage come parametro, i metodi in un elenco specifico e le risorse come funzioni Lambda esterne, infatti questo tipo di risorsa funziona solo se combinato con il tipo AWS::Serverless::Function. Un esempio completo e funzionante:

ApiGateway:
  Type: AWS::Serverless::Api
  Properties:
    StageName: !Ref Stage
    OpenApiVersion: 3.0.2
    CacheClusterEnabled: false
    CacheClusterSize: '0.5'
    Cors:
      AllowMethods: "'POST, GET, DELETE, OPTIONS'"
      AllowOrigins: "'*'"
      AllowHeaders: "'*'"
      MaxAge: "'600'"
    MethodSettings:
    - ResourcePath: /
      HttpMethod: GET
      CachingEnabled: false
      CacheTtlInSeconds: 300
ApiGetFunction: 
  Type: AWS::Serverless::Function
  Properties:
    Role: !GetAtt APIRole.Arn
    Events:
      ApiEvent:
        Type: Api
        Properties:
          Path: /
          Method: get
          RestApiId:
            Ref: ApiGateway
    CodeUri: lambda
    Handler: dynamo_crud.get_handler
    Runtime: python3.8
    MemorySize: 128
    Environment: 
      Variables:
        DynamoName: !Ref DynamoName
    Timeout: 500

L'esempio completo e funzionante di questo esempio (con API che esegue una chiamata ad una Lambda) è disponibile al solito repository;

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio11dynamoApiCrud

Per maggior informazioni sui tipi Serverless di CloudFormation si rimanda la documentazione ufficiale e agli articoli del blog ufficiale, perché bisogna sempre ricordare che questo tipo di risorse hanno dei parametri specifici studiati apposta per la creazione di applicazioni Serverless.


Per quanto riguarda la creazione delle Lambda con il tipo specifico AWS::Serverless::Function bisogna ricordare che questo tipo è diverso dal tipo base AWS::Lambda::Function, alcuni parametri sono uguali come ReservedConcurrentExecutions mentre altri sono diversi come il ProvisionedConcurrencyConfig, si rimanda sempre alla documentazione ufficiale per i dettagli di quali parametri sono previsti dal tipo serverless.

Le Lambda Application sono una combinazione di funzioni lambda, sorgenti di eventi e altre risorse AWS unite in un gruppo di risorse, questo raggruppamento può essere creato facilmente in template CloudFormation per raccogliere i componenti della applicazione in un unico pacchetto distribuito e gestito come un'unica risorsa e un unico sviluppo.

Il punto principale di questa tecnoloigia sono le funzioni: la console web presenta questa funzionalità nel servizio Lambda dove è gestire e creare tutte le risorse collegate come i trigger EventBridge e le regole IAM. E' possibile creare integrazione con i servizi di sviluppo come CodePipeline e AWS CodeBuild, tecnica che verrà esposta in un articolo dedicato.

La struttura di una application può essere creata come stack di CloudFormation ma è possibile confondere i due concetti e i due servizi che sono sicuramente legati: le Lambda Application è solo un raggruppamento logico di risorse AWS mentre CloudFormation è il servizio di Iac (infrastructure as code) che permette di creare gli stack di risorse con template.

I gruppo di risorse possono essere creati come Lambda Application con la CLI, esiste un semplice tutorial nel sito ufficiale con la guida completa di come costruire applicazioni di questo tipo.


Con CloudFormation è possibile creare Lambda Application in maniera veloce usando i servizi serverless, per esempio una piccola applicazione per creare un servizio per scrivere files in un bucket caricandolo in una pagina web:

Un esempio corrispondente di template CloudFormation per creare la struttura è disponibile nel solito repository:

https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio13lambdaApplicationS3Utils

In questo esempio sono disponibili più funzionalità divise in gruppi/application con vari metodi utili per la gestione del contenuto dei bucket S3.

È online la nuova versione del sito con contenuti aggiornati. Nessun articolo e nessun contenuto di questo sito è stato creato con IA o generatori automatici di testi ma tutto è stato scritto con molta pazienza personalmente da Alberto Nao. Tutto il codice presente nel sito Tutti gli esempi di codice nel sito sono coperti da

I contenuti di AlNao.it potrebbero avere inesattezze o refusi. Non potrà in alcun caso e per qualsiasi motivo essere ritenuta responsabile di eventuali imprecisioni ed errori né di danni causati. Il sito web e tutte le informazioni ed i contenuti in esso pubblicati potranno essere modificati in qualsiasi momento e di volta in volta e senza preavviso. Poiché ogni materiale sarà scaricato o altrimenti ottenuto attraverso l’uso del servizio a scelta e a rischio dell’utente, ogni responsabilità per eventuali danni a sistemi di computer o perdite di dati risultanti dalle operazioni di scarico effettuato dall'utente, ricade sull'utente stesso e non potrà essere imputata ad AlNao.it che declina ogni responsabilità per eventuali danni derivanti dall'inaccessibilità ai servizi presenti sul sito o da eventuali danni causati da virus, file danneggiati, errori, omissioni, interruzioni del servizio, cancellazioni dei contenuti, problemi connessi alla rete, ai provider o a collegamenti telefonici e/o telematici, ad accessi non autorizzati, ad alterazioni di dati, al mancato e/o difettoso funzionamento delle apparecchiature elettroniche dell’utente stesso.

AlNao.it ha adottato ogni possibile accorgimento al fine di evitare che siano pubblicati, nel sito web, contenuti che descrivano o rappresentino scene o situazioni inappropriate o tali che, secondo la sensibilità degli utenti, possano essere ritenuti lesivi delle convinzioni civili, dei diritti umani e della dignità delle persone, in tutte le sue forme ed espressioni. In ogni caso non garantisce che i contenuti del sito web siano appropriati o leciti in altri Paesi, al di fuori dell’Italia. Tuttavia, qualora tali contenuti siano ritenuti non leciti o illegali in alcuni di questi Paesi, ti preghiamo di evitare di accedere al nostro sito e ove scegliessi, in ogni caso, di accedervi, ti informiamo che l’uso che deciderai di fare dei servizi forniti dal sito sarà di tua esclusiva e personale responsabilità. L’utente sarà esclusivo responsabile della valutazione delle informazioni e del contenuto ottenibile mediante il sito web. Il sito web e tutte le informazioni ed i contenuti in esso pubblicati potranno essere modificati in qualsiasi momento e di volta in volta e senza preavviso.

Le pagine di questo sito sono protette dal diritto d’autore (copyright), in particolare a norma della legge sul diritto d’autore e il contenuto del sito è protetto contro duplicazioni, traduzioni, inserimento o trasformazione dello stesso in altri media, incluso l’inserimento o la trasformazione con mezzi elettronici. La riproduzione e lo sfruttamento economico di tutto o di parte del contenuto di questo sito sono consentite solo a seguito del consenso scritto dell’avente diritto. Sia il contenuto che la struttura del sito sono protetti dal diritto d’autore. In particolare, la duplicazione di informazioni o dati, l’uso dei testi o di parte di essi o delle immagini contenute nel sito (eccetto per le foto ad uso stampa) è consentita solo previo consenso scritto dell’avente diritto. Anche le illustrazioni, a norma dell’art. 1 della legge 633/1941 – e successive modifiche e integrazioni – sono protette dal diritto d’autore. Il diritto di pubblicazione e riproduzione di questi disegni è di titolarità dell’avente diritto. Il diritto d’autore sui disegni rimane in vigore anche per i disegni automaticamente o manualmente aggiunti a un archivio. Nulla di quanto contenuto in questo sito vale come concessione a terzi dei diritti di proprietà industriale ed intellettuale indicati in questa sezione. Ci riserviamo tutti i diritti di proprietà intellettuale del sito web.

Marchi ed immagini riferibili a soggetti terzi utilizzati in questo sito non appartengono a AlNao.it e sono da ritenersi di proprietà esclusiva dei rispettivi titolari. Le denominazioni dei prodotti pubblicati su questo sito web o delle società menzionate, anche qualora non siano indicate con il simbolo identificativo della registrazione del marchio sono marchi di titolarità di terzi e sono protetti dalla legge sui marchi (D.lgs. 30/2005 e successive modifiche e integrazioni) e dalle norme in tema di concorrenza sleale. Qualsiasi riproduzione degli stessi è da ritenersi vietata ai sensi di legge. In particolare è espressamente vietato qualsiasi uso di questi marchi senza il preventivo consenso scritto del relativo titolare ed, in particolare, è vietato utilizzarli in modo da creare confusione tra i consumatori in merito all'origine dei prodotti o per finalità di sponsorizzazione, nonché in qualsiasi modo tale da svilire e discreditare il titolare del marchio. Tutti i diritti che non sono espressamente concessi sono riservati al titolare del marchio.

In aggiunta a quanto indicato, AlNao.it non potrà essere ritenuto in alcun caso responsabile di alcun danno derivante dall'utilizzo o dall'impossibilità di utilizzare il sito web, i contenuti, le informazioni o connessi alla qualità degli stessi.

MENU