CICD CloudFormation
Introduction
CloudFormation is AWS's Infrastructure as Code (IaC) service that allows you to define your cloud infrastructure resources using templates. When integrated into a Continuous Integration and Continuous Deployment (CI/CD) pipeline, CloudFormation enables automated, consistent, and version-controlled infrastructure deployments.
In this tutorial, we'll explore how to incorporate CloudFormation into your CI/CD workflow, allowing you to treat infrastructure changes with the same rigor as application code changes.
What is CloudFormation?
AWS CloudFormation is a service that helps you model and set up your AWS resources. You create a template that describes all the AWS resources you want (like EC2 instances or RDS DB instances), and CloudFormation takes care of provisioning and configuring those resources for you.
Key benefits include:
- Infrastructure as Code: Define your infrastructure in template files that can be versioned
- Automation: Reduce manual processes and human error
- Dependency Management: CloudFormation handles resource dependencies automatically
- Change Tracking: Track infrastructure changes through template version control
CloudFormation Basics
Template Structure
CloudFormation templates are written in either JSON or YAML format. Here's a basic structure:
AWSTemplateFormatVersion: '2010-09-09'
Description: 'A sample template'
Parameters:
# Input parameters that customize your template
Resources:
# The AWS resources to create
Outputs:
# Values that are returned after stack creation
Key Concepts
- Stack: A collection of AWS resources that are created, updated, or deleted as a single unit
- StackSet: Enables you to create stacks across multiple accounts and regions with a single operation
- Change Set: A preview of changes to be applied to a stack
- Drift Detection: Identifies differences between the expected and actual configuration of resources
Integrating CloudFormation with CI/CD
CI/CD Pipeline Overview
A typical CI/CD pipeline for CloudFormation looks like this:
Setting Up AWS Authentication
Before running CloudFormation in your CI/CD pipeline, you need to set up authentication:
# Example AWS credentials setup in a GitHub Actions workflow
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
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
Validating Templates
Always validate your templates before deployment:
# Validate a CloudFormation template
aws cloudformation validate-template --template-body file://template.yaml
In your CI/CD pipeline:
# Example validation step in a Jenkins pipeline
stage('Validate Template') {
steps {
sh 'aws cloudformation validate-template --template-body file://infrastructure/template.yaml'
}
}
Creating and Updating Stacks
Deploy your infrastructure using the AWS CLI:
# Create a new stack
aws cloudformation create-stack \
--stack-name my-network-stack \
--template-body file://network.yaml \
--parameters ParameterKey=VPCCidr,ParameterValue=10.0.0.0/16
# Update an existing stack
aws cloudformation update-stack \
--stack-name my-network-stack \
--template-body file://network.yaml \
--parameters ParameterKey=VPCCidr,ParameterValue=10.0.0.0/16
Using Change Sets
For safer deployments, use change sets to preview changes:
# Create a change set
aws cloudformation create-change-set \
--stack-name my-network-stack \
--template-body file://network.yaml \
--change-set-name network-changes \
--parameters ParameterKey=VPCCidr,ParameterValue=10.0.0.0/16
# Execute the change set after review
aws cloudformation execute-change-set \
--stack-name my-network-stack \
--change-set-name network-changes
Practical Example: A Web Application Infrastructure
Let's create a CI/CD pipeline that deploys a web application infrastructure using CloudFormation.
The Infrastructure Template
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Web Application Infrastructure'
Parameters:
EnvironmentName:
Description: Environment name (dev, staging, prod)
Type: String
Default: dev
VpcCIDR:
Description: CIDR block for the VPC
Type: String
Default: 10.0.0.0/16
Resources:
# VPC Definition
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCIDR
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-vpc
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-igw
# Attach Internet Gateway to VPC
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# Public Subnet
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: !Select [0, !Cidr [!Ref VpcCIDR, 4, 8]]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-public-subnet
# Web Server Security Group
WebServerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP and SSH access
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
# Web Server Instance
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
SecurityGroupIds:
- !Ref WebServerSG
SubnetId: !Ref PublicSubnet
ImageId: ami-0c55b159cbfafe1f0 # Amazon Linux 2 (update as needed)
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from ${EnvironmentName} environment!</h1>" > /var/www/html/index.html
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-web-server
Outputs:
WebServerPublicIP:
Description: Public IP address of the web server
Value: !GetAtt WebServerInstance.PublicIp
WebsiteURL:
Description: URL of the website
Value: !Sub http://${WebServerInstance.PublicDnsName}
The CI/CD Pipeline
Here's an example GitHub Actions workflow that deploys this infrastructure:
name: Deploy Infrastructure
on:
push:
branches: [ main ]
paths:
- 'infrastructure/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Configure AWS credentials
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: Validate CloudFormation template
run: |
aws cloudformation validate-template \
--template-body file://infrastructure/webserver.yaml
- name: Create Change Set
id: change-set
run: |
CHANGE_SET_ID=$(aws cloudformation create-change-set \
--stack-name web-app-${GITHUB_REF##*/} \
--template-body file://infrastructure/webserver.yaml \
--change-set-name web-app-changes-${GITHUB_SHA::7} \
--parameters ParameterKey=EnvironmentName,ParameterValue=${GITHUB_REF##*/} \
--capabilities CAPABILITY_IAM \
--query 'Id' --output text || echo "")
echo "::set-output name=change-set-id::${CHANGE_SET_ID}"
- name: Wait for change set creation
if: steps.change-set.outputs.change-set-id != ''
run: |
aws cloudformation wait change-set-create-complete \
--stack-name web-app-${GITHUB_REF##*/} \
--change-set-name web-app-changes-${GITHUB_SHA::7}
- name: Execute Change Set
if: steps.change-set.outputs.change-set-id != ''
run: |
aws cloudformation execute-change-set \
--stack-name web-app-${GITHUB_REF##*/} \
--change-set-name web-app-changes-${GITHUB_SHA::7}
- name: Wait for stack update/creation
if: steps.change-set.outputs.change-set-id != ''
run: |
aws cloudformation wait stack-update-complete \
--stack-name web-app-${GITHUB_REF##*/} || \
aws cloudformation wait stack-create-complete \
--stack-name web-app-${GITHUB_REF##*/}
- name: Get stack outputs
run: |
aws cloudformation describe-stacks \
--stack-name web-app-${GITHUB_REF##*/} \
--query 'Stacks[0].Outputs' --output table
Best Practices for CloudFormation in CI/CD
Template Organization
- Break down complex templates: Use nested stacks for reusable components
- Use descriptive naming: Make resource names clear and consistent
- Organize by lifecycle: Group resources that change at similar rates
Parameter Management
Store environment-specific parameters in parameter stores:
# Example using AWS Systems Manager Parameter Store
Parameters:
DatabasePassword:
Type: AWS::SSM::Parameter::Value<String>
Default: /myapp/database/password
NoEcho: true
Cross-Stack References
Use exports and imports for cross-stack references:
# Stack A exports a value
Outputs:
VPCId:
Description: The ID of the VPC
Value: !Ref VPC
Export:
Name: !Sub "${AWS::StackName}-VPCID"
# Stack B imports the value
Resources:
Subnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !ImportValue MyNetworkStack-VPCID
# Other properties...
Environment Strategy
Use different stacks or parameters for different environments:
# Deploy to development
aws cloudformation create-stack \
--stack-name webapp-dev \
--template-body file://webapp.yaml \
--parameters ParameterKey=EnvironmentName,ParameterValue=dev
# Deploy to production
aws cloudformation create-stack \
--stack-name webapp-prod \
--template-body file://webapp.yaml \
--parameters ParameterKey=EnvironmentName,ParameterValue=prod
Security Considerations
- Least privilege: Use IAM roles with minimal permissions
- Secure parameters: Use NoEcho and AWS Systems Manager for sensitive data
- Enable stack policy: Prevent unintended updates to critical resources
Advanced CloudFormation Techniques
Custom Resources
Extend CloudFormation with custom resources:
Resources:
MyCustomResource:
Type: Custom::ResourceType
Properties:
ServiceToken: !GetAtt MyLambdaFunction.Arn
# Custom properties...
Dynamic References
Use dynamic references to securely reference secrets:
Resources:
MyDatabase:
Type: AWS::RDS::DBInstance
Properties:
# Other properties...
MasterUserPassword: '{{resolve:secretsmanager:MyRDSSecret:SecretString:password}}'
Macro Transformations
Use macros to transform templates:
Transform: 'MyMacro'
Resources:
# Resources to be transformed
Testing CloudFormation Templates
Unit Testing with cfn-lint
# Install cfn-lint
pip install cfn-lint
# Lint your templates
cfn-lint templates/*.yaml
Integration Testing with TaskCat
# Install TaskCat
pip install taskcat
# Configure project and run tests
taskcat test run
Rollback and Recovery Strategies
Automatic Rollbacks
CloudFormation performs automatic rollbacks on failed deployments:
aws cloudformation create-stack \
--stack-name my-stack \
--template-body file://template.yaml \
--on-failure ROLLBACK
Manual Rollbacks
To manually rollback to a previous version:
# Get the previous template
aws cloudformation get-template \
--stack-name my-stack \
--template-stage Original \
--output text > original-template.json
# Update the stack with the previous template
aws cloudformation update-stack \
--stack-name my-stack \
--template-body file://original-template.json
Drift Detection and Resolution
Detect and resolve infrastructure drift:
# Detect drift on the entire stack
aws cloudformation detect-stack-drift \
--stack-name my-stack
# Get the drift detection results
aws cloudformation describe-stack-resource-drifts \
--stack-name my-stack
Summary
In this tutorial, we've explored how to integrate AWS CloudFormation with CI/CD pipelines to automate infrastructure deployment. Key takeaways include:
- CloudFormation enables Infrastructure as Code for AWS resources
- CI/CD pipelines can automate the validation and deployment of CloudFormation templates
- Best practices include template organization, parameter management, and testing
- Advanced techniques like custom resources and macros extend CloudFormation's capabilities
By incorporating CloudFormation into your CI/CD workflow, you can achieve more reliable, consistent, and version-controlled infrastructure deployments.
Additional Resources
Here are some exercises to reinforce your learning:
- Create a CloudFormation template for a simple three-tier web application
- Set up a CI/CD pipeline to deploy the template to dev, staging, and production environments
- Implement drift detection and auto-remediation in your pipeline
- Create a custom resource that extends CloudFormation's capabilities
For further reading, check out:
- AWS CloudFormation documentation
- Infrastructure as Code by Kief Morris
- The DevOps Handbook by Gene Kim et al.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)