Tutorial : Continuous Delivery in the Cloud Part 3 of 6
In part 1 of this series, I introduced theContinuous Delivery (CD) pipeline for theManatee Tracking application. In part 2 I went over how we use this CD pipeline to deliver software from checkin to production. A list of topics for each of the articles is summarized below.
Part 1: Introduction
– introduction to continuous delivery in the cloud and the rest of the articles;
Part 2: CD Pipeline
– In-depth look at the CD Pipeline
Part 3: CloudFormation – What you’re reading now
Part 4: Dynamic Configuration
– “Property file less” infrastructure;
Part 5: Deployment Automation
– Scripted deployment orchestration;
Part 6: Infrastructure Automation
– Scripted environment provisioning (Infrastructure Automation)
In this part of the series, I am going to explain how we use CloudFormation to script our AWS infrastructure and provision our Jenkins environment.
What is CloudFormation? CloudFormation is an AWS offering for scripting AWS virtual resource allocation. A CloudFormation template is a JSON script which references various AWS resources that you want to use. When the template runs, it will allocate the AWS resources accordingly.
A CloudFormation template is split up into four sections:
- Parameters: Parameters are values that you define in the template. When creating the stack through the AWS console, you will be prompted to enter in values for the Parameters. If the value for the parameter generally stays the same, you can set a default value. Default values can be overridden when creating the stack. The parameter can be used throughout the template by using the “Ref” function.
- Mappings: Mappings are for specifying conditional parameter values in your template. For instance you might want to use a different AMI depending on the region your instance is running on. Mappings will enable you to switch AMIs depending on the region the instance is being created in.
- Resources: Resources are the most vital part of the CloudFormation template. Inside the resource section, you define and configure your AWS components.
- Outputs: After the stack resources are created successfully, you may want to have it return values such as the IP address or the domain of the created instance. You use Outputs for this. Outputs will return the values to the AWS console or command line depending on which medium you use for creating a stack.
CloudFormation parameters, and resources can be referenced throughout the template. You do this using intrinsic functions, Ref, Fn::Base64,Fn::FindInMap, Fn::GetAtt,Fn::GetAZs and Fn::Join.
These functions enable you to pass properties and resource outputs throughout your template – reducing the need for most hardcoded properties (something I will discuss in part 4 of this series, Dynamic Configuration).
How do you run a CloudFormation template? You can create a CloudFormation stack using either the AWS Console, CloudFormation CLI tools or the CloudFormation API.
Why do we use CloudFormation? We use CloudFormation in order to have a fully scripted, versioned infrastructure. From the application to the virtual resources, everything is created from a script and is checked into version control. This gives us complete control over our AWS infrastructure which can be recreated whenever necessary.
CloudFormation for Manatees In the Manatee Infrastructure, we use CloudFormation for setting up the Jenkins CD environment. I am going to go through each part of the jenkins template and explain its use and purpose. In template’s lifecycle, the user launches the stack using the jenkins.template and enters in the Parameters. The template then starts to work:
1. IAM User with AWS Access keys is created
2. SNS Topic is created
3. CloudWatch Alarm is created and SNS topic is used for sending alarm notifications
4. Security Group is created
5. Wait Condition created
6. Jenkins EC2 Instance is created with the Security Group from step #4. This security group is used for port configuration. It also uses AWSInstanceType2Arch and AWSRegionArch2AMI to decide what AMI and OS type to use
7. Jenkins EC2 Instance runs UserData
script and executes cfn_init
.
8. Wait Condition waits for Jenkins EC2 instance to finish UserData
script
9. Elastic IP is allocated and associated with Jenkins EC2 instance
10. Route53 domain name created and associated with Jenkins Elastic IP
11. If everything creates successfully, the stack signals complete and outputs are displayed
Now that we know at a high level what is being done, lets take a deeper look at what’s going on inside the jenkins.template
.
Parameters
- Email: Email address that SNS notifications will be sent. When we create or deploy to target environments, we use SNS to notify us of their status.
- ApplicationName: Name of A Record created by Route53. Inside the template, we dynamically create a domain with A record for easy access to the instance after creation. Example: jenkins.integratebutton.com, jenkins is the ApplicationName
- HostedZone: Name of Domain used Route53. Inside the template, we dynamically create a domain with A record for easy access to the instance after creation. Example: jenkins.integratebutton.com, integratebutton.com is the HostedZone.
- KeyName: EC2 SSH Keypair to create the Instance with. This is the key you use to ssh into the Jenkins instance after creation.
- InstanceType: Size of the EC2 instance. Example: t1.micro, c1.medium
- S3Bucket: We use a S3 bucket for containing the resources for the Jenkins template to use, this parameter specifies the name of the bucket to use for this.
Mappings
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
These Mappings are used to define what type of operating system architecture and AWS AMI (Amazon Machine Image) ID to use to use based upon the Instance size. The instance size is specified using the Parameter InstanceType
The conditional logic to interact with the Mappings is done inside the EC2 instance.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Resources
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
We create the AWS IAM user and then create the AWS Access and Secret access keys for the IAM user which are used throughout the rest of the template. Access and Secret access keys are authentication keys used to authenticate to the AWS account.
1 2 3 4 5 6 7 8 |
|
SNS is a highly available solution for sending notifications. In the Manatee infrastructure it is used for sending notifications to the development team.
1 2 3 4 5 6 7 8 9 10 11 |
|
Route53 is a highly available DNS service. We use Route53 to create domains dynamically using the given HostedZone and ApplicationName parameters. If the parameters are not overriden, the domain jenkins.integratebutton.com will be created. We then reference the Elastic IP and associate it with the created domain. This way the jenkins.integratebutton.com domain will route to the created instance
EC2 gives access to on-demand compute resources. In this template, we allocate a new EC2 instance and configure it with a Keypair, Security Group, and Image ID (AMI). Then for provisioning the EC2 instance we use the UserData
property. Inside UserData
we run a set of bash commands along with cfn_init
. The UserData
script is run during instance creation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
|
Calling cfn init from UserData
1 2 3 4 5 6 |
|
cfn_init
is used to retrieve and interpret the resource metadata, installing packages, creating files and starting services. In the Manatee template we use cfn_init
for easy access to other AWS resources, such as S3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
When possible, we try to use cfn_init
rather than UserData
bash commands because it stores a detailed log of Cfn events on the instance.
When creating a Jenkins instance, we only want certain ports to be open and only open to certain users. For this we use Security Groups. Security groups are firewall rules defined at the AWS level. You can use them to set which ports, or range of ports to be opened. In addition to defining which ports are to be open, you can define who they should be open to using CIDR.
1 2 3 4 5 6 7 8 9 10 11 |
|
In this security group we are opening ports 22, 80 and 8080. Since we are opening 8080, we are able to access Jenkins at the completion of the template. By default, ports on an instance are closed, meaning these are necessary to be specified in order to have access to Jenkins.
When an instance is created, it is given a public DNS name similar to: ec2-107-20-139-148.compute-1.amazonaws.com. By using Elastic IPs, you can associate your instance an IP rather than a DNS.
1 2 3 4 5 6 7 8 9 10 |
|
In the snippets above, we create a new Elastic IP and then associate it with the EC2 instance created above. We do this so we can reference the Elastic IP when creating the Route53 Domain name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
There are many reasons an instance can become unavailable. CloudWatch is used to monitor instance usage and performance. CloudWatch can be set to notify specified individuals if the instance experiences higher than normal CPU utilization, disk usage, network usage, etc. In the Manatee infrastructure we use CloudWatch to monitor disk utilization and notify team members if it reaches 90 percent.
If the Jenkins instance goes down, our CD pipeline becomes temporarily unavailable. This presents a problem as the development team is temporarily blocked from testing their code. CloudWatch helps notify us if this is an impending problem..
AWS::CloudFormation::WaitConditionHandle, AWS::CloudFormation::WaitCondition
Wait Conditions are used to wait for all of the resources in a template to be completed before signally template success.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
When creating the instance, if a wait condition is not used, CloudFormation won’t wait for the completion of the UserData
script. It will signal success if the EC2 instance is allocated successfully rather than waiting for the UserData
script to run and signal success.
Outputs
Outputs are used to return information from what was created during the CloudFormaiton stack creation to the user. In order to return values, you define the Output name and then the resource you want to reference:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
For instance with the InstanceIPAddress, we are refernceing the IPAddress resource which happens to be the Elastic IP. This will return the Elastic IP address to the CloudFormation console.
CloudFormation allows us to completely script and version our infrastructure. This enables our infrastructure to be recreated the same way every time by just running the CloudFormation template. Because of this, your environments can be run in a Continuous integration cycle, rebuilding with every change in the script.
In the next part of our series – which is all about Dynamic Configuration – we’ll go through building your infrastructure to only require a minimal amount of hard coded properties if any. In this next article, you’ll see how you can use CloudFormation to build “property file less” infrastructure.
Resources: