AWS CloudFormation is a "free" service that helps us to provide and manage AWS resources only through a template. One of the benefits that I love is we don't need to look for which modules are still valid to use or which ones that have been deprecated because AWS provides the complete information on the documentation, click here. What we need to define in the template are provided there. One of the most important things about this service is you have to remember that we are free to use the CloudFormation service but we will be billed for every service created through the stack (the ones that you define in the template).
Alright! We will do anything with CloudFormation through AWS CLI. So, make sure you have installed AWS CLI and set up the credential. If you're ready with them all, let's get started!
1. Create Template
In the first version of the template, I want to create an S3 bucket with versioning enabled. I prepare this for the next step because when we create a stack by uploading the template (stored locally) via CLI, it doesn't show us where it will be stored (I'll tell you in a second below). Ok, I named the template cfn-demo.yaml and it will be:
Resources:
S3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: cfn-bucket-template
VersioningConfiguration:
Status: Enabled
2. Create Stack
As I mentioned above when we create a stack by uploading the template (stored locally) via CLI, it doesn't show us where it will be stored. It's different when we create a stack by uploading the file via Console which will be stored in the S3 bucket generated by CloudFormation itself, but that bucket isn't deleted even though the stack has been deleted.
Then, to prevent that undeleted bucket off the stack. We will create the bucket first, upload the template to the bucket, and use the object as the template source. I just wanna let you know that we can do this.
Let's create the stack!
$ aws cloudformation create-stack --stack-name cfn-demo --template-body file://cfn-demo.yaml
{
"StackId": "arn:aws:cloudformation:ap-southeast-3:01234567890:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70"
}
Please wait till the status is complete.
$ aws cloudformation describe-stacks --stack-name cfn-demo
{
"Stacks": [
{
"StackId": "arn:aws:cloudformation:ap-southeast-3:01234567890:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70",
"StackName": "cfn-demo",
"CreationTime": "2022-05-08T13:24:17.386Z",
"RollbackConfiguration": {},
"StackStatus": "CREATE_COMPLETE",
"DisableRollback": false,
"NotificationARNs": [],
"Tags": [],
"EnableTerminationProtection": false,
"DriftInformation": {
"StackDriftStatus": "NOT_CHECKED"
}
}
]
}
Let's verify the S3 bucket! Has it been created or not?
$ aws s3 ls
2022-05-08 20:24:45 cfn-bucket-template
3. Update Stack
To update the stack, we will have some steps to do to follow the scenario. Those are:
Make any changes to the template. In this case, I'll create more resources (VPC, Subnet, Internet Gateway, Route Table, 2 Security Groups, and an EC2 instance).
Upload the second version of the template to the S3 bucket.
Update the stack by using the S3 object as a template source.
cfn-demo.yaml (second version)
AWSTemplateFormatVersion: 2010-09-09
Parameters:
EC2Type:
Description: Type of instance
Type: String
Default: t3.micro
AllowedValues:
- t3.micro
- t3.small
- t3.medium
- t3.large
Resources:
S3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: cfn-bucket-template
VersioningConfiguration:
Status: Enabled
VirtualPrivateCloud:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.16.0.0/16
Tags:
- Key: "Name"
Value: "CFN VPC"
Subnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VirtualPrivateCloud
CidrBlock: 172.16.1.0/28
AvailabilityZone: ap-southeast-3a
Tags:
- Key: "Name"
Value: "CFN Subnet"
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: "Name"
Value: "CFN IGW"
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VirtualPrivateCloud
InternetGatewayId: !Ref InternetGateway
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VirtualPrivateCloud
Tags:
- Key: "Name"
Value: "CFN Route"
Route:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref Subnet
RouteTableId: !Ref RouteTable
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow SSH HTTP
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 22
ToPort: 22
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 80
ToPort: 80
VpcId: !Ref VirtualPrivateCloud
Tags:
- Key: "Name"
Value: "CFN SG"
SecurityGroup2:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Only Allow SSH
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 22
ToPort: 22
VpcId: !Ref VirtualPrivateCloud
Tags:
- Key: "Name"
Value: "CFN SG2"
KeyPair:
Type: AWS::EC2::KeyPair
Properties:
KeyName: ec2-user
PublicKeyMaterial: ssh-rsa VWXYZ.....(your pubkey goes here)
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref EC2Type
AvailabilityZone: ap-southeast-3a
ImageId: "ami-021fb2b73ff1efc96"
KeyName: "ec2-user"
BlockDeviceMappings:
- DeviceName: "/dev/xvda"
Ebs:
DeleteOnTermination: "true"
VolumeSize: "8"
VolumeType: "gp2"
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeleteOnTermination: "true"
DeviceIndex: 0
SubnetId: !Ref Subnet
GroupSet:
- Ref: SecurityGroup2
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl enable httpd
systemctl start httpd
Tags:
- Key: "Name"
Value: "CFN Instance"
Outputs:
EC2IpAddress:
Description: "EC2 Public IP Address"
Value: !GetAtt EC2Instance.PublicIp
Upload the file to the S3 bucket by using the copy:
$ aws s3 cp cfn-demo.yaml s3://cfn-bucket-template
upload: ./cfn-demo.yaml to s3://cfn-bucket-template/cfn-demo.yaml
$ aws s3 ls s3://cfn-bucket-template
2022-05-08 20:31:46 4256 cfn-demo.yaml
Then, let's update the stack by using the S3 object URL as the template source!
$ aws cloudformation update-stack --stack-name cfn-demo --template-url https://cfn-bucket-template.s3.ap-southeast-3.amazonaws.com/cfn-demo.yaml
{
"StackId": "arn:aws:cloudformation:ap-southeast-3:01234567890:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70"
}
Please wait till the status is complete and we get the output.
$ aws cloudformation describe-stacks --stack-name cfn-demo
{
"Stacks": [
{
"StackId": "arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70",
"StackName": "cfn-demo",
"Parameters": [
{
"ParameterKey": "EC2Type",
"ParameterValue": "t3.micro"
}
],
"CreationTime": "2022-05-08T13:24:17.386Z",
"LastUpdatedTime": "2022-05-08T13:33:13.916Z",
"RollbackConfiguration": {},
"StackStatus": "UPDATE_COMPLETE",
"DisableRollback": false,
"NotificationARNs": [],
"Outputs": [
{
"OutputKey": "EC2IpAddress",
"OutputValue": "108.136.169.98",
"Description": "EC2 Public IP Address"
}
],
"Tags": [],
"EnableTerminationProtection": false,
"DriftInformation": {
"StackDriftStatus": "NOT_CHECKED"
}
}
]
}
Let's verify the EC2 instance port by accessing it via SSH and a web browser!
$ ssh ec2-user@108.136.169.98
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-172-16-1-8 ~]$ exit
logout
Connection to 108.136.169.98 closed.
Why do we verify the ports? We have created 2 security groups and we already used the first one for our EC2 instance. Now, let's make a change to the template and upload it again to the S3 bucket. From this action, we will have two versions of the object (template that is stored in the S3 bucket).
Before (to allow SSH and HTTP):
GroupSet:
- Ref: SecurityGroup
After (to only allow SSH):
GroupSet:
- Ref: SecurityGroup2
Then, upload that file!
$ aws s3 cp cfn-demo.yaml s3://cfn-bucket-template
upload: ./cfn-demo.yaml to s3://cfn-bucket-template/cfn-demo.yaml
Now, let's update the stack again by using the S3 object URL as the template source! (It will use the newest version of the template)
$ aws cloudformation update-stack --stack-name cfn-demo --template-url https://cfn-bucket-template.s3.ap-southeast-3.amazonaws.com/cfn-demo.yaml
{
"StackId": "arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70"
}
The output:
$ aws cloudformation describe-stacks --stack-name cfn-demo
{
"Stacks": [
{
"StackId": "arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70",
"StackName": "cfn-demo",
"Parameters": [
{
"ParameterKey": "EC2Type",
"ParameterValue": "t3.micro"
}
],
"CreationTime": "2022-05-08T13:24:17.386Z",
"LastUpdatedTime": "2022-05-08T13:40:42.678Z",
"RollbackConfiguration": {},
"StackStatus": "UPDATE_COMPLETE",
"DisableRollback": false,
"NotificationARNs": [],
"Outputs": [
{
"OutputKey": "EC2IpAddress",
"OutputValue": "108.136.160.193",
"Description": "EC2 Public IP Address"
}
],
"Tags": [],
"EnableTerminationProtection": false,
"DriftInformation": {
"StackDriftStatus": "NOT_CHECKED"
}
}
]
}
Even though we only change the security group, through the stack it will also replace the EC2 instance with the new one. So, it can be a review for us to choose which will be executed through CloudFormation or using other ways such as via the Console, CLI, or others. Here I'll just show you the main points when we use CloudFormation via CLI and use S3 to store templates.
Okay, let's continue to check! The current instance should be only accessible via SSH.
$ telnet 108.136.160.193 80
Trying 108.136.160.193...
^C
$ ssh ec2-user@108.136.160.193
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-172-16-1-12 ~]$ exit
logout
Connection to 108.136.160.193 closed.
It works! Now, I want to make one more change but I'll only use the first (oldest) object version without making any changes to the template. I just want to be back by creating a new instance using the first security group that allows SSH and HTTP. This is one of the benefits of using S3 versioning to store the template.
Get the version ID and I'll take the last one!
$ aws s3api list-object-versions --bucket cfn-bucket-template --query 'Versions[].[Key, VersionId]'
[
[
"cfn-demo.yaml",
"vtPszUgfpn8S9VZj5r6uHWKYL9HSlDRV"
],
[
"cfn-demo.yaml",
"0DY8PO_LdRI_1WcppeMBSFc_ey5Djuxf"
]
]
Now, let's do our last update to the stack by using the S3 object URL as the template source! Please don't forget to mention the version ID by adding ?versionId=xxx
right after the object URL.
$ aws cloudformation update-stack --stack-name cfn-demo --template-url https://cfn-bucket-template.s3.ap-southeast-3.amazonaws.com/cfn-demo.yaml?versionId=0DY8PO_LdRI_1WcppeMBSFc_ey5Djuxf
{
"StackId": "arn:aws:cloudformation:ap-southeast-3:01234567890:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70"
}
The output:
$ aws cloudformation describe-stacks --stack-name cfn-demo
{
"Stacks": [
{
"StackId": "arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70",
"StackName": "cfn-demo",
"Parameters": [
{
"ParameterKey": "EC2Type",
"ParameterValue": "t3.micro"
}
],
"CreationTime": "2022-05-08T13:24:17.386Z",
"LastUpdatedTime": "2022-05-08T13:49:09.816Z",
"RollbackConfiguration": {},
"StackStatus": "UPDATE_COMPLETE",
"DisableRollback": false,
"NotificationARNs": [],
"Outputs": [
{
"OutputKey": "EC2IpAddress",
"OutputValue": "108.136.160.169",
"Description": "EC2 Public IP Address"
}
],
"Tags": [],
"EnableTerminationProtection": false,
"DriftInformation": {
"StackDriftStatus": "NOT_CHECKED"
}
}
]
}
Then, the HTTP port of our newest EC2 instance should be opened back.
4. Delete Template
We have uploaded 2 versions of the template to the S3 bucket. So, we have to delete the object versions before we delete the stack because we upload the files manually (aren't included in the stack).
$ aws s3api delete-object --bucket cfn-bucket-template --key cfn-demo.yaml --version-id vtPszUgfpn8S9VZj5r6uHWKYL9HSlDRV
{
"VersionId": "vtPszUgfpn8S9VZj5r6uHWKYL9HSlDRV"
}
$ aws s3api delete-object --bucket cfn-bucket-template --key cfn-demo.yaml --version-id 0DY8PO_LdRI_1WcppeMBSFc_ey5Djuxf
{
"VersionId": "0DY8PO_LdRI_1WcppeMBSFc_ey5Djuxf"
}
5. Delete Stack
Now, the bucket is empty and we're ready to delete the stack.
$ aws cloudformation delete-stack --stack-name cfn-demo
$ aws cloudformation describe-stacks --stack-name cfn-demo
An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id cfn-demo does not exist
It's a wrap! We already reached the end of this post.
What we have done:
Create a CloudFormation template with the following tasks: create S3 Bucket and create EC2 (VPC, Subnet, Internet Gateway, Route Table, Security Group, and EC2 instance)
Create, update, and delete CloudFormation stack via AWS CLI
Upload object to the S3 bucket via AWS CLI
Delete S3 object version via AWS CLI
Use 2 different CloudFormation template sources, local file and S3 URL.
That's it! Thank you for coming and I'm looking forward to your feedback. Follow me to get notified when my new post is published!
Top comments (1)
Found this article very useful! Ran into only one issue
changing the template name
cfn-bucket-template
to something else worked not sure if that template name is reserved or somethingOtherwise worked great!