Getting Started with AWS Cloudformation

AWS Cloud

AWS Cloudformation is a tool and declaritive language you can use to declare what you want your “stack” (i.e. your deployment of AWS resources) to look like. You define all the resources you want AWS to spin up in a blueprint document, click a button, and then AWS magically creates it all. This blueprint is called a template in CloudFormation speak. CloudFormation makes sure that dependent resources in your template are all created in the proper order.

The template can be in either JSON or YAML format and it declares any and all AWS resources and their configuration. If you needed to deploy an EC2 instance with a Security Group, an S3 bucket and a DynamoDB database, you could. Meaning deploying it once or many times is the same.

export AWS_PROFILE=myprofile 
aws sso login

Although all of these tasks can be completed via the AWS Console (Web GUI), moving towards a IaC (Infrastructure as Code) approach we are building skills in managing everything via the CLI, scripts, SDKs and (REST) APIs, so knowing how to find and see resources via code or commands rather than the GUI is key; this guide therefore is written to this end.

Simple Example (1 x EC2 Instance, 1 x Security Group)

For this getting started guide we’ll use a simple example which is an EC2 Instance with a Security Group. AWS provide some Sample Cloudformation Templates which you can look at to see how they work.

We’ll use this template: EC2InstanceWithSecurityGroupSample.template, download, rename to ec2withSecurityGroup.yml and open and have a look.

Here’s a short explanation of what each means with the important ones bolded:

  • AWSTemplateFormatVersion: Specifies the AWS CloudFormation template version.
  • Description: A text string that describes the template.
  • Mappings: A mapping of keys and associated values that you can use to specify conditional parameter values. This is CloudFormation’s version of a “case” statement.
  • Outputs: Describes the values that are returned whenever you view your stack’s properties. This gets displayed in the AWS CloudFormation Console.
  • **Parameters: **Specifies values that you can pass into your template at runtime.
  • *Resources: **Specifies the stack resources and their properties, like our EC2 instance. This is the *only required property.

Parameters and Resources

The most important top-level properties of a CloudFormation template are Parameters and Resources.

The Parameters section is where you define any parameters such as Names, or KeyNames or Sizing or any other attributes you wish to have as an input into the template when you come to deploy it. I.e. you would have one here that takes your desired InstanceType (e.g. t1.micro) as an input; and this is then used within the Resources section to determine the size of the EC2 instance that will actually be deployed.

...
"Parameters" : {
    "KeyName": {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instance",
      "Type": "AWS::EC2::KeyPair::KeyName",
      "ConstraintDescription" : "must be the name of an existing EC2 KeyPair."
    },

    "InstanceType" : {
      "Description" : "WebServer EC2 instance type",
      "Type" : "String",
      "Default" : "t2.small",
      "AllowedValues" : [ "t1.micro", "t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large", "m1.small", "m1.medium", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "m3.medium", "m3.large", "m3.xlarge", "m3.2xlarge", "m4.large", "m4.xlarge", "m4.2xlarge", "m4.4xlarge", "m4.10xlarge", "c1.medium", "c1.xlarge", "c3.large", "c3.xlarge", "c3.2xlarge", "c3.4xlarge", "c3.8xlarge", "c4.large", "c4.xlarge", "c4.2xlarge", "c4.4xlarge", "c4.8xlarge", "g2.2xlarge", "g2.8xlarge", "r3.large", "r3.xlarge", "r3.2xlarge", "r3.4xlarge", "r3.8xlarge", "i2.xlarge", "i2.2xlarge", "i2.4xlarge", "i2.8xlarge", "d2.xlarge", "d2.2xlarge", "d2.4xlarge", "d2.8xlarge", "hi1.4xlarge", "hs1.8xlarge", "cr1.8xlarge", "cc2.8xlarge", "cg1.4xlarge"]
,
      "ConstraintDescription" : "must be a valid EC2 instance type."
    },

    "SSHLocation" : {
      "Description" : "The IP address range that can be used to SSH to the EC2 instances",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "0.0.0.0/0",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
   }
  },
...

(info) You’l notice the default InstanceType is “t2.small” you can override this when you come to deployment, you’ll also notice the KeyName, this has no default value, which means if you don’t specify one at deployment time the cloudformation template won’t be applied.

That then brings us to the Resources section, this is where you declare the AWS resources you actually want deployed, within this section you’ll be using References to refer to other parts of the template and with that your parameters when it comes to deployment time.

...
"Resources" : {
    "EC2Instance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "InstanceType" : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ],
        "KeyName" : { "Ref" : "KeyName" },
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }
      }
    },

    "InstanceSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable SSH access via port 22",
        "SecurityGroupIngress" : [ {
          "IpProtocol" : "tcp",
          "FromPort" : "22",
          "ToPort" : "22",
          "CidrIp" : { "Ref" : "SSHLocation"}
        } ]
      }
    }
  },
...

Preparation

You’ll need to do some preparations before we actually launch the stack.

Create Default VPC

In this particular example we need to ensure we have a default VPC created within the account, otherwise the deployment of the template fails.

(tick) You may not need to do this if your account already has the default VPC.

aws ec2 create-default-vpc

https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html

Create KeyPair

In this particular example we need to create an EC2 KeyPair to use with the EC2 instance we are trying to deploy. You can create the keypair as follows, then you need to change the permissions (on Linux) to ensure you can use it.

(tick) You may not need to do this if you already have an EC2 KeyPair created and the Private Key downloaded to your machine.

aws ec2 create-key-pair --key-name MyKeyPair --query 'KeyMaterial' --output text > MyKeyPair.pem chmod 400 MyKeyPair.pem

https://docs.aws.amazon.com/cli/latest/userguide/cli-services-ec2-keypairs.html

Creating the Stack

Now we are ready to create the stack. But before we do a few comments about the command itself. You’ll notice that there are some arguments we are passing, we’ll take each of these in turn and explain them.

  • Template-Body – The template body is the path and filename of the template file you are wanting to apply. 
  • Stack-Name – This is the name of the stack within Cloudformation, it will be how it appears and how you can refer to the stack, note that this name is not expressed in the template, it is applied as you run CloudFormation to create the stack.
  • Parameters – These are the parameters we declared within our template, so here we are passing values into those Parameters that will then be used within the Template. These are defined by ParameterKey followed by ParameterValue as a pair separated by a space.
    • KeyName – In this case we’re passing the value “MyKeyPair” which was the EC2 Keypair we had above.
    • InstanceType – In this case we’re passing the value “t2.micro” which is the size of EC2 instance we want.

Now run the command:

$ aws cloudformation create-stack --template-body file://ec2withSecurityGroup.yml --stack-name single-instance --parameters ParameterKey=KeyName,ParameterValue=MyKeyPair ParameterKey=InstanceType,ParameterValue=t2.micro

You should get an output such as:

{ "StackId": "arn:aws:cloudformation:eu-west-2:34912345614:stack/single-instance/7854f230-b854-11ed-8338-069354c2cde4" }

Now let’s see what is going on:

aws cloudformation describe-stacks

Within this output you can find the stack you just created, of course you can add a query to refine this output to only show the key bits of information you are looking for. Or you can specify the stack name to get the output of only just this stack (if you have multiple stacks present).

aws cloudformation describe-stacks

Checking the EC2 Instance (using SSH Connection)

Now the stack has been deployed, let’s try to SSH to the EC2 instance. Our Security Policy says this is allowed as there is a rule for SSH. So first we need to find the Public IPv4 DNS name for the instance.

aws ec2 describe-instances

This gives a lot of output, so let’s refine it for the pertinent information:

aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIP:PublicIpAddress,Name:Tags[?Key=='Name']|[0].Value,Status:State.Name,PublicDNSName:PublicDnsName}" --filters Name=instance-state-name,Values=running --output table

That’s better:

-------------------------------------------------------------------------------------------
|                                    DescribeInstances                                    |
+------+-----------------------------------------------------+----------------+-----------+
| Name |                    PublicDNSName                    |   PublicIP     |  Status   |
+------+-----------------------------------------------------+----------------+-----------+
|  None|  ec2-18-130-228-29.eu-west-2.compute.amazonaws.com  |  18.130.228.29 |  running  |
+------+-----------------------------------------------------+----------------+-----------+

So now let’s make a connection to the Public DNS Name with the EC2 Keypair via SSH:

ssh -i ~/MyKeyPair.pem ec2-user@ec2-18-130-228-29.eu-west-2.compute.amazonaws.com

And we are in:

me@computer:~/vscode-projects/misc/cloudformation$ ssh -i ~/MyKeyPair.pem ec2-user@ec2-18-130-228-29.eu-west-2.compu
te.amazonaws.com
Last login: Wed Mar  1 16:47:23 2023 from 1.2.3.4

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
33 package(s) needed for security, out of 50 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-172-31-41-251 ~]$

Clean-Up

To wrap up, we’ll delete the stack to ensure we’re not running up any bills, so we can run the following:

aws cloudformation delete-stack --stack-name single-instance

We can run the below to check it really has gone!

aws ec2 describe-instances

With a bit of refinement and you can be sure its no longer present.

aws cloudformation describe-stacks --stack-name single-instance

Additional Information

Image Attribution

Leave a Reply

Your email address will not be published. Required fields are marked *