Terraform: Create VPC, Subnets, and EC2 instances in multiple availability zones
Published: 2022-12-17 11:03:05

---
title: Terraform: Create VPC, Subnets, and EC2 instances in multiple availability zones
published: true
date: 2022-12-17 10:37:03 UTC
tags: 
canonical_url: https://palak-bhawsar.hashnode.dev/terraform-create-vpc-subnets-and-ec2-instances-in-multiple-availability-zones
---

In this article, I will demonstrate how to create VPC, Subnets, EC2 instances, Internet gateway, NAT gateway, and Security groups using Terraform in two availability zones.

**Architecture**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1670764593209/inbTIQI6Z.png)

**Prerequisite**

- AWS account and AWS Access key and Secret key created

- Terraform installed on your IDE

- AWS CLI installed and configured on your IDE

- Basic understanding of AWS services and Terraform

**Objective**

1. Choose a region in which you want your VPC to reside and availability zones where you want to create public and private subnets for high availability.

2. Decide the CIDR blocks range for your VPC and Subnets.

3. Create public and private subnets in each availability zone.

4. Create an internet gateway to allow communication between your VPC and the internet.

5. Create an EC2 instance in each public subnet in both the availability zones and create AWS key pair to SSH into your instances.

6. Create a route table for the public and private subnets and associate the route table with subnets to control where network traffic is directed.

7. Create a NAT gateway to enable private subnets to connect to services outside your VPC. A NAT gateway must be in a public subnet.

8. Finally, create a VPC security group and open port 80 to allow HTTP traffic from anywhere and open port 22 to SSH into the instances.

**Code Repository**

Use [GitHub](https://github.com/palakbhawsar98/Terraform/tree/main/terraform-vpc-hands-on) to find providers.tf, variables.tf, and outputs.tf files.

Let's get started with the configuration of the project

# Create VPC

We are creating VPC in the us-east-1 region and attaching it to the internet gateway.

```
resource "aws_vpc" "vpc" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "my-vpc"
  }
}

resource "aws_internet_gateway" "internet_gateway" {
  vpc_id = aws_vpc.vpc.id
  tags = {
    Name = "inernetGW"
  }
}

```

# Create public and private subnets

Creating one public and one private subnet in both us-east-1a and us-east-1b zones.

```
resource "aws_subnet" "vpc_public_subnet" {
  vpc_id = aws_vpc.vpc.id
  count = length(var.subnets_count)
  availability_zone = element(var.availability_zone, count.index)
  cidr_block = "10.0.${count.index}.0/24"
  map_public_ip_on_launch = true

  tags = {
    Name = "pub-sub-${element(var.availability_zone, count.index)}"
  }
}

resource "aws_subnet" "vpc_private_subnet" {
  count = length(var.subnets_count)
  availability_zone = element(var.availability_zone, count.index)
  cidr_block = "10.0.${count.index + 2}.0/24"
  vpc_id = aws_vpc.vpc.id

  tags = {
    Name = "pri-sub-${element(var.availability_zone, count.index)}"
  }
}

```

# Create a route table and associate it with the public subnet

A route table contains a set of rules that are used to determine where network traffic is directed. Associate a public subnet with the default route (0.0.0.0/0) pointing to an internet gateway.

```
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
  }
  tags = {
    Name = "public-route-tbl"
  }
}

resource "aws_route_table_association" "public_route_table_association" {
  count = length(var.subnets_count)
  subnet_id = element(aws_subnet.vpc_public_subnet.*.id, count.index)
  route_table_id = aws_route_table.public_route_table.id
}

```

# Create a NAT gateway and associate it with Elastic IP

Create a public NAT gateway in a public subnet and associate it with an elastic IP address to route traffic from the NAT gateway to the Internet gateway for the VPC.

```
resource "aws_eip" "elasticIP" {
  count = length(var.subnets_count)
  vpc = true
}

resource "aws_nat_gateway" "nat_gateway" {
  count = length(var.subnets_count)
  allocation_id = element(aws_eip.elasticIP.*.id, count.index)
  subnet_id = element(aws_subnet.vpc_public_subnet.*.id, count.index)

  tags = {
    Name = "nat-GTW-${count.index}"
  }
}

```

# Create a route table and associate it with the private subnet

```
resource "aws_route_table" "private_route_table" {
  count = length(var.subnets_count)
  vpc_id = aws_vpc.vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat_gateway[count.index].id
  }

  tags = {
    Name = "private-route-tbl"
  }
}

resource "aws_route_table_association" "private_route_table_association" {
  count = length(var.subnets_count)
  subnet_id = element(aws_subnet.vpc_private_subnet.*.id, count.index)
  route_table_id = element(aws_route_table.private_route_table.*.id,
  count.index)
}

```

# Create security group

For the inbound connections open port 80 to allow HTTP traffic from anywhere and open port 22 to SSH into the instance and open all the ports for the outbound connections.

```
resource "aws_security_group" "vpc_sg" {
  name = "vpc_sg"
  description = "Security group for vpc"
  vpc_id = aws_vpc.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 = "vpc-sg"
  }

}

```

# Create EC2 instances in public subnets

Create EC2 instance with user-data scripts to install Apache server and access static webpage. Also, create AWS key pair to SSH into instances

```
resource "tls_private_key" "key" {
  algorithm = "RSA"
  rsa_bits = 4096
}

resource "local_file" "private_rsa_key" {
  content = tls_private_key.key.private_key_pem
  filename = "private_rsa_key"
}

resource "aws_key_pair" "public_rsa_key" {
  key_name = "public_rsa_key"
  public_key = tls_private_key.key.public_key_openssh
}

resource "aws_instance" "my_app_server" {
  ami = var.instance_ami
  instance_type = var.instance_size
  key_name = aws_key_pair.public_rsa_key.key_name
  count = length(var.subnets_count)
  subnet_id = element(aws_subnet.vpc_public_subnet.*.id, count.index)
  security_groups = [aws_security_group.vpc_sg.id]
  associate_public_ip_address = true

  user_data = <<-EOF
  #!/bin/bash
  sudo apt update -y
  sudo apt install apache2 -y
  sudo systemctl start apache2
  sudo systemctl enable apache2
  sudo apt install git -y
  git clone https://github.com/palakbhawsar98/FirstWebsite.git
  cd /FirstWebsite
  sudo cp index.html /var/www/html/
  EOF

  tags = {
    Name = "my_app_server-${count.index}"
  }
}

```

We are ready to deploy all our changes to AWS. Perform the below commands:

- **terraform init** to initialize the working directory and download all the plugins for providers.

- **terraform fmt** to rewrite Terraform configuration files to a canonical format and style.

- **terraform validate** to check that our code is error-free.

- **terraform plan** to create the execution plan for the resources we are going to create in AWS.

- **terraform apply** to execute the actions proposed in a terraform plan and to deploy your infrastructure.

You can see the resources created in AWS Console. Take the public IPV4 and search it in the browser on port 80 that we opened for HTTP connections.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671212159651/fNhEAAsfb.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671212062421/RkGIJnY5-.png)

You can access the HTML static webpage using public IPV4

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671212024285/iu-X9i1_g.png)

If you want you can destroy the infrastructure we just create using **terraform destroy** command.