Home » Tech Tips » AWS CloudFormation Tutorial: Concepts, Templates and Use Case

AWS CloudFormation Tutorial: Concepts, Templates and Use Case

When it comes to creating an infrastructure in AWS, CloudFormation is a great tool. You can use it to manage your entire infrastructure, from the initial setup to any updates and removing it all again.

What is AWS CloudFormation?

AWS CloudFormation is a service that helps you model and set up your Amazon Web Services resources so that you can spend less time managing those resources and more time focusing on your applications that run in AWS.

I’ll be writing several articles about CloudFormation, progressing to more and more interesting setups. This article will start with the basics; explaining how CloudFormation works and walking through setting up a simple infrastructure containing a couple of load balanced EC2 instances in their own VPC.

AWS CloudFormation came in to solve this problem and did it seamlessly.

aws-cloudformation-mytechmint

AWS CloudFormation provides a common platform and language which you can use to describe and provision all the AWS infrastructure resources and services in your cloud environment. You can provision and use them in an orderly and predictable manner.

aws-cloudformation
 

 

You can describe, provision resources which are secure and also mention regions in a simple text file. AWS Cloudformation is free! Basically, you don’t need to pay for CloudFormation, you only pay for the services and resources which you are running using AWS. You can automate repeated tasks and make your work much easier. Interconnecting multiple resources using CloudFormation is as easy as creating a single EC2 instance.

AWS CloudFormation Features

  • CloudFormation allows you to model your entire infrastructure in a text file called a template. You can use JSON or YAML to describe what AWS resources you want to create and configure. If you want to design visually, you can use AWS CloudFormation Designer.
  • CloudFormation automates the provisioning and updating of your infrastructure in a safe and controlled manner. You can use Rollback Triggers to specify the CloudWatch alarm that CloudFormation should monitor during the stack creation and update process. If any of the alarms are breached, CloudFormation rolls back the entire stack operation to a previously deployed state.
  • CloudFormation Change Sets allow you to preview how proposed changes to a stack might impact your running resources.
  • AWS StackSets lets you provision a common set of AWS resources across multiple accounts and regions with a single CloudFormation template. StackSets takes care of automatically and safely provisioning, updating, or deleting stacks in multiple accounts and across multiple regions.
  • CloudFormation enables you to build custom extensions to your stack template using AWS Lambda.

CloudFormation vs Elastic Beanstalk

  • Elastic Beanstalk provides an environment to easily deploy and run applications in the cloud.
  • CloudFormation is a convenient provisioning mechanism for a broad range of AWS resources.

Terminology

As we’ll be talking about CloudFormation a lot, let’s start with defining a couple of terms.

Template

A template is the JSON file that describes your setup. It can be big or small, and has to adhere to the AWSTemplateFormat you define. You define this for the initial setup, and then you can make changes to it for further updates.

{
   "AWSTemplateFormatVersion" : "2010-09-09",
   "Description" : "An example template",
  • Templates include several major sections. The Resources section is the only required section.
  • CloudFormation Designer is a graphic tool for creating, viewing, and modifying CloudFormation templates. You can diagram your template resources using a drag-and-drop interface, and then edit their details using the integrated JSON and YAML editor.
  • Custom resources enable you to write custom provisioning logic in templates that CloudFormation runs anytime you create, update (if you changed the custom resource), or delete stacks.
  • Template macros enable you to perform custom processing on templates, from simple actions like find-and-replace operations to extensive transformations of entire templates.

Note that the order in which you define items in your template doesn’t matter, so you can organize it any way you prefer. The AWS documentation has a section devoted to the template anatomy, which goes into far more detail.

Stack

A stack is the implementation of your template. When you upload your stack to AWS it will turn that into the infrastructure you requested. This can range from a single EC2 instance to a complex system of hundreds of instances and using almost every AWS service.

  • If a resource cannot be created, CloudFormation rolls the stack back and automatically deletes any resources that were created. If a resource cannot be deleted, any remaining resources are retained until the stack can be successfully deleted.
  • Stack update methods
    • Direct update
    • Creating and executing change sets
  • Drift detection enables you to detect whether a stack’s actual configuration differs, or has drifted, from its expected configuration. Use CloudFormation to detect drift on an entire stack, or on individual resources within the stack.
    • A resource is considered to have drifted if any if its actual property values differ from the expected property values.
    • A stack is considered to have drifted if one or more of its resources have drifted.
  • To share information between stacks, export a stack’s output values. Other stacks that are in the same AWS account and region can import the exported values.
  • You can nest stacks.

StackSets

  • CloudFormation StackSets allow you to roll out CloudFormation stacks over multiple AWS accounts and in multiple Regions with just a couple of clicks. StackSets is commonly used together with AWS Organizations to centrally deploy and manage services in different accounts.
  • Administrator and target accounts – An administrator account is the AWS account in which you create stack sets. A stack set is managed by signing in to the AWS administrator account in which it was created. A target account is the account into which you create, update, or delete one or more stacks in your stack set.
  • Stack sets – A stack set lets you create stacks in AWS accounts across regions by using a single CloudFormation template. All the resources included in each stack are defined by the stack set’s CloudFormation template. A stack set is a regional resource.
  • Stack instances – A stack instance is a reference to a stack in a target account within a region. A stack instance can exist without a stack; for example, if the stack could not be created for some reason, the stack instance shows the reason for stack creation failure. A stack instance can be associated with only one stack set.
  • Stack set operations – Create stack set, update stack set, delete stacks, and delete stack set.
  • Tags – You can add tags during stack set creation and update operations by specifying key and value pairs.
Related:  Basic Useful Functions for PySpark DataFrame

Resource

Resources are the various services you define in your template. This can be anything from an EC2 instance to a SQS queue, and for each resource you define a number of properties. You can find a complete list of resources and their properties in the AWS Resource Types reference.

   "Resources" : {
      "IPAddress" : {
         "Type" : "AWS::EC2::EIP",
         "Properties" : {
            "InstanceId" : {
               "Ref" : "Webserver"
            },
            "Domain" : "vpc"
         }
      },

Parameter

A parameter is a value that can be provided when you upload your template. You can set default values so you don’t always have to provide them, but these are the only values you can override when you create or update your stack. The most common usage of a parameter is probably defining the instance type of an EC2 instance.

   "Parameters" : {
      "InstanceType" : {
         "Type" : "String",
         "Default" : "t2.micro",
         "Description" : "Type of EC2 instance to launch"
      },

Monitoring

  • CloudFormation is integrated with AWS CloudTrail, a service that provides a record of actions taken by a user, role, or an AWS service in CloudFormation. CloudTrail captures all API calls for CloudFormation as events, including calls from the CloudFormation console and from code calls to the CloudFormation APIs.

Security

  • You can use IAM with CloudFormation to control what users can do with AWS CloudFormation, such as whether they can view stack templates, create stacks, or delete stacks.
  • A service role is an IAM role that allows CloudFormation to make calls to resources in a stack on your behalf. You can specify an IAM role that allows CloudFormation to create, update, or delete your stack resources.
  • You can improve the security posture of your VPC by configuring CloudFormation to use an interface VPC endpoint.

Pricing

  • No additional charge for CloudFormation. You pay for AWS resources created using CloudFormation in the same manner as if you created them manually.

Limits

Limit Description Value
Resources Maximum number of resources that you can declare in your CloudFormation template. 200 resources
Stacks Maximum number of CloudFormation stacks that you can create. 200 stacks
StackSets Maximum number of CloudFormation stack sets you can create in your administrator account. 100 stack sets
Stack instances per stack set The number of stack instances you can have per stack set 2000

How do you use CloudFormation?

We’ve now discussed the terminology and (briefly) the idea behind CloudFormation, but what we’re still missing is an explanation of how to use it. I’ve explained in AWS CloudFormation Using AWS CLI that I prefer to work using the AWS CLI tools, so I will explain the usage in those terms.

CloudFormation Designer Diagram

This is what we are going to build in this tutorial, So we have architecture flow as shown belowcft-template-design-mytechmint

Build the Template

The first step is to create the CloudFormation template. This is most of the work, and is the main subject of the remainder of this article as well as a number of future articles. For now we’ll skip this though and move on to the next step in the process.

Validate the Template

Next up you will want to make sure that your template is valid, without running the risk of breaking a setup you already have. You can do so using the CLI validation, just take into account that this will only check for actual errors in the template. Which means it doesn’t validate against anything referred that is not present in the template.

aws cloudformation validate-template --template-body file://cft-mytechmint.json

Deploy the Template

Once the template is valid, you can then deploy it. As a sidenote, while I show all these commands with the --template-body parameter for a local file, it is possible to use --template-url instead if you store your template on S3.

aws cloudformation create-stack --stack-name babysteps --template-body file://cft-mytechmint.json --profile demo

If you want or need to fill in parameters you can do that with the --parameters option leading to command such as this.

aws cloudformation create-stack --stack-name babysteps --template-body file://cft-mytechmint.json --parameters ParameterKey=InstanceType,ParameterValue=t2.small --profile demo

Waiting Time

From this point on, you’ll have to wait for the creation of the CloudFormation stack. You can check the status with any combination of the below commands, but for a simple setup like this you can just watch it happen in the Console.

CloudFormation > Stacks > mytechmint   then Event Tab

waiting-time-cft-mytechmint-stack

aws cloudformation list-stacks --profile demo
aws cloudformation describe-stack-events --stack-name mytechmint --profile demo
aws cloudformation describe-stacks --stack-name mytechmint --profile demo

For big stacks these options aren’t all that useful as you’d just like to get an update once it’s finished. That goes beyond the basics though, so I’ll describe that in a future article.

Despite all your preparations however, it is always possible that there is a mistake in your template and the deployment will fail. When that happens, check the events to see what happened. In the below image I said I was going to provide a CIDR block while what I actually provided was a security group id. This passed validation because it was provided as a reference, but obviously I made a mistake here.

cft-failed-log-mytechmint

 

Updating and Deleting

The most common task you do with CloudFormation is probably updating your stack. This has the exact same syntax as the create command, only using update-stack instead of create-stack.

aws cloudformation update-stack --stack-name mytechmint --template-body file://cft-mytechmint.json --profile demo

Another task you will carry out frequently if you follow along with my examples is deleting stacks. Use this with care! It will not ask you for confirmation when deleting a stack, so make absolutely certain you’re deleting the right one. For this same reason I would recommend that any default profile you have configured for your CLI tools is not your production environment.

aws cloudformation delete-stack --stack-name mytechmint --profile demo

Domain Specific Language (DSL)

As you can see in the earlier snippets, there is a fair bit of writing involved in setting up a template. That in itself isn’t a major problem, but what is a bit annoying about the basic CloudFormation template is that it can quickly become unmanageable. One major issue is that you can’t have any comments in the JSON code, which makes it a lot harder to manage than it needs to be.

Related:  Amazon Web Services Outages Reported for Third Time this Month

Luckily, there are a number of people who have written a DSL for creating these files. This means that you write the CloudFormation template in a different language that you then parse into the JSON file CloudFormation understands.

There are a number of DSLs out there, which you can find by searching, but the one that I use is cfndsl. I chose to use this one mainly because it was mentioned in the “So you think you’re an AWS ninja” presentation at the AWS Summit in Sydney. It’s certainly possible better DSLs exist, and don’t hesitate to let me know about that, but I’m familiar with cfndsl so I’ll use it here.

I’m not going to explain how the DSL works as its README file does a good enough job of that. However, I’ll give a brief example of what the above examples look like using the DSL.

  Parameter("InstanceType") {
    Description "Type of EC2 instance to launch"
    Type "String"
    Default "t2.micro"
  }

  # The Elastic IP for the Webserver
  Resource("IPAddress") {
    Type "AWS::EC2::EIP"
    Property("Domain", "vpc")
    Property("InstanceId", Ref("Webserver"))
  }

To be fair, this doesn’t look all that different, but you can already see that the syntax is easier to read and that comments can improve on this even more.

Because it is more readable and concise, I will use the DSL code for the examples in my tutorial, but the full JSON templates will also be available at end of this tutorial. As both DSL and compiled JSON file.

Gotcha’s

Despite what we’d all want, CloudFormation isn’t perfect and has its pitfalls. There have been plenty of times where I wasted time because I didn’t pay enough attention to the documentation. For that reason I’ll once again link to the AWS Resource Types reference and remind you that reading the details is important.

Another thing to remember is to make sure you validate your template before you update. If an update fails, CloudFormation will try to roll back the changes but if the problem is big enough this might not always succeed and your stack could end up in a state of UPDATE_ROLLBACK_FAILED. This state will prevent you from updating your stack in the future and you can’t fix that through either the Console or CLI tools. No, you really don’t want to get in that state.

Building the Template

Both the DSL and JSON file will be available at end of this tutorial. If you prefer you can download them now, or do so in end of this tutorial.

In case you aren’t familiar with all the resource types mentioned here, there is a link to the relevant section of the AWS documentation when they come up. I won’t go into detail about each of them here as that would make this article a lot longer. Instead, I will walk through the CloudFormation template step by step, explaining what happens. The comments in the DSL file serve the same purpose, but to keep things more concise I’ve removed those from the below snippets.

First we initiate the template, by providing the necessary template version and description.

CloudFormation {
  AWSTemplateFormatVersion "2010-09-09"

  Description "mytechmint example consisting of 2 EC2 instances behind an ELB"

Next up are some variables we will use later in the template. A name we can use to identify the template, and the number of EC2 instances we wish to spin up.

  templateName = "mytechmint"

  numberOfInstances = 2

Now we’ve passed the preparations and we’ve reached more interesting things with the Parameters. I’ve explained what these are above, so I’ll limit myself here to just mentioning the ones we’ve got. First is the InstanceType for the EC2 instances, followed by the EC2 key pair for accessing the EC2 instances through SSH. You will have to ensure this key is registered in AWS first, which is easiest done in the Console.

The last parameter is for determining which IP addresses can SSH into your server.

  Parameter("InstanceType") {
    Description "Type of EC2 instance to launch"
    Type "String"
    Default "t2.micro"
  }

  Parameter("KeyName") {
    Description "Name of an existing EC2 key pair to enable SSH access to the new EC2 instance"
    Type "String"
    Default "demo-user-ec2"
  }

  Parameter("SshIp") {
    Description "IP address that should have direct access through SSH"
    Type "String"
    Default "10.42.0.0/24"
  }

I haven’t explained mappings yet, but they are a way within CloudFormation to refer to different values. The first mapping is a standard one where you can use the region to find the correct AMI. The second one is mostly for me to keep things clear in my mind, but this same effect could be achieved with variables.

A better solution for this would be to make it similar to the region setup and define these values by region. However, I like having an overview such as this and especially when the number of Subnets grows.

  Mapping("AWSRegionArch2AMI", {
            "us-east-1" => { "AMI" => "ami-864d84ee" }
  })

  Mapping("SubnetConfig", {
      "VPC"     => { "CIDR" => "10.42.0.0/16" },
      "Public"  => { "CIDR" => "10.42.0.0/24" }
  })

Now we’ll start creating resources and, as we need to put everything in there, the VPC and Subnet will come first. Using CloudFormation’s Fn::FindInMap functionality we get the values from the mapping above for the configuration.

You will also notice the first references to other resources here using Ref. This is the only way to refer to other resources in the same template, so it’s nice that AWS made it easy. Following the creation of the VPC and Subnet we need to ensure these have proper access to the Internet, which is what all the routing and gateway resources are for.

  Resource("myTechMintVPC") {
    Type "AWS::EC2::VPC"
    Property("CidrBlock", FnFindInMap("SubnetConfig", "VPC", "CIDR"))
  }

  Resource("PublicSubnet") {
    Type "AWS::EC2::Subnet"
    Property("VpcId", Ref("myTechMintVPC"))
    Property("CidrBlock", FnFindInMap("SubnetConfig", "Public","CIDR"))
  }

  Resource("InternetGateway") {
      Type "AWS::EC2::InternetGateway"
  }

  Resource("AttachGateway") {
       Type "AWS::EC2::VPCGatewayAttachment"
       Property("VpcId", Ref("myTechMintVPC"))
       Property("InternetGatewayId", Ref("InternetGateway"))
  }

  Resource("PublicRouteTable") {
    Type "AWS::EC2::RouteTable"
    Property("VpcId", Ref("myTechMintVPC"))
  }

  Resource("PublicRoute") {
    Type "AWS::EC2::Route"
    DependsOn "AttachGateway"
    Property("RouteTableId", Ref("PublicRouteTable"))
    Property("DestinationCidrBlock", "0.0.0.0/0")
    Property("GatewayId", Ref("InternetGateway"))
  }

  Resource("PublicSubnetRouteTableAssociation") {
    Type "AWS::EC2::SubnetRouteTableAssociation"
    Property("SubnetId", Ref("PublicSubnet"))
    Property("RouteTableId", Ref("PublicRouteTable"))
  }

Where the VPC and its requirements were for providing a place to store the resources, we’ll now have the last bit of preparation: the security groups. These security groups are required as they’ll define access rules for the EC2 instances and ELB we’ll set up later.

Related:  Best Email Hosting Providers of 2020

Again for readability, I define the various rules as Ruby arrays before attaching them to the security groups. The rules defined for the EC2 Instances are to only allow port 80 access from the ELB Security Group and SSH access from the previously defined SshIp parameter. For the ELB we only need world access on port 80.

Once we have these, the actual security group resources only need to refer to the rules and VPC. The only other things we define here are for our own convenience: the description (which is mandatory) and tags (which are not). These are both there to make it easier to find the resources again later. The Name tag is also recognised by the AWS Console and shown in the Name field there.

  ec2SecurityIngres = Array.new

  ec2SecurityIngres.push({
    "IpProtocol" => "tcp",
    "FromPort" => "80",
    "ToPort" => "80",
    "SourceSecurityGroupId" => Ref("ELBSecurityGroup")
  })

  ec2SecurityIngres.push({
    "IpProtocol" => "tcp",
    "FromPort" => "22",
    "ToPort" => "22",
    "CidrIp" => Ref("SshIp")
  })

  port80Open = [{
                  "IpProtocol" => "tcp",
                  "FromPort" => "80",
                  "ToPort" => "80",
                  "CidrIp" => "0.0.0.0/0"
                }]

  Resource("InstanceSecurityGroup") {
    Type "AWS::EC2::SecurityGroup"
    Property("Tags", [{"Key" => "Name", "Value" => "mytechmint EC2"}])
    Property("VpcId", Ref("myTechMintVPC"))
    Property("GroupDescription" , templateName + " - EC2 instances: HTTP and SSH access")
    Property("SecurityGroupIngress", ec2SecurityIngres)
  }

  Resource("ELBSecurityGroup") {
    Type "AWS::EC2::SecurityGroup"
    Property("Tags", [{"Key" => "Name", "Value" => "mytechmint ELB"}])
    Property("VpcId", Ref("myTechMintVPC"))
    Property("GroupDescription" , templateName + " - ELB: HTTP access")
    Property("SecurityGroupIngress", port80Open)
    Property("SecurityGroupEgress", port80Open)
  }

 

So, we have arrived at the EC2 instances. Much of what you see here has already been discussed earlier. We assign the instance type, image id, keyname, subnet, and security group using the tools we’re now familiar with. There are a couple of things worth noting though. First is of course the general syntax. Instead of defining each instance separately as we would for the JSON setup, we create a loop so we can be sure each instance is the same.

The next thing to note is the mytechmintServerRefs array. We’re going to fill this with references to each instance so we can use it when defining the ELB. We also need to assign an Elastic IP to each instance so we can access the servers from our computer.
Below that you will see something else we haven’t discussed yet: Outputs. An Output can show additional information when you collect information about the stack. Using, for example, the describe-stacks CLI command. Having the IPs in there means you don’t have to go looking for them. To get the IP Address of the instance we use Fn::GetAtt which can retrieve attributes made available by a resource.

You might have noticed I skipped a part in the instance resources themselves. The UserData property is provided to an instance only upon creation. You can use this to configure an instance when it’s first booted up, but only at that time. As it’s not the main focus of this example, the only thing we do here is install Apache so we can be sure the ELB will treat our instances as healthy. Just keep in mind that this is a powerful tool that you can use for any type of configuration management.

  mytechmintServerRefs = Array.new

  (1..numberOfInstances).each do |instanceNumber|
    instanceName = "mytechmint#{instanceNumber}"
    Resource(instanceName) {
      Type "AWS::EC2::Instance"
      Property("SubnetId", Ref("PublicSubnet"))
      Property("Tags", [{"Key" => "Name", "Value" => "#{templateName}-#{instanceNumber}"}])
      Property("ImageId",
                FnFindInMap( "AWSRegionArch2AMI", Ref("AWS::Region"),"AMI"))
      Property("InstanceType", Ref("InstanceType"))
      Property("KeyName", Ref("KeyName"))
      Property("SecurityGroupIds", [Ref("InstanceSecurityGroup")])
      Property("UserData", {
                          "Fn::Base64" =>
                            FnJoin("\n",[
                              "#!/bin/bash",
                              "apt-get install -y apache2"
                              ]
                          )})
    }

    Resource ("mytechmintIP#{instanceNumber}") {
      Type "AWS::EC2::EIP";
      Property("Domain", "vpc")
      Property("InstanceId", Ref(instanceName))
    }

    babystepsServerRefs.push(Ref(instanceName))

    Output("#{instanceName}IpAddress") {
      Value FnGetAtt(instanceName, "PublicIp")
    }
  end

 

With the Elastic LoadBalancer we have reached the last part of this example. Once again we go through the motions of connecting to the various resources we have defined earlier, including using the mytechmintServerRefs array. Other than that we set up the listeners (limited to port 80 as we have no other ports open) and define the health check.

  Resource("mytechmintLoadBalancer") {
    Type "AWS::ElasticLoadBalancing::LoadBalancer"
    Property("Subnets", [Ref("PublicSubnet")])
    Property("SecurityGroups", [Ref("ELBSecurityGroup")])
    Property("Listeners" , [{
                                "LoadBalancerPort" => "80",
                                "InstancePort" => "80",
                                "Protocol" => "HTTP"
                              }])
    Property("HealthCheck" , {
                "Target" => "HTTP:80/index.html",
                "HealthyThreshold" => "3",
                "UnhealthyThreshold" => "5",
                "Interval" => "30",
                "Timeout" => "5"
              })
    Property("Instances", mytechmintServerRefs)
  }

Complete Code/JSON File

{
"Parameters" : {
"KeyName" : {
"Type" : "String",
"Default" : "demo-user-ec2",
"Description" : "Name of an existing EC2 key pair to enable SSH access to the new EC2 instance"
},
"InstanceType" : {
"Type" : "String",
"Default" : "t2.micro",
"Description" : "Type of EC2 instance to launch"
},
"SshIp" : {
"Type" : "String",
"Default" : "10.42.0.0/24",
"Description" : "IP address that should have direct access through SSH"
}
},
"Resources" : {
"InstanceSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"VpcId" : {
"Ref" : "myTechMintVPC"
},
"GroupDescription" : "mytechmint - EC2 instances: HTTP and SSH access",
"SecurityGroupIngress" : [
{
"ToPort" : "80",
"FromPort" : "80",
"SourceSecurityGroupId" : {
"Ref" : "ELBSecurityGroup"
},
"IpProtocol" : "tcp"
},
{
"ToPort" : "22",
"FromPort" : "22",
"IpProtocol" : "tcp",
"CidrIp" : {
"Ref" : "SshIp"
}
}
],
"Tags" : [
{
"Value" : "mytechmint EC2",
"Key" : "Name"
}
]
}
},
"AttachGateway" : {
"Type" : "AWS::EC2::VPCGatewayAttachment",
"Properties" : {
"VpcId" : {
"Ref" : "myTechMintVPC"
},
"InternetGatewayId" : {
"Ref" : "InternetGateway"
}
}
},
"mytechmintIP1" : {
"Type" : "AWS::EC2::EIP",
"Properties" : {
"InstanceId" : {
"Ref" : "mytechmint1"
},
"Domain" : "vpc"
}
},
"mytechmintIP2" : {
"Type" : "AWS::EC2::EIP",
"Properties" : {
"InstanceId" : {
"Ref" : "mytechmint2"
},
"Domain" : "vpc"
}
},
"PublicSubnet" : {
"Type" : "AWS::EC2::Subnet",
"Properties" : {
"VpcId" : {
"Ref" : "myTechMintVPC"
},
"CidrBlock" : {
"Fn::FindInMap" : [
"SubnetConfig",
"Public",
"CIDR"
]
}
}
},
"ELBSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"VpcId" : {
"Ref" : "myTechMintVPC"
},
"SecurityGroupEgress" : [
{
"ToPort" : "80",
"FromPort" : "80",
"IpProtocol" : "tcp",
"CidrIp" : "0.0.0.0/0"
}
],
"GroupDescription" : "mytechmint - ELB: HTTP access",
"SecurityGroupIngress" : [
{
"ToPort" : "80",
"FromPort" : "80",
"IpProtocol" : "tcp",
"CidrIp" : "0.0.0.0/0"
}
],
"Tags" : [
{
"Value" : "mytechmint ELB",
"Key" : "Name"
}
]
}
},
"mytechmintLoadBalancer" : {
"Type" : "AWS::ElasticLoadBalancing::LoadBalancer",
"Properties" : {
"HealthCheck" : {
"Interval" : "30",
"Target" : "HTTP:80/index.html",
"Timeout" : "5",
"UnhealthyThreshold" : "5",
"HealthyThreshold" : "3"
},
"Subnets" : [
{
"Ref" : "PublicSubnet"
}
],
"Instances" : [
{
"Ref" : "mytechmint1"
},
{
"Ref" : "mytechmint2"
}
],
"Listeners" : [
{
"InstancePort" : "80",
"Protocol" : "HTTP",
"LoadBalancerPort" : "80"
}
],
"SecurityGroups" : [
{
"Ref" : "ELBSecurityGroup"
}
]
}
},
"PublicRoute" : {
"DependsOn" : "AttachGateway",
"Type" : "AWS::EC2::Route",
"Properties" : {
"GatewayId" : {
"Ref" : "InternetGateway"
},
"DestinationCidrBlock" : "0.0.0.0/0",
"RouteTableId" : {
"Ref" : "PublicRouteTable"
}
}
},
"mytechmint1" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"SecurityGroupIds" : [
{
"Ref" : "InstanceSecurityGroup"
}
],
"KeyName" : {
"Ref" : "KeyName"
},
"ImageId" : {
"Fn::FindInMap" : [
"AWSRegionArch2AMI",
{
"Ref" : "AWS::Region"
},
"AMI"
]
},
"InstanceType" : {
"Ref" : "InstanceType"
},
"SubnetId" : {
"Ref" : "PublicSubnet"
},
"UserData" : {
"Fn::Base64" : {
"Fn::Join" : [
"\n",
[
"#!/bin/bash",
"apt-get install -y apache2"
]
]
}
},
"Tags" : [
{
"Value" : "mytechmint-1",
"Key" : "Name"
}
]
}
},
"mytechmint2" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"SecurityGroupIds" : [
{
"Ref" : "InstanceSecurityGroup"
}
],
"KeyName" : {
"Ref" : "KeyName"
},
"ImageId" : {
"Fn::FindInMap" : [
"AWSRegionArch2AMI",
{
"Ref" : "AWS::Region"
},
"AMI"
]
},
"InstanceType" : {
"Ref" : "InstanceType"
},
"SubnetId" : {
"Ref" : "PublicSubnet"
},
"UserData" : {
"Fn::Base64" : {
"Fn::Join" : [
"\n",
[
"#!/bin/bash",
"apt-get install -y apache2"
]
]
}
},
"Tags" : [
{
"Value" : "mytechmint-2",
"Key" : "Name"
}
]
}
},
"myTechMintVPC" : {
"Type" : "AWS::EC2::VPC",
"Properties" : {
"CidrBlock" : {
"Fn::FindInMap" : [
"SubnetConfig",
"VPC",
"CIDR"
]
}
}
},
"InternetGateway" : {
"Type" : "AWS::EC2::InternetGateway"
},
"PublicRouteTable" : {
"Type" : "AWS::EC2::RouteTable",
"Properties" : {
"VpcId" : {
"Ref" : "myTechMintVPC"
}
}
},
"PublicSubnetRouteTableAssociation" : {
"Type" : "AWS::EC2::SubnetRouteTableAssociation",
"Properties" : {
"SubnetId" : {
"Ref" : "PublicSubnet"
},
"RouteTableId" : {
"Ref" : "PublicRouteTable"
}
}
}
},
"AWSTemplateFormatVersion" : "2010-09-09",
"Outputs" : {
"mytechmint2IpAddress" : {
"Value" : {
"Fn::GetAtt" : [
"mytechmint2",
"PublicIp"
]
}
},
"mytechmint1IpAddress" : {
"Value" : {
"Fn::GetAtt" : [
"mytechmint1",
"PublicIp"
]
}
}
},
"Mappings" : {
"SubnetConfig" : {
"Public" : {
"CIDR" : "10.42.0.0/24"
},
"VPC" : {
"CIDR" : "10.42.0.0/16"
}
},
"AWSRegionArch2AMI" : {
"us-east-1" : {
"AMI" : "ami-864d84ee"
}
}
},
"Description" : "mytechmint example consisting of 2 EC2 instances behind an ELB"
}

Download Required Files for Experiment  😎

DSL File

JSON File

GitHub Repository

1 thought on “AWS CloudFormation Tutorial: Concepts, Templates and Use Case”

Leave a Comment