DEV Community

Cover image for Building Infrastructure from Scratch: Creating Public Subnets in AWS
Grit Coding
Grit Coding

Posted on

Building Infrastructure from Scratch: Creating Public Subnets in AWS

In our previous guide, we explored how to create a VPC using Terraform. Now, let's dive deeper into AWS by creating public subnets using Terraform. Our focus will be on understanding the function of individual AWS resources rather than the syntax of Terraform.

By the end of this tutorial, you will have set up the following components in your AWS account:

  • VPC
  • Internet Gateway (IGW)
  • Route Tables
  • Subnets
  • IAM Role
  • Security Groups (SG)
  • EC2 Instances

Image description

Prerequisites

Make sure you have the following installed and configured:

Begin by setting up your AWS credentials. Export the following environment variables with your actual AWS credentials:

export AWS_ACCESS_KEY_ID=yourAccessKeyID
export AWS_SECRET_ACCESS_KEY=yourSecretAccessKey
export AWS_DEFAULT_REGION=yourAWSRegion
Enter fullscreen mode Exit fullscreen mode

Next, create a variables.tf file in your project directory and add the following:

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "vpc_name" {
  type    = string
  default = "my_vpc"
}

variable "environment" {
  description = "The deployment environment (e.g., dev, qa, prod)"
  type        = string

  validation {
    condition     = contains(["dev", "qa", "prod"], var.environment)
    error_message = "The environment must be one of: dev, qa, or prod."
  }
}

variable "env_cidr_map" {
  description = "Map of environment names to CIDR block second octet"
  type        = map(string)
  default = {
    "dev"     = "0"
    "qa"      = "10"
    "prod"    = "20"
  }
}

variable "public_subnets" {
  default = {
    "public_subnet_1" = 1
    "public_subnet_2" = 2
    "public_subnet_3" = 3
  }
}
Enter fullscreen mode Exit fullscreen mode

Also, create a main.tf file in the same directory with this initial content:

provider "aws" {
  region = "us-east-1"
}

data "aws_availability_zones" "available" {}
data "aws_region" "current" {}

Enter fullscreen mode Exit fullscreen mode

Create Public Subnets

Image description

Now, let's create the infrastructure as depicted in the above chart. Make sure to add the code examples to your main.tf file.

VPC Creation

Configure the VPC with a specific IPv4 CIDR block based on the environment variable. For example, if you choose dev as the environment, the IPv4 CIDR block will be 10.0.0.0/16.
Refer to our previous post for more details on VPC creation.

resource "aws_vpc" "vpc" {
  cidr_block = "10.${lookup(var.env_cidr_map, var.environment, "0")}.0.0/16"
  assign_generated_ipv6_cidr_block = true

  tags = {
    Name        = var.vpc_name
    Environment = var.environment
    Terraform   = "true"
  }
}

Enter fullscreen mode Exit fullscreen mode

Default NACL

A Network Access Control List (NACL) is automatically created with the VPC.

Internet Gateway

Establish an Internet Gateway to allow internet access to the VPC.

resource "aws_internet_gateway" "internet_gateway" {
  vpc_id = aws_vpc.vpc.id
  tags = {
    Name = "grit_coding_igw"
  }
}
Enter fullscreen mode Exit fullscreen mode

Subnets

Create a subnet in each of the three Availability Zones (AZs). Subnets will use the default NACL automatically. For custom NACL applications, you can modify NACL associations.

resource "aws_subnet" "public_subnets" {
  for_each                = var.public_subnets
  vpc_id                  = aws_vpc.vpc.id
  cidr_block              = cidrsubnet(aws_vpc.vpc.cidr_block, 8, each.value)
  availability_zone       = tolist(data.aws_availability_zones.available.names)[each.value]
  map_public_ip_on_launch = true
  assign_ipv6_address_on_creation = true
  ipv6_cidr_block               = cidrsubnet(aws_vpc.vpc.ipv6_cidr_block, 8, each.value)

  tags = {
    Name      = each.key
    Terraform = "true"
  }
}
Enter fullscreen mode Exit fullscreen mode

Route Table

After creating the route table, add a route to the Internet Gateway. Subnets linked to this route table with the IGW route are defined as public subnets, facilitating access to and from the internet.

resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.vpc.id

  route {
    cidr_block     = "0.0.0.0/0"
    gateway_id     = aws_internet_gateway.internet_gateway.id
  }

  route {
    ipv6_cidr_block = "::/0"
    gateway_id      = aws_internet_gateway.internet_gateway.id
  }

  tags = {
    Name      = "grit_coding_public_rtb"
    Terraform = "true"
  }
}


resource "aws_route_table_association" "public" {
  depends_on     = [aws_subnet.public_subnets]
  route_table_id = aws_route_table.public_route_table.id
  for_each       = aws_subnet.public_subnets
  subnet_id      = each.value.id
}
Enter fullscreen mode Exit fullscreen mode

With the public subnets in place, let’s add a few more resources to test the public subnet connection. We will need IAM Role, a Security Group, and EC2 instances.

Image description

IAM Role with AmazonSSMManagedInstanceCore

This role, when attached to an EC2 instance, provides the specified permissions. The AmazonSSMManagedInstanceCore policy is necessary for access via the session manager, which is part of the AWS Systems Manager.

resource "aws_iam_role" "ec2_ssm_role" {
  name = "ec2-ssm-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Action = "sts:AssumeRole",
        Effect = "Allow",
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })

  tags = {
    Terraform = "true"
  }
}
resource "aws_iam_role_policy_attachment" "ssm_managed_instance_core" {
  role       = aws_iam_role.ec2_ssm_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_instance_profile" "ec2_ssm_instance_profile" {
  name = "ec2-ssm-instance-profile"
  role = aws_iam_role.ec2_ssm_role.name
}
Enter fullscreen mode Exit fullscreen mode

Alternative Option: If you prefer using a key pair for EC2 access, create one in the AWS console, then specify its ID when creating the EC2 instance. Make sure to add an ingress rule for SSH (port 22) to the security group if you choose this method.

Security Group

We'll set up a security group with only egress rules. Ingress rules are not required since we are using the Systems Manager.

resource "aws_security_group" "ec2_sg" {
  name        = "EC2_Instance_SG"
  description = "Security group for EC2 instances with default outbound rules"
  vpc_id      = aws_vpc.vpc.id
}

resource "aws_security_group_rule" "ec2_sg_egress_rule" {
  type              = "egress"
  to_port           = 0
  protocol          = "-1"
  from_port         = 0
  cidr_blocks       = ["0.0.0.0/0"]
  ipv6_cidr_blocks  = ["::/0"]
  security_group_id = aws_security_group.ec2_sg.id
}
Enter fullscreen mode Exit fullscreen mode

EC2 Instance Creation

Create EC2 instances in each public subnet, attaching the security group and IAM role.

resource "aws_instance" "public_instance" {
  for_each                    = aws_subnet.public_subnets
  ami                         = "ami-0c101f26f147fa7fd"
  instance_type               = "t2.micro"
  subnet_id                   = each.value.id
  associate_public_ip_address = true
  vpc_security_group_ids      = [aws_security_group.ec2_sg.id]
  iam_instance_profile        = aws_iam_instance_profile.ec2_ssm_instance_profile.name

  tags = {
    Name      = "Public_Instance_${each.key}"
    Terraform = "true"
  }
}
Enter fullscreen mode Exit fullscreen mode

Deploy and Test

Execute the following commands in your project directory to deploy your Terraform configuration.

terraform init
terraform apply -auto-approve
Enter fullscreen mode Exit fullscreen mode

Enter dev as the environment value.

Connect to the EC2 instance using Session Manager. Run the following commands to retrieve the ID of the connected EC2 instance. You can find more information about the Session Manager in this post.

TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id
Enter fullscreen mode Exit fullscreen mode

Wrap-up

Today, we successfully set up public subnets and an EC2 instance, demonstrating how Infrastructure as Code (IaC) can make infrastructure deployment efficient, repeatable, and scalable.

For those new to cloud technology, these concepts can initially seem complex. However, I've found that hands-on experience, such as replicating these setups in the AWS console, makes learning more intuitive.

For further guidance, please watch the below demo video, which provides a manual walkthrough of the same setup in the AWS console.

You can find the source code from today's session in my GitHub Repository.

Note
If you're just beginning or not yet familiar with AWS terminology and concepts, consider looking into the AWS Cloud Practitioner certification. It provides comprehensive and clear insights into cloud concepts, making it an excellent starting point for beginners.

Top comments (1)

Collapse
 
evotik profile image
Evotik

That is too complex, I need to take your note intro concideration