Managing AWS CloudFormation with Terraform
A comprehensive guide to managing AWS CloudFormation stacks and resources using Terraform Infrastructure as Code
Managing AWS CloudFormation with Terraform
AWS CloudFormation allows you to create and manage AWS infrastructure deployments predictably and repeatedly. This guide shows how to manage CloudFormation using Terraform.
Prerequisites
- AWS CLI configured
- Terraform installed
- Understanding of CloudFormation templates
- Basic knowledge of AWS services
Project Structure
aws-cloudformation-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars
Basic CloudFormation Configuration
# main.tf
provider "aws" {
region = var.aws_region
}
# S3 Bucket for Templates
resource "aws_s3_bucket" "templates" {
bucket = "${var.project_name}-cfn-templates"
tags = {
Environment = var.environment
}
}
# S3 Bucket Policy
resource "aws_s3_bucket_policy" "templates" {
bucket = aws_s3_bucket.templates.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCloudFormationAccess"
Effect = "Allow"
Principal = {
Service = "cloudformation.amazonaws.com"
}
Action = [
"s3:GetObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.templates.arn,
"${aws_s3_bucket.templates.arn}/*"
]
}
]
})
}
# IAM Role for CloudFormation
resource "aws_iam_role" "cloudformation" {
name = "${var.project_name}-cfn-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "cloudformation.amazonaws.com"
}
}
]
})
}
# IAM Policy for CloudFormation
resource "aws_iam_role_policy" "cloudformation" {
name = "${var.project_name}-cfn-policy"
role = aws_iam_role.cloudformation.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:*",
"ec2:*",
"rds:*",
"elasticloadbalancing:*",
"autoscaling:*",
"cloudwatch:*",
"sns:*"
]
Resource = "*"
}
]
})
}
# CloudFormation Stack
resource "aws_cloudformation_stack" "network" {
name = "${var.project_name}-network"
template_body = jsonencode({
AWSTemplateFormatVersion = "2010-09-09"
Description = "Network Infrastructure"
Parameters = {
VpcCidr = {
Type = "String"
Default = "10.0.0.0/16"
Description = "CIDR block for VPC"
}
}
Resources = {
VPC = {
Type = "AWS::EC2::VPC"
Properties = {
CidrBlock = { Ref = "VpcCidr" }
EnableDnsHostnames = true
EnableDnsSupport = true
Tags = [
{
Key = "Name"
Value = "${var.project_name}-vpc"
}
]
}
}
InternetGateway = {
Type = "AWS::EC2::InternetGateway"
Properties = {
Tags = [
{
Key = "Name"
Value = "${var.project_name}-igw"
}
]
}
}
AttachGateway = {
Type = "AWS::EC2::VPCGatewayAttachment"
Properties = {
VpcId = { Ref = "VPC" }
InternetGatewayId = { Ref = "InternetGateway" }
}
}
}
Outputs = {
VpcId = {
Description = "VPC ID"
Value = { Ref = "VPC" }
}
}
})
parameters = {
VpcCidr = var.vpc_cidr
}
capabilities = ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
timeout_in_minutes = 30
tags = {
Environment = var.environment
}
}
# CloudFormation Stack Set
resource "aws_cloudformation_stack_set" "baseline" {
name = "${var.project_name}-baseline"
permission_model = "SERVICE_MANAGED"
template_body = jsonencode({
AWSTemplateFormatVersion = "2010-09-09"
Description = "Baseline Security Configuration"
Resources = {
SecurityGroup = {
Type = "AWS::EC2::SecurityGroup"
Properties = {
GroupDescription = "Baseline security group"
SecurityGroupIngress = [
{
IpProtocol = "tcp"
FromPort = 443
ToPort = 443
CidrIp = "0.0.0.0/0"
}
]
Tags = [
{
Key = "Name"
Value = "baseline-sg"
}
]
}
}
CloudWatchLogGroup = {
Type = "AWS::Logs::LogGroup"
Properties = {
LogGroupName = "/aws/baseline"
RetentionInDays = 30
}
}
}
})
auto_deployment {
enabled = true
retain_stacks_on_account_removal = false
}
capabilities = ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
administration_role_arn = aws_iam_role.stackset_admin.arn
execution_role_name = "AWSCloudFormationStackSetExecutionRole"
}
Variables Configuration
# variables.tf
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "project_name" {
description = "Project name"
type = string
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
Best Practices
-
Stack Management
- Use nested stacks
- Implement change sets
- Configure proper rollbacks
- Regular stack reviews
-
Security
- Use IAM roles
- Implement stack policies
- Configure access controls
- Regular security reviews
-
Cost Optimization
- Monitor resource usage
- Clean up unused stacks
- Use cost allocation tags
- Regular cost reviews
-
Performance
- Optimize dependencies
- Use proper timeouts
- Monitor stack operations
- Regular performance reviews
Nested Stacks
# Parent Stack
resource "aws_cloudformation_stack" "parent" {
name = "${var.project_name}-parent"
template_body = jsonencode({
AWSTemplateFormatVersion = "2010-09-09"
Description = "Parent Stack"
Resources = {
NetworkStack = {
Type = "AWS::CloudFormation::Stack"
Properties = {
TemplateURL = "https://${aws_s3_bucket.templates.bucket_regional_domain_name}/network.yaml"
Parameters = {
VpcCidr = var.vpc_cidr
}
}
}
SecurityStack = {
Type = "AWS::CloudFormation::Stack"
Properties = {
TemplateURL = "https://${aws_s3_bucket.templates.bucket_regional_domain_name}/security.yaml"
Parameters = {
VpcId = { "Fn::GetAtt" = ["NetworkStack", "Outputs.VpcId"] }
}
}
}
}
})
capabilities = ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
depends_on = [
aws_s3_bucket.templates,
aws_s3_bucket_policy.templates
]
}
Change Set Management
# Change Set
resource "aws_cloudformation_stack" "with_changeset" {
name = "${var.project_name}-with-changeset"
template_body = jsonencode({
AWSTemplateFormatVersion = "2010-09-09"
Description = "Stack with Change Set"
Resources = {
Bucket = {
Type = "AWS::S3::Bucket"
Properties = {
BucketName = "${var.project_name}-bucket"
VersioningConfiguration = {
Status = "Enabled"
}
}
}
}
})
capabilities = ["CAPABILITY_IAM"]
lifecycle {
create_before_destroy = true
}
}
# Stack Policy
resource "aws_cloudformation_stack_policy" "protect_resources" {
stack_name = aws_cloudformation_stack.with_changeset.name
policy_body = jsonencode({
Statement = [
{
Effect = "Allow"
Action = "Update:*"
Principal = "*"
Resource = "*"
},
{
Effect = "Deny"
Action = "Update:Delete"
Principal = "*"
Resource = "*"
Condition = {
StringEquals = {
"ResourceType" = [
"AWS::S3::Bucket"
]
}
}
}
]
})
}
Monitoring Configuration
# CloudWatch Dashboard
resource "aws_cloudwatch_dashboard" "cloudformation" {
dashboard_name = "${var.project_name}-cloudformation-dashboard"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
x = 0
y = 0
width = 12
height = 6
properties = {
metrics = [
["AWS/CloudFormation", "StackCreationTime", "StackName", aws_cloudformation_stack.network.name],
["AWS/CloudFormation", "StackDeleteTime", "StackName", aws_cloudformation_stack.network.name]
]
period = 300
stat = "Average"
region = var.aws_region
title = "Stack Operation Times"
}
}
]
})
}
# CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "stack_operation_failed" {
alarm_name = "${var.project_name}-stack-operation-failed"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "StackOperationFailed"
namespace = "AWS/CloudFormation"
period = "300"
statistic = "Sum"
threshold = "0"
alarm_description = "This metric monitors CloudFormation stack operation failures"
alarm_actions = [var.sns_topic_arn]
dimensions = {
StackName = aws_cloudformation_stack.network.name
}
}
Common Use Cases
- Application Stack
resource "aws_cloudformation_stack" "application" {
name = "${var.project_name}-application"
template_body = jsonencode({
AWSTemplateFormatVersion = "2010-09-09"
Description = "Application Stack"
Parameters = {
InstanceType = {
Type = "String"
Default = "t3.micro"
AllowedValues = ["t3.micro", "t3.small", "t3.medium"]
}
}
Resources = {
LaunchTemplate = {
Type = "AWS::EC2::LaunchTemplate"
Properties = {
LaunchTemplateData = {
InstanceType = { Ref = "InstanceType" }
ImageId = "ami-12345678"
SecurityGroupIds = [{ Ref = "SecurityGroup" }]
UserData = {
"Fn::Base64" = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
EOF
}
}
}
}
AutoScalingGroup = {
Type = "AWS::AutoScaling::AutoScalingGroup"
Properties = {
MinSize = "1"
MaxSize = "3"
DesiredCapacity = "2"
LaunchTemplate = {
LaunchTemplateId = { Ref = "LaunchTemplate" }
Version = { "Fn::GetAtt" = ["LaunchTemplate", "LatestVersionNumber"] }
}
}
}
}
})
parameters = {
InstanceType = var.instance_type
}
capabilities = ["CAPABILITY_IAM"]
}
- Database Stack
resource "aws_cloudformation_stack" "database" {
name = "${var.project_name}-database"
template_body = jsonencode({
AWSTemplateFormatVersion = "2010-09-09"
Description = "Database Stack"
Parameters = {
DBInstanceClass = {
Type = "String"
Default = "db.t3.micro"
}
DBName = {
Type = "String"
}
DBUsername = {
Type = "String"
NoEcho = true
}
DBPassword = {
Type = "String"
NoEcho = true
}
}
Resources = {
DBInstance = {
Type = "AWS::RDS::DBInstance"
Properties = {
DBInstanceClass = { Ref = "DBInstanceClass" }
Engine = "postgres"
MasterUsername = { Ref = "DBUsername" }
MasterUserPassword = { Ref = "DBPassword" }
DBName = { Ref = "DBName" }
AllocatedStorage = "20"
BackupRetentionPeriod = "7"
MultiAZ = false
}
}
}
})
parameters = {
DBInstanceClass = var.db_instance_class
DBName = var.db_name
DBUsername = var.db_username
DBPassword = var.db_password
}
}
Advanced Features
# Custom Resource
resource "aws_cloudformation_stack" "custom_resource" {
name = "${var.project_name}-custom-resource"
template_body = jsonencode({
AWSTemplateFormatVersion = "2010-09-09"
Description = "Stack with Custom Resource"
Resources = {
CustomFunction = {
Type = "AWS::Lambda::Function"
Properties = {
Handler = "index.handler"
Role = aws_iam_role.lambda.arn
Code = {
ZipFile = <<-EOF
exports.handler = async (event, context) => {
console.log('Event:', JSON.stringify(event));
return {
Status: 'SUCCESS',
PhysicalResourceId: context.logStreamName,
Data: {
Message: 'Custom resource executed successfully'
}
};
};
EOF
}
Runtime = "nodejs14.x"
}
}
CustomResource = {
Type = "Custom::Resource"
Properties = {
ServiceToken = { "Fn::GetAtt" = ["CustomFunction", "Arn"] }
CustomData = "Example"
}
}
}
})
capabilities = ["CAPABILITY_IAM"]
}
Conclusion
This setup provides a comprehensive foundation for managing CloudFormation using Terraform. Remember to:
- Plan your stack structure carefully
- Implement proper change management
- Monitor stack operations and costs
- Keep your configurations versioned
- Test thoroughly before production deployment
The complete code can be customized based on your specific requirements and use cases.