Amazon CloudFormation Part 2: Creating a CloudFormation Template

The first part of this series introduced Amazon CloudFormation with a basic explanation of what this service is, the resources that this Amazon technology offers, and the general structure that a file should have to describe CloudFormation resources. In this second part, we are going to see how to define our first template and how to create this template from two sources: the Web CloudFormation Console and Amazon CLI.

For this first template, we will use one of the predefined templates that Amazon provides for application frameworks: the LAMP stack.

 

LAMP Stack

According to TechTarget, LAMP refers to a stack with four layers that has Linux, APACHE as the web server, MySQL as the relational database and PHP as the scripting language (this is the most common one, but you can also use Perl or Python instead). Some variations of this stack are: WAMP (Windows, Apache, MySQL, PHP), LAPP (Linux, Apache, PostgreSQL, PHP), MAMP (Mac OS X, Apache, MySQL, PHP) and XAMPP (Linux, Mac OS X, Windows, Apache, MySQL, PHP, Perl). Also, a more modern stack based on JavaScript that is currently widely used is the MEAN stack (MongoDB database, Express.js, AngularJS, Node JS) (Programmable Web).

From our infrastructure’s point of view, these variations don’t greatly affect the way we instantiate our resources on CloudFormation, because we design our template in terms of resources that run these technologies, OS, and databases.

For our first template, we are going to use this LAMP stack because its simplicity helps us concentrate on the main objective of this post: learning about CloudFormation.

Creating our First Template

For this example, we are going to use this template: LAMP stack. We will define each section of the template:

AWSVersion and Description Sections

The first two sections of this template are AWSTemplateFormatVersion and Description. As you remember from the first blog post, the only AWS version that is currently used for CloudFormation is “2010-09-09,” and the description allows us to understand what the template is for, which is especially important for future programmers and their improvement.

AWSTemplateFormatVersion and Description

"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Sample Template LAMP_Single_Instance: Create a LAMP stack using a single EC2 instance and a local MySQL database for storage. This template demonstrates using the AWS CloudFormation bootstrap scripts to install the packages and files necessary to deploy the Apache web server, PHP and MySQL at instance launch time. **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.",

Parameters Section

Here, we define some parameters that are needed to create a more generic template. First, we have KeyName; this parameter is the name of the key pair that will be used to connect to EC2 instances declared on this template (we will create a new key pair to be used on this template in the following section). DBName, DBUser, DBPassword and DBRootPassword are parameters that are used in the RDS instance (MySQL) that we will create. These parameters have several validations like MinLength, MaxLength, AllowedPattern and Type. The InstanceType parameter is the type of available EC2 instance that we want to define for our EC2 instance, and SSHLocation corresponds to the range of IP addresses that can access the EC2 instance with SSH (“0.0.0.0/0” by default means any IP address can access this EC2 instance).

Parameters

"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."
    },   
     "DBName": {
      "Default": "MyDatabase",
      "Description" : "MySQL database name",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "64",
      "AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
      "ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
    },
     "DBUser": {
      "NoEcho": "true",
      "Description" : "Username for MySQL database access",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "16",
      "AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
      "ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
    },
     "DBPassword": {
      "NoEcho": "true",
      "Description" : "Password for MySQL database access",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "41",
      "AllowedPattern" : "[a-zA-Z0-9]*",
      "ConstraintDescription" : "must contain only alphanumeric characters."
    },
    "DBRootPassword": {
      "NoEcho": "true",
      "Description" : "Root password for MySQL",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "41",
      "AllowedPattern" : "[a-zA-Z0-9]*",
      "ConstraintDescription" : "must contain only alphanumeric characters."
    },
     "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."
    }
  },

Mappings Section

In this section, we define some mappings that are used for some EC2 properties: AWSInstanceType2Arch is used to verify if the chosen InstanceType is a valid one; AWSRegionArch2AMI is used to verify if the region on the InstanceType and AWSInstanceType2NATArch is not used on this template.

Mappings

 "Mappings" : {
    "AWSInstanceType2Arch" : {
      "t1.micro"    : { "Arch" : "HVM64"  },
      "t2.nano"     : { "Arch" : "HVM64"  },
      "t2.micro"    : { "Arch" : "HVM64"  },
      "t2.small"    : { "Arch" : "HVM64"  },
      "t2.medium"   : { "Arch" : "HVM64"  },
      "t2.large"    : { "Arch" : "HVM64"  },
      "m1.small"    : { "Arch" : "HVM64"  },
      "m1.medium"   : { "Arch" : "HVM64"  },
      "m1.large"    : { "Arch" : "HVM64"  },
      "m1.xlarge"   : { "Arch" : "HVM64"  },
      "m2.xlarge"   : { "Arch" : "HVM64"  },
      "m2.2xlarge"  : { "Arch" : "HVM64"  },
      "m2.4xlarge"  : { "Arch" : "HVM64"  },
      "m3.medium"   : { "Arch" : "HVM64"  },
      "m3.large"    : { "Arch" : "HVM64"  },
      "m3.xlarge"   : { "Arch" : "HVM64"  },
      "m3.2xlarge"  : { "Arch" : "HVM64"  },
      "m4.large"    : { "Arch" : "HVM64"  },
      "m4.xlarge"   : { "Arch" : "HVM64"  },
      "m4.2xlarge"  : { "Arch" : "HVM64"  },
      "m4.4xlarge"  : { "Arch" : "HVM64"  },
      "m4.10xlarge" : { "Arch" : "HVM64"  },
      "c1.medium"   : { "Arch" : "HVM64"  },
      "c1.xlarge"   : { "Arch" : "HVM64"  },
      "c3.large"    : { "Arch" : "HVM64"  },
      "c3.xlarge"   : { "Arch" : "HVM64"  },
      "c3.2xlarge"  : { "Arch" : "HVM64"  },
      "c3.4xlarge"  : { "Arch" : "HVM64"  },
      "c3.8xlarge"  : { "Arch" : "HVM64"  },
      "c4.large"    : { "Arch" : "HVM64"  },
      "c4.xlarge"   : { "Arch" : "HVM64"  },
      "c4.2xlarge"  : { "Arch" : "HVM64"  },
      "c4.4xlarge"  : { "Arch" : "HVM64"  },
      "c4.8xlarge"  : { "Arch" : "HVM64"  },
      "g2.2xlarge"  : { "Arch" : "HVMG2"  },
      "g2.8xlarge"  : { "Arch" : "HVMG2"  },
      "r3.large"    : { "Arch" : "HVM64"  },
      "r3.xlarge"   : { "Arch" : "HVM64"  },
      "r3.2xlarge"  : { "Arch" : "HVM64"  },
      "r3.4xlarge"  : { "Arch" : "HVM64"  },
      "r3.8xlarge"  : { "Arch" : "HVM64"  },
      "i2.xlarge"   : { "Arch" : "HVM64"  },
      "i2.2xlarge"  : { "Arch" : "HVM64"  },
      "i2.4xlarge"  : { "Arch" : "HVM64"  },
      "i2.8xlarge"  : { "Arch" : "HVM64"  },
      "d2.xlarge"   : { "Arch" : "HVM64"  },
      "d2.2xlarge"  : { "Arch" : "HVM64"  },
      "d2.4xlarge"  : { "Arch" : "HVM64"  },
      "d2.8xlarge"  : { "Arch" : "HVM64"  },
      "hi1.4xlarge" : { "Arch" : "HVM64"  },
      "hs1.8xlarge" : { "Arch" : "HVM64"  },
      "cr1.8xlarge" : { "Arch" : "HVM64"  },
      "cc2.8xlarge" : { "Arch" : "HVM64"  }
    },
 
    "AWSInstanceType2NATArch" : {
      "t1.micro"    : { "Arch" : "NATHVM64"  },
      "t2.nano"     : { "Arch" : "NATHVM64"  },
      "t2.micro"    : { "Arch" : "NATHVM64"  },
      "t2.small"    : { "Arch" : "NATHVM64"  },
      "t2.medium"   : { "Arch" : "NATHVM64"  },
      "t2.large"    : { "Arch" : "NATHVM64"  },
      "m1.small"    : { "Arch" : "NATHVM64"  },
      "m1.medium"   : { "Arch" : "NATHVM64"  },
      "m1.large"    : { "Arch" : "NATHVM64"  },
      "m1.xlarge"   : { "Arch" : "NATHVM64"  },
      "m2.xlarge"   : { "Arch" : "NATHVM64"  },
      "m2.2xlarge"  : { "Arch" : "NATHVM64"  },
      "m2.4xlarge"  : { "Arch" : "NATHVM64"  },
      "m3.medium"   : { "Arch" : "NATHVM64"  },
      "m3.large"    : { "Arch" : "NATHVM64"  },
      "m3.xlarge"   : { "Arch" : "NATHVM64"  },
      "m3.2xlarge"  : { "Arch" : "NATHVM64"  },
      "m4.large"    : { "Arch" : "NATHVM64"  },
      "m4.xlarge"   : { "Arch" : "NATHVM64"  },
      "m4.2xlarge"  : { "Arch" : "NATHVM64"  },
      "m4.4xlarge"  : { "Arch" : "NATHVM64"  },
      "m4.10xlarge" : { "Arch" : "NATHVM64"  },
      "c1.medium"   : { "Arch" : "NATHVM64"  },
      "c1.xlarge"   : { "Arch" : "NATHVM64"  },
      "c3.large"    : { "Arch" : "NATHVM64"  },
      "c3.xlarge"   : { "Arch" : "NATHVM64"  },
      "c3.2xlarge"  : { "Arch" : "NATHVM64"  },
      "c3.4xlarge"  : { "Arch" : "NATHVM64"  },
      "c3.8xlarge"  : { "Arch" : "NATHVM64"  },
      "c4.large"    : { "Arch" : "NATHVM64"  },
      "c4.xlarge"   : { "Arch" : "NATHVM64"  },
      "c4.2xlarge"  : { "Arch" : "NATHVM64"  },
      "c4.4xlarge"  : { "Arch" : "NATHVM64"  },
      "c4.8xlarge"  : { "Arch" : "NATHVM64"  },
      "g2.2xlarge"  : { "Arch" : "NATHVMG2"  },
      "g2.8xlarge"  : { "Arch" : "NATHVMG2"  },
      "r3.large"    : { "Arch" : "NATHVM64"  },
      "r3.xlarge"   : { "Arch" : "NATHVM64"  },
      "r3.2xlarge"  : { "Arch" : "NATHVM64"  },
      "r3.4xlarge"  : { "Arch" : "NATHVM64"  },
      "r3.8xlarge"  : { "Arch" : "NATHVM64"  },
      "i2.xlarge"   : { "Arch" : "NATHVM64"  },
      "i2.2xlarge"  : { "Arch" : "NATHVM64"  },
      "i2.4xlarge"  : { "Arch" : "NATHVM64"  },
      "i2.8xlarge"  : { "Arch" : "NATHVM64"  },
      "d2.xlarge"   : { "Arch" : "NATHVM64"  },
      "d2.2xlarge"  : { "Arch" : "NATHVM64"  },
      "d2.4xlarge"  : { "Arch" : "NATHVM64"  },
      "d2.8xlarge"  : { "Arch" : "NATHVM64"  },
      "hi1.4xlarge" : { "Arch" : "NATHVM64"  },
      "hs1.8xlarge" : { "Arch" : "NATHVM64"  },
      "cr1.8xlarge" : { "Arch" : "NATHVM64"  },
      "cc2.8xlarge" : { "Arch" : "NATHVM64"  }
    }
,
    "AWSRegionArch2AMI" : {
      "us-east-1"        : {"HVM64" : "ami-0ff8a91507f77f867", "HVMG2" : "ami-0a584ac55a7631c0c"},
      "us-west-2"        : {"HVM64" : "ami-a0cfeed8", "HVMG2" : "ami-0e09505bc235aa82d"},
      "us-west-1"        : {"HVM64" : "ami-0bdb828fd58c52235", "HVMG2" : "ami-066ee5fd4a9ef77f1"},
      "eu-west-1"        : {"HVM64" : "ami-047bb4163c506cd98", "HVMG2" : "ami-0a7c483d527806435"},
      "eu-west-2"        : {"HVM64" : "ami-f976839e", "HVMG2" : "NOT_SUPPORTED"},
      "eu-west-3"        : {"HVM64" : "ami-0ebc281c20e89ba4b", "HVMG2" : "NOT_SUPPORTED"},
      "eu-central-1"     : {"HVM64" : "ami-0233214e13e500f77", "HVMG2" : "ami-06223d46a6d0661c7"},
      "ap-northeast-1"   : {"HVM64" : "ami-06cd52961ce9f0d85", "HVMG2" : "ami-053cdd503598e4a9d"},
      "ap-northeast-2"   : {"HVM64" : "ami-0a10b2721688ce9d2", "HVMG2" : "NOT_SUPPORTED"},
      "ap-northeast-3"   : {"HVM64" : "ami-0d98120a9fb693f07", "HVMG2" : "NOT_SUPPORTED"},
      "ap-southeast-1"   : {"HVM64" : "ami-08569b978cc4dfa10", "HVMG2" : "ami-0be9df32ae9f92309"},
      "ap-southeast-2"   : {"HVM64" : "ami-09b42976632b27e9b", "HVMG2" : "ami-0a9ce9fecc3d1daf8"},
      "ap-south-1"       : {"HVM64" : "ami-0912f71e06545ad88", "HVMG2" : "ami-097b15e89dbdcfcf4"},
      "us-east-2"        : {"HVM64" : "ami-0b59bfac6be064b78", "HVMG2" : "NOT_SUPPORTED"},
      "ca-central-1"     : {"HVM64" : "ami-0b18956f", "HVMG2" : "NOT_SUPPORTED"},
      "sa-east-1"        : {"HVM64" : "ami-07b14488da8ea02a0", "HVMG2" : "NOT_SUPPORTED"},
      "cn-north-1"       : {"HVM64" : "ami-0a4eaf6c4454eda75", "HVMG2" : "NOT_SUPPORTED"},
      "cn-northwest-1"   : {"HVM64" : "ami-6b6a7d09", "HVMG2" : "NOT_SUPPORTED"}
    }
 
  },

Resources Section

For this LAMP stack, we just need to declare two resources: an EC2 instance that will have all LAMP elements (in this case, we have a local MySQL inside the EC2 instance, not an RDS instance) and a security group to allow HTTP access to the EC2 by port 80. First, for the EC2 instance called WebServerInstance, we declare several packages that will be installed (“Install” property inside the object), also inside the “files” property, we set the basic configuration for our initial PHP file and the initial configuration for a local MySQL. On the “Services” property, we declare the set of services that will be executed for MySQL and other services. In the “Configure” section, we define the commands to set the root password for our new MySQL instance, and we create the new database we are going to use in the stack. Inside “Properties,” we associate this EC2 instance with the security group we created for the EC2 and we update several Linux packages.

For the resource WebServerSecurityGroup, we define a couple of rules for the EC2 instance. For port 80, we give all IPs access to the EC2 instance, and for port 22, we set just the IP range we chose on SSHLocation.

Resources

 
"Resources" : {    
     
  "WebServerInstance": { 
    "Type": "AWS::EC2::Instance",
    "Metadata" : {
      "AWS::CloudFormation::Init" : {
        "configSets" : {
          "InstallAndRun" : [ "Install", "Configure" ]
        },
 
        "Install" : {
          "packages" : {
            "yum" : {
              "mysql"        : [],
              "mysql-server" : [],
              "mysql-libs"   : [],
              "httpd"        : [],
              "php"          : [],
              "php-mysql"    : []
            }
          },
 
          "files" : {
            "/var/www/html/index.php" : {
              "content" : { "Fn::Join" : [ "", [
                "\n",
                "\n",
                "\n",
                "\n",
                "  \n",
                "\n",
                "

Welcome to the AWS CloudFormation PHP Sample

\n", " \n", " < ?php\n", " // Print out the current data and time\n", " print \"The Current Date and Time is: \";\n", " print date(\"g:i A l, F j Y.\");\n", " ? >\n", "   \n", " < ?php\n", " // Setup a handle for CURL\n", " $curl_handle=curl_init();\n", " curl_setopt($curl_handle,CURLOPT_CONNECTTIMEOUT,2);\n", " curl_setopt($curl_handle,CURLOPT_RETURNTRANSFER,1);\n", " // Get the hostname of the intance from the instance metadata\n", " curl_setopt($curl_handle,CURLOPT_URL,'http://169.254.169.254/latest/meta-data/public-hostname');\n", " $hostname = curl_exec($curl_handle);\n", " if (empty($hostname))\n", " {\n", " print \"Sorry, for some reason, we got no hostname back \";\n", " }\n", " else\n", " {\n", " print \"Server = \" . $hostname . \" \";\n", " }\n", " // Get the instance-id of the intance from the instance metadata\n", " curl_setopt($curl_handle,CURLOPT_URL,'http://169.254.169.254/latest/meta-data/instance-id');\n", " $instanceid = curl_exec($curl_handle);\n", " if (empty($instanceid))\n", " {\n", " print \"Sorry, for some reason, we got no instance id back \";\n", " }\n", " else\n", " {\n", " print \"EC2 instance-id = \" . $instanceid . \" \";\n", " }\n", " $Database = \"localhost\";\n", " $DBUser = \"", {"Ref" : "DBUser"}, "\";\n", " $DBPassword = \"", {"Ref" : "DBPassword"}, "\";\n", " print \"Database = \" . $Database . \" \";\n", " $dbconnection = mysql_connect($Database, $DBUser, $DBPassword)\n", " or die(\"Could not connect: \" . mysql_error());\n", " print (\"Connected to $Database successfully\");\n", " mysql_close($dbconnection);\n", " ?>\n", "

PHP Information

\n", " \n", " < ?php\n", " phpinfo();\n", " ? >\n", "\n", "\n" ]]}, "mode" : "000600", "owner" : "apache", "group" : "apache" }, "/tmp/setup.mysql" : { "content" : { "Fn::Join" : ["", [ "CREATE DATABASE ", { "Ref" : "DBName" }, ";\n", "GRANT ALL ON ", { "Ref" : "DBName" }, ".* TO '", { "Ref" : "DBUser" }, "'@localhost IDENTIFIED BY '", { "Ref" : "DBPassword" }, "';\n" ]]}, "mode" : "000400", "owner" : "root", "group" : "root" }, "/etc/cfn/cfn-hup.conf" : { "content" : { "Fn::Join" : ["", [ "[main]\n", "stack=", { "Ref" : "AWS::StackId" }, "\n", "region=", { "Ref" : "AWS::Region" }, "\n" ]]}, "mode" : "000400", "owner" : "root", "group" : "root" }, "/etc/cfn/hooks.d/cfn-auto-reloader.conf" : { "content": { "Fn::Join" : ["", [ "[cfn-auto-reloader-hook]\n", "triggers=post.update\n", "path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init\n", "action=/opt/aws/bin/cfn-init -v ", " --stack ", { "Ref" : "AWS::StackName" }, " --resource WebServerInstance ", " --configsets InstallAndRun ", " --region ", { "Ref" : "AWS::Region" }, "\n", "runas=root\n" ]]}, "mode" : "000400", "owner" : "root", "group" : "root" } }, "services" : { "sysvinit" : { "mysqld" : { "enabled" : "true", "ensureRunning" : "true" }, "httpd" : { "enabled" : "true", "ensureRunning" : "true" }, "cfn-hup" : { "enabled" : "true", "ensureRunning" : "true", "files" : ["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"]} } } }, "Configure" : { "commands" : { "01_set_mysql_root_password" : { "command" : { "Fn::Join" : ["", ["mysqladmin -u root password '", { "Ref" : "DBRootPassword" }, "'"]]}, "test" : { "Fn::Join" : ["", ["$(mysql ", { "Ref" : "DBName" }, " -u root --password='", { "Ref" : "DBRootPassword" }, "' >/dev/null 2>&1 </dev/null); (( $? != 0 ))"]]} }, "02_create_database" : { "command" : { "Fn::Join" : ["", ["mysql -u root --password='", { "Ref" : "DBRootPassword" }, "' < /tmp/setup.mysql"]]}, "test" : { "Fn::Join" : ["", ["$(mysql ", { "Ref" : "DBName" }, " -u root --password='", { "Ref" : "DBRootPassword" }, "' >/dev/null 2>&1 </dev/null); (( $? != 0 ))"]]} } } } } }, "Properties": { "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" }, { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }, "InstanceType" : { "Ref" : "InstanceType" }, "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ], "KeyName" : { "Ref" : "KeyName" }, "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ "#!/bin/bash -xe\n", "yum update -y aws-cfn-bootstrap\n", "# Install the files and packages from the metadata\n", "/opt/aws/bin/cfn-init -v ", " --stack ", { "Ref" : "AWS::StackName" }, " --resource WebServerInstance ", " --configsets InstallAndRun ", " --region ", { "Ref" : "AWS::Region" }, "\n", "# Signal the status from cfn-init\n", "/opt/aws/bin/cfn-signal -e $? ", " --stack ", { "Ref" : "AWS::StackName" }, " --resource WebServerInstance ", " --region ", { "Ref" : "AWS::Region" }, "\n" ]]}} }, "CreationPolicy" : { "ResourceSignal" : { "Timeout" : "PT5M" } } }, "WebServerSecurityGroup" : { "Type" : "AWS::EC2::SecurityGroup", "Properties" : { "GroupDescription" : "Enable HTTP access via port 80", "SecurityGroupIngress" : [ {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"}, {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "SSHLocation"}} ] } } },

Outputs Section

In this last section, the values that are described will be printed to the AWS console when the template is correctly created. We will see the URL for our new website.

Outputs

"Outputs" : {
  "WebsiteURL" : {
    "Description" : "URL for newly created LAMP stack",
    "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "WebServerInstance", "PublicDnsName" ]}]] }
  }
}

Creating the Template on the AWS Web Console

Now that we have our template defined, we have to go to the AWS management console to create the infrastructure. The following image shows the AWS login page:

AWS login page

 

On the AWS main page, click on

 

Services menu / EC2 Console

 

“Services” and look for “EC2 instance” (we need to create a new key pair before the template creation). Inside the EC2 console, select the “Key Pairs” option on the left sidebar:

Click on the “Create Key Pair” button at the top of the page; after that, choose a name and select “Create.” Save the key pair as a .pem file (this format is used if you want to access the EC2 from your terminal):

 

Create the key pair / Save the key pair file

Create the key pair / Save the key pair file

 

After the key pair creation, search for the CloudFormation console inside Services; you will see the CloudFormation console main page. Click on “Create Stack”:

 

Go to the CloudFormation console / Click on “Create Stack”

Go to the CloudFormation console / Click on “Create Stack”

 

To create a new stack, you have to define the template you will use. Four options are available: “Design template,” “Select a sample template,” “Upload a template to an Amazon S3,” or “Specify an S3 URL.” After that, click “Next,” define the list of parameters that you want to use, and click “Next” again.

 

Select the template / Select the key pair and parameters

Select the template / Select the key pair and parameters

 

The next two pages will show some options that you can add to your template (not necessary in this example), so click “Next.” The last page is a “Review” page where you see all the values chosen for the template. Click “Create”:

 

Some options / Review of the parameters chosen for the template

 

Some options / Review of the parameters chosen for the template

 

Now the creation is in progress, so you can check the process with the tabs that are available (Overview, Resources, Events, Template, Parameters).

 

Overview / Resources

Overview / Resources

 

When the process is finally done, you can see the status CREATE_COMPLETE and the list of events that were done.

 

 CREATE_COMPLETE

Creating the Template on AWS CLI

The process on CLI is pretty similar. You have to install AWS. After that, configure the AWS credentials in your console and execute the following command (just set stack name, template and parameters):

 

Credentials configuration

Credentials configurations

 

 

Template execution on AWS CLI on Mac OS

Template execution on AWS CLI on Mac OS

 

 

AWS CLI

aws cloudformation create-stack --stack-name LAMP-cloudformation-cli --template-url https://s3-us-west-1.amazonaws.com/cloudformation-templates-us-west-1/LAMP_Single_Instance.template --parameters ParameterKey=KeyName,ParameterValue=blog-post-cloudformation ParameterKey=DBName,ParameterValue=blogCLI ParameterKey=DBUser,ParameterValue=blog ParameterKey=DBPassword,ParameterValue=admin123 ParameterKey=DBRootPassword,ParameterValue=admin123 ParameterKey=InstanceType,ParameterValue=t2.micro ParameterKey=SSHLocation,ParameterValue=0.0.0.0/0

 

You will see the creation process was triggered by the CLI:

 

Creation triggered by AWS CLI

Creation triggered by AWS CLI

 

Now the process is done exactly as with the AWS Web console:

 

Outputs/Events

Outputs/Events

 

You can access the new URL printed to the “Outputs” section, and you will see your first LAMP working!

 

Index page

Index page

 

Now we know how to create a Cloudformation template and how to use tools provided by the Amazon console. In the next and final blog post, we are going to see some advanced features that Cloudformation provides us, such as change sets and nested stacks.

 

 

Subscribe to our Blog

Gaudy Blanco
Gaudy Blanco
Gaudy Blanco is one of Gorilla Logic’s talented software engineers. Gaudy graduated with a degree in Computer Science and English from the University of Costa Rica in 2016. Gaudy is interested in programming, web development, databases, and teaching. She enjoys learning new technologies.

Deliver off-the-chart results.

WordPress Video Lightbox Plugin