Development -> Testing -> Deployment -> Dev -> Test -> Dep -> ...
Docker rende l'esecuzione di questi task estremamente più semplice.
E' conveniente generare due Dockerfile, uno per development (Dockerfile.dev) e uno per produzione.
Per la configurazione del progetto occorre scrivere il file ".travis.yml" in cui indichiamo:
Passaggi:
sudo: required
" per i permessiscript:
" andremo ad indicare l'esecuzione dei test-- --coverage
" per non farlo rimanere running all'infinito in ascolto sul sdin del processo.Quindi:
language: generic
sudo: required
services:
- docker
before_install:
- docker build -t <nome immagine> -f Dockerfile.dev .
script:
- docker run -e CI=true <nome immagine> <comando per eseguire i test> -- --coverage
after_success:
- docker build -t <nome immagine prod> . ← utilizza il Dockerfile di prod
L’ultimo comando di build va eseguito per ogni servizio nel caso di una configurazione multi-container. Pushando il file nella repository GitHub verrà riconosciuto da Travis CI che eseguirà tutti gli step indicati.
Host server su cui Travis CI va a deployare.
Il servizio da utilizzare su AWS si chiama "Elastic Beanstalk".
Creo la nuova applicazione indicando "Docker" come piattaforma (quest'ultimo passaggio è implicito nella creazione dell'applicazione nella versione corrente di Elastik).
In Elastic è presente un Load Balancer gestisce le chiamate ad una VM la quale contiene il container Docker il quale a sua volta contiene l'app. Questo Load Balancer ha ovviamente la peculiarità di scalare i container su diverse VMs per supportare il traffico dati. Tutto verrà gestito da Elastik, ovviamente il contro è che costui è collegato alla tua carta di credito, quindi "be careful".
Aggiungere al .travis.yml:
deploy:
provider: elasticbeanstalk
region: "<region di AWS, es: us-west-2, es: frankfurt, lo si può recuperare dall'id dell'environment Elastik generato>"
app: "<nome dell'app in Elastic>"
env: "<Nomeapp-env>"
bucket_name: "<il bucket S3, l'hard drive dove runna la VM e su cui Trevis deve copiare lo zip. Il nome si trova nel service S3 e dovrebbe essere chiamatoelasticbeanstalk-<region>-<id>>"
bucket_path: "<nome dell'app, necessario per creare la folder all'interno del bucket>"
on:
branch: master
"on: branch:master
" indica a Travic CI di eseguire il deploy quando viene eseguito appunto un pull del branch "master".
Le chiavi si trovano nel servizio IAM di AWS. In Users fare "Add user" che sarebbe l'utente con cui Travis andrebbe a deployare sul bucket. Spuntare "Programmatic access" e "AWS Managment Console".
Aggiungergli la policy "provide full access to AWS Elastic Beanstalk". Attenzione che la Secret access key viene mostrara solo una volta, quindi copiarsela.
Per ovvie questioni di discurezza non inserire le chiavi direttamente nel file ".travis.yml" (soprattutto la repo sarà pubblica), ma usare le variabili d'ambiente.
In Travis CI -> Settings -> tab Environment Variables:
- AWS_ACCESS_KEY: <chiave copiata da IAM>
- AWS_SECRET_KEY: <chiave copiata da IAM mostrata solo una volta>
Nel ".travis.yml" aggiungere, sotto "deploy
":
access_key_id: $AWS_ACCESS_KEY
secret_access_key: $AWS_SECRET_KEY
Su Elastic Beanstalk va eseguito il mapping delle porte dei container Docker su quelle della VM di Elastic che ospita i containers. Nel Dockerfile aggiungere:
EXPOSE 80
Questo è un comando di "lettura" da parte degli sviluppatori normalmente, non esegue nulla. Tuttavia viene letto da Elastic Beanstalk il quale esegue automaticamente il mapping della porta 80.
.
", utilizzare invece "./
", es:
COPY package*.json ./
Su GitHub, nella repo del progetto, premere su "Compare & Pull request" per eseguire la pull request dal branch di sviluppo a master. Travis CI esegue dei test per "validare" ciò di cui è stato richiesto di fare il merge. Se i test sono stati superati (si vede sempre da GitHub) si può proseguire con il "Merge pull request" che, vista l'indicazione
on:
branch: master
scritta nel ".travis.yml" andrà, scatenerà l'avvio del rebuild e redeploy da parte di Travis CI su AWS.
Travis CI può buildare le immagini per la produzione e rilasciare queste build su Docker Hub (repositories private). Quindi Travis CI notifica AWS EB dell'aggiornamento in Docker Hub ciò conduce EB a eseguire il pull le immagini da Docker Hub e successivamente eseguire il deploy.
In principio va creato, per ogni servizio da buildare, il Dockerfile di produzione. Come abbiamo visto con applicazioni client in React e Angular occorre eseguire la copia del compilato (in "dist" o "build") e avviarlo. Ovviamente la build va eseguita in modo che sia destinata alla produzione (es: ng build --prod
).
Quindi occorrerà eseguire un "multi-stage-deployment" come descritto precedentemente (build + copia di quanto buildato all'interno del server + esposizione della porta per EB).
Nel caso ci sia un sistema di proxy che gestisce il routing e lo smista tra applicazione client e applicazione server, è necessario fare le dovute configurazioni per distribuire correttamente le chiamate da un'unica porta esterna alle due porte interne dei servizi (come deve anche essere fatta ovviamente in fase di sviluppo). Nel caso delle applicazioni server è possibile che il Dockerfile sia uguale al Dockerfile.dev, ossia sia sufficiente eseguire le copie necessarie, la configurazione a dovere del web container e l'avvio del wc con l'applicativo.
Quindi si crea ed esegue il push sulla repository su Github, si esegue la pull request e quindi il merge che in Trevis CI andrà a scatenare la build di produzione.
Per fare funzionare Trevis CI, come visto prima, occorre scrivere il file ".travis.yml". Occorre inoltre buildare la versione test del progetto. Dopodiché Travis CI eseguirà la pull su Docker Hub e cominicherà ad EB di eseguire la pull dell'immagine aggiornata. Ai comandi di build ed esecuzione dei test (services, before_install, script, after_success…) va aggiunta la configurazione del deploy su Docker Hub. Ciò consiste in:
- DOCKER_ID = <proprio docker id>
- DOCKER_PASSWORD = <propria docker password>
Inserire quindi nell'"after_success
" del “.travis.yml”, dopo le varie build:
- echo "$DOCKER_PASSSWORD" | docker login -u "$DOCKER_ID" --password-stdin
Questo consentirà a Trevis CI di eseguire l'accesso a Docker Hub. Quindi aggiungere:
- docker push <nome immagine>
per ogni service/container/immagine da pushare appunto su Docker.
Per avere più di una repository privata su Docker Hub occorre pagare.
Nella directory del progetto aggiungere il file "Dockerrun.aws.json" il quale andrà a dare le informazioni necessarie ad AWS EB. Avendo già a disposizione le immagini, in questo file va indicato dove andare a pullare le immagini da GitHub.
Per capire come eseguire i container EB fa riferimento ad Amazon Elastic Container Service (ECS) dove vanno definiti delle "task definition".
Documentazione per la scrittura delle task definition (paragrafo Container Definitions).
La struttura del file è la seguente:
{
"AWSEBDockerrunVersion": 1,
"containerDefinitions": [
{
"name": "<nome che si intende dare>",
"image": "<nome dell'immagine su Docker Hub",
"hostname": "<nome del servizio usato nel file docker-compose.yml e riconosciuto dagli altri servizi. E' opzionale, inserire solo se altri servizi hanno accesso al container>"
"essential": <false o true, se imposto "true" nel caso in cui il container crashasse vengono anche gli altri vanno in shut down. Almeno un container deve essere segnato come essential. Normalmente si imposta "true" il container contenente la configurazione del web container>
"portMappings": [
{
"hostPort": 80,
"containerPort": 80
<necessario solo per chi deve esporre le porte pubblicamente, ovvero il web container>
}
],
"links": ["<nome container>", "<nome container>"] <sono necessari al webcontainer per accedere agli altri container quando deve fare per esempio il rederiect delle chiamate al server o al client. Il nome è quello usato come "name" della container definition>,
}
... <container definition degli altri containers> ...
]
}
In AWS accedo al servizio Elastic Beanstalk e creo la nuova applicazione configurando l'environment come "Web Server" e utilizzando "Multi-container Docker" come preconfigured platform.
Ora, in produzione l'architettura dei conteiner multipli cambia rispetto al locale. I data services (in meomry data cache, database...) non verranno gestiti all'interno dell'istanza di Elastic Beanstalk ma su servizi separati di AWS (Elastic Cache per Redis e memory data stores, AWS Relational Database Service per i database relazionali).
AWS Elastic Cache:
AWS Relational Database Service:
Ovviamente questi servizi hanno diversi livelli di costi, sull'ordine del dollaro al mese, ma fanno risparmiare sicuramente molto tempo. Di default RDS ed EC non comunicano automaticamente con le istanze EB. Occorre quindi configurare una serie di "link".
Il VPC è un ambiente cloud raggiungibile solo dai servizi del mio account configurato sulla region sulla quale le mie istanze sono ospitate. Di default sono a disposizione un VPC per ogni regione.
Raggiungo la VPC Dashboard su AWS dove troverò già presente il VPC di default della regione su cui sono impostato.
Sfrutteremo questo servizio per generare un Security Group ossia delle "firewall rules".
Configurando Elastic Beanstalk è stato generato un Security Group che consente il traffico sulla porta 80 da qualsiasi indirizzo IP. Queste configurazioni sono visibili nel tab "Security Groups" del servizio VPC. Cliccando sul SG posso vedere e modificare le tiplogie di configurazioni (in entrata e uscita, dove 0.0.0.0:80 significa tutti gli indirizzi IP worldwide).
Conoscendo ciò è possibile configurare un nuovo Security Group che consenta l'accesso di traffico da ogni altro AWS al servizio che possiede questo Security Group. Questo mette in comunicazione i miei servizi. Occorre ovviamente eseguire la configurazione nella stessa region dei servizi che si vogliono mettere in comunicazione.
Raggiungo la dashboard del servizio RDS e creo un nuovo database scegliendo il DBMS (Amazon Aurora, MySQL, PostgreSQL, Oracle, MSQL Server ecc...) e spunto "only enable options eligible for RDS Free Usage Tier" se non intendo pagare.
Completo la configurazione del db (allocated storage, db instance class) indicando il nome e lo username e la password. Questi ultimi due devono coincidere con quanto configurato nelle variabili d'ambiente del docker-compose.
Proseguendo nelle configurazioni avanzate posso indicare il VPC e spuntare "no" nella scelta "pubblic accessibility". Vado quindi a selezionare anche il VPC Security Group di appartenenza.
Quindi indico il nome del database, la porta e imposto la ricorrenza dei backup.
Raggiungo la dashboard del servizio EC e creo un "Amazon ElastiCache cluster" selezionando il cluster engine tra Redis (spuntando se desidero o meno il cluster) e altri engine.
Navigo alla dashboard di VPC, qui navigo alla sezione Security Groups. Creo un nuovo Security Gruop destinato alla comunicazione tra i tre servizi:
inserisco il nome del tag e del gruppo e la descrizione;
seleziono la VPC di default.
Seleziono quindi il nuovo SG e navigo al tab "Inbound Rules" e vado a restringere il traffico degli indirizzi IP e delle porte. Indico nel Port Range le porte dei servizi (es. "5432-6379" per indicare le porte di Postgres e Redis) e come Source indico il Security Group stesso il che indica che possono raggiungere i servizi di questo SG solo coloro che appartengono al SG stesso.
Applicazione del Security Group ai servizi:
Configuro le variabili d'ambiente (utilizzate nel “docker-compose.yml”) per consentire ai servizi in EB di raggiungere i servizi ospitati negli ambienti EC e RDS. Navigo all'environment dell'applicazione in EB, nel tab Configuration, sezione Environment properties.
Es (le variabili dipendono dalla configurazione nel docker-compose):
- REDIS_HOST: in Elastic Cache, nel Redis creato, copio il campo "Primary Endpoint" ricordandosi di tenere esclusa la porta;
- REDIS_PORT: la porta indicata separatamente;
- PGUSER: lo user indicato nella configurazione del db;
- PGPASSWORD: la password indicato nella configurazione del db;
- PGHOST: in RDS, nella sezione instances, clicco sul db e nella sezione "Connect" copio l'Endpoint;
- PGDATABASE: nome del db;
- PGPORT: la porta.
Ogni container descritto nel file "Dockerrun.aws.json" ha accesso alle variabili d'ambiente qui impostate.
Aggiungere la sezione "deploy" al file ".travis.yml". Per farlo ci servono le chiavi dello IAM user. Vado nel service IAM e creo il nuovo utente con nome, spunto "multi-programmatic-access" e aggiungo alle policies tutte quelle relative ad “AWSElasticBeanstalk”. Copio l'Acess Key ID e la Secret access key.
Quindi nella repository di Travis CI vado nelle opzioni e setto le variabili d'ambiente AWS_ACCESS_KEY
e AWS_SECRET_KEY
, dove già avevo impostato quelle di Docker. Dopodiché in AWS creo il bucket (o ne utilizzo uno già esistente). Quindi aggiorno il file ".travis.yml" aggiungendo:
deploy:
provider: elastickbeanstalk
region: <region su cui è collocato l'environment, ottenibile dal sub-domain dell'url dell'applicazione>
app: <nome app>
env: <nome environment>
bucket_name: <nome del bucket>
bucket_path: <nome del path>
on:
branch: master
access_key_id: $AWS_ACCESS_KEY
secret_access_key: $AWS_SECRET_KEY
Nel caso dovesse verificarsi l'errore "Missing <nome bucket> error
" aggiugnere:
edge: true
provider: elasticbeanstalk
...
alla sezione deploy
nel ".travis.yml", cosa che forzerà Travis a usare la versione v2 (sperimentale) del dpl script priva del bug.
Nelle containerDefinitions
, nel file Dockerrun.aws.json, posso specificare l'allocazione di memoria dinamica da rendere disponibole:
"memory": 128
Navigo nell'applicazione e nella sezione logs dove posso trovare una tabella dei file log che posso consultare.
Docker ci ha consentito di ridurre tutto il processo di testing, deploy e pubblicazione ad un semplice file .yml, andando a strutturare una deployment pipeline drammaticamente più semplice da controllorare e gestire.