Getting started with EC2 and Cloudformation
EC2 stands for “Elastic Compute Cloud” and is AWS’s scalable virtual servers offering. The great benefit of using the cloud is you just pay for what you use and can scale when needed. Here is the documentation.
In this series I want to focus on using Cloudformation and the AWS CLI instead of the AWS console. Using the console is more prone to error, and gets very tedious when you’re constantly creating and re-creating similar infrastructure.
What we will be building
In this article I’ll be going through how to use Cloudformation to
Create an EC2 instance
Add a security group to allow ssh and http traffic
Install a webserver using a userdata script
Prerequisites
Before getting started, you will need the following prerequisites:
Creating an EC2 instance
When getting started with EC2 and Cloudformation the best thing is to check out the documentation. It can be difficult learning all the various options to use with a new Cloudformation resources. But I’ll try to cover the most important ones to get started in this article.
# cfn.yml
Resources:
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: "ami-04a2b113b98bfa0e8" # eu-central-1 region
InstanceType: t2.micro
This is the simplest Cloudformation template for an EC2 instance. We choose an instance type available in the AWS free tier and an image for the eu-central-1 availability zone.
An easy way to find the latest amazon 2 linux AMI for your region is using the following command.
aws ssm get-parameter \
--name /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Now, save the file as cfn.yml and create the Cloudformation stack using the AWS CLI.
aws cloudformation create-stack \
--stack-name ec2-getting-started \
--template-body file://cfn.yml
You can view the status of the stack creation by running
aws cloudformation describe-stacks --stack-name ec2-getting-started
Or simply wait until the stack is finished using
aws cloudformation wait stack-create-complete \
--stack-name ec2-getting-started
When it’s done you can view the created instance using
aws ec2 describe-instances
Adding SSH connectivity
Ok great, so we have an EC2 instance running, but we can’t do anything with it. Let’s add the ability to SSH into it by opening up port 22 and outputting the public ip. For that we need a keypair as well a security group.
Security groups control what network traffic is allowed to and from our instances. It’s similar to opening ports with for instance iptables.
Resources:
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: "ami-04a2b113b98bfa0e8"
InstanceType: t2.micro
KeyName: myKeyPair
SecurityGroupIds:
- !Ref MySecurityGroup
MySecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: MySecurityGroup
GroupDescription: "Allow SSH traffic"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Outputs:
PublicIp:
Value: !GetAtt [Ec2Instance, PublicIp]
Description: 'Servers public IP'
Here we’ve created a security group allowing us to use the port 22 to SSH onto the instance, and reference it under the EC2Instance section, as well as the keypair. But we still need to create the keypair.
aws ec2 create-key-pair --key-name myKeyPair
Copy the private key in the output and create a local file myKeyPair.pem with the contents. Remember that SSH keys need appropriate file permissions to work. So also run
chmod 0400 myKeyPair.pem
In case you need a longer introduction to SSH keys I recommend this link for a quick read.
Now, simply update the stack with
aws cloudformation update-stack \
--stack-name ec2-getting-started \
--template-body file://cfn.yml
And copy the public IP from the outputs once it is done.
aws cloudformation describe-stacks \
--stack-name ec2-getting-started \
--query "Stacks[0].Outputs[0].OutputValue"
If everything worked out you should now be able to SSH onto the instance using
ssh -i myKeyPair.pem ec2-user@<public ip>
If this isn’t working, make sure you have set up the security group correctly.
Installing a webserver with userdata
Now, let’s also set up a webserver so we can curl the web server over HTTP. To accomplish this we need to add a userdata script. Userdata scripts run the first time a machine boots and are used to bootstrap a server.
Add the userdata script to the EC2Instance configuration
UserData:
Fn::Base64: !Sub |
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello World from $(hostname -f)</h1>" > /var/www/html/index.html
Also, we need to add port 80 to our security group.
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Now, simply update the stack using
aws cloudformation update-stack \
--stack-name ec2-getting-started \
--template-body file://cfn.yml
and again grab the public IP using
aws cloudformation describe-stacks \
--stack-name ec2-getting-started \
--query "Stacks[0].Outputs[0].OutputValue"
Note that the IP should have changed, since the userdata script forces the instance to be replaced (since it only runs on boot). Try out the webserver with
curl <public ip>
Debugging
When I was going through this tutorial I had an issue with the userdata script that another process was using the yum package manager. Because the process had a lock on yum our userdata script couldn’t run correctly.
I got around this issue adding this line to the beginning of my script.
kill -9 $(cat /var/run/yum.pid)
If you’re having issues with the userdata script, the best way to debug is using the log files. AWS saves the log files of the userdata script to
/var/log/cloud-init.log and
/var/log/cloud-init-output.log