Sessão 002 — CloudFormation: stacks, templates, parameters, outputs, Ref/GetAtt
Duração estimada: 60 minutos
Pré-requisitos: session-001 — AWS CLI avançado (SSO, perfis, assume-role)
Objetivo
Ao final, você conseguirá escrever um template YAML completo com Parameters tipados, Resources referenciando outros com Ref e Fn::GetAtt, Outputs exportados entre stacks, e fazer deploy via aws cloudformation deploy com changesets.
Contexto
[FATO] O CloudFormation é o serviço nativo de IaC da AWS desde 2011. Ele opera no modelo declarativo: você descreve o estado desejado da infraestrutura num template, e o CloudFormation calcula o delta entre o estado atual da stack e o desejado, executando as operações necessárias na ordem correta.
[CONSENSO] Mesmo que você use CDK (que vem nas próximas sessões), entender CloudFormation puro é essencial — o CDK gera CloudFormation como artefato de saída, e qualquer debugging de deploy acontece no nível do CloudFormation. Engenheiros que pulam essa camada ficam cegos quando algo dá errado no cdk deploy.
A distinção central desta sessão é: template é o documento, stack é a instância em execução. O mesmo template pode criar múltiplas stacks em regiões ou contas diferentes.
Conceitos principais
1. Anatomia do template
[FATO] Um template CloudFormation tem 10 seções possíveis. Apenas Resources é obrigatória.
AWSTemplateFormatVersion: "2010-09-09" # fixo, nunca muda
Description: "O que essa stack faz"
Metadata: {} # dados para ferramentas externas (ex: Console UI hints)
Parameters: {} # inputs do usuário ao criar/atualizar a stack
Rules: {} # validações de parâmetros (cross-parameter constraints)
Mappings: {} # lookup tables estáticas (ex: AMI por região)
Conditions: {} # flags booleanas baseadas em parâmetros
Transform: {} # macros (ex: SAM usa AWS::Serverless-2016-10-31)
Resources: {} # OBRIGATÓRIO — recursos a criar
Outputs: {} # valores a exportar ou exibir
Ordem de processamento do CloudFormation:
1. Parameters → resolve inputs
2. Rules → valida combinações de parâmetros
3. Mappings → disponibiliza lookup tables
4. Conditions → avalia flags booleanas
5. Resources → determina ordem de criação pelo grafo de dependências
6. Outputs → resolve referências após criação dos recursos
Essa ordem importa porque Ref e Fn::GetAtt em Outputs só funcionam depois que os recursos existem.
2. Parameters — tipagem, constraints e boas práticas
Parameters recebem valores em runtime. Ao contrário de variáveis de ambiente, eles são validados pelo CloudFormation antes de qualquer recurso ser criado.
Tipos disponíveis:
Parameters:
# Tipos primitivos
Env:
Type: String
AllowedValues: [dev, staging, prod]
Default: dev
InstanceType:
Type: String
AllowedPattern: "^(t3|m5|c5)\\.(micro|small|medium|large)$"
ConstraintDescription: "Apenas instâncias t3, m5 ou c5"
Port:
Type: Number
MinValue: 1024
MaxValue: 65535
# Tipos AWS — CloudFormation valida a existência do recurso na conta
VpcId:
Type: AWS::EC2::VPC::Id # dropdown no console, validado
SubnetIds:
Type: List<AWS::EC2::Subnet::Id> # lista de subnets
AmiId:
Type: AWS::EC2::Image::Id
# SSM Parameter Store — resolve o valor em runtime
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
[CONSENSO] Usar AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> para AMIs é a prática recomendada — o template sempre usa a AMI mais recente sem precisar ser atualizado. A desvantagem é que o deploy pode mudar sem alteração no template, o que complica o rastreamento de drift.
Referenciando parâmetros:
Resources:
MyInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType # !Ref retorna o valor do parâmetro
ImageId: !Ref LatestAmiId
3. Resources — o coração do template
Cada recurso tem: logical ID (nome no template), Type e Properties.
Resources:
# Logical ID: nome interno — referenciado por Ref/GetAtt
# Convenção: PascalCase descritivo (AppBucket, WebServerSG, etc.)
AppBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain # Snapshot | Retain | Delete (padrão)
UpdateReplacePolicy: Retain # comportamento ao substituir o recurso
DependsOn: LogGroup # força ordem explícita (use só quando necessário)
Properties:
BucketName: !Sub "app-${Env}-${AWS::AccountId}"
VersioningConfiguration:
Status: Enabled
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
WebServerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Web server security group"
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
WebServer:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LatestAmiId
SecurityGroupIds:
- !Ref WebServerSG # Ref num recurso retorna o ID físico
Tags:
- Key: Name
Value: !Sub "web-${Env}"
DeletionPolicy e UpdateReplacePolicy — diferença crítica:
DeletionPolicy → o que acontece quando a STACK é deletada
UpdateReplacePolicy → o que acontece quando o recurso precisa ser SUBSTITUÍDO
(ex: mudar o nome de um S3 bucket obriga substituição)
Valores:
Delete → recurso é deletado (padrão para a maioria)
Retain → recurso permanece na conta, desassociado da stack
Snapshot → tira snapshot antes de deletar (RDS, EBS, ElastiCache)
[FATO] DeletionPolicy: Retain num bucket S3 não impede que o CloudFormation tente deletar o bucket ao deletar a stack — apenas faz com que o CloudFormation "esqueça" o recurso em vez de deletá-lo. Se o bucket tiver objetos e não tiver Retain, o delete da stack vai falhar com BucketNotEmpty.
4. Ref e Fn::GetAtt — o que cada um retorna
Esta é a maior fonte de confusão em CloudFormation. Ref e Fn::GetAtt retornam coisas diferentes dependendo do tipo de recurso.
Ref — retorna o identificador principal do recurso:
# Cada tipo de recurso tem um "Ref value" diferente:
!Ref AppBucket # → nome do bucket (ex: "app-prod-123456789")
!Ref WebServerSG # → Group ID (ex: "sg-0abc123")
!Ref WebServer # → Instance ID (ex: "i-0abc123")
!Ref MyQueue # → URL da fila (ex: "https://sqs.us-east-1...")
!Ref MyTable # → nome da tabela DynamoDB
!Ref MyFunction # → nome da função Lambda
!Ref MyRole # → nome da role IAM (NÃO o ARN)
[FATO] Para saber o que Ref retorna para um tipo específico, consulte a documentação do recurso em "Return values > Ref". Não existe regra universal — cada serviço define o que é seu "identificador principal".
Fn::GetAtt — retorna atributos específicos:
# Sintaxe longa
Fn::GetAtt:
- AppBucket
- Arn
# Sintaxe curta (YAML)
!GetAtt AppBucket.Arn # → arn:aws:s3:::app-prod-123456789
!GetAtt AppBucket.DomainName # → app-prod-123456789.s3.amazonaws.com
!GetAtt WebServerSG.GroupId # → sg-0abc123 (mesmo que Ref aqui)
!GetAtt WebServer.PrivateIp # → 10.0.1.50
!GetAtt WebServer.PublicIp # → 3.x.x.x (se tiver IP público)
!GetAtt MyRole.Arn # → arn:aws:iam::123:role/MyRole
!GetAtt MyFunction.Arn # → arn:aws:lambda:us-east-1:123:function:name
Diagrama da diferença:
Recurso: AWS::S3::Bucket (logical ID: AppBucket)
│
├── Ref → nome do bucket "app-prod-123456789012"
├── GetAtt .Arn → ARN completo "arn:aws:s3:::app-prod-..."
└── GetAtt .DomainName → endpoint "app-prod-....s3.amazonaws.com"
Recurso: AWS::IAM::Role (logical ID: MyRole)
│
├── Ref → nome da role "MyRole-XXXXXXXXXXX"
└── GetAtt .Arn → ARN completo "arn:aws:iam::123:role/MyRole-..."
(para IAM Roles você quase sempre quer o ARN → use GetAtt, não Ref)
Funções de string mais usadas junto com Ref/GetAtt:
# Sub — interpolação de strings (mais legível que Join)
!Sub "arn:aws:s3:::${AppBucket}/*"
!Sub "https://${WebServer.PublicIp}:443"
!Sub "${AWS::StackName}-${AWS::Region}-logs" # pseudo-parâmetros
# Join — une lista com delimitador
!Join [",", [!Ref SubnetA, !Ref SubnetB]]
# Select — pega elemento de lista por índice
!Select [0, !GetAZs ""] # primeira AZ da região atual
# Pseudo-parâmetros sempre disponíveis (sem declarar em Parameters):
# AWS::AccountId, AWS::Region, AWS::StackName, AWS::StackId, AWS::NoValue
5. Outputs e cross-stack references
Outputs servem para dois propósitos distintos: exibir valores úteis após o deploy, e exportar valores para outras stacks consumirem.
Estrutura de um Output:
Outputs:
BucketName:
Description: "Nome do bucket de aplicação"
Value: !Ref AppBucket # valor exibido no console e retornado pela API
BucketArn:
Description: "ARN do bucket"
Value: !GetAtt AppBucket.Arn
Export:
Name: !Sub "${AWS::StackName}-BucketArn" # nome único na região/conta
Consumindo um export em outra stack:
# Stack B importa o valor exportado pela Stack A
Resources:
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
BUCKET_ARN: !ImportValue "minha-stack-infra-BucketArn"
Restrições críticas do Export/ImportValue — [FATO]:
1. Nomes de export são únicos por região/conta — não existem namespaces
2. Você NÃO pode deletar uma stack que tem outputs exportados
enquanto outra stack estiver usando esses exports via ImportValue
3. Você NÃO pode modificar ou remover um output exportado
enquanto ele estiver sendo importado
4. Exports NÃO funcionam cross-region — só cross-stack na mesma região
5. O nome do Export NÃO pode usar Ref/GetAtt de recursos
(pode usar !Sub com pseudo-parâmetros como ${AWS::StackName})
[OPINIÃO — Alex Pulver, AWS CDK team] Para arquiteturas complexas, o acoplamento criado por Export/ImportValue torna operações como delete e refatoração difíceis. Uma alternativa é usar SSM Parameter Store para compartilhar valores entre stacks, mantendo o desacoplamento.
6. aws cloudformation deploy — o que acontece por baixo
aws cloudformation deploy é um wrapper de alto nível que:
1. Cria um changeset (nunca aplica direto)
2. Aguarda o changeset ser calculado
3. Executa o changeset automaticamente
4. Aguarda a stack atingir CREATE_COMPLETE ou UPDATE_COMPLETE
# Deploy básico
aws cloudformation deploy \
--template-file template.yaml \
--stack-name minha-stack \
--parameter-overrides Env=prod InstanceType=t3.medium \
--capabilities CAPABILITY_IAM \
--profile prod
# Ver o changeset antes de executar (não aplica)
aws cloudformation deploy \
--template-file template.yaml \
--stack-name minha-stack \
--no-execute-changeset
# Ver o changeset gerado
aws cloudformation describe-change-set \
--stack-name minha-stack \
--change-set-name <nome-do-changeset>
# Executar manualmente após revisar
aws cloudformation execute-change-set \
--stack-name minha-stack \
--change-set-name <nome-do-changeset>
--capabilities — quando usar:
CAPABILITY_IAM → template cria roles ou policies IAM sem nome customizado
CAPABILITY_NAMED_IAM → template cria roles com nome explícito (mais permissivo)
CAPABILITY_AUTO_EXPAND → template usa Transform (SAM, macros customizadas)
[FATO] Sem a capability correta, o deploy falha com InsufficientCapabilitiesException. Isso é intencional — é uma proteção contra criação acidental de recursos IAM.
Diagrama do fluxo de aws cloudformation deploy:
aws cloudformation deploy
│
▼
Stack existe?
│ │
Não Sim
│ │
▼ ▼
CREATE_ UPDATE_
CHANGESET CHANGESET
│ │
└────┬─────┘
▼
Aguarda REVIEW_IN_PROGRESS
│
▼
Executa changeset
│
▼
Aguarda CREATE_COMPLETE
ou UPDATE_COMPLETE
│
Erro? → ROLLBACK_COMPLETE
(stack volta ao estado anterior)
Status de stack mais importantes:
CREATE_IN_PROGRESS → criação em andamento
CREATE_COMPLETE → criação concluída com sucesso
CREATE_FAILED → falha na criação (stack permanece)
ROLLBACK_IN_PROGRESS → rollback em andamento após falha
ROLLBACK_COMPLETE → rollback concluído (stack existe mas vazia)
UPDATE_IN_PROGRESS → atualização em andamento
UPDATE_COMPLETE → atualização concluída
UPDATE_ROLLBACK_* → rollback de atualização
DELETE_IN_PROGRESS → deleção em andamento
DELETE_FAILED → falha na deleção (recurso não pôde ser deletado)
Exemplo prático
Template completo para uma aplicação com bucket S3 + role IAM + exportação de valores:
AWSTemplateFormatVersion: "2010-09-09"
Description: "Infraestrutura base — bucket de app + role de acesso"
Parameters:
Env:
Type: String
AllowedValues: [dev, staging, prod]
Default: dev
ProjectName:
Type: String
MinLength: 3
MaxLength: 20
AllowedPattern: "^[a-z][a-z0-9-]+$"
ConstraintDescription: "Lowercase, começa com letra, apenas letras/números/hifens"
Resources:
AppBucket:
Type: AWS::S3::Bucket
DeletionPolicy: !If [IsProd, Retain, Delete]
Properties:
BucketName: !Sub "${ProjectName}-${Env}-${AWS::AccountId}-${AWS::Region}"
VersioningConfiguration:
Status: !If [IsProd, Enabled, Suspended]
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
AppBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref AppBucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: DenyNonTLS
Effect: Deny
Principal: "*"
Action: s3:*
Resource:
- !GetAtt AppBucket.Arn
- !Sub "${AppBucket.Arn}/*"
Condition:
Bool:
aws:SecureTransport: false
AppRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${ProjectName}-${Env}-app-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: BucketAccess
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
Resource: !Sub "${AppBucket.Arn}/*"
Conditions:
IsProd: !Equals [!Ref Env, prod]
Outputs:
BucketName:
Description: "Nome do bucket"
Value: !Ref AppBucket
Export:
Name: !Sub "${AWS::StackName}-BucketName"
BucketArn:
Description: "ARN do bucket"
Value: !GetAtt AppBucket.Arn
Export:
Name: !Sub "${AWS::StackName}-BucketArn"
AppRoleArn:
Description: "ARN da role da aplicação"
Value: !GetAtt AppRole.Arn
Export:
Name: !Sub "${AWS::StackName}-AppRoleArn"
Deploy:
# Validar o template antes de enviar
aws cloudformation validate-template --template-body file://template.yaml
# Deploy em dev
aws cloudformation deploy \
--template-file template.yaml \
--stack-name meu-projeto-infra \
--parameter-overrides Env=dev ProjectName=meu-projeto \
--capabilities CAPABILITY_NAMED_IAM \
--profile dev
# Ver os outputs após o deploy
aws cloudformation describe-stacks \
--stack-name meu-projeto-infra \
--query 'Stacks[0].Outputs' \
--output table
# Ver todos os exports disponíveis na conta/região
aws cloudformation list-exports --query 'Exports[*].[Name,Value]' --output table
Armadilhas comuns
1. Usar Ref em IAM Roles quando precisa do ARN
!Ref MyRole retorna o nome da role, não o ARN. Quando você passa isso para um campo que espera ARN (como RoleArn de um ECS TaskDefinition), o CloudFormation aceita o template mas o serviço retorna erro em runtime. Sempre use !GetAtt MyRole.Arn para ARNs.
2. Deletar uma stack com exports em uso
Se outra stack usa !ImportValue "minha-stack-BucketArn", você não pode deletar minha-stack nem modificar esse output. O CloudFormation retorna Export cannot be updated as it is in use by another stack. Para resolver: primeiro remova o ImportValue da stack consumidora, faça deploy dela, depois delete ou modifique a stack exportadora.
3. DependsOn desnecessário
Quando você usa !Ref ou !GetAtt de um recurso em outro, o CloudFormation já infere a dependência automaticamente. DependsOn explícito só é necessário quando um recurso depende de outro sem referenciar diretamente — por exemplo, uma função Lambda que consulta um endpoint que só existe após uma instância EC2 estar rodando.
Exercício de reflexão
Você está migrando uma arquitetura existente para CloudFormation e precisa dividir a infra em duas stacks: infra-base (VPC, subnets, security groups) e infra-app (ECS service, ALB, roles IAM).
A stack infra-app precisa referenciar o VPC ID e os IDs das subnets criados pela infra-base. Um colega propõe usar Export/ImportValue. Outro propõe usar SSM Parameter Store como intermediário (a infra-base escreve os valores no SSM, a infra-app lê via AWS::SSM::Parameter::Value).
Quais são os trade-offs reais de cada abordagem? Considere: ciclo de vida das stacks, facilidade de debugging, operações de refatoração futura, e o que acontece quando você precisa recriar a infra-base do zero numa nova conta. Qual você escolheria e por quê?
Recursos para aprofundar
Anatomia e seções do template:
- CloudFormation template sections — referência completa de todas as seções com exemplos de cada campo.
Funções intrínsecas:
- Intrinsic function reference — lista todas as funções com o que retornam por tipo de recurso. Consulte aqui sempre que tiver dúvida sobre o que Ref retorna para um recurso específico.
Cross-stack references:
- Refer to resource outputs in another CloudFormation stack — walkthrough completo de Export/ImportValue com os cenários de falha documentados.
cfn-lint:
- cfn-lint no GitHub — linter estático para templates CloudFormation. Detecta tipos errados, referências inválidas e propriedades inexistentes antes do deploy. Instale com pip install cfn-lint e rode com cfn-lint template.yaml.