1239 words
6 minutes
Complete Guide to AWS CloudFormation: Infrastructure as Code Made Simple

Complete Guide to AWS CloudFormation: Infrastructure as Code Made Simple#

AWS CloudFormation is a service that helps you model and set up your AWS resources so that you can spend less time managing those resources and more time focusing on your applications. You create a template that describes all the AWS resources you want, and CloudFormation takes care of provisioning and configuring those resources for you.

Overview#

CloudFormation enables Infrastructure as Code (IaC), allowing you to define your cloud infrastructure in JSON or YAML templates. This provides version control, repeatability, and consistency across environments.

Key Benefits#

1. Infrastructure as Code#

  • Version control your infrastructure
  • Track changes over time
  • Peer review infrastructure changes
  • Rollback to previous versions

2. Consistency and Repeatability#

  • Deploy identical environments
  • Reduce human error
  • Standardize deployments
  • Ensure compliance

3. Automation and Efficiency#

  • Automate resource provisioning
  • Integrate with CI/CD pipelines
  • Reduce manual intervention
  • Scale infrastructure quickly

4. Cost Management#

  • Delete entire stacks to clean up resources
  • Estimate costs before deployment
  • Tag resources for cost allocation
  • Implement cost controls

Core Concepts#

1. Templates#

CloudFormation templates are JSON or YAML files that define your infrastructure:

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Simple EC2 instance template'
Parameters:
InstanceType:
Type: String
Default: t3.micro
Description: EC2 instance type
Resources:
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: ami-0c02fb55956c7d316
KeyName: my-key-pair
Outputs:
InstanceId:
Description: Instance ID
Value: !Ref MyEC2Instance

2. Stacks#

A stack is a collection of AWS resources that you can manage as a single unit:

Terminal window
# Create stack
aws cloudformation create-stack \
--stack-name my-web-app \
--template-body file://template.yaml \
--parameters ParameterKey=InstanceType,ParameterValue=t3.small
# Update stack
aws cloudformation update-stack \
--stack-name my-web-app \
--template-body file://updated-template.yaml
# Delete stack
aws cloudformation delete-stack --stack-name my-web-app

3. Change Sets#

Preview changes before applying them:

Terminal window
# Create change set
aws cloudformation create-change-set \
--stack-name my-web-app \
--template-body file://new-template.yaml \
--change-set-name my-changeset
# Describe changes
aws cloudformation describe-change-set \
--stack-name my-web-app \
--change-set-name my-changeset
# Execute change set
aws cloudformation execute-change-set \
--stack-name my-web-app \
--change-set-name my-changeset

Template Structure#

1. Complete Template Format#

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Template description'
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Network Configuration"
Parameters:
- VpcCIDR
- PublicSubnetCIDR
Parameters:
VpcCIDR:
Type: String
Default: 10.0.0.0/16
Description: CIDR block for VPC
Mappings:
RegionMap:
us-east-1:
AMI: ami-0c02fb55956c7d316
us-west-2:
AMI: ami-0892d3c7ee96c0bf7
Conditions:
CreateProdResources: !Equals [!Ref Environment, production]
Resources:
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCIDR
EnableDnsHostnames: true
EnableDnsSupport: true
Outputs:
VPCId:
Description: VPC ID
Value: !Ref MyVPC
Export:
Name: !Sub "${AWS::StackName}-VPC-ID"

2. Intrinsic Functions#

# Reference parameters and resources
InstanceType: !Ref InstanceTypeParameter
# Join strings
UserData: !Base64
!Sub |
#!/bin/bash
echo "Hello from ${AWS::StackName}"
# Get attribute from resource
WebsiteURL: !GetAtt LoadBalancer.DNSName
# Conditional values
InstanceType: !If [CreateProdResources, m5.large, t3.micro]
# Find in map
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
# Split and select
AvailabilityZone: !Select [0, !GetAZs ""]

Common Resource Types#

1. VPC and Networking#

Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs ""]
InternetGateway:
Type: AWS::EC2::InternetGateway
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Web server security group
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0

2. EC2 Instances#

Resources:
WebServer:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
KeyName: !Ref KeyPairName
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref WebServerSecurityGroup
UserData: !Base64
!Sub |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from CloudFormation</h1>" > /var/www/html/index.html
ElasticIP:
Type: AWS::EC2::EIP
Properties:
InstanceId: !Ref WebServer
Domain: vpc

3. Auto Scaling#

Resources:
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub "${AWS::StackName}-launch-template"
LaunchTemplateData:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
InstanceType: !Ref InstanceType
SecurityGroupIds:
- !Ref WebServerSecurityGroup
UserData: !Base64
!Sub |
#!/bin/bash
# Install and configure application
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
VPCZoneIdentifier:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
MinSize: 1
MaxSize: 3
DesiredCapacity: 2
TargetGroupARNs:
- !Ref TargetGroup

4. RDS Database#

Resources:
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for RDS database
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
Database:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: !Sub "${AWS::StackName}-database"
DBInstanceClass: db.t3.micro
Engine: mysql
EngineVersion: '8.0'
MasterUsername: admin
MasterUserPassword: !Ref DBPassword
AllocatedStorage: 20
DBSubnetGroupName: !Ref DBSubnetGroup
VPCSecurityGroups:
- !Ref DatabaseSecurityGroup

Advanced Features#

1. Nested Stacks#

# Parent template
Resources:
NetworkStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/my-bucket/network.yaml
Parameters:
VpcCIDR: 10.0.0.0/16
ApplicationStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/my-bucket/application.yaml
Parameters:
VPCId: !GetAtt NetworkStack.Outputs.VPCId

2. Stack Sets#

Deploy stacks across multiple accounts and regions:

Terminal window
# Create stack set
aws cloudformation create-stack-set \
--stack-set-name my-cross-account-stack \
--template-body file://template.yaml \
--capabilities CAPABILITY_NAMED_IAM
# Deploy to accounts and regions
aws cloudformation create-stack-instances \
--stack-set-name my-cross-account-stack \
--accounts 123456789012 234567890123 \
--regions us-east-1 us-west-2

3. Custom Resources#

Resources:
CustomResource:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken: !GetAtt CustomResourceLambda.Arn
CustomProperty: CustomValue
CustomResourceLambda:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
import json
import boto3
import urllib3
def handler(event, context):
# Handle create, update, delete operations
response_url = event['ResponseURL']
# Process the request and send response
return send_response(response_url, event, context, "SUCCESS", {})
Runtime: python3.9

Best Practices#

1. Template Organization#

# Use clear naming conventions
Resources:
WebServerInstance:
Type: AWS::EC2::Instance
# Clear, descriptive names
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
# Consistent naming pattern
# Use parameters for flexibility
Parameters:
Environment:
Type: String
AllowedValues: [dev, staging, prod]
InstanceType:
Type: String
Default: t3.micro
AllowedValues: [t3.micro, t3.small, t3.medium]

2. Security Best Practices#

# Use least privilege IAM roles
WebServerRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Encrypt sensitive data
Database:
Type: AWS::RDS::DBInstance
Properties:
StorageEncrypted: true
KmsKeyId: !Ref DatabaseKMSKey
# Use Secrets Manager for passwords
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
GenerateSecretString:
SecretStringTemplate: '{"username": "admin"}'
GenerateStringKey: password
PasswordLength: 16
ExcludeCharacters: '"@/\'

3. Error Handling#

# Use rollback configuration
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
CreationPolicy:
ResourceSignal:
Count: 2
Timeout: PT10M
UpdatePolicy:
AutoScalingRollingUpdate:
MinInstancesInService: 1
MaxBatchSize: 1
WaitOnResourceSignals: true
PauseTime: PT10M
# Use DeletionPolicy for critical resources
Database:
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
Properties:
# Database properties

Deployment Strategies#

1. Blue/Green Deployment#

Parameters:
BlueGreenDeployment:
Type: String
Default: blue
AllowedValues: [blue, green]
Conditions:
IsBlueDeployment: !Equals [!Ref BlueGreenDeployment, blue]
Resources:
BlueTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Condition: IsBlueDeployment
# Blue environment configuration
GreenTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Condition: !Not [!Condition IsBlueDeployment]
# Green environment configuration

2. Multi-Environment Templates#

Mappings:
EnvironmentMap:
dev:
InstanceType: t3.micro
MinSize: 1
MaxSize: 2
prod:
InstanceType: t3.medium
MinSize: 2
MaxSize: 6
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
MinSize: !FindInMap [EnvironmentMap, !Ref Environment, MinSize]
MaxSize: !FindInMap [EnvironmentMap, !Ref Environment, MaxSize]

Monitoring and Troubleshooting#

1. CloudFormation Events#

Terminal window
# Monitor stack events
aws cloudformation describe-stack-events --stack-name my-stack
# Get stack status
aws cloudformation describe-stacks --stack-name my-stack

2. Stack Drift Detection#

Terminal window
# Detect drift
aws cloudformation detect-stack-drift --stack-name my-stack
# Get drift results
aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id drift-id

3. Troubleshooting Failed Deployments#

# Add detailed logging to user data
UserData: !Base64
!Sub |
#!/bin/bash
exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1
echo "Starting user data execution"
# Your commands here
yum update -y
# Signal success/failure
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} \
--resource AutoScalingGroup --region ${AWS::Region}

Integration with CI/CD#

1. GitHub Actions#

name: Deploy CloudFormation Stack
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy CloudFormation Stack
run: |
aws cloudformation deploy \
--template-file template.yaml \
--stack-name my-application \
--parameter-overrides Environment=prod \
--capabilities CAPABILITY_IAM

2. AWS CodePipeline#

Resources:
CodePipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !GetAtt CodePipelineRole.Arn
Stages:
- Name: Source
Actions:
- Name: SourceAction
ActionTypeId:
Category: Source
Owner: AWS
Provider: S3
Version: 1
- Name: Deploy
Actions:
- Name: CreateChangeSet
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: CloudFormation
Version: 1
Configuration:
ActionMode: CHANGE_SET_REPLACE
StackName: my-stack
ChangeSetName: my-changeset
TemplatePath: SourceOutput::template.yaml

Cost Optimization#

1. Resource Tagging#

Resources:
EC2Instance:
Type: AWS::EC2::Instance
Properties:
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Project
Value: !Ref ProjectName
- Key: CostCenter
Value: Engineering

2. Lifecycle Policies#

# S3 bucket with lifecycle policy
S3Bucket:
Type: AWS::S3::Bucket
Properties:
LifecycleConfiguration:
Rules:
- Id: DeleteOldVersions
Status: Enabled
NoncurrentVersionExpirationInDays: 30
- Id: TransitionToIA
Status: Enabled
TransitionInDays: 30
StorageClass: STANDARD_IA

Additional Resources#

Complete Guide to AWS CloudFormation: Infrastructure as Code Made Simple
https://mranv.pages.dev/posts/complete-guide-to-aws-cloudformation/
Author
Anubhav Gain
Published at
2024-08-20
License
CC BY-NC-SA 4.0