As a follow up to my last post An Introduction to CloudFormation Tool from AWS, this post covers the basics of a CloudFormation templates.

A CloudFormation template consists of 6 sections – Description, Parameters, Mappings, Conditions, Resources and Outputs.  Only the Resources section is required. However, as a good practice, we highly recommend using all the sections of a template. Each template section is separated by a comma. A template is written in JSON and always starts with an open brace { and always closes with a closed brace }.

Here’s a sample template from AWS website:

{
    "AWSTemplateFormatVersion" : "version date",
    "Description" : "Valid JSON strings",

    "Parameters" : {
        set of parameters
    },

    "Mappings" : {
            set of mappings
            },

    "Conditions" : {
            set of conditions
            },

    "Resources" : {
        set of resources
     },

    "Outputs" : {
        set of outputs
    }

}

Now let us take a look at a CloudFormation template that creates a bastion host (a jump box) to connect to instances in a VPC.

The template Description enables you to provide arbitrary comments about your template. It is a literal JSON string that is between 0 and 1024 bytes in length; its value cannot be based on a parameter or function.

“Description” : “Add a bastion host to an existing VPC. VPC must have an internet gateway already. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.”,

The optional Parameters section enables you to pass values into your template at stack creation time. Parameters let you create templates that can be customized for each stack deployment. When you create a stack from a template containing parameters, you can specify values for those parameters. Within the template, you can use the “Ref” intrinsic function to specify those parameter values in properties values for resources. For example, you can pass parameters in the AWS CLI or you can type the values in the AWS console while creating an instance.

aws cloudformation create-stack –stack-name MyStack –template-body file:///mytemplate.json –parameters ParameterKey=URL,ParameterValue=127.0.0.1

Here’s the Parameters section defined in a CloudFormation template:

  "Parameters" : {

    "KeyName" : {

      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instances",

      "Type" : "String",

      "MinLength": "1",

      "MaxLength": "64",

      "AllowedPattern" : "[-_ a-zA-Z0-9]*",

      "ConstraintDescription" : "can contain only alphanumeric characters, spaces, dashes and underscores."

    },


    "BastionKeyName" : {

      "Description" : "Name of the EC2 KeyPair we will create internally to access instances in our VPC",

      "Type" : "String",

      "MinLength": "1",

      "MaxLength": "64",

      "AllowedPattern" : "[-_ a-zA-Z0-9]*",

      "ConstraintDescription" : "can contain only alphanumeric characters, spaces, dashes and underscores."

    },


    "VpcId" : {

      "Type" : "String",

      "Description" : "VpcId of your existing Virtual Private Cloud (VPC)"

    },


    "SubnetId" : {

      "Type" : "String",

      "Description" : "SubnetId of an existing Public facing subnet in your Virtual Private Cloud (VPC)"

    }

  },

More information on the Parameters section can be found at the following URL: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html

Mappings match a key to a corresponding set of named values. For example, if you want to set values based on a region, you can create a mapping that uses the region name as a key and contains the values you want to specify for each specific region. Mappings are declared in the Mappings section, with each mapping separated by a comma. The keys and values in mappings must be literal strings. Each mapping is formatted as a double-quoted key, a single colon, and braces enclosing the sets of values to map.
  "Mappings" : {

    "AWSInstanceType2Arch" : {

      "t1.micro"    : { "Arch" : "64" },

      "m1.small"    : { "Arch" : "64" },

      "m1.medium"   : { "Arch" : "64" },

      "m1.large"    : { "Arch" : "64" },

      "m1.xlarge"   : { "Arch" : "64" },

      "m2.xlarge"   : { "Arch" : "64" },

      "m2.2xlarge"  : { "Arch" : "64" },

      "m2.4xlarge"  : { "Arch" : "64" },

      "c1.medium"   : { "Arch" : "64" },

      "c1.xlarge"   : { "Arch" : "64" }

    },


    "AWSRegionArch2AMI" : {

      "us-east-1"      : { "64" : "ami-35792c5c" },

      "us-west-1"      : { "64" : "ami-687b4f2d" },

      "us-west-2"      : { "64" : "ami-d03ea1e0" },

      "eu-west-1"      : { "64" : "ami-149f7863" },

      "sa-east-1"      : { "64" : "ami-9f6ec982" },

      "ap-southeast-1" : { "64" : "ami-14f2b946" },

      "ap-southeast-2" : { "64" : "ami-a148d59b" },

      "ap-northeast-1" : { "64" : "ami-3561fe34" }

    }

  },
More examples of Mappings section can be found at the following URL: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html
You define all conditions in the Conditions section of a template by using intrinsic functions. With these intrinsic functions, you can, for example, compare whether a value is equal to another value. Based on the result of a condition, you can conditionally create resources. Within each condition, you can reference another condition, a parameter value, or a mapping. The values for these conditions, parameter values, or mappings can change based on input parameters that you specify when you create or update a stack. After you define all your conditions, you can associate them with resources and resource properties in the Resources and Outputs sections of a template. At stack creation or stack update, AWS CloudFormation evaluates all the conditions in your template before creating any resources. Any resources that are associated with a true condition are created. Any resources that are associated with a false condition are ignored.
More examples on Conditions section of a CloudFormation template can be found at the following URL: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html
In the Resources section you declare the AWS resources you want as part of your stack. Resources are separated by a comma. Each resource must have a logical name unique within the template. This is the name you use elsewhere in the template as a dereference argument. Because constraints on the names of resources vary by service, all resource logical names must be alphanumeric [a-zA-Z0-9] only. You must specify the type for each resource. Type names are fixed according to those listed in Resource Property Types Reference on the AWS website. If a resource does not require any properties to be declared, you can omit the Properties section of that resource.
  "Resources" : {

    "CfnUser": {

      "Type": "AWS::IAM::User",

      "Properties": {

        "Path": "/",

          "Policies": [

          {

            "PolicyName": "root",

            "PolicyDocument": {

              "Statement": [

              {

                "Effect" : "Allow",

                "Action": [

                  "ec2:CreateKeyPair",

                  "ec2:DescribeKeyPairs",

                  "ec2:DescribeRegions",

                  "ec2:ImportKeyPair"

                ],

                "Resource" : "*"

              },

              {

                "Effect": "Allow",

                "Action": "cloudformation:DescribeStackResource",

                "Resource": "*"

              }]

            }

          }

        ]

      }

    },

    "CfnKeys": {

      "Type": "AWS::IAM::AccessKey",

      "Properties": {

        "UserName": {

          "Ref": "CfnUser"

        }

      }

    },


    "IPAddress" : {

      "Type" : "AWS::EC2::EIP",

      "Properties" : {

        "Domain" : "vpc",

        "InstanceId" : { "Ref" : "BastionHost" }

      }

    },


    "BastionSecurityGroup" : {

      "Type" : "AWS::EC2::SecurityGroup",

      "Properties" : {

        "VpcId" : { "Ref" : "VpcId" },

        "GroupDescription" : "Enable SSH access via port 22",

        "SecurityGroupIngress" : [ { "IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "0.0.0.0/0" } ]

      }

    },


    "BastionHost" : {

      "Type" : "AWS::EC2::Instance",

      "Metadata": {

        "AWS::CloudFormation::Init": {

            "config": {

              "packages": {

                "yum": {

                  "python-boto": []

                }

              },

              "files": {

               "/home/ec2-user/create-keypair" : {

                "content" : {

                  "Fn::Join" : ["", ["#!/usr/bin/pythonn",

                   "import stringn",

                   "import randomn",

                   "import boto.ec2n",

                   "kp_name = '",{ "Ref" : "BastionKeyName" },"'n",

                   "ec2 = boto.ec2.connect_to_region('", {"Ref" : "AWS::Region" }, "')n",

                   "keypair = ec2.create_key_pair(kp_name)n",

                   "keypair.save('/home/ec2-user/.ssh/')n",

                   "print 'Created keypair: %s' % kp_namen"]]

                },

              "mode" : "000750",

              "owner" : "ec2-user",

              "group" : "ec2-user"

              },

              "/home/ec2-user/.boto": {

                "content": {

                  "Fn::Join": ["", [ "[Credentials]n",

                    "aws_access_key_id = ", { "Ref": "CfnKeys" }, "n",

                    "aws_secret_access_key = ", { "Fn::GetAtt": ["CfnKeys", "SecretAccessKey"] }, "n",

                    "[Boto]n",

                    "ec2_region_name = ", { "Ref" : "AWS::Region" }, "n",

                    "ec2_region_endpoint = ec2.", { "Ref" : "AWS::Region" }, ".amazonaws.comn"]]

                },

                "mode": "000600",

                "owner": "ec2-user",

                "group": "ec2-user"

              }

            },

            "commands" : {

              "00create-keypair" : {

                "command" : ["su", "ec2-user", "-c", "python create-keypair"],

                "cwd" : "/home/ec2-user"

              }

            }

          }

        }

      },
More information on Resources section can be found at the following URL: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html
A Properties section is declared for each resource immediately after the Resource Type declaration. Multiple properties are separated by a comma. Each property is declared with a double-quoted name, a single colon, and the value for the property. Property values can be literal strings, lists of strings, parameter references, pseudo references, or the value returned by a function. When a property value is a literal string, the value is enclosed in double quotes. If a value is the result of a list of any kind, it is enclosed in brackets (“[ ]”). If a value is the result of an intrinsic function or reference, it is enclosed in braces (“{ }”). These rules apply when you combine literals, lists, references, and functions to obtain a value.
      "Properties" : {

        "InstanceType" : "m1.small",

        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" }, "64" ]},

        "SecurityGroupIds" : [{ "Ref" : "BastionSecurityGroup" }],

        "SubnetId" : { "Ref" : "SubnetId" },

        "KeyName" : { "Ref" : "KeyName" },

        "UserData": {

          "Fn::Base64" : { "Fn::Join" : ["", [

                "#!/bin/bash -vn",

                "yum update -y aws-cfn-bootstrapn",


                "# Helper functionn",

                "function error_exitn",

                "{n",

                "  /opt/aws/bin/cfn-signal -e 1 -r "$1" '", { "Ref" : "BastionHostHandle" }, "'n",

                "  exit 1n",

                "}n",


                "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackName" }, " -r BastionHost ",

                "    --access-key ",  { "Ref" : "CfnKeys" },

                "    --secret-key ", {"Fn::GetAtt": ["CfnKeys", "SecretAccessKey"]},

                "    --region ", { "Ref" : "AWS::Region" }, " || error_exit 'Failed to run cfn-init'n",


                "# All is well so signal successn",

                "/opt/aws/bin/cfn-signal -e 0 -r "Server setup complete" '", { "Ref" : "BastionHostHandle" }, "'n"

              ]

            ]

          }

        }

      }

    },

    "BastionHostHandle" : {

      "Type" : "AWS::CloudFormation::WaitConditionHandle"

    },

    "ControllerCondition" : {

      "Type" : "AWS::CloudFormation::WaitCondition",

      "DependsOn" : "BastionHost",

      "Properties" : {

        "Handle" : { "Ref" : "BastionHostHandle" },

        "Timeout" : "900"

      }

    }

  }
More information on the properties section can be found at the following URL: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/properties-section-structure.html
The template Outputs section enables you to return one or more values to the user in response to the AWS CloudFormation describe-stacks command. To declare the Outputs section, the double-quoted key name Outputs is followed by a single colon. All outputs declared in the Outputs section are contained within a single set of braces, and are delimited by a comma.
  "Outputs" : {


    "InstanceID" : {

      "Value" : {"Ref": "BastionHost"},

      "Description" : "Bastion Instance ID"

    },


    "IPAddress" : {

      "Value" : { "Ref" : "IPAddress" },

      "Description" : "Public IP address of instance"

    },


    "BastionKeyName" : {

      "Value" : { "Ref" : "BastionKeyName" },

      "Description" : "The internal bastion KeyPair name"

    }

  }

}
More information on the Outputs section can be found at the following URL: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html
In conclusion, if you have more than one instance that you would like to spin up or if you have an infrastructure that needs to be spun up at a rapid pace, AWS CloudFormation templates provide an excellent alternative to spinning up instances on an ad hoc basis. AWS CloudFormation templates also reduce the risk of human error while spinning up instances. Finally, the CloudFormation templates can be stored in a version control system. This allows various versions of a datacenter to be stored in a repository. CloudFormation is an excellent tool and when used properly, can make organizations be efficient and less error prone.
For any questions you may have about our AWS Cloud Services, please contact us!
Share This