Create AWS VCP

October 08, 2023

vpc_logo

In this article, we'll guide you through the step-by-step process of setting up and configuring your Virtual Private Cloud in AWS, to host your cloud infrastructure

You can find source code in our Github repo

Prerequisites:

  1. AWS Account: You must have an active AWS account. If you don't have one, you can sign up for an AWS account on the AWS website. You can create it here
  2. IAM User or Role: Create an IAM (Identity and Access Management) user or role in your AWS account with the necessary permissions. At a minimum, the user or role should have permissions to create VPCs and related resources.
  3. AWS CLI: Install and configure the AWS Command Line Interface (CLI) on your local machine. You'll use the AWS CLI to interact with your AWS account and configure your AWS credentials. You can download it here
  4. Terraform Installed: Install Terraform on your local machine. You can download Terraform from the official Terraform website and follow the installation instructions for your operating system here

Intro

In the vast landscape of cloud computing, Virtual Private Clouds (VPCs) stand as the foundation upon which complex network architectures are built. A VPC is a private, isolated section of the AWS cloud where you can launch resources like EC2 instances, RDS databases, and Lambda functions. It offers a secure and controlled environment for your applications and services.

But, like any powerful tool, understanding how to use the capabilities of a VPC effectively is paramount. This includes not only comprehending its inner workings but also having the ability to create and configure it according to your specific requirements.

In this comprehensive guide, we'll walk you through the process of creating a VPC in AWS using Terraform, an Infrastructure as Code (IaC) tool that allows you to define and provision your cloud infrastructure. We'll leverage the Terraform module terraform-aws-modules/vpc/aws created by Anton Babenko, which provides a robust and battle-tested foundation for constructing VPCs.

Terraform

Let's start as usual with provider.tf file, create it in your project folder with following content:

provider "aws" {
  region  = var.aws_region
  profile = var.aws_profile
}

We will declare our variables in variables.tf file:

variable "aws_profile" {
  description = "Set this variable if you use another profile besides the default awscli profile called 'default'."
  type        = string
  default     = "default"
}

variable "aws_region" {
  description = "Set this variable if you use another aws region."
  type        = string
  default     = "us-east-1"
}

In our example, we will use the terraform-aws-modules/vpc/aws module, skillfully maintained by Anton Babenko. This module provides us with the flexibility to tailor our VPC configuration according to our specific requirements, all within the framework of a well-established and widely recognized module. You can access the module's documentation here

Now let's create main.tf file:

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.2"
}
  • source: Here, we specify the source of the module. The module "vpc" is sourced from the "terraform-aws-modules" organization's "vpc/aws" module. This module is available on the Terraform Registry.
  • version: This line specifies the version of the module we want to use. Using a specific version ensures that your Terraform configurations remain consistent, even if newer versions of the module become available. This can help prevent unintended changes due to module updates. If not mentioned - latest version of the module will be used

We can run our terraform code now - it will create a AWS Default Network ACL (Network Access Control List) within your VPC, default route tables, default security group and a VPC itself with default configurations:

resource "aws_vpc" "this" {
      + 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" = ""
        }
      + tags_all                             = (known after apply)
    }

But if we would like to configure it to our needs we can use other inputs to overwrite default variables and fit new VPC to our needs

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.2"

  name = "${var.env}-vpc"

  cidr = var.cidr
  azs  = var.aws_availability_zones

  private_subnets = var.private_subnets
  public_subnets  = var.public_subnets

  enable_nat_gateway   = true
  single_nat_gateway   = true
  enable_dns_hostnames = true

  public_subnet_tags = merge(
    var.additional_tags,
    {
      "Subnet" = "Public"
    }
  )

  private_subnet_tags = merge(
    var.additional_tags,
    {
      "Subnet" = "Private"
    }
  )
}

Here we would adjust our VPC with following inputs:

  1. name: This line defines the name of the VPC and uses the value of the variable "env" to create a name that includes the environment
  2. cidr: This line configures the Classless Inter-Domain Routing (CIDR) block for the VPC.
  3. azs: This line specifies the availability zones (AZs) in which the VPC will be deployed.
  4. private_subnets and public_subnets: These lines specify the configurations for the private and public subnets of the VPC
  5. enable_nat_gateway: This line enables the creation of Network Address Translation (NAT) gateways in the VPC, which allow instances in private subnets to access the internet while maintaining security.
  6. single_nat_gateway: This line specifies that only a single NAT gateway should be created for all private subnets. This is a cost-effective option if you have multiple private subnets.
  7. enable_dns_hostnames: This line enables DNS hostnames for the VPC, allowing instances within the VPC to have DNS hostnames assigned to them
  8. public_subnet_tags and private_subnet_tags: These sections specify key-value tags to be applied to the public and private subnets. We are using var.additional_tags with common tag values and merge it with specyfic tag. The resulting map will include all the key-value pairs from var.additional_tags along with the "Private_subnet" or "Public_subnet" tag.

Now we would need to updates our variables.tf file:

variable "aws_profile" {
  description = "Set this variable if you use another profile besides the default awscli profile called 'default'."
  type        = string
  default     = "default"
}

variable "aws_region" {
  description = "Set this variable if you use another aws region."
  type        = string
  default     = "us-east-1"
}

variable "env" {
  description = "Set this variable to specify environment"
  type        = string
  default     = "dev"
}

variable "private_subnets" {
  description = "private subnets to create, need to have 1 for each AZ"
  default     = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}

variable "public_subnets" {
  description = "public subnets to create, need to have 1 for each AZ"
  default     = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"]
}

variable "aws_availability_zones" {
  description = "AWS availability zones"
  default     = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

variable "cidr" {
  description = "Cird block for your VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "additional_tags" {
  default     = {
    "project"     = "cloudtipss-vpc"
    "owner"       = "cloudtipss.com"
    "environment"       = "dev"
  }
  description = "Additional resource tags"
  type        = map(string)
}
Specifying Outputs

Outputs provide a way to communicate critical information about the infrastructure to other team members or systems. They serve as documentation for what the infrastructure has created and what information is important to extract from the Terraform configuration. Also it allows resources to depend on the outputs of other resources. By defining outputs, you can explicitly declare which values from one resource should be used by another. This ensures proper sequencing and dependencies within your infrastructure.

We would create a separate outputs.tf file for that:

output "vpc_id" {
  description = "Id of vpc created"
  value       = module.vpc.vpc_id
}

output "azs" {
  description = "A list of availability zones specified as argument to this module"
  value       = module.vpc.azs
}

output "private_subnets" {
  description = "List of IDs of private subnets"
  value       = module.vpc.private_subnets
}

output "public_subnets" {
  description = "List of IDs of public subnets"
  value       = module.vpc.public_subnets
}

Final step - Running Terraform

After we prepared our code, we would need to initialize it. From your project folder run:

terraform init

Just to to see which resources terraform is going to create you can run:

terraform plan

In order to create resources run:

terraform apply

After apply complete you will see output similar to this:

module.vpc.aws_route.private_nat_gateway[0]: Creating...
module.vpc.aws_route.private_nat_gateway[0]: Creation complete after 2s [id=r-rtb-06de6860c06f6cd611080289494]

Apply complete! Resources: 23 added, 0 changed, 0 destroyed.

Outputs:

azs = tolist([
  "us-east-1a",
  "us-east-1b",
  "us-east-1c",
])
private_subnets = [
  "subnet-0fad2ad3c5db64bc2",
  "subnet-0e17efe8e4be31f43",
  "subnet-0ab454683d1067e83",
]
public_subnets = [
  "subnet-0e55f93882ac2af12",
  "subnet-05377b70f661be082",
  "subnet-0e8377449a74e8dc5",
]
vpc_id = "vpc-0484d5bba74df8491"

You can find source code in our Github repo