In Madisoft utilizziamo da sempre le buone pratiche per la gestione delle nostre infrastrutture. Abbiamo sempre pensato che il cloud fosse la scelta giusta per i nostri servizi, ma soprattutto eravamo certi che gestire la nostra infrastruttura come codice, ci avrebbe rallentato leggermente nelle fasi iniziali dei nostri progetti, ma poi, sul lungo periodo, avremmo avuto solo grandi vantaggi. E cosi è stato.
Fino ad oggi abbiamo usato quasi esclusivamente Ansible, anche se adesso stiamo iniziando ad utilizzare anche Amazon CloudFormation, strumento potente e completo, ma, a nostro avviso, troppo dispersivo in particolare nella scrittura del codice dei modelli complessi.
Amazon ha rilasciato al re:invent 2018 AWS Cloud Development Kit (AWS CDK):
Il kit di sviluppo del cloud AWS (AWS CDK) è un framework di sviluppo software open source che consente di modellare ed erogare risorse di applicazioni cloud tramite linguaggi di programmazione noti.
AWS CDK può essere definito come un wrapper per Amazon CloudFormation, infatti, dove prima era necessario scrivere modelli abbastanza complessi e lunghi, ora, grazie ad AWS CDK, è possibile definire le sole risorse necessarie all’interno del nostro codice e distribuirle con CloudFormation.
AWS CDK ti permette di modellare l’infrastruttura dell’applicazione utilizzando (per il momento) TypeScript, Python, Java e .NET.
Si esatto fa proprio quello che stai pensando, permette di scrivere codice con uno dei linguaggi descritti e automaticamente offre la possibilità di utilizzare la potenza e la sicurezza di CloudFormation per distribuire le nostre infrastrutture su AWS.
In questo esempio utilizzeremo TypeScript, linguaggio di programmazione che si basa su un Super-set di JavaScript, molto intuitivo e semplice.
L’infrastruttura che andremo a creare è composta dall’orchestratore ECS, nella modalità di configurazione Fargate, che gestisce al suo interno un container Docker con un semplice web server. L’infrastruttura verrà creata in una VPC con un bilanciatore di traffico davanti.
La prima cosa da fare è creare la directory che ospiterà il nostro codice, con i relativi files:
1 2 |
mkdir typescript/madisoft_cdk touch cdk.json index.ts package.json tsconfig.json |
Inseriamo nei rispettivi file il contenuto necessario per utilizzare il framework CDK:
1 2 3 4 5 6 |
cdk.json { "app": "node index", "requireApproval": "never" } |
Il file cdk.json contiene i parametri di esecuzione degli script di CDK. Nello specifico definiamo l’eseguibile node, utilizzato per invocare lo script index (sostituisce il –app da linea di comando). Mentre il secondo parametro specifica di non richiedere conferma durante l’esecuzione dello script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package.json { "name": "fargate-load-balanced-service", "version": "1.0.0", "description": "Running a load balanced service on Fargate", "private": true, "scripts": { "build": "tsc", "watch": "tsc -w", "cdk": "cdk" }, "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com", "organization": true }, "license": "Apache-2.0", "devDependencies": { "@types/node": "^8.10.38", "typescript": "^3.2.4" }, "dependencies": { "@aws-cdk/aws-ec2": "*", "@aws-cdk/aws-ecs": "*", "@aws-cdk/aws-ecs-patterns": "*", "@aws-cdk/core": "*" } } |
Il file package.json è il manifest di npm. E’ necessario specificare in questo file informazioni come il nome dell’app, la versione, le dipendenze, etc…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
tsconfig.json { "compilerOptions": { "target":"ES2018", "module": "commonjs", "lib": ["es2016", "es2017.object", "es2017.string"], "strict": true, "noImplicitAny": true, "strictNullChecks": true, "noImplicitThis": true, "alwaysStrict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": false, "inlineSourceMap": true, "inlineSources": true, "experimentalDecorators": true, "strictPropertyInitialization":false } } |
Il file tsconfig.json è il file di configurazione di TypeScript, specifica, tra le altre cose, le opzioni del compilatore richieste per il progetto.
Infine il file centrale della nostra applicazione, dove scriveremo il codice per creare la nostra infrastruttura:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
index.ts import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import ecs_patterns = require('@aws-cdk/aws-ecs-patterns'); import cdk = require('@aws-cdk/core'); import * as Variables from "../vars/vars_madisoft_cdk"; class MadisoftCdk extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); const vpc = new ec2.Vpc(this, Variables.vpc_name, { maxAzs: Variables.max_availability_zone }); const cluster = new ecs.Cluster(this, 'Cluster', { vpc }); const fargateService = new ecs_patterns.NetworkLoadBalancedFargateService(this, "FargateService", { cluster, taskImageOptions: { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample") }, }); new cdk.CfnOutput(this, 'LoadBalancerDNS', { value: fargateService.loadBalancer.loadBalancerDnsName }); } } const app = new cdk.App(); new MadisoftCdk(app, Variables.environment); app.synth(); |
I primi 4 import permettono di ereditare i moduli e le classi necessarie per costruire la nostra infrastruttura.
Una volta creata la classe MadisoftCdk, è possibile richiedere un’istanza del servizio che ci occorre in poche righe di codice.
Verrà creata una VPC e tutte le sue configurazioni interne (route tables, subnet, security group, InternetGateway, etc…). Ovviamente, se si vuole, possono essere sovrascritte o gestite nel codice.
Poi, andando avanti con il codice, procediamo alla creazione di un cluster ECS, con la modalità di configurazione Fargate.
In questo esempio abbiamo preso come container Docker la seguente immagine amazon/amazon-ecs-sample, ovviamente è possibile caricare in Fargate qualsiasi immagine Docker.
Infine l’ultima operazione che facciamo è istanziare un Load Balancer che smisterà il traffico verso il nostro cluster Fargate.
Tutta questa operazione è abbastanza complessa se fatta con CloudFormation, mentre con CDK necessita di circa 20 righe di codice!
Ora vediamo, praticamente, come creare la nostra infrastruttura.
Abbiamo creato un semplice script bash per inizializzare l’ambiente, secondo le necessità di AWS CDK, e non ripetere ogni volta i comandi. Nel file bash sono esportate le credenziali AWS dentro il container locale (facendo un export della AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY e della region):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
provision_env.sh #!/bin/bash export AWS_ACCESS_KEY_ID=access_key export AWS_SECRET_ACCESS_KEY=secret_key export AWS_DEFAULT_REGION=region pushd ../typescript/madisoft_cdk/ &> /dev/null if [ "$2" == "-a" ] || [ "$2" == "-d" ]; then npm install -g aws-cdk npm install npm run build fi if [ "$2" == "-a" ]; then cdk deploy elif [ "$2" == "-d" ]; then cdk destroy --force fi popd &> /dev/null |
AWS CDK richiede che node.js sia installato sull’ambiente utilizzato per creare l’infrastruttura, per semplicità abbiamo usato un container Docker in locale, con node.js preinstallato, per fare i nostri test. Anche i comandi di seguito sono stati inseriti in un file bash:
1 2 3 4 5 6 7 8 9 10 |
infrastructure_madisoft_cdk.sh #!/bin/bash TAG_NODE_VERSION="10.16.3-buster"; ENV="dev" OPERATION="-a" #-a per attivare un nuovo ambiente -d per distruggerlo docker run --name madisoft_cdk -v "$(pwd):/tmp" --workdir="/tmp/" --rm node:${TAG_NODE_VERSION} \ bash -c "./provision_env.sh $ENV $OPERATION" |
Quindi, provando ad eseguire il nostro container locale (che con l’opzione –rm viene distrutto dopo il suo utilizzo), in pochi minuti abbiamo la nostra infrastruttura pronta per l’utilizzo. Di seguito l’output del comando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
./infrastructure_madisoft_cdk.sh -e Dev -a Do you wish to continue the infrastructuring of Madisoft_cdk dev infrastructure (y/n) ? y /usr/local/bin/cdk -> /usr/local/lib/node_modules/aws-cdk/bin/cdk > core-js@2.6.10 postinstall /usr/local/lib/node_modules/aws-cdk/node_modules/core-js > node postinstall || echo "ignore" Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library! The project needs your help! Please consider supporting of core-js on Open Collective or Patreon: > https://opencollective.com/core-js > https://www.patreon.com/zloirock Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -) + aws-cdk@1.18.0 added 251 packages from 234 contributors in 14.323s npm notice created a lockfile as package-lock.json. You should commit this file. added 47 packages from 30 contributors and audited 35226 packages in 12.625s found 0 vulnerabilities > fargate-load-balanced-service@1.0.0 build /tmp/aws-cdk/typescript/madisoft_cdk > tsc Dev: deploying... Dev: creating CloudFormation changeset... 0/36 | 11:16:42 AM | CREATE_IN_PROGRESS | AWS::ECS::Cluster | Cluster (ClusterEB0386A7) 0/36 | 11:16:42 AM | CREATE_IN_PROGRESS | AWS::EC2::EIP | MadisoftCdk/PublicSubnet1/EIP (MadisoftToolsPublicSubnet1EIP8C3865C6) 0/36 | 11:16:42 AM | CREATE_IN_PROGRESS | AWS::IAM::Role | FargateService/TaskDef/ExecutionRole (FargateServiceTaskDefExecutionRole9194820E) 0/36 | 11:16:42 AM | CREATE_IN_PROGRESS | AWS::Logs::LogGroup | FargateService/TaskDef/web/LogGroup (FargateServiceTaskDefwebLogGroup71FAF541) 0/36 | 11:16:42 AM | CREATE_IN_PROGRESS | AWS::CDK::Metadata ... 34/36 Currently in progress: FargateServiceECC8084D ✅ Dev Outputs: Dev.FargateServiceLoadBalancerDNS9433D5F6 = Dev-Fargate-8PKB3O06PVX-e145f83406c91308.elb.us-east-2.amazonaws.com Dev.LoadBalancerDNS = Dev-Fargate-8PKB3O06PVX-e145f83406c91308.elb.us-east-2.amazonaws.com |
A questo punto la nostra infrastruttura è pronta. Per connetterci al nostro container basta prelevare l’URL dall’output del comando precedente, ed inserirlo nel nostro browser:
Ovviamente possiamo personalizzare il nostro script in modo da poter scalare automaticamente i container dentro Fargate, oppure creare nuovi servizi semplicemente aggiungendo qualche riga di codice. Tutto questo gestendo la nostra infrastruttura come codice in modo semplice ed intuitivo.
Per distruggere quanto fatto basta eseguire il seguente comando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
./infrastructure_madisoft_cdk.sh -e Dev -d /usr/local/bin/cdk -> /usr/local/lib/node_modules/aws-cdk/bin/cdk > core-js@2.6.10 postinstall /usr/local/lib/node_modules/aws-cdk/node_modules/core-js > node postinstall || echo "ignore" Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library! The project needs your help! Please consider supporting of core-js on Open Collective or Patreon: > https://opencollective.com/core-js > https://www.patreon.com/zloirock Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -) + aws-cdk@1.18.0 added 251 packages from 234 contributors in 19.645s npm notice created a lockfile as package-lock.json. You should commit this file. added 47 packages from 30 contributors and audited 35226 packages in 21.942s found 0 vulnerabilities > fargate-load-balanced-service@1.0.0 build /tmp/aws-cdk/typescript/madisoft_cdk > tsc Dev: destroying... 0 | 1:22:21 PM | DELETE_IN_PROGRESS | AWS::CloudFormation::Stack | Dev User Initiated 0 | 1:22:23 PM | DELETE_IN_PROGRESS | AWS::IAM::Policy | FargateService/TaskDef/ExecutionRole/DefaultPolicy (FargateServiceTaskDefExecutionRoleDefaultPolicy827E7CA2) 0 | 1:22:23 PM | DELETE_IN_PROGRESS | AWS::EC2::SubnetRouteTableAssociation | MadisoftCdk/PublicSubnet2/RouteTableAssociation (MadisoftCdkPublicSubnet2RouteTableAssociationD2925DE7) 0 | 1:22:23 PM | DELETE_IN_PROGRESS | AWS::EC2::SubnetRouteTableAssociation | MadisoftCdk/PrivateSubnet1/RouteTableAssociation (MadisoftCdkPrivateSubnet1RouteTableAssociation8392B1FF) 27 | 1:29:14 PM | DELETE_IN_PROGRESS | AWS::EC2::RouteTable | MadisoftCdk/PublicSubnet1/RouteTable (MadisoftCdkPublicSubnet1RouteTable66E87180) 28 | 1:29:14 PM | DELETE_COMPLETE | AWS::EC2::RouteTable | MadisoftCdk/PublicSubnet2/RouteTable (MadisoftCdkPublicSubnet2RouteTableD419F6F0) 29 | 1:29:15 PM | DELETE_COMPLETE | AWS::EC2::RouteTable | MadisoftCdk/PublicSubnet1/RouteTable (MadisoftCdkPublicSubnet1RouteTable66E87180) 29 Currently in progress: Dev, MadisoftToolsPublicSubnet1Subnet68C22D37, MadisoftToolsPublicSubnet2Subnet136F9359, MadisoftCdkVPCGW10C4FF13 30 | 1:29:55 PM | DELETE_COMPLETE | AWS::EC2::VPCGatewayAttachment | MadisoftCdk/VPCGW (MadisoftCdkVPCGW10C4FF13) 30 | 1:29:56 PM | DELETE_IN_PROGRESS | AWS::EC2::InternetGateway | MadisoftCdk/IGW (MadisoftCdkIGWE3445407) 31 | 1:30:10 PM | DELETE_COMPLETE | AWS::EC2::Subnet | MadisoftCdk/PublicSubnet1/Subnet (MadisoftCdkPublicSubnet1Subnet68C22D37) 32 | 1:30:11 PM | DELETE_COMPLETE | AWS::EC2::InternetGateway | MadisoftCdk/IGW (MadisoftCdkIGWE3445407) 33 | 1:30:15 PM | DELETE_COMPLETE | AWS::EC2::Subnet | MadisoftCdk/PublicSubnet2/Subnet (MadisoftCdkPublicSubnet2Subnet136F9359) 33 | 1:30:15 PM | DELETE_IN_PROGRESS | AWS::EC2::VPC | MadisoftCdk (MadisoftCdkE2A573DA) Dev: destroyed |
Il comando cancellerà tutte le risorse precedentemente create utilizzando CloudFormation:
Rispetto a strumenti utilizzati in passato come Terraform, Ansible, CloudFormation, ci sembra che AWS CDK sia veramente il punto di svolta per rispondere al meglio al paradigma: Infrastructure as Code (IaC)!