DEV Community

Vivek Raj
Vivek Raj

Posted on

Deploying a Three-Tier Architecture in AWS Using Terraform

Introduction

Infrastructure as code or IaC allows an architect to deploy services using code rather than logging into the console and deploying using the GUI. The benefits of this over deploying through the console are that it allows for a more consistent environment and also the code can be reused and edited as needed which saves time. The IaC tool that I will be using in this project is Terraform which is compatible with most major cloud providers.

This project will involve building out a three-tier architecture on AWS using Terraform. In a three-tier architecture an application is broken down into a web facing presentation layer, and internal application and database layers. This is a change from the previous ways of building an application where the frontend, backend, and database are all sitting in the same place. The services that we will be using to build out this architecture will be Virtual Private Cloud (VPC), Elastic Compute Cloud (EC2), Elastic Load Balancer (ELB), Security Groups, Route Tables, and Internet Gateway (IGW)

Image description


Why Should We Architect This Way?

1. Modularity: One benefit of building our architecture out this way is that each layer can be managed separately. This can be beneficial to keeping a secure environment where users can only log in and manage the specific servers and services that they need to be working on ie. the DB developer works on the DB, front-end developer works on the front-end. Another benefit of this is more efficient troubleshooting and if one layer were to go down we can focus on that specifically without needing to take down the entire application.

2. High Availability: Another thing we will be focusing on is high availability which we will address by building in two separate AWS availability zones. In case one availability zone were to go down for whatever reason our application would still be running in the other. This is a huge benefit of cloud compared to the traditional on-site server where if power were to go out or internet connection was lost the application would be completely down.

3. Security: We will be building the front-end on a public subnet which will be accessed through a load balancer. Our application and database layers will be on private subnets which can only be accessed using SSH from specified security groups, or IP addresses. The security groups will allow us to set the criteria for ingress and egress traffic and specify which protocols can be used from what CIDR blocks.

4. Consistency: An important part of rolling out these projects is maintaining consistency. When we make any changes in this project, we will be doing so using Terraform and not the AWS console. The reason for this is that if we need to find out any kind of information on the configuration, we can look through our Terraform files which will let us know exactly how things are set up. Another benefit of this is we can reuse the code again, for example if we need to add additional server or if we are working on another similar project, we can use the code for this project to deploy the architecture which saves us time.


Let's Get To It!

One of the beautiful things about Terraform is that when we write the code, as long as the services are in the document it doesn't matter which order they are in. For example, if we put the VPC information after the subnet, Terraform would still build out the VPC first and then the subnets. Best practice is still to build out things in order so that's what we will be following here.

Before we get started with everything else, the first piece of code we will write will be our provider configuration. This will specify which cloud provider we are using as well as the secret and access keys for your AWS account. For the secret and access keys the best practice is to use environmental variables instead of typing the values in the code. The commands for this are export AWS_ACCESS_KEY_ID="anaccesskey" and export AWS_SECRET_ACCESS_KEY="asecretkey".

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region     = "us-east-1"
  access_key = AWS_ACCESS_KEY_ID
  secret_key = AWS_SECRET_ACCESS_KEY
}
Enter fullscreen mode Exit fullscreen mode

Variables

Another best practice when using Terraform is to use variables, the reason being is that when updating something, it provides much more consistency to update one variable rather than going in and updating every value. For example lets say we have a variable for our instance type, if we want to change the type we would just update one variable and all the code using that variable would be updated. If we didn't use a variable we would need to go in and update each item and we may forget to update an item which would lead to inconsistency in our architecture. Let's go ahead and create the variables now.

Vpc_cidr

This is the default value for the CIDR block of our VPC which will be 10.0.0.0/16

### VPC variable

variable "vpc_cidr" {
  description = "default VPC CIDR block"
  type        = string
  default     = "10.0.0.0/16"
}

Enter fullscreen mode Exit fullscreen mode

Availability_zone_names

This variable will create a list with the availability zones we'll be using

### Availability Zone variable

variable "availability_zone_names" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b"]
}

Enter fullscreen mode Exit fullscreen mode

Subnet_cidr

These variables will be lists that hold the CIDR values for our subnets

### Web Subnet CIDR

variable "web_subnet_cidr" {
  type    = list(string)
  default = ["10.0.1.0/24", "10.0.2.0/24"]

}

### Application Subnet CIDR

variable "application_subnet_cidr" {
  type    = list(string)
  default = ["10.0.11.0/24", "10.0.12.0/24"]
}

### Database Subnet CIDR

variable "database_subnet_cidr" {
  type    = list(string)
  default = ["10.0.21.0/24", "10.0.22.0/24"]

}

Enter fullscreen mode Exit fullscreen mode

Rds_instance

We will use this variable later on when we are building our database instance. It holds the values for storage, DB engine, version, instance type, and whether or not to deploy in multiple availability zones

### Database variables

variable "rds_instance" {
  type = map(any)
  default = {
    allocated_storage   = 10
    engine              = "mysql"
    engine_version      = "8.0.20"
    instance_class      = "db.t2.micro"
    multi_az            = true
    name                = "my_db"
    skip_final_snapshot = true
  }
}

### Create DB Variables
variable "user_information" {
  type = map(any)
  default = {
    username = "username"
    password = "password"
  }
  sensitive = true
}

Enter fullscreen mode Exit fullscreen mode

Instance Variables

The following variables will hold the information for our EC2 instances such as the AMI ID, and the instance type

### Instance variable 

variable "ami_id" {
  description = "default ami"
  type        = string
  default     = "ami-0cff7528ff583bf9a"
}

variable "instance_type" {
  description = "default instance type"
  type        = string
  default     = "t2.micro"
}
Enter fullscreen mode Exit fullscreen mode

Count Variable

This will be used as the count for our Availability Zones and Instances, we will keep this at 2 for this project.

### Count variable

variable "item_count" {
  description = "default count used to set AZs and instances"
  type        = number
  default     = 2
}
Enter fullscreen mode Exit fullscreen mode

Creating our services

1. VPC

Our first step will be to create a Virtual Private Cloud (VPC). This is a virtual network where we will be deploying our services. When creating a VPC we will give it a name and CIDR range, one thing to keep in mind here is that the CIDR range cannot be changed later.

For this project we will use the very original name vpc-1 and our CIDR range will be 10.0.0.0/16

### Create a VPC
resource "aws_vpc" "vpc-1" {
  cidr_block = var.vpc_cidr
  tags = {
    Name = "Demo VPC"
  }

}
Enter fullscreen mode Exit fullscreen mode

2. IGW

Next will be the Internet Gateway or IGW, this will allow the EC2 instances in our public subnet to access the public internet. Also in this step I've create a route table which will add the route to the public internet through our IGW, as well as a route table association which will attach the route table to the desired subnets.

One thing you'll see below and in the further sections is count which refers to variable we created earlier and that will be the number of route_table_associations we are creating. Another thing is [count.index] in the subnet_id field. What this means is it will run through the two subnets we will be creating which are web-facing[0], and web-facing[1]. I will cover this a bit more in the next section.

### Create an Internet Gateway
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc-1.id

  tags = {
    Name = "IGW"
  }

}

### Create a Web Facing Routing Table
resource "aws_route_table" "public-rt" {
  vpc_id = aws_vpc.vpc-1.id


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

  }

  tags = {
    Name = "Public-Rt"
  }

}

### Create Subnet Association with Route Table
resource "aws_route_table_association" "a" {
  count          = var.item_count
  subnet_id      = aws_subnet.web-facing[count.index].id
  route_table_id = aws_route_table.public-rt.id
}

Enter fullscreen mode Exit fullscreen mode

3. Subnets

Now that our VPC has been created we can move on to creating the subnets. For this project we will have six subnets, 2 public subnets for the presentation layer, 2 private subnets for the application layer, and 2 more private subnets for the database layer. The instances in the public subnet will have public IPs and will be able to access the internet, while the private subnets will have private IPs and only send traffic internally.

As I mentioned in the previous section, we are not typing up two different subnets like web-facing1, and web-facing2. Instead, we are using the count variable to reference the number 2, so it will loop twice and create two subnets which saves us from having to type out extra code giving us a cleaner look as well. We will do the same for cidr_block which references the cidr_block variable and index we created earlier.

### Create Web Public Subnet
resource "aws_subnet" "web-facing" {
  count                   = var.item_count
  vpc_id                  = aws_vpc.vpc-1.id
  cidr_block              = var.web_subnet_cidr[count.index]
  availability_zone       = var.availability_zone_names[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "web-${count.index + 1}"
  }

}

### Create Application Private Subnet
resource "aws_subnet" "application" {
  count                   = var.item_count
  vpc_id                  = aws_vpc.vpc-1.id
  cidr_block              = var.application_subnet_cidr[count.index]
  availability_zone       = var.availability_zone_names[count.index]
  map_public_ip_on_launch = false

  tags = {
    Name = "application-${count.index + 1}"
  }

}


### Create Database Private Subnet
resource "aws_subnet" "db" {
  count             = var.item_count
  vpc_id            = aws_vpc.vpc-1.id
  cidr_block        = var.database_subnet_cidr[count.index]
  availability_zone = var.availability_zone_names[count.index]

  tags = {
    Name = "db-${count.index + 1}"
  }

}

Enter fullscreen mode Exit fullscreen mode

4. Application Load Balancer

We will be provisioning a load balancer to distribute traffic between our instances in the different availability zones. When accessing our application, the users will hit IP address of the load balancer which will then direct the traffic to our EC2 instances.

Along with the load balancer itself, we also have:

  • A target group, which we use to route requests to a registered target
  • A target group attachment which lets us attach our instances to the load balancer
  • A listener which checks for connection requests from the port and protocol that we specify
### Create External Load Balancer

resource "aws_lb" "external-lb" {
  name               = "External-LB"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.web-sg.id]
  subnets            = aws_subnet.web-facing[count.index]

  enable_deletion_protection = true
}

### Create an External Target Group

resource "aws_lb_target_group" "external-elb" {
  name     = "ALB-TG"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.vpc-1.id
}

### Create Target Group Attachment

resource "aws_lb_target_group_attachment" "external-elb1" {
  count            = var.item_count
  target_group_arn = aws_lb_target_group.external-elb.arn
  target_id        = aws_instance.webserver[count.index].id
  port             = 80

  depends_on = [
    aws_instance.webserver,
  ]
}

### Create LB Listener

resource "aws_lb_listener" "external-elb" {
  load_balancer_arn = aws_lb.external-lb.arn
  port              = "80"
  protocol          = "HTTP"


  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.external-elb.arn
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Security Groups

The next step is to create security groups. Security groups allow traffic from specified ports and it also lets you set where you want to allow that traffic from, whether it be a CIDR range or another AWS security group. We will create 4 security groups, one for the load balancer, one for the web server, one for the application layer, and one for the database layer.

The load balancer will allow HTTP and HTTPS traffic from anywhere, the web server will allow HTTP traffic from the load balancer, the application layer will allow SSH from the public layer where we can set a bastion host, and the database layer will allow traffic on port 3306 from the application layer which would be for MySQL.

### Create Security Groups

resource "aws_security_group" "web-sg" {
  name        = "Web-SG"
  description = "Allow HTTP Inbound Traffic"
  vpc_id      = aws_vpc.vpc-1.id

  ingress {
    description = "HTTP from VPC"
    from_port   = 80
    to_port     = 80
    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"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "Web-SG"
  }
}

### Create Web Server Security Group

resource "aws_security_group" "webserver-sg" {
  name        = "Webserver-SG"
  description = "Allow Inbound Traffic from ALB"
  vpc_id      = aws_vpc.vpc-1.id

  ingress {
    description     = "Allow traffic from web layer"
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.web-sg.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "Webserver-SG"
  }
}

### Create Application Security Group

resource "aws_security_group" "app-sg" {
  name        = "App-SG"
  description = "Allow SSH Inbound Traffic"
  vpc_id      = aws_vpc.vpc-1.id

  ingress {
    description     = "SSH from VPC"
    from_port       = 22
    to_port         = 22
    protocol        = "tcp"
    security_groups = [aws_security_group.web-sg.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "App-SG"
  }
}
### Created Database Security Group

resource "aws_security_group" "database-sg" {
  name        = "Database-SG"
  description = "Allow Inbound Traffic from application layer"
  vpc_id      = aws_vpc.vpc-1.id

  ingress {
    description     = "Allow traffic from application layer"
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.webserver-sg.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "Database-SG"
  }
}
Enter fullscreen mode Exit fullscreen mode

6. EC2 Instances

Now that the networking side of things has been completed, we can start working on EC2 instances. In this section we will be creating the instances for the web-facing and application layers. We will be spinning up 2 instances of both types in different availability zones.

We once again use the count variable to specify the number of instances we want to create. Also in the web-facing instances, I'm using a shell command file("install_apache.sh") that the instances will execute on creation which will install Apache to make it a web server.

### Create EC2 Instance
resource "aws_instance" "webserver" {
  count                  = var.item_count
  ami                    = var.ami_id
  instance_type          = var.instance_type
  availability_zone      = var.availability_zone_names[count.index]
  vpc_security_group_ids = [aws_security_group.webserver-sg.id]
  subnet_id              = aws_subnet.web-facing[count.index].id
  user_data              = file("install_apache.sh")

  tags = {
    Name = "Web Server-${count.index}"
  }
}

### Create App Instance
resource "aws_instance" "appserver" {
  count                  = var.item_count
  ami                    = var.ami_id
  instance_type          = var.instance_type
  availability_zone      = var.availability_zone_names[count.index]
  vpc_security_group_ids = [aws_security_group.app-sg.id]
  subnet_id              = aws_subnet.application[count.index].id

  tags = {
    Name = "App Server-${count.index}"
  }

}

Enter fullscreen mode Exit fullscreen mode

7. RDS Instance

Next we'll move to creating the RDS instance which will be our backend database. Along with the instance itself, we will a subnet_group for our db instances. This is a subnet we specify for all of our database instances.

### Create RDS Instance

resource "aws_db_instance" "default" {
  allocated_storage      = var.rds_instance.allocated_storage
  db_subnet_group_name   = aws_db_subnet_group.default.id
  engine                 = var.rds_instance.engine
  engine_version         = var.rds_instance.engine_version
  instance_class         = var.rds_instance.instance_class
  multi_az               = var.rds_instance.multi_az
  name                   = var.rds_instance.name
  username               = var.user_information.username
  password               = var.user_information.password
  skip_final_snapshot    = var.rds_instance.skip_final_snapshot
  vpc_security_group_ids = [aws_security_group.database-sg.id]

}

### Create RDS Subnet Group

resource "aws_db_subnet_group" "default" {
  name       = "main"
  subnet_ids = aws_subnet.db[count.index]

  tags = {
    name = "My DB subnet group"
  }
}
Enter fullscreen mode Exit fullscreen mode

8.Output

This next step will print out the DNS name of the load balancer into our terminal so that once our instances are up and running, we can copy and paste this into our browser to make sure that we are able to access the site.

### Create ouput to print

output "lb_dns_name" {
  description = "The DNS name of the load balancer"
  value       = aws_lb.external-lb.dns_name

}
Enter fullscreen mode Exit fullscreen mode

9. Terraform Commands

Now that the template for our services has been created, the next step is to initialize Terraform and get our infrastructure built out. These are the Terraform commands that we will use to get things going. We will first need to open terminal and move to the directory in which we have our Terraform files.

  • Terraform init will initialize Terraform
  • Terraform fmt will make sure that our code is in the correct format and will modify our code to match the format
  • Terraform validate will make sure that we have no errors in our syntax and if we do it will return the file and line where the error occurs
  • Terraform plan is what we will do to plan out and see what resources will be created
  • Terraform apply after we run the plan command and ensure everything looks correct, we will run the apply command which is what actually builds out our infrastructure.

10. Testing

Once the services have been created, the terminal should also list the DNS name of our load balancer. We will copy and paste this into our browser to make sure that we are able to access the site.

11. Clean Up

Whenever we are finished with this project it is important to delete our infrastructure so that we don't continue to get charged. To do so we issue the Terraform destroy command which will delete everything we have created.

Success! We've just created and our three-tier AWS architecture using Terraform


Final Thoughts

When I first started using AWS I wondered why people would use the CLI or an IaC tool when creating their architecture instead of the GUI. For a beginner the GUI just seems so much easier to use, but after playing around with Terraform I can clearly see why this is the preferred way.

It is much easier and quicker to create services this way, and as an added benefit you have the code that you can reuse for other projects which saves a lot of time, and while Terraform code may look daunting for someone new to the tool, it is actually really easy to understand. Also the documentation on the Terraform website really is amazing and makes using the tool so much easier.


Full Code

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region     = "us-east-1"
  access_key = AWS_ACCESS_KEY_ID
  secret_key = AWS_SECRET_ACCESS_KEY
}


### VPC variable

variable "vpc_cidr" {
  description = "default VPC CIDR block"
  type        = string
  default     = "10.0.0.0/16"
}

### Availability Zone variable

variable "availability_zone_names" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b"]
}

### Web Subnet CIDR

variable "web_subnet_cidr" {
  type    = list(string)
  default = ["10.0.1.0/24", "10.0.2.0/24"]

}

### Application Subnet CIDR

variable "application_subnet_cidr" {
  type    = list(string)
  default = ["10.0.11.0/24", "10.0.12.0/24"]
}

### Database Subnet CIDR

variable "database_subnet_cidr" {
  type    = list(string)
  default = ["10.0.21.0/24", "10.0.22.0/24"]

}

### Database variables

variable "rds_instance" {
  type = map(any)
  default = {
    allocated_storage   = 10
    engine              = "mysql"
    engine_version      = "8.0.20"
    instance_class      = "db.t2.micro"
    multi_az            = true
    name                = "my_db"
    skip_final_snapshot = true
  }
}

### Create DB Variables
variable "user_information" {
  type = map(any)
  default = {
    username = "username"
    password = "password"
  }
  sensitive = true
}

### Instance variable 

variable "ami_id" {
  description = "default ami"
  type        = string
  default     = "ami-0cff7528ff583bf9a"
}

variable "instance_type" {
  description = "default instance type"
  type        = string
  default     = "t2.micro"
}
### Count variable

variable "item_count" {
  description = "default count used to set AZs and instances"
  type        = number
  default     = 2
}

### Create a VPC
resource "aws_vpc" "vpc-1" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "Demo VPC"
  }

}


### Create an Internet Gateway
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc-1.id

  tags = {
    Name = "IGW"
  }

}

### Create a Web Facing Routing Table
resource "aws_route_table" "public-rt" {
  vpc_id = aws_vpc.vpc-1.id


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

  }

  tags = {
    Name = "Public-Rt"
  }

}

### Create Subnet Association with Route Table
resource "aws_route_table_association" "a" {
  count          = var.item_count
  subnet_id      = aws_subnet.web-facing[count.index].id
  route_table_id = aws_route_table.public-rt.id
}

### Create Web Public Subnet
resource "aws_subnet" "web-facing" {
  count                   = var.item_count
  vpc_id                  = aws_vpc.vpc-1.id
  cidr_block              = var.web_subnet_cidr[count.index]
  availability_zone       = var.availability_zone_names[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "web-${count.index + 1}"
  }

}

### Create Application Private Subnet
resource "aws_subnet" "application" {
  count                   = var.item_count
  vpc_id                  = aws_vpc.vpc-1.id
  cidr_block              = var.application_subnet_cidr[count.index]
  availability_zone       = var.availability_zone_names[count.index]
  map_public_ip_on_launch = false

  tags = {
    Name = "application-${count.index + 1}"
  }

}


### Create Database Private Subnet
resource "aws_subnet" "db" {
  count             = var.item_count
  vpc_id            = aws_vpc.vpc-1.id
  cidr_block        = var.database_subnet_cidr[count.index]
  availability_zone = var.availability_zone_names[count.index]

  tags = {
    Name = "db-${count.index + 1}"
  }

}

### Create External Load Balancer

resource "aws_lb" "external-lb" {
  name               = "External-LB"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.web-sg.id]
  subnets            = [aws_subnet.web-facing[0].id, aws_subnet.web-facing[1].id]

  enable_deletion_protection = true
}

### Create Internal Load Balancer

resource "aws_lb" "internal-lb" {
  name               = "Internal-LB"
  internal           = true
  load_balancer_type = "application"
  security_groups    = [aws_security_group.app-sg.id]
  subnets            = [aws_subnet.application[0].id, aws_subnet.application[1].id]

  enable_deletion_protection = true
}

### Create an External Target Group

resource "aws_lb_target_group" "external-elb" {
  name     = "ALB-TG"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.vpc-1.id
}

### Create and Internal Target Group

resource "aws_lb_target_group" "internal-elb" {
  name     = "ILB-TG"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.vpc-1.id
}
### Create Target Group Attachment

resource "aws_lb_target_group_attachment" "external-elb1" {
  count            = var.item_count
  target_group_arn = aws_lb_target_group.external-elb.arn
  target_id        = aws_instance.webserver[count.index].id
  port             = 80

  depends_on = [
    aws_instance.webserver,
  ]
}

resource "aws_lb_target_group_attachment" "internal-elb1" {
  count            = var.item_count
  target_group_arn = aws_lb_target_group.internal-elb.arn
  target_id        = aws_instance.appserver[count.index].id
  port             = 80

  depends_on = [
    aws_instance.webserver,
  ]
}


### Create LB Listener

resource "aws_lb_listener" "external-elb" {
  load_balancer_arn = aws_lb.external-lb.arn
  port              = "80"
  protocol          = "HTTP"


  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.external-elb.arn
  }
}

resource "aws_lb_listener" "internal-elb" {
  load_balancer_arn = aws_lb.internal-lb.arn
  port              = "80"
  protocol          = "HTTP"


  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.internal-elb.arn
  }
}

### Create Security Groups

resource "aws_security_group" "web-sg" {
  name        = "Web-SG"
  description = "Allow HTTP Inbound Traffic"
  vpc_id      = aws_vpc.vpc-1.id

  ingress {
    description = "HTTP from VPC"
    from_port   = 80
    to_port     = 80
    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"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "Web-SG"
  }
}

### Create Web Server Security Group

resource "aws_security_group" "webserver-sg" {
  name        = "Webserver-SG"
  description = "Allow Inbound Traffic from ALB"
  vpc_id      = aws_vpc.vpc-1.id

  ingress {
    description     = "Allow traffic from web layer"
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.web-sg.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "Webserver-SG"
  }
}

### Create Application Security Group

resource "aws_security_group" "app-sg" {
  name        = "App-SG"
  description = "Allow SSH Inbound Traffic"
  vpc_id      = aws_vpc.vpc-1.id

  ingress {
    description     = "SSH from VPC"
    from_port       = 22
    to_port         = 22
    protocol        = "tcp"
    security_groups = [aws_security_group.web-sg.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "App-SG"
  }
}
### Created Database Security Group

resource "aws_security_group" "database-sg" {
  name        = "Database-SG"
  description = "Allow Inbound Traffic from application layer"
  vpc_id      = aws_vpc.vpc-1.id

  ingress {
    description     = "Allow traffic from application layer"
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.webserver-sg.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "Database-SG"
  }
}

### Create EC2 Instance
resource "aws_instance" "webserver" {
  count                  = var.item_count
  ami                    = var.ami_id
  instance_type          = var.instance_type
  availability_zone      = var.availability_zone_names[count.index]
  vpc_security_group_ids = [aws_security_group.webserver-sg.id]
  subnet_id              = aws_subnet.web-facing[count.index].id
  user_data              = file("install_apache.sh")

  tags = {
    Name = "Web Server-${count.index}"
  }
}

### Create App Instance
resource "aws_instance" "appserver" {
  count                  = var.item_count
  ami                    = var.ami_id
  instance_type          = var.instance_type
  availability_zone      = var.availability_zone_names[count.index]
  vpc_security_group_ids = [aws_security_group.app-sg.id]
  subnet_id              = aws_subnet.application[count.index].id

  tags = {
    Name = "App Server-${count.index}"
  }

}
### Create RDS Instance

resource "aws_db_instance" "default" {
  allocated_storage      = var.rds_instance.allocated_storage
  db_subnet_group_name   = aws_db_subnet_group.default.id
  engine                 = var.rds_instance.engine
  engine_version         = var.rds_instance.engine_version
  instance_class         = var.rds_instance.instance_class
  multi_az               = var.rds_instance.multi_az
  name                   = var.rds_instance.name
  username               = var.user_information.username
  password               = var.user_information.password
  skip_final_snapshot    = var.rds_instance.skip_final_snapshot
  vpc_security_group_ids = [aws_security_group.database-sg.id]

}

### Create RDS Subnet Group

resource "aws_db_subnet_group" "default" {
  name       = "main"
  subnet_ids = aws_subnet.db[count.index]

  tags = {
    name = "My DB subnet group"
  }
}

### Create ouput to print

output "lb_dns_name" {
  description = "The DNS name of the load balancer"
  value       = aws_lb.external-lb.dns_name

}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)