INTRODUCTION
Amazon Web Services (AWS) provides a robust set of tools to manage cloud infrastructure, and Terraform enhances this capability by enabling Infrastructure as Code (IaC). In this article, we will explore how to use Terraform to create a custom VPC (Virtual Private Cloud) in AWS, complete with an internet gateway, a route table, public and private subnets, security groups, network interfaces, and EC2 instances.
PREREQUISITES
Before embarking on the journey of creating a custom VPC infrastructure in AWS using Terraform, ensure you have the following in place:
1. AWS Account:
-
Create an AWS account if you don't have one already. Navigate to the AWS Console at
www.aws.amazon.com
to set up your account.
2. AWS CLI and Credentials:
Install the AWS Command Line Interface (CLI) on your local machine. You can download it here.
Configure your AWS credentials using the
aws configure
command.Ensure your credentials have the necessary permissions to create and manage resources.
3.Terraform Installation:
Download and install the latest version of Terraform on your local machine.
4. Text Editor or Integrated Development Environment (IDE):
Choose a text editor or IDE of your preference for editing Terraform configuration files. Popular choices include VSCode, Sublime Text, or Atom.
(For this demonstration, we will be using VSCode.)
5. Basic Understanding of AWS Services:
- Familiarize yourself with basic AWS concepts, including VPC, subnetting, internet gateways, security groups, and EC2 instances. This foundational knowledge will help you design an infrastructure that meets your specific requirements.
6. SSH Key Pair:
- Generate an SSH key pair if you plan to access your EC2 instances. This key pair will be used when creating EC2 instances in your Terraform configuration.
7. Knowledge of Terraform Basics:
-
Understand the fundamental concepts of Terraform, such as providers, resources, variables, and modules. Refer to the official Terraform documentation at
https://developer.hashicorp.com/terraform/docs
for guidance.
8. Customization:
- Modify the provided Terraform configuration according to your specific needs. Update variables like cidr_block, availability_zone, key_name, and ami with values suitable for your project.
9. Security Considerations:
- Be mindful of security best practices. Restrict inbound and outbound traffic in your security groups to only the necessary ports and IP ranges.
Let dive right in!
STEP 1: CREATE YOUR WORKING DIRECTORY AND CONFIGURATION FILES
Go to your Git Bash interface and type this command
cd Desktop
to navigate to the location you want to create the folder. In this very instance, we are creating it at the Desktop.Create a Directory (folder). Type the following command
mkdir <folder name>
to create the directory.
Navigate to the directory you just created using the
cd command
.While in the directory, use the command
code .
to open the VScode IDE.
-
Create'main.tf' and
provider.tf
files.
STEP 2: CONFIGURE AWS ACCESS AND PROVIDER
Run the following commands:
aws --version
to confirm your version of aws-cli andaws configure
to provide your AWS credentials in your VScode terminal.The credentials:
AWS Access Key ID [****************ZYAS]:
AWS Secret Access Key [****************6H9/]:
Default region name [us-east-1]:
Default output format [json]:
Go to the official Terraform site
https://registry.terraform.io/providers/hashicorp/aws/latest/docs
to get your Terraform codes to customize for your deployment.In the provider.tf file, paste the AWS Provider configuration code, then save.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "us-east-1"
}
- Then run the
terraform init
command to initialize Terraform. This command will create a folder containing terraform plugins and a.terraform.lock.hcl
file, indicating that your environment is ready to apply your deployments.
STEP 3 DEFINE THE VPC, INTERNET GATEWAY, AND ROUTE TABLE
- Go to the
main.tf
file and start declaring the resources you want provisioned using the HashiCorp Configuration Language (HCL).
# Create a VPC
resource "aws_vpc" "main-vpc" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
tags = {
Name = "main-vpc"
}
}
# Create an Internet Gateway
resource "aws_internet_gateway" "classgw" {
vpc_id = aws_vpc.main-vpc.id
tags = {
Name = "classgw"
}
}
# Create a Route Table
resource "aws_route_table" "classRT" {
vpc_id = aws_vpc.main-vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.classgw.id
}
}
-
Run the commands
terraform init
,terraform validate
,terraform plan
andterraform apply
in sequence to provision the resources. You can choose to do this in batches, as we are doing here, or run the commands after declaring all your resources.
-
Output of
terraform plan
command
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_internet_gateway.classgw will be created
+ resource "aws_internet_gateway" "classgw" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = "classgw"
}
+ tags_all = {
+ "Name" = "classgw"
}
+ vpc_id = (known after apply)
}
# aws_route_table.classRT will be created
+ resource "aws_route_table" "classRT" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ propagating_vgws = (known after apply)
+ route = [
+ {
+ carrier_gateway_id = ""
+ cidr_block = "0.0.0.0/0"
+ core_network_arn = ""
+ destination_prefix_list_id = ""
+ egress_only_gateway_id = ""
+ gateway_id = (known after apply)
+ ipv6_cidr_block = ""
+ local_gateway_id = ""
+ nat_gateway_id = ""
+ network_interface_id = ""
+ transit_gateway_id = ""
+ vpc_endpoint_id = ""
+ vpc_peering_connection_id = ""
},
]
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}
# aws_vpc.main-vpc will be created
+ resource "aws_vpc" "main-vpc" {
+ arn = (known after apply)
+ cidr_block = "10.0.0.0/16"
+ default_network_acl_id = (known after apply)
+ default_route_table_id = (known after apply)
+ default_security_group_id = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_dns_hostnames = (known after apply)
+ enable_dns_support = true
+ enable_network_address_usage_metrics = (known after apply)
+ id = (known after apply)
+ instance_tenancy = "default"
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ ipv6_cidr_block_network_border_group = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = "main-vpc"
}
+ tags_all = {
+ "Name" = "main-vpc"
}
}
Plan: 3 to add, 0 to change, 0 to destroy.
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
-
Output of
terraform plan
command
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_internet_gateway.classgw will be created
+ resource "aws_internet_gateway" "classgw" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = "classgw"
}
+ tags_all = {
+ "Name" = "classgw"
}
+ vpc_id = (known after apply)
}
# aws_route_table.classRT will be created
+ resource "aws_route_table" "classRT" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ propagating_vgws = (known after apply)
+ route = [
+ {
+ carrier_gateway_id = ""
+ cidr_block = "0.0.0.0/0"
+ core_network_arn = ""
+ destination_prefix_list_id = ""
+ egress_only_gateway_id = ""
+ gateway_id = (known after apply)
+ ipv6_cidr_block = ""
+ local_gateway_id = ""
+ nat_gateway_id = ""
+ network_interface_id = ""
+ transit_gateway_id = ""
+ vpc_endpoint_id = ""
+ vpc_peering_connection_id = ""
},
]
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}
# aws_vpc.main-vpc will be created
+ resource "aws_vpc" "main-vpc" {
+ arn = (known after apply)
+ cidr_block = "10.0.0.0/16"
+ default_network_acl_id = (known after apply)
+ default_route_table_id = (known after apply)
+ default_security_group_id = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_dns_hostnames = (known after apply)
+ enable_dns_support = true
+ enable_network_address_usage_metrics = (known after apply)
+ id = (known after apply)
+ instance_tenancy = "default"
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ ipv6_cidr_block_network_border_group = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = "main-vpc"
}
+ tags_all = {
+ "Name" = "main-vpc"
}
}
Plan: 3 to add, 0 to change, 0 to destroy.
aws_vpc.main-vpc: Creating...
aws_vpc.main-vpc: Creation complete after 5s [id=vpc-07733c710c0a92868]
aws_internet_gateway.classgw: Creating...
aws_internet_gateway.classgw: Creation complete after 2s [id=igw-08480f88ccaf9b886]
aws_route_table.classRT: Creating...
aws_route_table.classRT: Creation complete after 2s [id=rtb-0bed4113ba3664e06]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
We will repeat the above steps until we finish provisioning our resources.
The provisioned resources in AWS management console
STEP 4: CREATE PUBLIC AND PRIVATE SUBNETS
- On the
main.tf
file, go ahead and declare the commands for the subnets to provision both public and private subnets
# Create public subnets
resource "aws_subnet" "main_pubs1" {
vpc_id = aws_vpc.main-vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
tags = {
Name = "main_pubs1"
}
}
resource "aws_subnet" "main_pubs2" {
vpc_id = aws_vpc.main-vpc.id
cidr_block = "10.0.2.0/24"
availability_zone = "us-east-1b"
tags = {
Name = "main_pubs2"
}
}
# Create private subnets
resource "aws_subnet" "main_privs1" {
vpc_id = aws_vpc.main-vpc.id
cidr_block = "10.0.3.0/24"
availability_zone = "us-east-1c"
tags = {
Name = "main_privs1"
}
}
resource "aws_subnet" "main_privs2" {
vpc_id = aws_vpc.main-vpc.id
cidr_block = "10.0.4.0/24"
availability_zone = "us-east-1d"
tags = {
Name = "main_privs2"
}
}
In this demonstration, we have two public and two private subnets.
STEP 5: ASSOCIATE SUBNETS WITH THE ROUTE TABLE
# Associate public subnets with the route table
resource "aws_route_table_association" "a" {
subnet_id = aws_subnet.main_pubs1.id
route_table_id = aws_route_table.classRT.id
}
resource "aws_route_table_association" "b" {
subnet_id = aws_subnet.main_pubs2.id
route_table_id = aws_route_table.classRT.id
}
STEP 6: CREATE A SECURITY GROUP
# Create a security group
resource "aws_security_group" "class_SG" {
name = "class_SG"
description = "Allow SSH, HTTP, HTTPS inbound traffic"
vpc_id = aws_vpc.main-vpc.id
ingress {
description = "SSH from VPC"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS from VPC"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTP from VPC"
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 = "allow_traffic"
}
}
STEP 7: CREATE NETWORK INTERFACES
- Create network interfaces for the two instances.
# Create network interfaces
resource "aws_network_interface" "class_NI" {
subnet_id = aws_subnet.main_pubs1.id
private_ips = ["10.0.1.50"]
security_groups = [aws_security_group.class_SG.id]
}
resource "aws_network_interface" "main-NIC" {
subnet_id = aws_subnet.main_pubs2.id
private_ips = ["10.0.2.20"]
security_groups = [aws_security_group.class_SG.id]
}
STEP 8: ALLOCATE ELASTIC IPS AND ASSOCIATE WITH NETWORK INTERFACES
# Create Elastic IPs
resource "aws_eip" "class_EIP" {
vpc = true
network_interface = aws_network_interface.class_NI.id
associate_with_private_ip = "10.0.1.50"
}
resource "aws_eip" "class_EIP1" {
vpc = true
network_interface = aws_network_interface.main-NIC.id
associate_with_private_ip = "10.0.2.20"
}
STEP 9: LAUNCH TWO EC2 INSTANCES
- One in a Public Subnet and the other in a Private Subnet.
# Create EC2 instances
resource "aws_instance" "class_instance" {
ami = "ami-0005e0cfe09cc9050"
instance_type = "t2.micro"
key_name = "Don-KP"
network_interface {
network_interface_id = aws_network_interface.class_NI.id
device_index = 0
}
tags = {
Name = "hello1"
}
}
resource "aws_instance" "class_instance222" {
ami = "ami-0005e0cfe09cc9050"
instance_type = "t2.micro"
key_name = "Don-KP"
network_interface {
network_interface_id = aws_network_interface.main-NIC.id
device_index = 0
}
tags = {
Name = "hello2"
}
}
Resources Created
STEP 10: DESTROY
- Use the command
terraform destroy
to clean-up the resources provisioned to have a clean slate and avoid paying for resources not used for production.
CONCLUSION
In this tutorial, we've walked through the process of creating a custom VPC infrastructure in AWS using Terraform. This modular approach allows for easy maintenance, scalability, and reproducibility of your cloud environment. By leveraging Terraform, you gain the benefits of Infrastructure as Code, making it simpler to manage and collaborate on complex AWS architectures.
Thank you for your time!
Top comments (0)