AWS na Prática · 04 — CloudFormation
Toda a infraestrutura do projeto versionada como código YAML.
Toda a infraestrutura do projeto versionada como código YAML.
Sobre este módulo
Este é o módulo mais transformador do curso. Tudo que você criou manualmente nos módulos anteriores vai virar arquivos YAML versionáveis. Você nunca mais vai querer clicar em console para criar recursos depois disso.
Nível: Intermediário · Duração estimada: 4-5 horas
Sumário
- Por que Infrastructure as Code?
- Anatomia de um template
- Resources, Parameters, Outputs
- Stacks: criando, atualizando, deletando
- Template para o DynamoDB
- Template para o IAM
- Template para o Beanstalk
- Stacks aninhadas e composição
- Boas práticas e armadilhas
- Migrando o que já existe
- Exercícios de fixação
- Próximos passos
01. Por que Infrastructure as Code?
Até agora você criou tabelas, roles e environments clicando no console. Funcionou. Mas o que acontece se você precisar recriar tudo amanhã, em outra conta, em outra região? Ou se quiser saber qual era o estado da infraestrutura há 30 dias?
O problema do clickops
- Não é reproduzível — você não consegue recriar exatamente o que fez.
- Não é versionado — não tem histórico do que mudou e quando.
- É lento — criar 30 recursos manualmente leva horas; via código, segundos.
- É propenso a erros — esquecer uma policy, uma tag, uma configuração.
- Não é auditável — quem mudou o quê e por quê?
- É frágil — uma pessoa sai do time e leva o conhecimento da infraestrutura.
A proposta da IaC
Toda a infraestrutura é descrita em arquivos de texto declarativos que vivem no Git, junto com o código da aplicação. Para criar/atualizar a infraestrutura, você aplica esses arquivos via comando.
É um modelo radicalmente diferente, e quando você se acostuma, voltar a clicar em console parece bárbaro.
As ferramentas do mercado
| Ferramenta | Características |
|---|---|
| CloudFormation | Nativo da AWS, YAML/JSON, sem custo |
| Terraform | Multi-cloud, HCL, requer state management |
| AWS CDK | Programático (TypeScript, Python), gera CloudFormation |
| Pulumi | Programático multi-cloud, similar ao CDK |
| Serverless Framework | Específico para Lambda/serverless, simplificado |
02. Anatomia de um template
Um template CloudFormation é um arquivo YAML (ou JSON) com uma estrutura fixa. Vamos dissecá-lo.
Esqueleto mínimo
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Minha primeira stack'
Parameters:
# parâmetros de entrada
Resources:
# os recursos AWS a serem criados
Outputs:
# valores expostos pela stackAs seções principais
- AWSTemplateFormatVersion — versão do formato. Use sempre '2010-09-09'.
- Description — descrição livre da stack.
- Parameters — valores configuráveis na hora do deploy (ex: nome do ambiente).
- Mappings — tabelas de lookup estáticas (ex: AMI por região).
- Conditions — condições para criar ou não certos recursos.
- Resources — seção obrigatória — os recursos AWS a criar.
- Outputs — valores expostos pela stack, úteis para outras stacks.
Um template completo simples
AWSTemplateFormatVersion: '2010-09-09'
Description: 'S3 bucket com versionamento'
Parameters:
BucketName:
Type: String
Description: 'Nome do bucket S3'
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
VersioningConfiguration:
Status: Enabled
Tags:
- Key: Project
Value: order-system
Outputs:
BucketArn:
Description: 'ARN do bucket criado'
Value: !GetAtt MyBucket.ArnOs famosos !Ref e !GetAtt
- !Ref — referencia outro elemento (parâmetro, recurso). Para um recurso, geralmente retorna o ID/nome principal.
!GetAtt <Recurso>.<Atributo>— pega um atributo específico de um recurso (ex: ARN, URL).- !Sub — substituição de variáveis em string.
- !Join — concatenação de strings.
03. Resources, Parameters, Outputs
Vamos aprofundar nas três seções que você vai usar mais.
Resources: o coração do template
Cada recurso tem um nome lógico (você escolhe), um tipo (AWS::<Service>::<Resource>) e propriedades específicas daquele tipo.
Resources:
OrdersTable: # nome lógico (escolhido por você)
Type: AWS::DynamoDB::Table # tipo do recurso
Properties: # propriedades específicas
TableName: Orders
AttributeDefinitions:
- AttributeName: orderId
AttributeType: S
KeySchema:
- AttributeName: orderId
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5Parameters: tornando o template configurável
Parameters:
EnvironmentName:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]
Description: 'Ambiente de deploy'
ReadCapacity:
Type: Number
Default: 5
MinValue: 1
MaxValue: 100Tipos comuns: String, Number, List<Number>, CommaDelimitedList, e tipos especiais AWS como AWS::EC2::VPC::Id que validam que o valor existe na conta.
Outputs: expondo valores
Outputs:
OrdersTableName:
Description: 'Nome da tabela criada'
Value: !Ref OrdersTable
Export:
Name: !Sub '${AWS::StackName}-TableName'
OrdersTableArn:
Description: 'ARN da tabela'
Value: !GetAtt OrdersTable.ArnOutputs ficam visíveis no console e podem ser importados por outras stacks via !ImportValue. Vamos usar isso para conectar a stack de DynamoDB com a stack de IAM.
04. Stacks: criando, atualizando, deletando
Uma stack é uma instância criada a partir de um template. É a unidade de deploy do CloudFormation.
Ciclo de vida de uma stack
- CREATE_IN_PROGRESS — stack está sendo criada — recursos sendo provisionados.
- CREATE_COMPLETE — tudo criado com sucesso. ✓
- CREATE_FAILED — algo falhou. CloudFormation faz rollback automaticamente.
- UPDATE_IN_PROGRESS — atualizações sendo aplicadas.
- UPDATE_ROLLBACK_IN_PROGRESS — houve erro no update; voltando ao estado anterior.
- DELETE_IN_PROGRESS — stack está sendo deletada — recursos sendo removidos.
- DELETE_COMPLETE — stack e todos os recursos deletados.
Criando a stack via CLI
aws cloudformation create-stack \
--profile aws-curso \
--stack-name orders-database \
--template-body file://orders-table.yaml \
--parameters \
ParameterKey=EnvironmentName,ParameterValue=dev \
--tags Key=Project,Value=order-systemAcompanhando o progresso
# Status atual
aws cloudformation describe-stacks \
--profile aws-curso \
--stack-name orders-database \
--query "Stacks[0].StackStatus"
# Eventos detalhados (últimos)
aws cloudformation describe-stack-events \
--profile aws-curso \
--stack-name orders-databaseAtualizando a stack
aws cloudformation update-stack \
--profile aws-curso \
--stack-name orders-database \
--template-body file://orders-table.yaml \
--parameters \
ParameterKey=EnvironmentName,ParameterValue=devDeletando a stack
aws cloudformation delete-stack \
--profile aws-curso \
--stack-name orders-databaseEsse comando deleta todos os recursos criados pela stack. É a forma mais limpa de “limpar tudo” no fim de uma sessão de estudo.
05. Template para o DynamoDB
Vamos transformar a tabela Orders (que você criou no console no Módulo 02) em um template CloudFormation. Esse exercício é a essência da migração para IaC.
Template: orders-table.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Tabela DynamoDB para o serviço Orders'
Parameters:
EnvironmentName:
Type: String
Default: dev
ReadCapacity:
Type: Number
Default: 5
WriteCapacity:
Type: Number
Default: 5
Resources:
OrdersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub 'Orders-${EnvironmentName}'
BillingMode: PROVISIONED
AttributeDefinitions:
- AttributeName: orderId
AttributeType: S
- AttributeName: customerId
AttributeType: S
- AttributeName: createdAt
AttributeType: S
- AttributeName: status
AttributeType: S
KeySchema:
- AttributeName: orderId
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: !Ref ReadCapacity
WriteCapacityUnits: !Ref WriteCapacityContinuação — os GSIs:
GlobalSecondaryIndexes:
- IndexName: by-customer-index
KeySchema:
- AttributeName: customerId
KeyType: HASH
- AttributeName: createdAt
KeyType: RANGE
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: !Ref ReadCapacity
WriteCapacityUnits: !Ref WriteCapacity
- IndexName: by-status-index
KeySchema:
- AttributeName: status
KeyType: HASH
- AttributeName: createdAt
KeyType: RANGE
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: !Ref ReadCapacity
WriteCapacityUnits: !Ref WriteCapacity
Tags:
- Key: Project
Value: order-system
- Key: Environment
Value: !Ref EnvironmentName
Outputs:
TableName:
Value: !Ref OrdersTable
Export:
Name: !Sub '${AWS::StackName}-TableName'
TableArn:
Value: !GetAtt OrdersTable.Arn
Export:
Name: !Sub '${AWS::StackName}-TableArn'06. Template para o IAM
Vamos criar a policy IAM que dá acesso à tabela Orders, e a instance role do Beanstalk com essa policy anexada — tudo em um único template.
Template: orders-iam.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'IAM Role para o serviço Orders'
Parameters:
DatabaseStackName:
Type: String
Description: 'Nome da stack que criou a tabela Orders'
Resources:
OrdersServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: orders-service-instance-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTierInline policy para acesso ao DynamoDB
Policies:
- PolicyName: dynamodb-orders-access
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:Query
Resource:
- !ImportValue
Fn::Sub: '${DatabaseStackName}-TableArn'
- !Sub
- '${TableArn}/index/*'
- TableArn: !ImportValue
Fn::Sub: '${DatabaseStackName}-TableArn'
OrdersInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: orders-service-instance-profile
Roles:
- !Ref OrdersServiceRole
Outputs:
InstanceProfileArn:
Value: !GetAtt OrdersInstanceProfile.Arn
Export:
Name: !Sub '${AWS::StackName}-ProfileArn'Deploy em ordem
# 1. Crie a stack do banco primeiro
aws cloudformation create-stack \
--profile aws-curso \
--stack-name orders-database \
--template-body file://orders-table.yaml
# 2. Aguarde concluir
aws cloudformation wait stack-create-complete \
--profile aws-curso \
--stack-name orders-database
# 3. Crie a stack do IAM, referenciando a primeira
aws cloudformation create-stack \
--profile aws-curso \
--stack-name orders-iam \
--template-body file://orders-iam.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameters \
ParameterKey=DatabaseStackName,ParameterValue=orders-database--capabilities CAPABILITY_NAMED_IAM é necessário sempre que o template cria recursos IAM com nome customizado. CloudFormation pede confirmação explícita por motivos de segurança.
07. Template para o Beanstalk
Por fim, o template do environment Beanstalk. Aqui você verá a complexidade real — environments têm muitas opções de configuração.
Template: orders-beanstalk.yaml (parte 1)
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Beanstalk environment para Orders'
Parameters:
EnvironmentName:
Type: String
Default: dev
IamStackName:
Type: String
DatabaseStackName:
Type: String
SolutionStackName:
Type: String
Default: '64bit Amazon Linux 2023 v6.1.0 running Node.js 20'
Resources:
OrdersApplication:
Type: AWS::ElasticBeanstalk::Application
Properties:
ApplicationName: orders-service
Description: 'Microserviço de Pedidos'Template: orders-beanstalk.yaml (parte 2)
OrdersEnvironment:
Type: AWS::ElasticBeanstalk::Environment
Properties:
ApplicationName: !Ref OrdersApplication
EnvironmentName: !Sub 'orders-${EnvironmentName}'
SolutionStackName: !Ref SolutionStackName
OptionSettings:
- Namespace: aws:autoscaling:launchconfiguration
OptionName: IamInstanceProfile
Value: !ImportValue
Fn::Sub: '${IamStackName}-ProfileArn'
- Namespace: aws:autoscaling:launchconfiguration
OptionName: InstanceType
Value: t2.micro
- Namespace: aws:elasticbeanstalk:environment
OptionName: EnvironmentType
Value: SingleInstance
- Namespace: aws:elasticbeanstalk:application:environment
OptionName: DYNAMODB_TABLE_NAME
Value: !ImportValue
Fn::Sub: '${DatabaseStackName}-TableName'
- Namespace: aws:elasticbeanstalk:application:environment
OptionName: AWS_REGION
Value: !Ref AWS::Region
- Namespace: aws:elasticbeanstalk:cloudwatch:logs
OptionName: StreamLogs
Value: trueDeploy do código (separado)
O template cria o environment vazio. Para fazer deploy do código, use o comando aws elasticbeanstalk create-application-version e depois update-environment. Manter código e infraestrutura separados é boa prática: a infraestrutura muda raramente, o código todo dia.
08. Stacks aninhadas e composição
Um único template gigante é difícil de manter. Quando o sistema cresce, você quebra em múltiplas stacks que se referenciam — ou usa nested stacks, onde uma stack mãe orquestra várias filhas.
Duas estratégias de composição
| Estratégia | Vantagens | Desvantagens |
|---|---|---|
| Multiple stacks + Imports | Lifecycle independente, cada stack standalone | Requer ordem de deploy |
| Nested stacks | Um único deploy resolve tudo | Acoplamento forte, deploy lento |
Para o nosso projeto vamos usar multiple stacks — uma para o banco, uma para IAM, uma para Beanstalk de cada serviço. Isso espelha a divisão lógica do sistema.
Estrutura recomendada de pastas
infra/
├── databases/
│ ├── orders-table.yaml
│ ├── inventory-table.yaml
│ └── payments-table.yaml
├── iam/
│ ├── orders-iam.yaml
│ ├── inventory-iam.yaml
│ └── payments-iam.yaml
├── beanstalk/
│ ├── orders-beanstalk.yaml
│ ├── inventory-beanstalk.yaml
│ └── payments-beanstalk.yaml
└── deploy.sh # script que orquestra a ordem
Script de deploy
#!/bin/bash
set -e
PROFILE=aws-curso
deploy_stack() {
local STACK_NAME=$1
local TEMPLATE=$2
shift 2
echo "Deploying ${STACK_NAME}..."
aws cloudformation deploy \
--profile $PROFILE \
--stack-name $STACK_NAME \
--template-file $TEMPLATE \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset \
"$@"
}
# Camada 1: bancos
deploy_stack orders-db databases/orders-table.yaml
deploy_stack inventory-db databases/inventory-table.yaml
deploy_stack payments-db databases/payments-table.yaml
# Camada 2: IAM (depende dos bancos)
deploy_stack orders-iam iam/orders-iam.yaml \
--parameter-overrides DatabaseStackName=orders-db
# ... análogo para inventory e payments09. Boas práticas e armadilhas
Princípios que vão evitar dores de cabeça quando o projeto crescer.
Boas práticas
- Versione tudo em Git — o template é código.
- Use Change Sets antes de update em produção — veja antes de aplicar.
- Templates pequenos e focados — uma stack por “domínio” de infra.
- Outputs com Export — facilita compor stacks.
- Tags em todos os recursos — para encontrar e organizar custos.
- Parameters com Default — torna o template usável sem muita digitação.
- Description em todos os recursos importantes — documentação inline.
- Use aws cloudformation validate-template antes de deployar.
Armadilhas comuns
- Hardcoded ARNs — sempre use !Ref, !GetAtt, pseudo-parâmetros.
- DeletionPolicy ausente em recursos com dados — uma deleção acidental e os dados se vão.
- Templates gigantes — fica impossível de revisar.
- Ignorar drift — alguém muda algo no console e o template fica fora de sincronia.
- Esquecer CAPABILITY_NAMED_IAM — falha óbvia em recursos IAM com nome.
- Circular dependencies — A importa de B, B importa de A.
DeletionPolicy: protegendo dados
OrdersTable:
Type: AWS::DynamoDB::Table
DeletionPolicy: Retain # NÃO deleta a tabela ao deletar a stack
UpdateReplacePolicy: Retain # idem em casos de replace
Properties:
# ...Retain: o recurso permanece mesmo se a stack for deletada. Snapshot: tira um snapshot antes de deletar (RDS, EBS). Delete: padrão, deleta. Para tabelas com dados de produção, sempre Retain.
10. Migrando o que já existe
Você já tem tabelas, roles e environments criados manualmente. Como passa tudo isso para CloudFormation sem perder nada?
Estratégia recomendada
- Escreva o template que descreve os recursos como você quer que sejam.
- Use o recurso de Import do CloudFormation para “adotar” os recursos existentes.
- Verifique drift após a importação para ver diferenças.
- Ajuste o template para refletir o estado real, ou aplique correções.
Alternativa: nova stack, antigos descartados
Para um projeto de estudo, é mais simples: delete os recursos antigos via console, depois crie tudo via CloudFormation. Você perde os dados de teste (que são fictícios mesmo), mas ganha um sistema 100% IaC desde o começo.
Detectando drift
# Inicia detecção
aws cloudformation detect-stack-drift \
--profile aws-curso \
--stack-name orders-database
# Aguarda terminar e mostra resultado
aws cloudformation describe-stack-resource-drifts \
--profile aws-curso \
--stack-name orders-databaseSe o status retornar DRIFTED, alguém mexeu no recurso fora do CloudFormation. Você precisa decidir: trazer a mudança para o template, ou reverter o recurso ao estado do template.
11. Exercícios de fixação
Migre a tabela Orders para CFN
Delete a tabela Orders criada manualmente. Escreva o template orders-table.yaml seguindo a seção 5 e suba via aws cloudformation deploy. Confirme: a tabela está criada com os mesmos GSIs? Os tags estão corretos?
Crie templates para Inventory e Payments
Aplique o mesmo padrão para os outros dois bancos. Você deve ter três templates separados, três stacks separadas, com Outputs e Exports. Confirme via console que tudo foi criado corretamente.
Adicione DeletionPolicy
Modifique o template de Orders adicionando DeletionPolicy: Retain e UpdateReplacePolicy: Retain. Faça update da stack. Em seguida, tente deletar a stack. O que acontece? Por quê? (Depois, delete a tabela manualmente para limpar.)
Stack de IAM completa
Escreva e suba a stack orders-iam seguindo a seção 6. Verifique: (a) a role foi criada; (b) a policy permite só as ações certas; (c) o instance profile existe e está associado à role.
Script de deploy completo
Escreva o deploy.sh que cria, na ordem, todas as stacks: 3 bancos + 3 IAMs + 3 beanstalks. Teste rodando em conta limpa (depois de deletar tudo). Tempo total esperado: 15-20 minutos para criar todo o sistema.
12. Próximos passos
Esse módulo é um divisor de águas. Antes dele, você criava infraestrutura clicando. Agora ela vive em código, é versionada, é reproduzível. Voltar para clickops nunca mais.
O que você aprendeu
- Por que IaC é não-negociável em sistemas sérios.
- Anatomia de templates CloudFormation: Resources, Parameters, Outputs.
- Intrinsic functions: !Ref, !GetAtt, !Sub, !ImportValue.
- Ciclo de vida de stacks: criar, atualizar, deletar.
- Templates concretos para DynamoDB, IAM e Beanstalk.
- Composição de stacks com Imports/Exports.
- Boas práticas, armadilhas, DeletionPolicy.
- Como migrar recursos manuais para IaC.
O que vem no Módulo 05
Mensageria: SNS e SQS. Vamos implementar a comunicação assíncrona entre Orders e Inventory — quando um pedido é criado, um evento é publicado no SNS, uma fila SQS o consome, e o Inventory decrementa o estoque. É o padrão fundamental de sistemas distribuídos resilientes.
Infraestrutura é código. Boa jornada — Módulo 05 a seguir.