{"id":3679,"date":"2023-03-04T11:43:40","date_gmt":"2023-03-04T11:43:40","guid":{"rendered":"https:\/\/geekmungus.co.uk\/?p=3679"},"modified":"2023-03-04T11:43:40","modified_gmt":"2023-03-04T11:43:40","slug":"getting-started-with-aws-cloudformation","status":"publish","type":"post","link":"https:\/\/geekmungus.co.uk\/?p=3679","title":{"rendered":"Getting Started with AWS Cloudformation"},"content":{"rendered":"\n<p>AWS Cloudformation is a tool and declaritive language you can use to declare what you want your &#8220;stack&#8221; (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.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export AWS_PROFILE=myprofile \naws sso login<\/code><\/pre>\n\n\n\n<p>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.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Simple Example (1 x EC2 Instance, 1 x Security Group)<\/h1>\n\n\n\n<p>For this getting started guide we&#8217;ll use a simple example which is an <strong>EC2 Instance<\/strong> with a <strong>Security Group<\/strong>. AWS provide some&nbsp;<a href=\"https:\/\/docs.aws.amazon.com\/AWSCloudFormation\/latest\/UserGuide\/sample-templates-services-eu-west-2.html\">Sample Cloudformation Templates<\/a> which you can look at to see how they work.<\/p>\n\n\n\n<p>We&#8217;ll use this template: <a href=\"https:\/\/s3.eu-west-2.amazonaws.com\/cloudformation-templates-eu-west-2\/EC2InstanceWithSecurityGroupSample.template\">EC2InstanceWithSecurityGroupSample.template<\/a>, download, rename to <strong>ec2withSecurityGroup.yml<\/strong> and open and have a look.<\/p>\n\n\n\n<p>Here\u2019s a short explanation of what each means with the important ones bolded:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>AWSTemplateFormatVersion: Specifies the AWS CloudFormation template version.<\/li>\n\n\n\n<li>Description: A text string that describes the template.<\/li>\n\n\n\n<li>Mappings: A mapping of keys and associated values that you can use to specify conditional parameter values. This is CloudFormation\u2019s version of a \u201ccase\u201d statement.<\/li>\n\n\n\n<li>Outputs: Describes the values that are returned whenever you view your stack\u2019s properties. This gets displayed in the AWS CloudFormation Console.<\/li>\n\n\n\n<li>**Parameters: **Specifies values that you can pass into your template at runtime.<\/li>\n\n\n\n<li><em>*Resources: **Specifies the stack resources and their properties, like our EC2 instance. This is the *only<\/em>&nbsp;<em>required<\/em>&nbsp;property.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Parameters and Resources<\/h2>\n\n\n\n<p>The most important top-level properties of a CloudFormation template are <strong>Parameters <\/strong>and <strong>Resources<\/strong>.<\/p>\n\n\n\n<p>The <strong>Parameters<\/strong> 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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>...\n\"Parameters\" : {\n    \"KeyName\": {\n      \"Description\" : \"Name of an existing EC2 KeyPair to enable SSH access to the instance\",\n      \"Type\": \"AWS::EC2::KeyPair::KeyName\",\n      \"ConstraintDescription\" : \"must be the name of an existing EC2 KeyPair.\"\n    },\n\n    \"InstanceType\" : {\n      \"Description\" : \"WebServer EC2 instance type\",\n      \"Type\" : \"String\",\n      \"Default\" : \"t2.small\",\n      \"AllowedValues\" : &#91; \"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\"]\n,\n      \"ConstraintDescription\" : \"must be a valid EC2 instance type.\"\n    },\n\n    \"SSHLocation\" : {\n      \"Description\" : \"The IP address range that can be used to SSH to the EC2 instances\",\n      \"Type\": \"String\",\n      \"MinLength\": \"9\",\n      \"MaxLength\": \"18\",\n      \"Default\": \"0.0.0.0\/0\",\n      \"AllowedPattern\": \"(\\\\d{1,3})\\\\.(\\\\d{1,3})\\\\.(\\\\d{1,3})\\\\.(\\\\d{1,3})\/(\\\\d{1,2})\",\n      \"ConstraintDescription\": \"must be a valid IP CIDR range of the form x.x.x.x\/x.\"\n   }\n  },\n...<\/code><\/pre>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/ssg-confluence.internal.sanger.ac.uk\/s\/-r41nvw\/8703\/51k4y0\/_\/images\/icons\/emoticons\/information.svg\" alt=\"(info)\"> <em>You&#8217;l notice the default <strong>InstanceType <\/strong>is &#8220;t2.small&#8221; you can override this when you come to deployment, you&#8217;ll also notice the <strong>KeyName<\/strong>, this has no default value, which means if you don&#8217;t specify one at deployment time the cloudformation template won&#8217;t be applied.<\/em><\/p>\n\n\n\n<p>That then brings us to the <strong>Resources <\/strong>section, this is where you declare the AWS resources you actually want deployed, within this section you&#8217;ll be using References to refer to other parts of the template and with that your parameters when it comes to deployment time.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>...\n\"Resources\" : {\n    \"EC2Instance\" : {\n      \"Type\" : \"AWS::EC2::Instance\",\n      \"Properties\" : {\n        \"InstanceType\" : { \"Ref\" : \"InstanceType\" },\n        \"SecurityGroups\" : &#91; { \"Ref\" : \"InstanceSecurityGroup\" } ],\n        \"KeyName\" : { \"Ref\" : \"KeyName\" },\n        \"ImageId\" : { \"Fn::FindInMap\" : &#91; \"AWSRegionArch2AMI\", { \"Ref\" : \"AWS::Region\" },\n                          { \"Fn::FindInMap\" : &#91; \"AWSInstanceType2Arch\", { \"Ref\" : \"InstanceType\" }, \"Arch\" ] } ] }\n      }\n    },\n\n    \"InstanceSecurityGroup\" : {\n      \"Type\" : \"AWS::EC2::SecurityGroup\",\n      \"Properties\" : {\n        \"GroupDescription\" : \"Enable SSH access via port 22\",\n        \"SecurityGroupIngress\" : &#91; {\n          \"IpProtocol\" : \"tcp\",\n          \"FromPort\" : \"22\",\n          \"ToPort\" : \"22\",\n          \"CidrIp\" : { \"Ref\" : \"SSHLocation\"}\n        } ]\n      }\n    }\n  },\n...<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Preparation<\/h2>\n\n\n\n<p>You&#8217;ll need to do some preparations before we actually launch the stack.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Create Default VPC<\/h3>\n\n\n\n<p>In this particular example we need to ensure we have a default VPC created within the account, otherwise the deployment of the template fails.<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/ssg-confluence.internal.sanger.ac.uk\/s\/-r41nvw\/8703\/51k4y0\/_\/images\/icons\/emoticons\/check.svg\" alt=\"(tick)\"> You may not need to do this if your account already has the default VPC.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws ec2 create-default-vpc<\/code><\/pre>\n\n\n\n<p><a href=\"https:\/\/docs.aws.amazon.com\/vpc\/latest\/userguide\/default-vpc.html\">https:\/\/docs.aws.amazon.com\/vpc\/latest\/userguide\/default-vpc.html<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Create KeyPair<\/h3>\n\n\n\n<p>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.<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/ssg-confluence.internal.sanger.ac.uk\/s\/-r41nvw\/8703\/51k4y0\/_\/images\/icons\/emoticons\/check.svg\" alt=\"(tick)\"> <em>You may not need to do this if you already have an EC2 KeyPair created and the Private Key downloaded to your machine.<\/em><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws ec2 create-key-pair --key-name MyKeyPair --query 'KeyMaterial' --output text > MyKeyPair.pem chmod 400 MyKeyPair.pem<\/code><\/pre>\n\n\n\n<p><a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-services-ec2-keypairs.html\">https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-services-ec2-keypairs.html<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Stack<\/h2>\n\n\n\n<p>Now we are ready to create the stack. But before we do a few comments about the command itself. You&#8217;ll notice that there are some arguments we are passing, we&#8217;ll take each of these in turn and explain them.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Template-Body &#8211; The template body is the path and filename of the template file you are wanting to apply.&nbsp;<\/li>\n\n\n\n<li>Stack-Name &#8211; 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.<\/li>\n\n\n\n<li>Parameters &#8211; 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.\n<ul class=\"wp-block-list\">\n<li>KeyName &#8211; In this case we&#8217;re passing the value &#8220;MyKeyPair&#8221; which was the EC2 Keypair we had above.<\/li>\n\n\n\n<li>InstanceType &#8211; In this case we&#8217;re passing the value &#8220;t2.micro&#8221; which is the size of EC2 instance we want.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>Now run the command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ aws cloudformation create-stack --template-body file:\/\/ec2withSecurityGroup.yml --stack-name single-instance --parameters ParameterKey=KeyName,ParameterValue=MyKeyPair ParameterKey=InstanceType,ParameterValue=t2.micro<\/code><\/pre>\n\n\n\n<p>You should get an output such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{ \"StackId\": \"arn:aws:cloudformation:eu-west-2:34912345614:stack\/single-instance\/7854f230-b854-11ed-8338-069354c2cde4\" }<\/code><\/pre>\n\n\n\n<p>Now let&#8217;s see what is going on:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws cloudformation describe-stacks<\/code><\/pre>\n\n\n\n<p>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).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws cloudformation describe-stacks<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Checking the EC2 Instance (using SSH Connection)<\/h2>\n\n\n\n<p>Now the stack has been deployed, let&#8217;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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws ec2 describe-instances<\/code><\/pre>\n\n\n\n<p>This gives a lot of output, so let&#8217;s refine it for the pertinent information:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws ec2 describe-instances --query \"Reservations&#91;*].Instances&#91;*].{PublicIP:PublicIpAddress,Name:Tags&#91;?Key=='Name']|&#91;0].Value,Status:State.Name,PublicDNSName:PublicDnsName}\" --filters Name=instance-state-name,Values=running --output table<\/code><\/pre>\n\n\n\n<p>That&#8217;s better:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-------------------------------------------------------------------------------------------\n|                                    DescribeInstances                                    |\n+------+-----------------------------------------------------+----------------+-----------+\n| Name |                    PublicDNSName                    |   PublicIP     |  Status   |\n+------+-----------------------------------------------------+----------------+-----------+\n|  None|  ec2-18-130-228-29.eu-west-2.compute.amazonaws.com  |  18.130.228.29 |  running  |\n+------+-----------------------------------------------------+----------------+-----------+<\/code><\/pre>\n\n\n\n<p>So now let&#8217;s make a connection to the Public DNS Name with the EC2 Keypair via SSH:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh -i ~\/MyKeyPair.pem ec2-user@ec2-18-130-228-29.eu-west-2.compute.amazonaws.com<\/code><\/pre>\n\n\n\n<p>And we are in:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>me@computer:~\/vscode-projects\/misc\/cloudformation$ ssh -i ~\/MyKeyPair.pem ec2-user@ec2-18-130-228-29.eu-west-2.compu\nte.amazonaws.com\nLast login: Wed Mar  1 16:47:23 2023 from 1.2.3.4\n\n       __|  __|_  )\n       _|  (     \/   Amazon Linux AMI\n      ___|\\___|___|\n\nhttps:&#47;&#47;aws.amazon.com\/amazon-linux-ami\/2018.03-release-notes\/\n33 package(s) needed for security, out of 50 available\nRun \"sudo yum update\" to apply all updates.\n&#91;ec2-user@ip-172-31-41-251 ~]$<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Clean-Up<\/h2>\n\n\n\n<p>To wrap up, we&#8217;ll delete the stack to ensure we&#8217;re not running up any bills, so we can run the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws cloudformation delete-stack --stack-name single-instance<\/code><\/pre>\n\n\n\n<p>We can run the below to check it really has gone!<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws ec2 describe-instances<\/code><\/pre>\n\n\n\n<p>With a bit of refinement and you can be sure its no longer present.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws cloudformation describe-stacks --stack-name single-instance<\/code><\/pre>\n\n\n\n<h1 class=\"wp-block-heading\">Additional Information<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/blog.boltops.com\/2017\/03\/06\/a-simple-introduction-to-aws-cloudformation-part-1-ec2-instance\/\">https:\/\/blog.boltops.com\/2017\/03\/06\/a-simple-introduction-to-aws-cloudformation-part-1-ec2-instance\/<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.middlewareinventory.com\/blog\/aws-cli-ec2\/\">https:\/\/www.middlewareinventory.com\/blog\/aws-cli-ec2\/<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/stackoverflow.com\/questions\/27166910\/launching-instances-in-default-vpc-produces-error\">https:\/\/stackoverflow.com\/questions\/27166910\/launching-instances-in-default-vpc-produces-error<\/a><\/li>\n<\/ul>\n\n\n\n<p><a href=\"https:\/\/unsplash.com\/@nimbus_vulpis\" data-type=\"URL\" data-id=\"https:\/\/unsplash.com\/@nimbus_vulpis\">Image Attribution<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>AWS Cloudformation is a tool and declaritive language you can use to declare what you want your &#8220;stack&#8221; (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 &#8230; <a title=\"Getting Started with AWS Cloudformation\" class=\"read-more\" href=\"https:\/\/geekmungus.co.uk\/?p=3679\" aria-label=\"Read more about Getting Started with AWS Cloudformation\">Read more<\/a><\/p>\n","protected":false},"author":4,"featured_media":3681,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,3],"tags":[],"class_list":["post-3679","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aws","category-cloud"],"_links":{"self":[{"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/posts\/3679","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3679"}],"version-history":[{"count":2,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/posts\/3679\/revisions"}],"predecessor-version":[{"id":3682,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/posts\/3679\/revisions\/3682"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=\/wp\/v2\/media\/3681"}],"wp:attachment":[{"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3679"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3679"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/geekmungus.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3679"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}