Implementando AWS Well-Architected Framework com Pulumi
A implementação de uma infraestrutura robusta e segura é fundamental para o sucesso de qualquer aplicação em nuvem. Para ajudar os usuários a criarem infraestruturas de alta qualidade, a Amazon Web Services (AWS) criou o Well-Architected Framework. Este framework fornece diretrizes e define melhores práticas para ajudar a projetar, avaliar e melhorar sua infraestrutura na nuvem da AWS.
Mas a criação de uma infraestrutura seguindo os princípios do Well-Architected Framework pode ser uma tarefa complexa e demorada, especialmente quando feita manualmente. É aí que entra o Pulumi, uma plataforma de gerenciamento de infraestrutura como código.
# tldr
https://github.com/gabrielsclimaco/pulumi-aws-waf-article
O Pulumi permite que a infraestrutura seja criada, gerenciada e automatizada na nuvem da AWS (entre outras plataformas de Cloud), usando linguagens de programação que você provavelmente já tem familiaridade, como TypeScript, Python e Go.
Neste artigo vamos mostrar como implementar o Well-Architected Framework da AWS utilizando o Pulumi com Typescript. Discutiremos as vantagens de usar o Pulumi, como criar uma infraestrutura seguindo os princípios propostos pelo framework, junto a um exemplo prático de implementação.
O que é o AWS Well-Architected Framework
O AWS Well-Architected Framework é um conjunto de práticas recomendadas, desenvolvidas pela Amazon Web Services (AWS), para ajudar empresas e organizações a projetar e operar aplicativos e infraestrutura na nuvem.
O framework é baseado em cinco pilares fundamentais:
- Excelência operacional (Operational Excellence): busca otimizar continuamente as operações e processos, melhorando a eficiência, a resiliência e a capacidade de rec- uperação em caso de falhas.
- Segurança (Security): prioriza a proteção dos dados, identidade e recursos, além de promover a conformidade com as políticas e regulamentações de segurança.
- Confiabilidade (Reliability): visa garantir a disponibilidade, a escalabilidade e a tolerância a falhas das aplicações, minimizando os impactos de possíveis interrupções.
- Eficiência de desempenho (Performance Efficiency): busca maximizar o uso dos recursos computacionais, reduzindo os custos e melhorando o desempenho das aplicações.
- Otimização de custos (Cost Optimization): busca reduzir os custos operacionais, eliminando desperdícios e otimizando a utilização dos recursos disponíveis na nuvem.
Cada pilar inclui práticas recomendadas e diretrizes que ajudam os usuários a identificar possíveis problemas e oportunidades de melhoria em suas arquiteturas e processos.
O framework é aplicável a uma ampla gama de casos de uso e setores, desde aplicativos web simples até soluções de grande escala, como processamento de dados, big data e aprendizado de máquina. Ele é projetado para ajudar as empresas a atender a requisitos de conformidade, aumentar a segurança e a confiabilidade de suas infraestruturas e reduzir os custos operacionais.
O que é o Pulumi e por que usá-lo
O Pulumi é uma plataforma de gerenciamento de infraestrutura como código que permite que desenvolvedores escrevam código para criar e gerenciar recursos na nuvem, como redes privadas, máquinas virtuais, bancos de dados, entre outros, usando uma, ou mais, linguagens de programação, dentre TypeScript, JavaScript, Python, Go, .NET, Java e YAML (que não é uma linguagem de progamação, mas todo mundo gosta de YAML ❤️).
Além disso, o Pulumi oferece uma série de recursos avançados, como automação de pipelines de delivery, suporte à testes unitários, de aceitação e de integração, criptografia de valores sensíveis, integrações via API, entre outras funcionalidades. Além disso também possui compatibilidade com Terraform, uma das ferramentas mais populares de infraestrutura como código.
O Pulumi simplifica e agiliza a implementação de infraestuturas na nuvem. Ao invés de criar recursos manualmente através da interface da AWS você pode automatizar o processo inteiro, garantindo assim maior precisão, consistência e facilidade na hora de implementar sua infraestrutura.
Como implementar o Well-Architected Framework da AWS com o Pulumi
Pré requisitos
Antes de começar é necessário:
- Instalar o Node.js
- Instalar a CLI do Pulumi
- Fazer login na plataforma do Pulumi
- Configurar o acesso à sua conta AWS
Iniciando um projeto
Este exemplo irá criar uma aplicação web hospedada na AWS usando um balanceador de carga, um servidor de aplicação e um banco de dados MySQL.
Para começar, crie uma nova pasta para o projeto e em seguida utilize a CLI do Pulumi para criar um projeto:
mkdir pulumi-new-project && cd pulumi-new-project
pulumi new aws-typescript
Siga o passo a passo dando o nome que desejar para a aplicação. Utilize também a região da AWS de sua preferência, neste exemplo vou usar a us-west-1:
This command will walk you through creating a new Pulumi project.
Enter a value or leave blank to accept the (default), and press <ENTER>.
Press ^C at any time to quit.
project name: (pulumi-aws-article) pulumi-aws-waf
project description: (A minimal AWS TypeScript Pulumi program)
Created project 'pulumi-aws-waf'
Please enter your desired stack name.
To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
stack name: (dev)
Created stack 'dev'
aws:region: The AWS region to deploy into: (us-east-1) us-west-1
Saved config
Installing dependencies...
added 169 packages, and audited 170 packages in 2m
54 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Finished installing dependencies
Your new project is ready to go!
To perform an initial deployment, run `pulumi up`
Criando recursos com Pulumi
Agora abra o arquivo index.ts no seu editor favorito e vamos começar criando uma VPC e um internet gateway para os recursos ficarem acessíveis pela internet:
import * as pulumi from '@pulumi/pulumi';
import * as aws from '@pulumi/aws';
// Criação do recurso de VPC
const vpc = new aws.ec2.Vpc('vpc', {
cidrBlock: '10.0.0.0/16',
});
// Criando um internet gateway para a VPC
const igw = new aws.ec2.InternetGateway('my-igw', {
vpcId: vpc.id,
});
Em seguida vamos criar duas subnets públicas para a aplicação, junto de uma tabela de roteamento para cada subnet poder ser acessada pelo internet gateway:
// Criando uma subnet pública para a aplicação
const webSubnet1 = new aws.ec2.Subnet('web-subnet-1', {
cidrBlock: '10.0.1.0/24',
vpcId: vpc.id,
mapPublicIpOnLaunch: true,
availabilityZone: aws.getAvailabilityZones().then((zones) => zones.names[0]),
});
// Criando uma subnet pública para a aplicação
const webSubnet2 = new aws.ec2.Subnet('web-subnet-2', {
cidrBlock: '10.0.2.0/24',
vpcId: vpc.id,
mapPublicIpOnLaunch: true,
availabilityZone: aws.getAvailabilityZones().then((zones) => zones.names[1]),
});
// Criando uma tabela de roteamento para as subnets publicas ficarem acessíveis pelo internet gateway
const publicRouteTable = new aws.ec2.RouteTable('public-route-table', {
vpcId: vpc.id,
routes: [
{
cidrBlock: '0.0.0.0/0',
gatewayId: igw.id,
},
],
});
// Associando a tabela de roteamneto a primeira vpc publica
const publicRouteAssociation1 = new aws.ec2.RouteTableAssociation(
'public-route-association-1',
{
routeTableId: publicRouteTable.id,
subnetId: webSubnet1.id,
},
);
// Associando a tabela de roteamneto a segunda vpc publica
const publicRouteAssociation2 = new aws.ec2.RouteTableAssociation(
'public-route-association-2',
{
routeTableId: publicRouteTable.id,
subnetId: webSubnet2.id,
},
);
Vamos criar também duas subnets para o banco de dados, porém privadas:
// Criando uma subnet privada para o banco de dados rds
const dbSubnet1 = new aws.ec2.Subnet('db-subnet-1', {
cidrBlock: '10.0.3.0/24',
vpcId: vpc.id,
availabilityZone: aws.getAvailabilityZones().then((zones) => zones.names[0]),
});
// Criando uma subnet pública para o servidor web
const dbSubnet2 = new aws.ec2.Subnet('db-subnet-2', {
cidrBlock: '10.0.4.0/24',
vpcId: vpc.id,
availabilityZone: aws.getAvailabilityZones().then((zones) => zones.names[1]),
});
Para finalizar os recursos de rede, vamos criar um grupo de segurança para cada um dos mesmos recursos. O grupo de segurança do servidor web deve permitir apenas trafego para o acesso da aplicação, enquanto o banco de dados deve ser acessivel somente pelo servidor da aplicação:
// Criando um grupo de segurança para permitir o acesso HTTP
const webSg = new aws.ec2.SecurityGroup("web-sg", {
vpcId: vpc.id,
ingress: [
{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
],
});
// Criando um grupo de segurança para permitir o acesso aos dados
const dbSg = new aws.ec2.SecurityGroup("db-sg", {
vpcId: vpc.id,
ingress: [
{ protocol: "tcp", fromPort: 3306, toPort: 3306, cidrBlocks: ["10.0.0.0/16"] },
],
});
Para garantir maior segurança dos dados vamos setar o parâmetro storageEncrypted=true para garantir que os dados sejam criptografados enquanto não são lidos.
Além disso vamos setar também como true o parâmetro multiAz para garantir que o banco seja criado em mais de uma zona de disponibilidade com failover automático, garantindo assim uma maior disponibilidade dos dados.
Utilizando a respectiva subnet e grupo de segurança a implementação fica:
const dbSubnetGroupName = 'my-db-group';
// Criando um grupo de subnets para associar ao banco
const dbSubnetGroup = new aws.rds.SubnetGroup('my-db-group', {
name: dbSubnetGroupName,
subnetIds: [dbSubnet1.id, dbSubnet2.id],
});
// Criando um banco de dados RDS
const db = new aws.rds.Instance(
'my-db',
{
engine: 'mysql',
instanceClass: 'db.t2.small',
allocatedStorage: 10,
dbSubnetGroupName,
vpcSecurityGroupIds: [dbSg.id],
username: 'admin',
password: 'password',
storageEncrypted: true,
multiAz: true,
},
{
dependsOn: dbSubnetGroup,
},
);
Agora vamos criar o servidor da aplicação web a respectiva subnet e grupo de segurança:
// Criando um'a instância EC2 para o servidor web
const instance = new aws.ec2.Instance('my-instance', {
instanceType: 't2.micro',
ami: 'ami-00569e54da628d17c',
vpcSecurityGroupIds: [webSg.id],
subnetId: webSubnet1.id,
userData: pulumi.interpolate`#!/bin/bash
echo "Pulumi + AWS = <3" > index.html
nohup python -m SimpleHTTPServer 80 &`,
});
Vamos criar também um balanceador de carga para a intância para distribuir o trafego e garantir maior estabilidade:
// Criando um balanceador de carga
const lb = new aws.lb.LoadBalancer('my-lb', {
internal: false,
subnets: [webSubnet1.id, webSubnet2.id],
});
// Criando um target group para o baleaceador de carga
const targetGroup = new aws.lb.TargetGroup(
'web-target',
{
port: 80,
protocol: 'HTTP',
vpcId: vpc.id,
targetType: 'instance',
healthCheck: {
protocol: 'HTTP',
port: '80',
path: '/',
timeout: 10,
interval: 30,
matcher: '200-299',
},
},
{
dependsOn: instance,
},
);
// Criando um listener para o baleaceador de carga
const listener = new aws.lb.Listener('web-listener', {
loadBalancerArn: lb.arn,
port: 80,
protocol: 'HTTP',
defaultActions: [
{
type: 'forward',
targetGroupArn: targetGroup.arn,
},
],
});
// Anexando a instância ao target group para direcionar o trafego
const instanceTarget = new aws.lb.TargetGroupAttachment(
'web-target-attachment',
{
targetGroupArn: targetGroup.arn,
targetId: instance.id,
port: 80,
},
);
Por fim vamos expor algumas informações sobre os recursos para que possamos acessá-los:
// Exportando informações sobre a aplicação
export const lbDnsName = lb.dnsName;
export const dbEndpoint = db.endpoint;
Criando os recursos na AWS
Uma vez que tudo está de acordo com o desejado no código podemos utilizar a CLI do Pulumi para que ele possa de fato criar os recursos na AWS:
pulumi up
Previewing update (dev)
View Live: https://app.pulumi.com/<username>/pulumi-aws-waf/dev/previews/<uuid>
Type Name Plan
+ pulumi:pulumi:Stack pulumi-aws-waf-dev create
+ ├─ aws:ec2:Vpc vpc create
+ ├─ aws:ec2:InternetGateway my-igw create
+ ├─ aws:ec2:SecurityGroup db-sg create
+ ├─ aws:ec2:SecurityGroup web-sg create
+ ├─ aws:ec2:RouteTable public-route-table create
+ ├─ aws:ec2:Subnet db-subnet-2 create
+ ├─ aws:ec2:Subnet web-subnet-2 create
+ ├─ aws:ec2:Subnet web-subnet-1 create
+ ├─ aws:ec2:Subnet db-subnet-1 create
+ ├─ aws:ec2:RouteTableAssociation public-route-association-2 create
+ ├─ aws:ec2:Instance my-instance create
+ ├─ aws:ec2:RouteTableAssociation public-route-association-1 create
+ ├─ aws:lb:LoadBalancer my-lb create
+ ├─ aws:rds:SubnetGroup my-db-group create
+ ├─ aws:rds:Instance my-db create
+ ├─ aws:lb:TargetGroup web-target create
+ ├─ aws:lb:TargetGroupAttachment web-target-attachment create
+ └─ aws:lb:Listener web-listener create
Outputs:
dbEndpoint: output<string>
lbDnsName : output<string>
Resources:
+ 19 to create
Do you want to perform this update? yes
Updating (dev)
View Live: https://app.pulumi.com/<username>/pulumi-aws-waf/dev/updates/1
Type Name Status
+ pulumi:pulumi:Stack pulumi-aws-waf-dev created (620s)
+ ├─ aws:ec2:Vpc vpc created (3s)
+ ├─ aws:ec2:InternetGateway my-igw created (1s)
+ ├─ aws:ec2:Subnet db-subnet-1 created (2s)
+ ├─ aws:ec2:Subnet web-subnet-1 created (13s)
+ ├─ aws:ec2:Subnet db-subnet-2 created (2s)
+ ├─ aws:ec2:SecurityGroup db-sg created (5s)
+ ├─ aws:ec2:Subnet web-subnet-2 created (13s)
+ ├─ aws:ec2:SecurityGroup web-sg created (5s)
+ ├─ aws:ec2:RouteTable public-route-table created (3s)
+ ├─ aws:rds:SubnetGroup my-db-group created (3s)
+ ├─ aws:rds:Instance my-db created (602s)
+ ├─ aws:ec2:RouteTableAssociation public-route-association-1 created (1s)
+ ├─ aws:ec2:Instance my-instance created (24s)
+ ├─ aws:ec2:RouteTableAssociation public-route-association-2 created (1s)
+ ├─ aws:lb:LoadBalancer my-lb created (125s)
+ ├─ aws:lb:TargetGroup web-target created (2s)
+ ├─ aws:lb:TargetGroupAttachment web-target-attachment created (0.61s)
+ └─ aws:lb:Listener web-listener created (1s)
Outputs:
dbEndpoint: "my-xxxxxxxxx.xxxxxxxxxxxx.us-west-1.rds.amazonaws.com:3306"
lbDnsName : "my-lb-xxxxxxx-xxxxxxxxxx.us-west-1.elb.amazonaws.com"
Resources:
+ 19 created
Duration: 10m22s
Após criar os recursos o Pulumi salva o estado da infraestrutura na plataforma para servir de base de comparação em futuras implementações e garantir que o que está na AWS reflete o que está no código.
Podemos utilizar o DNS do balanceador de carga exposto pelo output lbDnsName para acessar a aplicação:
Destruindo os recursos na AWS
Lembre-se de destruir os recursos caso não vá mais utilizá-los e evite uma cobrança desnecessária no cartão de crédito:
pulumi destroy
Conclusão
Por meio do AWS Well-Architected Framework as empresas podem projetar e gerenciar aplicativos e infraestrutura em nuvem de maneira mais eficiente e econômica, reduzindo riscos e minimizando custos.
É uma ferramenta valiosa para qualquer empresa que queira aproveitar ao máximo os benefícios da nuvem AWS.
O Pulumi entra como uma ferramenta adicional para auxiliar e simplificar a implementação do framework, reduzindo a possibilidade de erro humano com a familiaridade de uma linguagem de programação que você já conhece e utiliza, além de garantir a implementação das melhores práticas de Delivery e DevOps.
Caso queria ver o código completo ele está dispoível no repositório https://github.com/gabrielsclimaco/pulumi-aws-waf-article.
Obrigado por ler até aqui e todo feedback nos comentários será muito bem vindo.
#paz ☕️