Creating a VPC with an EC2 Instance: Terraform for Beginners
I recently purchased W J Pearce’s eBook ‘Tech One Twenty’. Within it are what he refers to as ‘micro projects’.
The first on the list was a Terraform project. As someone just getting into it, I wasn’t just interested in making it work. I wanted to understand why it works.
Even though he gives you the code in the book, you still have to challenge yourself on understanding what it’s actually doing.
What we will do:
- Create a VPC with a CIDR block of 10.0.0.0/16.
- Add a subnet to the VPC in the
ap-southeast-2availability zone with a CIDR block of10.0.1.0/24. - Set up a security group within the VPC that allows SSH and HTTP from any IP address, and allows all egress traffic.
- Launch an EC2 within this subnet and security group using a specified AMI.
These are the relevant Terraform files we’ll create:
main.tfoutputs.tf— Terraform output values let you export structured data about your resources. You can use this data to configure other parts of your infrastructure with automation tools, or as a data source for another Terraform workspace.providers.tfvariables.tfversions.tf
CREATE THE TERRAFORM FILES
Find a directory you want to hold the Terraform files in.
Create the files:
touch providers.tf variables.tf main.tf outputs.tf versions.tfEdit the files:
providers.tf:
provider "aws" {
region = var.aws_region
}variables.tf:
variable "aws_region" {
default = "ap-southeast-2"
description = "The AWS region to create resources in."
}
variable "instance_type" {
default = "t2.micro"
description = "The instance type of the EC2 instance."
}
variable "ami_id" {
description = "The AMI ID to use for the EC2 instance."
}main.tf:
resource "aws_vpc" "techone_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "techone_vpc"
}
}
resource "aws_subnet" "example_subnet" {
vpc_id = aws_vpc.techone_vpc.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
availability_zone = "ap-southeast-2a"
tags = {
Name = "example-subnet"
}
}
resource "aws_security_group" "techonetwenty_sg" {
name = "techonetwenty-security-group"
description = "Allow SSH and HTTP"
vpc_id = aws_vpc.techone_vpc.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "techonetwenty_sg"
}
}
resource "aws_instance" "example_instance" {
ami = var.ami_id
instance_type = var.instance_type
subnet_id = aws_subnet.example_subnet.id
vpc_security_group_ids = [aws_security_group.techonetwenty_sg.id]
tags = {
Name = "example-instance"
}
}- Documentation for aws_vpc.
- Documentation for aws_subnet.
- Documentation for aws_security_group.
- Documentation for aws_instance.
outputs.tf:
output "vpc_id" {
description = "The ID of the VPC."
value = aws_vpc.techone_vpc.id
}
output "instance_public_ip" {
description = "The public IP of the EC2 instance."
value = aws_instance.example_instance.public_ip
}versions.tf:
terraform {
required_version = ">= 0.12"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}OPTIONAL — CHANGE THE SECURITY GROUPS FOR TERRAFORM V5.0
Read over main.tf if you want to learn more. Basically ingress and egress rules have their own resource called aws_vpc_security_group_[ ingress | egress ]_rule. This is optional however we’ll stick with Terraform V3.0 in this demo.
versions.tf:
terraform {
required_version = ">= 0.12"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}main.tf:
resource "aws_vpc" "techone_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "techone_vpc"
}
}
resource "aws_subnet" "example_subnet" {
vpc_id = aws_vpc.techone_vpc.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
availability_zone = "ap-southeast-2a"
tags = {
Name = "example-subnet"
}
}
resource "aws_security_group" "techonetwenty_sg" {
name = "techonetwenty-security-group"
description = "Allow SSH and HTTP"
vpc_id = aws_vpc.techone_vpc.id
# ingress {
# from_port = 22
# to_port = 22
# protocol = "tcp"
# cidr_blocks = ["0.0.0.0/0"]
# }
# ingress {
# from_port = 80
# to_port = 80
# protocol = "tcp"
# cidr_blocks = ["0.0.0.0/0"]
# }
# egress {
# from_port = 0
# to_port = 0
# protocol = "-1"
# cidr_blocks = ["0.0.0.0/0"]
# }
# tags = {
# Name = "techonetwenty_sg"
# }
}
## Added as a replacement of the ingress rules above ##
resource "aws_vpc_security_group_ingress_rule" "ssh" {
security_group_id = aws_security_group.techonetwenty_sg.id
ip_protocol = "tcp"
cidr_ipv4 = "0.0.0.0/0"
from_port = 22
to_port = 22
}
## Added as a replacement of the ingress rules above ##
resource "aws_vpc_security_group_ingress_rule" "http" {
security_group_id = aws_security_group.techonetwenty_sg.id
ip_protocol = "tcp"
cidr_ipv4 = "0.0.0.0/0"
from_port = 80
to_port = 80
}
## Added as a replacement of the ingress rules above ##
resource "aws_vpc_security_group_ingress_rule" "allow_ping" {
security_group_id = aws_security_group.techonetwenty_sg.id
ip_protocol = "icmp"
cidr_ipv4 = "0.0.0.0/0"
from_port = -1
to_port = -1
}
## Added as a replacement of the egress rule above ##
resource "aws_vpc_security_group_egress_rule" "allow_all_outbound_traffic" {
security_group_id = aws_security_group.techonetwenty_sg.id
ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0"
from_port = 0
to_port = 0
}
resource "aws_instance" "example_instance" {
ami = var.ami_id
instance_type = var.instance_type
subnet_id = aws_subnet.example_subnet.id
vpc_security_group_ids = [aws_security_group.techonetwenty_sg.id]
tags = {
Name = "example-instance"
}
}For help with the ICMP rule I used this post.
APPLYING THE CONFIGURATION TO AWS
Authenticate to AWS with either of these two commands:
Authenticate with Access Keys:
aws configureOr authenticate with a web page:
aws loginIf the aws command doesn’t work for you, it means you need to install AWS CLI. Choose from the list here to download it.
Get Lumenyx’s stories in your inbox
Join Medium for free to get updates from this writer.
Now we can initialise Terraform:
terraform initNow we can plan our configuration. What this simply shows us is what Terraform will create (or change) without actually committing to that change.
terraform planIt then prompted me for an AMI (since we didn’t define it in the variables.tf file).
The AMI is used was ami-0a11f7293cd9a562e.
NOTE: This could different depending on your region.
I got my AMI ID from EC2 > Instances > Launch an Instance > Choose my AMI and copy the ID:
Now we can apply the changes to AWS:
terraform applyCONFIRMING IT’S ACTUALLY ON AWS
Go to the EC2 management console and check out your running EC2 instances.
You’ll notice a new instance:
What about the VPC?
Go to the VPC management console and take a look:
Security groups?
Yep right here:
Can we use EC2 Instance Connect to SSH into the EC2 instance?
No. It needs an Internet Gateway:
Let’s quickly add one!
ADD AN INTERNET GATEWAY AND A ROUTING TABLE
Append this to the main.tf file:
resource "aws_internet_gateway" "tech120-igw" {
vpc_id = aws_vpc.techone_vpc.id
tags = {
Name = "techone_vpc"
}
}
resource "aws_route_table" "tech120-rt" {
vpc_id = aws_vpc.techone_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.tech120-igw.id
}
tags = {
Name = "techone_vpc"
}
}
resource "aws_route_table_association" "rt-association" {
subnet_id = aws_subnet.example_subnet.id
route_table_id = aws_route_table.tech120-rt.id
}- Documentation for aws_internet_gateway.
- Documentation for aws_route_table.
- Documentation for aws_route_table_association.
And we’re in!
DELETE YOUR RESOURCES — SO YOU DON’T GET CHARGED
Now we want to destroy (delete) all the resources we just made.
You can do that by following this command:
terraform destroyLet’s confirm that everything is deleted within the AWS management console.
NOTE: There are default VPCs along with default subnets/route tables/security groups etc that are there by default. Do not delete these. I distinguish them by noting if they’re in the 172.16.X.X range (that means they’re default), and then checking the resource map to see what’s connected. If they’re connected then they’re likely to be there by default.
Check these to be sure everything is deleted:
- VPC
- Subnet
- Route tables
- Security groups
- Internet Gateway










