AWS na Prática · 04 — CloudFormation

Toda a infraestrutura do projeto versionada como código YAML.

15 min de leitura

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

  1. Por que Infrastructure as Code?
  2. Anatomia de um template
  3. Resources, Parameters, Outputs
  4. Stacks: criando, atualizando, deletando
  5. Template para o DynamoDB
  6. Template para o IAM
  7. Template para o Beanstalk
  8. Stacks aninhadas e composição
  9. Boas práticas e armadilhas
  10. Migrando o que já existe
  11. Exercícios de fixação
  12. 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

FerramentaCaracterísticas
CloudFormationNativo da AWS, YAML/JSON, sem custo
TerraformMulti-cloud, HCL, requer state management
AWS CDKProgramático (TypeScript, Python), gera CloudFormation
PulumiProgramático multi-cloud, similar ao CDK
Serverless FrameworkEspecí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 stack

As 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.
  • Resourcesseçã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.Arn

Os 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: 5

Parameters: 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: 100

Tipos 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.Arn

Outputs 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-system

Acompanhando 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-database

Atualizando a stack

aws cloudformation update-stack \
  --profile aws-curso \
  --stack-name orders-database \
  --template-body file://orders-table.yaml \
  --parameters \
    ParameterKey=EnvironmentName,ParameterValue=dev

Deletando a stack

aws cloudformation delete-stack \
  --profile aws-curso \
  --stack-name orders-database

Esse 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 WriteCapacity

Continuaçã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/AWSElasticBeanstalkWebTier

Inline 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: true

Deploy 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égiaVantagensDesvantagens
Multiple stacks + ImportsLifecycle independente, cada stack standaloneRequer ordem de deploy
Nested stacksUm único deploy resolve tudoAcoplamento 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 payments

09. 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

  1. Escreva o template que descreve os recursos como você quer que sejam.
  2. Use o recurso de Import do CloudFormation para “adotar” os recursos existentes.
  3. Verifique drift após a importação para ver diferenças.
  4. 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-database

Se 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

Exercício
01

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?

Exercício
02

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.

Exercício
03

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.)

Exercício
04

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.

Exercício
05

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.

Posts relacionados