Abbiamo già scritto più volte di continuous delivery, solitamente concentrandoci sulla parte di backend di un'applicazione. Naturalmente, le applicazioni web richiedono anche un front-end, quindi rendere il processo di implementazione della parte front-end agile e veloce è cruciale per il successo di un progetto.In questo articolo, esporremo il progetto completo per un'infrastruttura AWS in grado di supportare l’hosting di un’applicazione Angular completa di CDN, una funzione personalizzata per invalidare la cache e una pipeline di CD completamente automatizzata.Questo progetto mira a soddisfare le best practises e fornirà una soluzione solida, altamente scalabile e completamente gestita per l'hosting di applicazioni front-end su AWS.Questa è anche la soluzione più ottimizzata in termini di costi che abbiamo trovato finora e consente di servire i contenuti statici in un modo altamente affidabile, performante ed economico.La nostra architettura è suddivisa logicamente in 2 parti, l'hosting rivolto al cliente e l'infrastruttura di continuous delivery rivolta agli sviluppatori.I servizi
Per l'hosting utilizzeremo i seguenti servizi AWS: - Un bucket S3 per archiviare tutti i file front-end
- Una distribuzione CloudFront per servire il contenuto del bucket agli utenti finali
- Route53 per configurare e utilizzare un dominio personalizzato per l'applicazione
Per l'infrastruttura rivolta agli sviluppatori saranno impiegati: - CodeDeploy come repository Git privato
- CodePipeline come orchestrator per la pipeline di CD
- CodeBuild per eseguire la build della soluzione Angular.
- Una funzione Lambda personalizzata per creare un invalidazione sulla distribuzione CloudFront ad ogni deploy
L'architettura
Il diagramma mostra l'infrastruttura completa, mettendo in evidenza i trigger e le azioni che svolgerà la pipeline.L'hosting della soluzione è basato su CloudFront, che consente di servire l'applicazione Angular in modo efficiente e affidabile. L'uso di CloudFront è inoltre necessario per poter utilizzare HTTPS ed un dominio custom.Le risorse compilate dell'applicazione front-end verranno archiviate in un bucket S3, utilizzato come origine per la distribuzione CloudFront. Consigliamo vivamente di mantenere privato il bucket S3, in questo modo nessun utente esterno sarà in grado di accedere direttamente ai file, limitando i costi e le possibilità di exploit.Abbiamo incluso Route53 nell'architettura perché è il modo più semplice per gestire i record DNS per il dominio, comunque, è possibile utilizzare qualsiasi servizio DNS, purché sia possibile aggiungere un record CNAME per l'applicazione front-end.La parte davvero interessante dell'architettura è quella rivolta agli sviluppatori. Il cuore della soluzione è CodePipeline, che usiamo come orchestratore per reagire alle modifiche apportate nel repository e passare i dati tra tutti i servizi utilizzati durante l'esecuzione della pipeline.Abbiamo anche usato CodeCommit come repository per il codice dell'applicazione. Al momento della scrittura, CodePipeline supporta CodeCommit e GitHub. Per supportare altre configurazioni di repository Git sarebbe necessaria un'ulteriore personalizzazione della pipeline.Per creare la soluzione Angular, sfruttiamo CodeBuild per eseguire automaticamente il provisioning di un container per il processo di compilazione.Per accelerare l’aggiornamento della distribuzione, ed evitare di attendere la scadenza di ciascun oggetto nella rete CDN, abbiamo incluso un passaggio Invoke alla fine della pipeline per eseguire una funzione Lambda che crea una richiesta di invalidazione per la distribuzione CloudFront.I passaggi della pipeline
La nostra pipeline sarà composta da 4 passaggi, analizziamo il meccanismo della pipeline.Source
Questo passaggio è completamente gestito da AWS CodePipeline. Questo step è configurato per avviare automaticamente la pipeline quando viene rilevato un push in un ramo specifico, scaricare il codice sorgente, creare un archivio e inviarlo come artefatto per il passaggio successivo.Build
Lo step di compilazione richiede come input l'artefatto del codice sorgente del passaggio precedente.CodeBuild eseguirà quindi il provisioning di un container per la build, scaricherà il codice sorgente ed eseguirà i comandi contenuti nel file buildspec.yml.Il file yml deve essere archiviato nella radice del repository.Ecco un esempio di file buildspec.yml per un'applicazione Angular standard:version: '0.2'
phases:
install:
runtime-versions:
nodejs: 12
pre_build:
commands:
- echo "Prebuild, installing npm dependencies"
- npm install
build:
commands:
- echo "Starting the build step"
- npm run build
- echo "Finished"
artifacts:
name: "BuildOutput"
files:
- '**/*'
base-directory: 'dist'
L'output del processo di build è un archivio dei file e delle cartelle contenuti nella cartella dist.Deploy
Anche lo step di deploy è completamente gestito, CodePipeline scompatterà e copierà su S3 tutti i file e le cartelle dell'archivio ottenuto in output dal processo di build.Questa azione, al momento in cui scriviamo questo articolo, non è in grado di eliminare i file da S3. Data la tipica struttura di un’app Angular, questa limitazione non rappresenta solitamente un problema.Tuttavia, è importante tenere presente che se si desidera eliminare un file dal sito Web è necessario aggiungere un passaggio per svuotare il bucket prima di procedere al nuovo deploy, oppure occorre aggiungere un apposito passaggio che elimina i file desiderati successivamente.CloudFront Invalidation
L'ultimo passaggio della pipeline richiama una funzione Lambda per creare un invalidazione per la distribuzione CloudFront. Creare l’invalidazione consente agli utenti di ottenere la versione aggiornata dell’applicazione pochi minuti dopo la distribuzione, invece di attendere la scadenza degli oggetti in ciascun nodo CDN che può verificarsi in momenti diversi.La funzione lambda può utilizzare l'SDK AWS per creare l'invalidazione. Questa funzione deve inoltre informare CodePipeline ogni volta che riscontra errori o termina con successo utilizzando un'API specifica di CodePipeline. Ecco un esempio lambda per invalidare completamente una distribuzione CloudFront e quindi notificare a CodePipeline il risultato:import boto3
import os
from botocore.exceptions import ClientError
cloudfront_client = boto3.client('cloudfront')
codepipeline_client = boto3.client('codepipeline')
def lambda_handler(event, context):
try:
cdn_id = os.environ["CDN"]
cloudfront_client.create_invalidation(
DistributionId=cdn_id,
InvalidationBatch={
'Paths': {
'Quantity': 1,
'Items': [
'/*'
],
},
'CallerReference': event['CodePipeline.job']['id']
}
)
codepipeline_client.put_job_success_result(jobId=event['CodePipeline.job']['id'])
except ClientError as e:
print("Boto3 exception", e)
codepipeline_client.put_job_failure_result(
jobId=event['CodePipeline.job']['id'],
failureDetails={
'type': 'JobFailed',
'message': e.response
})
except Exception as e:
print("Error", e)
codepipeline_client.put_job_failure_result(
jobId=event['CodePipeline.job']['id'],
failureDetails={
'type': 'JobFailed',
'message': e.args
})
Come costruire la soluzione
Di seguito è riportato un elenco di operazioni da seguire per creare la soluzione. Si noti che questo non è un tutorial di tipo copia-incolla, ma fornisce il giusto ordine per ogni operazione da eseguire e una breve descrizione per ciascuno dei passi.Prima di iniziare, assicurarsi di disporre del codice sorgente in un repository CodeCommit e di avere pieno accesso all'account per creare e configurare ciascun servizio della soluzione. -
Innanzitutto, è necessario creare un bucket S3 privato per il front-end.
- Segui la procedura guidata, assicurati di selezionare le opzioni per rendere privato il bucket.
- Non è necessario scegliere il nome del bucket come il nome di dominio completo (FQDN) dell’applicazione perché serviremo il contenuto utilizzando CloudFront
-
Crea una distribuzione CloudFront.
- Seleziona il bucket creato nel primo passaggio come origine.
- Seleziona l'opzione per consentire a CloudFront di gestire la bucket policy, in questo modo la procedura guidata creerà una policy perfettamente sicura e valida per il bucket, che consente alla distribuzione di servirne i contenuti mantenendo il bucket privato allo stesso tempo.
- Reindirizzare tutti gli errori alla pagina index, questo è fondamentale per il funzionamento del router di Angular.
-
NOTA: se non state creando l'infrastruttura nella regione us-east-1, attendete fino a 2 ore dopo il deploy prima di testarla. Ciò è dovuto al modo in cui funziona la propagazione dei DNS tra S3 e CloudFront al momento in cui stiamo scrivendo. Un bucket S3 appena creato, in qualsiasi altra region AWS, produce un redirect http temporaneo che impedisce la corretta integrazione dei due servizi. Una volta atteso il TTL la soluzione funzionerà correttamente senza alcuna modifica.
-
Creare la funzione Lambda per invalidare la CDN.
- Puoi copiare e incollare il codice dell'esempio sopra.
- Se si utilizza il codice di esempio, aggiungere una variabile di ambiente denominata "CDN" e valorizzarla con l'id della distribuzione CloudFront.
-
Assicurati di creare un nuovo ruolo IAM per la funzione, con tutte le autorizzazioni del ruolo BasicLambdaExecution, oltre a:
-
cloudfront: CreateInvalidation sulla distribuzione CloudFront
-
codepipeline: PutJobSuccessResult su *
-
codepipeline: PutJobFailureResult su *
- Prendi nota del nome della funzione Lambda e dell'ARN per dopo.
-
Crea una nuova CodePipeline
- Seguire la procedura guidata e aggiungere lo step di source, selezionare il repository CodeCommit e il ramo git desiderato.
-
Aggiungi uno step di build, crea un nuovo progetto CodeBuild seguendo la procedura guidata
- Seleziona un'immagine standard di Ubuntu
- Creare o selezionare un ruolo di servizio CodeBuild standard
- Lasciare tutte le impostazioni predefinite e proseguire
-
Aggiungi uno step di deploy
- Seleziona deploy S3
- Segui la procedura guidata per configurarlo in modo da inviare l'output di generazione sul bucket del front-end
-
Infine, aggiungi l'ultimo passaggio, scegli Invoke -> AWS Lambda
- Seleziona la funzione Lambda creata nel passaggio precedente
- Lascia tutti i parametri al loro valore predefinito
- Completa la creazione della pipeline e testala. La pipeline dovrebbe avviarsi non appena si completa la creazione.
Complimenti! Hai completato l'implementazione della soluzione! Ora dovresti avere un hosting Angular completamente funzionale, completo di una pipeline di CD.Siete interessanti all'argomento?Leggete anche
Commentate l'articolo o scriveteci per approfondire l'argomento! Arrivederci al prossimo articolo!