Terraform to Set Up Multi-Tier Infrastructure on AWS

Launching WordPress with MySQL in a VPC with Public and Private Subnets on AWS by Terraform.

Ajay Pathak
9 min readOct 24, 2020

Hey everyone,

In this article, We will gonna learn how to setup a VPC with Public and Private Subnets on AWS. And then we will setup a Multi-tier Infrastructure in which we will launch the WordPress site on the Public Subnet and MySQL Database in the Private Subnet. And we will do everything using Terraform only.

So Let me first tell you something about Terraform.

What is Terraform ?

Terraform is an open-source infrastructure as code software tool created by HashiCorp. Users define and provision data center infrastructure using a declarative configuration language known as HashiCorp Configuration Language (HCL), or optionally JSON.

The key features of Terraform are:

  • Infrastructure as Code: Infrastructure is described using a high-level configuration syntax. This allows a blueprint of your datacenter to be versioned and treated as you would any other code. Additionally, infrastructure can be shared and re-used.
  • Execution Plans: Terraform has a “planning” step where it generates an execution plan. The execution plan shows what Terraform will do when you call apply. This lets you avoid any surprises when Terraform manipulates infrastructure.
  • Resource Graph: Terraform builds a graph of all your resources, and parallelizes the creation and modification of any non-dependent resources. Because of this, Terraform builds infrastructure as efficiently as possible, and operators get insight into dependencies in their infrastructure.
  • Change Automation: Complex changesets can be applied to your infrastructure with minimal human interaction. With the previously mentioned execution plan and resource graph, you know exactly what Terraform will change and in what order, avoiding many possible human errors.

In short, I can say we will write a Terraform code to setup or to manage our whole infrastructure, so basically, here we are implementing Infrastructure as Code (IaC). And due to this we just have to run this code to setup our complete infrastructure whenever we want or to do some changes in our infrastructure we just have to do changes in this code. So it saves our time and we can setup the whole infrastructure as many times we want and we don't have to manually create all the resources every time.

So now let us discuss what we actually gonna do to setup all these things.

Resources we need to create

  • Create a VPC with 2 Subnets i.e., Public and Private
  • Create and attach an Internet Gateway to Public Subnet
  • Create Security groups for both WordPress and MySQL
  • Launch WordPress site in Public Subnet, so that anyone from the outside world can able to access our site
  • Launch MySQL Database in Private Subnet to make it secure from the outside world
  • Create a NAT Gateway in Public Subnet and attach it with Private Subnet so that we can able to access the internet in MySQL instance

Let’s write Terraform code to create all these things…

Before doing anything we first need to install Terraform and then setup the AWS IAM account in the same system in which we have installed terraform. And then just write this terraform code to tell terraform about our AWS account.

provider "aws" {
region = "ap-south-1"
profile = "Ajay"
}

And then run this terraform command

terraform init

Create a VPC

In every cloud, we always need a VPC(Virtual Private Cloud) to launch any service so we first create a VPC, and to create VPC the terraform code is.

resource "aws_vpc" "vpc" {
cidr_block = "10.2.0.0/16"
instance_tenancy = "default"
enable_dns_hostnames = true
tags = {
Name = "vpc_task3"
}
}

Create Subnets

It is the subnets in which we launch any service, so in every VPC we need to create subnets. And here we create 2 Subnets in the VPC created above.

Public Subnet

In this Subnet, we will gonna launch our WordPress site and it is public since we want that our site is accessible by the outside world so that's why we need to give public connectivity to this subnet and any instance or service we launch in this subnet will have some Public IP or URL. So the terraform code for creating subnet is

resource "aws_subnet" "pub_subnet" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.2.1.0/24"
availability_zone = "ap-south-1b"
tags = {
Name = "public-subnet"
}
}

Private Subnet

In this subnet, we will launch our MySQL database instances and for every company, their databases are the most critical thing for them as the data is money for them and no one wants their databases to get hacked or breach. Everyone wants their database to be as secure as it can. So this Private Subnet would not have Public connectivity only the local connectivity within the VPC.

resource "aws_subnet" "priv_subnet" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.2.2.0/24"
availability_zone = "ap-south-1a"
tags = {
Name = "private-subnet"
}
}

Create Route Table

when we create a VPC in AWS then it also creates a Route table i.e., the main route table but here we are creating separate Route Tables for the Subnets.

Route Table for Public Subnet

resource "aws_route_table" "public" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "public-rt"
}
}
resource "aws_route_table_association" "rt_pub_subnet" {
subnet_id = aws_subnet.pub_subnet.id
route_table_id = aws_route_table.public.id
}

Route Table for Private Subnet

resource "aws_route_table" "private" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "private-rt"
}
}
resource "aws_route_table_association" "rt_priv_subnet" {
subnet_id = aws_subnet.priv_subnet.id
route_table_id = aws_route_table.private.id
}

Create Internet Gateway

Now to make our Subnet Public we have to give Internet connectivity to it and for this, we have to attach an Internet Gateway with it. So for creating the internet gateway the terraform code is

resource "aws_internet_gateway" "ig" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "task3-ig"
}
}

Terraform code to attach Internet Gateway with Public Subnet

resource "aws_route" "route-ig" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.ig.id
}

Create NAT Gateway

In the Private Subnet, we haven't attached any internet gateway as we want to secure it from the outside world but when we launch some instance or any other service then we may need internet connectivity like to download something or upload. But if we attach an internet gateway to it then there is no meaning of private in this subnet.

So if we want to access the internet in a private manner then we can attach a NAT Gateway between the Public and Private Subnet, due to this we can able to go to the internet world from the Private Subnet and no one from the internet can able to connect with us. And we go to the internet through the internet gateway of the Public Subnet. So the terraform code to create and attach NAT Gateway is

resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat_eip.id
subnet_id = aws_subnet.pub_subnet.id
tags = {
Name = "task3-NAT"
}
}
resource "aws_route" "route-nat" {
route_table_id = aws_route_table.private.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id
}

Now everything is ready and we just have to launch WordPress and MySQL instances.

Create a Key Pair

We will attach this keypair with the WordPress and MySQL instances.

resource "tls_private_key" "task3_key"  {
algorithm = "RSA"
}
resource "aws_key_pair" "keypair" {
key_name = var.key
public_key = tls_private_key.task3_key.public_key_openssh
}
resource "local_file" "download_key" {
content = tls_private_key.task3_key.private_key_pem
filename = "${var.key}.pem"
}

Create Security Groups

For WordPress Instances

Since we want that anyone from the outside world can access our WordPress site and WordPress runs on Port No 80 so we have to create an ingress rule for port 80 and also for port 22 so that we can do SSH into the WordPress instance. The terraform code for creating the security group is

resource "aws_security_group" "sg_wp" {
name = "wordpress-sg"
description = "Allow HTTP and SSH inbound traffic"
vpc_id = aws_vpc.vpc.id
ingress {
description = "Allow SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [ "0.0.0.0/0" ]
}
ingress {
description = "Allow HTTP"
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 = "allow_ssh_http"
}
}

For MySQL Instance

For MySQL, we want that only WordPress instances can able to connect with it and MySQL runs on Port No 3306 so we have to create an ingress rule for Port 3306 that allows only WordPress instance to connect and also for Port 22 so that we can able to do SSH into this but here we can only do SSH from the instances in Public Subnet. So the terraform code for this is

resource "aws_security_group" "sg_mysql" {
name = "mysql-sg"
description = "Allow MySQL inbound traffic"
vpc_id = aws_vpc.vpc.id
ingress {
description = "Allow MySQL"
from_port = 3306
to_port = 3306
protocol = "tcp"
cidr_blocks = [ "${aws_instance.wordpress.private_ip}/32" ]
}
ingress {
description = "Allow SSH"
from_port = 22
to_port = 22
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 = "allow_mysql_ssh"
}
}

Launch WordPress and MySQL Instances

Now we have to launch WordPress and MySQL instances but in AWS we first have to launch a simple instance like amazon-linux, redhat, ubuntu etc and then in these instances we have to install and configure our WordPress and MySQL programs. And if we want to launch multiple instances for them then we have to install both the programs again.

So to solve this I have installed and setup WordPress and MySQL programs in some instances and created an AMI(Amazon Machine Image) from those instances. So next time we just have to launch the instances using these AMI’s and we can directly use WordPress and MySQL programs.

Code for WordPress Instance

resource "aws_instance" "wordpress" {
ami = "ami-049609235004b64bc"
instance_type = var.inst_type
subnet_id = aws_subnet.pub_subnet.id
associate_public_ip_address = true
key_name = aws_key_pair.keypair.key_name
security_groups = [ aws_security_group.sg_wp.id ]
tags = {
Name = "Wordpress"
}
}

Code for MySQL Instance

resource "aws_instance" "mysql" {
ami = "ami-0df8aa28f5a7d2957"
instance_type = var.inst_type
subnet_id = aws_subnet.priv_subnet.id
associate_public_ip_address = false
key_name = aws_key_pair.keypair.key_name
security_groups = [ aws_security_group.sg_mysql.id ]
tags = {
Name = "MySQL"
}
}

In the MySQL instance code, you can see I have made the Public IP Address false because we don't want anyone from the outside world to connect with it.

Note:- The AMI used for launching both the instances would not work as they are private so you have to create your own AMI or setup the WordPress and MySQL in any other way you know.

To access the WordPress site we need the Public IP of the WordPress instance and we also need the Private IP of the MySQL instance to connect it with WordPress, so for this, we can write this code.

output "ip_wordpress" {
value = aws_instance.wordpress.public_ip
}
output "ip_mysql" {
value = aws_instance.mysql.private_ip
}

Now all the code is ready and to setup everything we need to run this Terraform code and to run the whole terraform code, we use this command.

terraform apply 
or
terraform apply --auto-approve

Here is the output of the above command

Now we can access our WordPress site from the browser

So now our WordPress site is ready and MySQL Database is also gets connected with it and the interesting thing is our MySQL Database is so secure that even having the internet access no one from the outside world can able to connect with it and also we can do SSH to it only through the instances in Public Subnet.

Thanks for reading this….

I have uploaded all the codes on my GitHub Repository.

Connect with me on LinkedIn

--

--

Ajay Pathak

Automation Tech Enthusiast | MLOps, DevOps Assembly Lines, Hybrid Multi Cloud, Terraform, Ansible and many more