Provisioning an EC2 Instance with CloudFormation (part 1)
As a full-stack developer, you dont have time to deal with infrastructure, environment inconsistencies, manual testing, and manual deployment of your teams web app. Its time-consuming, frustrating, and requires yet another skill-set, especially since youre part of a small development team at a budding startup.
You know that as your app takes off, your infrastructure and processes need to scale with it. Inconsistencies between environments are creating problems down the pipeline, costing your small team time and money. Today, your build and deploy process is manual and done infrequently. Mistakes are made, bugs are introduced to production code when they are more difficult and expensive to fix and your customers ultimately suffer.
Its time to fix some of these gaps so you can deliver smaller changes more quickly, offer your customers a higher level of quality, and scale when you need to. Youve decided to use infrastructure as code (IaC) to automate your infrastructure.
Today, you have an Amazon EC2 instance hosting your web app and a Postgresql database running on the same instance. Sometimes the web app traffic impacts the database and vice versa. You want to manage the EC2 instance with IaC and start using a managed database instead of running it on the same instance. Since youre already using AWS, youve decided on Amazon RDS for a managed Postgresql database. Because RDS is a managed service, you wont have to do operating system patches or database patches its all managed for you. And finally, you want to introduce a continuous integration/continuous deployment (CI/CD) pipeline to automate the build, test, and deploy phases of your release process.
In the next three posts, youll learn how to:
- automate the provisioning of your Amazon EC2 instance using AWS CloudFormation (this post, part 1),
- add an Amazon RDS Postgresql database to your stack with CloudFormation (part 2), and
- create an AWS CodePipeline with CloudFormation (part 3).
Prerequisites
To work through the examples in this post, youll need:
- an AWS account (you can create your account here if you dont already have one),
- the AWS CLI installed (you can find instructions for installing the AWS CLI here), and
- a key-pair to use for SSH (you can create a key-pair following these instructions).
Unfamiliar with CloudFormation or feeling a little rusty? Check out my Intro to CloudFormation post before getting started.
Creating the CloudFormation Template
Im of the mindset of Make it work. Make it right. Make it fast. so well iterate to get to our final template and and make it better at the end. At the end of this post, well delete the stack weve created so that you dont incur any charges and then you can (quickly) recreate the stack when we move on to the next post.
Make it work. Make it right. Make it fast. Kent Beck
Just want the code? Grab it here.
Lets get started!
1. Create Basic Amazon EC2 Instance
First, well create a basic EC2 instance with CloudFormation.
# 01_ec2.yamlAWSTemplateFormatVersion: 2010-09-09Description: Part 1 - Build a webapp stack with CloudFormationResources: WebAppInstance: Type: AWS::EC2::Instance Properties: ImageId: ami-0d5eff06f840b45e9 # ImageID valid only in us-east-1 region InstanceType: t2.micro
In the template above, we have one resource with a type of AWS::EC2::Instance. Weve hardcoded both the ImageId
(AMI) and InstanceType
. Note this ImageId
will only work in the us-east-1 region.
To create the stack using this template, run the create-stack
command-line:
$ aws cloudformation create-stack --stack-name ec2-example --template-body file://01_ec2.yaml
You now have an EC2 instance in the us-east-1 region! But we have no way to access this instance. We cannot SSH into it yet because we didnt assign it a security group allowing SSH traffic or specify a key-pair name. Theres nothing running on HTTP or HTTPS ports yet, but even if there were, we wouldnt be able to access that either. Lets fix that now.
2. Enable SSH and HTTP/HTTPS Traffic
Now, well update the CloudFormation template to add a security group resource that allows traffic in on port 22 for SSH and ports 80 and 443 for HTTP and HTTPS traffic. Here, weve allowed all IP addresses to access these ports, but you may want to lock this down further (especially the SSH rule) to IP addresses you trust.
# 02_ec2.yamlAWSTemplateFormatVersion: 2010-09-09Description: Part 1 - Build a webapp stack with CloudFormationResources: WebAppInstance: Type: AWS::EC2::Instance Properties: ImageId: ami-0d5eff06f840b45e9 # ImageID valid only in us-east-1 region InstanceType: t2.micro KeyName: jenna # <-- Change to use your key-pair name SecurityGroupIds: - !Ref WebAppSecurityGroup WebAppSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Join [ '-', [ webapp-security-group, dev ] ] GroupDescription: 'Allow HTTP/HTTPS and SSH inbound and outbound traffic' SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0
Again, weve hardcoded some values in our template, but well fix that up soon. Before updating the stack with this template, youll need to make one small change to use your key-pair name.
You can update your stack using the update-stack
command:
$ aws cloudformation update-stack --stack-name ec2-example --template-body file://02_ec2.yaml
Now your EC2 instance should be accessible with SSH using your key-pair. To test this out, first navigate to your new stack in the AWS CloudFormation Console to find the instance you created.
Then go to the instance and copy the public DNS for your instance.
Then, SSH into the instance like this:
$ ssh -i "YOUR_KEY_PAIR_NAME.pem" ec2-user@PUBLIC_DNS
Your command will look something like this:
$ ssh -i "jenna.pem" [email protected]
Hint: You can also grab the command directly by viewing the "Connect" details at the top of the instance and copying the example command at the bottom of the "SSH client" tab.
There is nothing being served on port 80 or 443 yet, so you wont be able to test HTTP/HTTPS access yet.
3. Assign an IP Address and Output the Website URL
We also need to give our EC2 instance an elastic IP address (EIP). An elastic ip address is a static IP address that wont change every time we re-provision the instance. Well also output the website URL.
# 03_ec2.yamlAWSTemplateFormatVersion: 2010-09-09Description: Part 1 - Build a webapp stack with CloudFormationResources: WebAppInstance: Type: AWS::EC2::Instance Properties: ImageId: ami-0d5eff06f840b45e9 # ImageID valid only in us-east-1 region InstanceType: t2.micro KeyName: jenna SecurityGroupIds: - !Ref WebAppSecurityGroup WebAppSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Join [ '-', [ webapp-security-group, dev ] ] GroupDescription: 'Allow HTTP/HTTPS and SSH inbound and outbound traffic' SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0 WebAppEIP: Type: AWS::EC2::EIP Properties: Domain: vpc InstanceId: !Ref WebAppInstance Tags: - Key: Name Value: !Join [ '-', [ webapp-eip, dev ] ]Outputs: WebsiteURL: Value: !Sub http://${WebAppEIP} Description: WebApp URL
In this template update, weve created a resource AWS::EC2::EIP for an elastic IP address and assigned it to our EC2 instance. We also added the Outputs
section and out the website URL, which at this point is our elastic IP address.
You can run the same update-stack
command from above (make sure to reference the right file name). When it completes (the EIP takes a few minutes to provision), your EC2 instance will have a static IP address and youll be able to see both the IP address and URL in the Outputs section of the CloudFormation stack in the Console.
4. Make the Template Dynamic
Now that weve made it work, the last task we have to do is to make it better. Remember that our template has hardcoded values for a number of configurations that should really be dynamic. We want these values to be dynamic for a few reasons. Maybe we want our development team to create a stack in their own AWS account for development purposes. And because its development, maybe we only want t2.micro instances, whereas in production we need t2.medium instances. We also might want to create this stack in other regions, so well need to use the region-specific ImageId
(AMI).
We can make our template more dynamic by using parameters and mappings. Lets add them now!
# 04_ec2.yamlAWSTemplateFormatVersion: 2010-09-09Description: Part 1 - Build a webapp stack with CloudFormationParameters: AvailabilityZone: Type: AWS::EC2::AvailabilityZone::Name EnvironmentType: Description: 'Specify the Environment type of the stack.' Type: String Default: dev AllowedValues: - dev - test - prod AmiID: Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> Description: 'The ID of the AMI.' Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 KeyPairName: Type: String Description: The name of an existing Amazon EC2 key pair in this region to use to SSH into the Amazon EC2 instances.Mappings: EnvironmentToInstanceType: dev: InstanceType: t2.nano test: InstanceType: t2.micro prod: InstanceType: t2.smallResources: WebAppInstance: Type: AWS::EC2::Instance Properties: AvailabilityZone: !Ref AvailabilityZone ImageId: !Ref AmiID InstanceType: !FindInMap [EnvironmentToInstanceType, !Ref EnvironmentType, InstanceType] KeyName: !Ref KeyPairName SecurityGroupIds: - !Ref WebAppSecurityGroup WebAppSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Join [ '-', [ webapp-security-group, !Ref EnvironmentType ] ] GroupDescription: 'Allow HTTP/HTTPS and SSH inbound and outbound traffic' SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0 WebAppEIP: Type: AWS::EC2::EIP Properties: Domain: vpc InstanceId: !Ref WebAppInstance Tags: - Key: Name Value: !Join [ '-', [ webapp-eip, !Ref EnvironmentType ] ]Outputs: WebsiteURL: Value: !Sub http://${WebAppEIP} Description: WebApp URL
Weve added parameters for AvailabilityZone
, EnvironmentType
, AmiID
, and KeyPairName
. AvailabilityZone
will pull from AWS::EC2::AvailabilityZone::Name. EnvironmentType
will be one of dev, test, or prod and default to dev. The ImageId
will be an AWS::SSM::Parameter::ValueAWS::EC2::Image::Id type. By using the public parameter /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
, it will use the region-specific AMI, the value of which is stored in the AWS Systems Manager Parameter Store. In this case, we'll rely on the default value. And the KeyPairName
is the name of an existing key-pair.
The other section weve added is the Mappings
section. Well use the EnvironmentToInstanceType
mapping to lookup the instance type for the selected environment.
You can update the stack with this command, passing in the parameter values:
$ aws cloudformation update-stack --stack-name ec2-example --template-body file://04_ec2.yaml \--parameters ParameterKey=AvailabilityZone,ParameterValue=us-east-1a \ParameterKey=EnvironmentType,ParameterValue=dev \ParameterKey=KeyPairName,ParameterValue=jenna
Now it will be easier to reuse this template for other environments and in other regions!
Wrapping Up
Delete Your Stack
Dont forget to delete your stack so you dont accrue charges. You can do that with the delete-stack
command:
$ aws cloudformation delete-stack --stack-name ec2-example
What You Learned
In this post, we created a CloudFormation template that provisions an EC2 instance in any region for your different environments, enables SSH, HTTP, and HTTPS inbound traffic, and we assigned a static IP address to the instance. We created and updated the stack from the command line using the AWS CLI. You can grab the final CloudFormation template created here.
In the next post we'll add an Amazon RDS Postgresql database to our template and configure it for traffic from the EC2 instance.
Like what you read? Follow me here on Dev.to or on Twitter to stay updated!
Original Link: https://dev.to/aws/provisioning-an-ec2-instance-with-cloudformation-part-1-443f
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To