Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Creating custom VPC on AWS using OpenTofu
Vinod Kumar
Vinod Kumar

Posted on

     

Creating custom VPC on AWS using OpenTofu

The OpenTofu is a Linux Foundation project which is a complete opensource Infrastructure as Code tool, an alternative to the popular Terraform. This essentially means it supports natively Terraform’s HCL (HashiCorp Configuration Language) to write the infrastructure as code.

OpenTofu

In this blog, we will see how we can you OpenTofu as Infrastructure as Code (IaC) to provision a custom Virtual Private Cloud (VPC) on Amazon Web Services.

Following is the internal architecture of our custom VPC that we are going to provision on AWS using the OpenTofu:-

AWS Virtual Private Cloud (Custom) in Northern Virginia Region

However, let us first compare OpenTofu with other popular IaC tools like AWS Cloud Formation or Terraform.

Image description

Now let us go ahead and create the VPC using OpenTofu.

Step 1. Install OpenTofu in your system first.

You can execute the following command on MacOS terminal to install the binary for OpenTofu. For other operating systems, refer this documentation link.

brew updatebrew install opentofu
Enter fullscreen modeExit fullscreen mode

Step 2. Setup the AWS provider configuration

Create a file, say00_provider.tf and copy the following code. Here we have used a variable for AWS region with default as us-east-1 (Northern Virginia) and AWS as the required provider. Also, to connect to our account, we have mapped this to the profile name myaws (which is there in the local path$HOME/.aws/profile).

variable aws_region {    default = "us-east-1"    description = "AWS region where the resources will be provisioned"}# Configure the AWS Providerterraform {  required_providers {    aws = {      source  = "hashicorp/aws"      version = "~> 5.0"    }    # helm = {    #     source = "hashicorp/aws"    #     version = "~> 2.6"    # }  }}# Configure region and profileprovider "aws" {  region = var.aws_region  profile = "myaws"}
Enter fullscreen modeExit fullscreen mode

Step 3. Create a custom VPC configuration and save it in a file, say01_vpc.tf

resource "aws_vpc" "mycustomvpc" {    cidr_block = "10.0.0.0/16"    enable_dns_support = true    enable_dns_hostnames = true    tags = {        "owner" = "vinod"        "Name" = "my custom VPC"    }}
Enter fullscreen modeExit fullscreen mode

Step 4. Create Internet Gateway and attach it to the VPC

resource "aws_internet_gateway" "igw" {     vpc_id = aws_vpc.mycustomvpc.id    tags = {        "owner" = "vinod"        "Name" = "IGW"    }}
Enter fullscreen modeExit fullscreen mode

Step 5. Create Subnets for the VPC

resource "aws_subnet" "private-us-east-1a" {  vpc_id     = aws_vpc.mycustomvpc.id  cidr_block = "10.0.1.0/24"  availability_zone = "us-east-1a"  tags = {    "subnet" = "private-us-east-1a"    "Name" = "Private Subnet"  }}resource "aws_subnet" "private-us-east-1b" {  vpc_id     = aws_vpc.mycustomvpc.id  cidr_block = "10.0.2.0/24"  availability_zone = "us-east-1b"  tags = {    "subnet" = "private-us-east-1b"    "Name" = "Private Subnet"  }}resource "aws_subnet" "public-us-east-1a" {  vpc_id     = aws_vpc.mycustomvpc.id  cidr_block = "10.0.3.0/24"  availability_zone = "us-east-1a"  map_public_ip_on_launch = true  tags = {    "subnet" = "public-us-east-1a"    "Name" = "Public Subnet"  }}resource "aws_subnet" "public-us-east-1b" {  vpc_id     = aws_vpc.mycustomvpc.id  cidr_block = "10.0.4.0/24"  availability_zone = "us-east-1b"  map_public_ip_on_launch = true  tags = {    "subnet" = "public-us-east-1b"    "Name" = "Public Subnet"  }}
Enter fullscreen modeExit fullscreen mode

I have created 4 subnets (2 private and 2 public).

Step 6. Create a NAT Gateway and EIP configuration

Create a NAT gateway and attach it to the public subnet. The NAT Gateway allows our instances running within private subnets to access Public Internet for Operating Systems and other software patch updates.

resource "aws_eip" "nat" {  vpc = true  tags = {    "Name" = "EIP"    "Owner" = "Vinod"  }}resource "aws_nat_gateway" "nat" {  allocation_id = aws_eip.nat.id  subnet_id     = aws_subnet.public-us-east-1a.id  tags = {    "Name" = "NAT Gateway"    "Owner" = "Vinod"  }  # To ensure proper ordering, it is recommended to add an explicit dependency  # on the Internet Gateway for the VPC.  depends_on = [aws_internet_gateway.igw]}
Enter fullscreen modeExit fullscreen mode

Step 7. Create route configuration and its association with subnets

Create two route tables (one as private and another as public) with route to NAT Gateway and Internet Gateway respectively. Associate them to their respective private and public subnets.

resource "aws_route_table" "privateroute" {  vpc_id = aws_vpc.mycustomvpc.id  route {    cidr_block = "0.0.0.0/0"    nat_gateway_id = aws_nat_gateway.nat.id  }  tags = {    Name = "private"  }}resource "aws_route_table" "publicroute" {  vpc_id = aws_vpc.mycustomvpc.id  route {    cidr_block = "0.0.0.0/0"    gateway_id = aws_internet_gateway.igw.id  }  tags = {    Name = "public"  }}resource "aws_route_table_association" "privateassociation_a" {  subnet_id      = aws_subnet.private-us-east-1a.id  route_table_id = aws_route_table.privateroute.id}resource "aws_route_table_association" "privateassociation_b" {  subnet_id      = aws_subnet.private-us-east-1b.id  route_table_id = aws_route_table.privateroute.id}resource "aws_route_table_association" "publicassociation_a" {  subnet_id      = aws_subnet.public-us-east-1a.id  route_table_id = aws_route_table.publicroute.id}resource "aws_route_table_association" "publicassociation_b" {  subnet_id      = aws_subnet.public-us-east-1b.id  route_table_id = aws_route_table.publicroute.id}
Enter fullscreen modeExit fullscreen mode

Initialize the tofu project to install all dependencies, modules, etc. by executing on the same directory where all the above .tf files are present

tofu init
Enter fullscreen modeExit fullscreen mode

To validate our configuration and doing a dry run (without actually provisioning any resources), execute

tofu validatetofu plan
Enter fullscreen modeExit fullscreen mode

This will output a summary plan of our change like below for us to review:-

OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:  + createOpenTofu will perform the following actions:  # aws_eip.nat will be created  + resource "aws_eip" "nat" {      + allocation_id        = (known after apply)      + arn                  = (known after apply)      + association_id       = (known after apply)      + carrier_ip           = (known after apply)      + customer_owned_ip    = (known after apply)      + domain               = (known after apply)      + id                   = (known after apply)      + instance             = (known after apply)      + network_border_group = (known after apply)      + network_interface    = (known after apply)      + private_dns          = (known after apply)      + private_ip           = (known after apply)      + ptr_record           = (known after apply)      + public_dns           = (known after apply)      + public_ip            = (known after apply)      + public_ipv4_pool     = (known after apply)      + tags                 = {          + "Name"  = "EIP"          + "Owner" = "Vinod"        }      + tags_all             = {          + "Name"  = "EIP"          + "Owner" = "Vinod"        }      + vpc                  = true    }  # aws_internet_gateway.igw will be created  + resource "aws_internet_gateway" "igw" {      + arn      = (known after apply)      + id       = (known after apply)      + owner_id = (known after apply)      + tags     = {          + "Name"  = "IGW"          + "owner" = "vinod"        }      + tags_all = {          + "Name"  = "IGW"          + "owner" = "vinod"        }      + vpc_id   = (known after apply)    }  # aws_nat_gateway.nat will be created  + resource "aws_nat_gateway" "nat" {      + allocation_id                      = (known after apply)      + association_id                     = (known after apply)      + connectivity_type                  = "public"      + id                                 = (known after apply)      + network_interface_id               = (known after apply)      + private_ip                         = (known after apply)      + public_ip                          = (known after apply)      + secondary_private_ip_address_count = (known after apply)      + secondary_private_ip_addresses     = (known after apply)      + subnet_id                          = (known after apply)      + tags                               = {          + "Name"  = "NAT Gateway"          + "Owner" = "Vinod"        }      + tags_all                           = {          + "Name"  = "NAT Gateway"          + "Owner" = "Vinod"        }    }  # aws_route_table.privateroute will be created  + resource "aws_route_table" "privateroute" {      + 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                 = ""              + ipv6_cidr_block            = ""              + local_gateway_id           = ""              + nat_gateway_id             = (known after apply)              + network_interface_id       = ""              + transit_gateway_id         = ""              + vpc_endpoint_id            = ""              + vpc_peering_connection_id  = ""            },        ]      + tags             = {          + "Name" = "private"        }      + tags_all         = {          + "Name" = "private"        }      + vpc_id           = (known after apply)    }  # aws_route_table.publicroute will be created  + resource "aws_route_table" "publicroute" {      + 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             = {          + "Name" = "public"        }      + tags_all         = {          + "Name" = "public"        }      + vpc_id           = (known after apply)    }  # aws_route_table_association.privateassociation_a will be created  + resource "aws_route_table_association" "privateassociation_a" {      + id             = (known after apply)      + route_table_id = (known after apply)      + subnet_id      = (known after apply)    }  # aws_route_table_association.privateassociation_b will be created  + resource "aws_route_table_association" "privateassociation_b" {      + id             = (known after apply)      + route_table_id = (known after apply)      + subnet_id      = (known after apply)    }  # aws_route_table_association.publicassociation_a will be created  + resource "aws_route_table_association" "publicassociation_a" {      + id             = (known after apply)      + route_table_id = (known after apply)      + subnet_id      = (known after apply)    }  # aws_route_table_association.publicassociation_b will be created  + resource "aws_route_table_association" "publicassociation_b" {      + id             = (known after apply)      + route_table_id = (known after apply)      + subnet_id      = (known after apply)    }  # aws_subnet.private-us-east-1a will be created  + resource "aws_subnet" "private-us-east-1a" {      + arn                                            = (known after apply)      + assign_ipv6_address_on_creation                = false      + availability_zone                              = "us-east-1a"      + availability_zone_id                           = (known after apply)      + cidr_block                                     = "10.0.1.0/24"      + enable_dns64                                   = false      + enable_resource_name_dns_a_record_on_launch    = false      + enable_resource_name_dns_aaaa_record_on_launch = false      + id                                             = (known after apply)      + ipv6_cidr_block_association_id                 = (known after apply)      + ipv6_native                                    = false      + map_public_ip_on_launch                        = false      + owner_id                                       = (known after apply)      + private_dns_hostname_type_on_launch            = (known after apply)      + tags                                           = {          + "Name"   = "Private Subnet"          + "subnet" = "private-us-east-1a"        }      + tags_all                                       = {          + "Name"   = "Private Subnet"          + "subnet" = "private-us-east-1a"        }      + vpc_id                                         = (known after apply)    }  # aws_subnet.private-us-east-1b will be created  + resource "aws_subnet" "private-us-east-1b" {      + arn                                            = (known after apply)      + assign_ipv6_address_on_creation                = false      + availability_zone                              = "us-east-1b"      + availability_zone_id                           = (known after apply)      + cidr_block                                     = "10.0.2.0/24"      + enable_dns64                                   = false      + enable_resource_name_dns_a_record_on_launch    = false      + enable_resource_name_dns_aaaa_record_on_launch = false      + id                                             = (known after apply)      + ipv6_cidr_block_association_id                 = (known after apply)      + ipv6_native                                    = false      + map_public_ip_on_launch                        = false      + owner_id                                       = (known after apply)      + private_dns_hostname_type_on_launch            = (known after apply)      + tags                                           = {          + "Name"   = "Private Subnet"          + "subnet" = "private-us-east-1b"        }      + tags_all                                       = {          + "Name"   = "Private Subnet"          + "subnet" = "private-us-east-1b"        }      + vpc_id                                         = (known after apply)    }  # aws_subnet.public-us-east-1a will be created  + resource "aws_subnet" "public-us-east-1a" {      + arn                                            = (known after apply)      + assign_ipv6_address_on_creation                = false      + availability_zone                              = "us-east-1a"      + availability_zone_id                           = (known after apply)      + cidr_block                                     = "10.0.3.0/24"      + enable_dns64                                   = false      + enable_resource_name_dns_a_record_on_launch    = false      + enable_resource_name_dns_aaaa_record_on_launch = false      + id                                             = (known after apply)      + ipv6_cidr_block_association_id                 = (known after apply)      + ipv6_native                                    = false      + map_public_ip_on_launch                        = true      + owner_id                                       = (known after apply)      + private_dns_hostname_type_on_launch            = (known after apply)      + tags                                           = {          + "Name"   = "Public Subnet"          + "subnet" = "public-us-east-1a"        }      + tags_all                                       = {          + "Name"   = "Public Subnet"          + "subnet" = "public-us-east-1a"        }      + vpc_id                                         = (known after apply)    }  # aws_subnet.public-us-east-1b will be created  + resource "aws_subnet" "public-us-east-1b" {      + arn                                            = (known after apply)      + assign_ipv6_address_on_creation                = false      + availability_zone                              = "us-east-1b"      + availability_zone_id                           = (known after apply)      + cidr_block                                     = "10.0.4.0/24"      + enable_dns64                                   = false      + enable_resource_name_dns_a_record_on_launch    = false      + enable_resource_name_dns_aaaa_record_on_launch = false      + id                                             = (known after apply)      + ipv6_cidr_block_association_id                 = (known after apply)      + ipv6_native                                    = false      + map_public_ip_on_launch                        = true      + owner_id                                       = (known after apply)      + private_dns_hostname_type_on_launch            = (known after apply)      + tags                                           = {          + "Name"   = "Public Subnet"          + "subnet" = "public-us-east-1b"        }      + tags_all                                       = {          + "Name"   = "Public Subnet"          + "subnet" = "public-us-east-1b"        }      + vpc_id                                         = (known after apply)    }  # aws_vpc.mycustomvpc will be created  + resource "aws_vpc" "mycustomvpc" {      + 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                 = true      + 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"  = "my custom VPC"          + "owner" = "vinod"        }      + tags_all                             = {          + "Name"  = "my custom VPC"          + "owner" = "vinod"        }    }Plan: 14 to add, 0 to change, 0 to destroy.
Enter fullscreen modeExit fullscreen mode

Finally, execute the following command to create the custom VPC on AWS

tofu apply
Enter fullscreen modeExit fullscreen mode

You will need to confirm with yes when prompted on the terminal. If you wish to avoid that prompt then use the flag as—-auto-approve like shown below

tofu apple --auto-approve
Enter fullscreen modeExit fullscreen mode

Voila! its all done :-)

All your custom VPC resources will be created.

Output

AWS VPC

If you wish to delete all the resources of the custom VPC, then execute:-

tofu destroy

Enter fullscreen modeExit fullscreen mode




Summary

In this blog, we have seen what OpenTofu is, how it compares as an open source project with other popular IaC tools and how we can install it in our system to create a custom VPC on Amazon Web Services.

Hope you like the article. Please do share your feedback.

Like always, you will find all the source code used in this blog as a reference at this GitHub project. You can star this GitHub repository to get all updates happening on this active project.

https://github.com/vinod827/k8s-nest/tree/main/iac/aws/terraform/creating-custom-vpc

All k8s manifests lives here. Contribute to vinod827/k8s-nest development by creating an account on GitHub.
github.com

Connect me onLinkedIn andTwitter.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

With over 16 years of software development and architectural expertise, I specialize in AWS, Google Cloud, and Azure.
  • Work
    Principal Engineer
  • Joined

More fromVinod Kumar

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp