One of the first components I created on AWS cloud was an EC2 instance by watching hands-on tutorials. Little did I know about the infrastructure bits that went behind that. In this note, I list down the AWS infrastructure and their specific configurations that I created to host an EC2 instance using Terraform. To make this use case a bit interesting, I added the capability to access the internet from the EC2 instance. While I was trying to learn the underlying concepts, I came across numerous videos that demoed how to host an EC2 in AWS using the console. However, I was not sure how many of these approaches were secure or, said differently, had security as a top consideration.
An AWS EC2 instance is a virtual machine that you host in a virtual private cloud. I required a few infrastructure components before I could create an EC2 instance in my AWS account. These were:
1. A VPC.
2. A subnet in the VPC.
3. A route table in the VPC associated with the subnet.
4. An internet gateway in the VPC.
5. A route in the route table to allow network traffic from the EC2 instance to the internet.
6. A security group.
7. A network interface associated with the subnet and the security group.
8. And an elastic IP that is associated with the network interface.
I stored the Terraform code associated with this project at ec2-userdata-terraform.
As I listed above, I created eight types of infrastructure components before launching an EC2 instance in my AWS account. I’ll briefly mention what these components are and how and why they are necessary.
Note: The best resource to study for AWS networking is the AWS-Docs. That is my go-to resource for any clarifications. It could be exhaustive to go through it, but it is worth it.
VPC: This is the platform on which I hosted the entire infrastructure. This AWS resource requires the CIDR block (list of permissible IP addresses) to allocate to network interface resources. There is generally a tendency to allocate a large block(/16 or even /20) which is fine, but I prefer keeping it realistic, and hence I have it at “/25” which is 128 IP addresses, more than my current requirement.
Subnet: This is a section of the VPC CIDR block. A VPC CIDR block can be divided into multiple subnets associated with CIDR blocks (IP addresses) that do not overlap. I created two subnets, called “public” and “private,” with 64 IP addresses and allocated them separate CIDR blocks. The idea behind the segregation of the IP addresses is to enable and disable communication. E.g., IP addresses in the “public subnet” could be accessible from the internet while the IP addresses in the “private subnet” would not be accessible from the internet but from within the VPC or a VPN.
Route table: This table with routes determines where network traffic flows from and within the VPC.
Route table association: Per AWS-Docs (and that’s because I couldn’t have done a better job at explaining), “Your VPC has an implicit router, and you use route tables to control where network traffic is directed. Each subnet in your VPC must be associated with a route table, which controls the routing for the subnet (subnet route table). You can explicitly associate a subnet with a particular route table. Otherwise, the subnet is implicitly associated with the main route table. A subnet can only be associated with one route table at a time, but you can associate multiple subnets with the same subnet route table.”
Internet gateway: This is a VPC component that allows communication between the VPC and the internet.
The route in the route table: This is an entry (row) in a route table with a destination IP address/CIDR and what target to use. In my terraform configuration, I added the below aws_route resource type to enable access to the internet (0.0.0.0/0) using the internet gateway that I created earlier.
Security Group: This is a virtual firewall for the EC2 to control inbound (ingress) and outbound (egress) traffic. I enabled RDP access (port# 3389) into the EC2 instance from my IP address in the below code. I also have the “egress” block, which is currently open to all, which implies that all kinds of outbound IPv4 traffic from the instance can flow.
Network interface: I am quoting from AWS-Docs -An elastic network interface is a logical networking component in a VPC that represents a virtual network card. As you can see in the below code, the network interface is attached to a subnet and a security group and has a list of IP addresses associated with it (I associated only one IP address). This relationship also implies that when we associate a security group to an EC2 instance, we’re associating it to the network interface associated with the EC2 instance and not the EC2 instance directly. The value of
var.private_ip_address is “10.20.20.120”
Elastic IP: This is the public IP address that is associated with the NIC. Using the public IP, I could remote into the EC2 instance.
Note: As I was tinkering with the
aws_network_interface resource type, I changed the value of the variable
private_ip_address and ran a
terraform apply, resulting in an error message.
On further investigation, I realized that when I ran a
terraform apply the first time, “10.20.20.120” was allocated to be the “primary IP address” of the network interface that it could not disassociate without a destroy and an add. However, I failed to understand why terraform could not destroy and create a new network interface? If you know the answer, please do mention that in the comments section.
Note2: Continuing with my tinker, I added another IP address to the variable and associated two IP addresses to the network interface; one primary and the other secondary. I then changed the secondary IP address, and
terraform apply ran just fine. No errors. On the AWS console, I found the secondary private IP address had been updated.
And that brings us to the end of all the underlying infrastructure required to host the EC2. As far as provisioning the EC2 goes, two configuration blocks are necessary: (a) the data block to query an appropriate AMI and (b) a resource block to create the EC2. I added the below code to create two EC2 instances, one using the network interface and the other using a
private_ip. Notice, when I provided the network interface to the EC2 instance, I did not require the
vpc_security_group_ids and the
subnet_id. Also, although I did not associate a public IP to the EC2 instance, it still had a public IP due to the
elastic_ip association with the
network_interface. Moreover, for the “app-server-2” EC2 instance, even though I did not provide a network interface explicitly, it still created one in my AWS account. Same for the
public_ip address associated with “app-server2” since I did not provide an explicit
elastic IP but set the flag value of
associate_public_ip_address value to
Note: I faced an interesting challenge while trying to uniquely identify an AMI name and have a separate note on that at: create-ec2-instance-from-an-aws-ami-using-terraform.
terraform apply, I could log in to these EC2 instances and access the internet from there, which was my primary use case.
If you are interested, you should be able to take this code and run it after making a few modifications. I have the details in the repository: ec2-userdata-terraform.
Thank you for reading my note. If you believe I have misstated some things, please let me know via the comments. Also, please reach out if you have questions or suggestions.