Working with AWS and Terraform for NaaS — An Introduction

Akshaya Balaji
7 min readJul 12, 2020
Source: Google Images

In a previous article, I had explained how to create an AWS EC2 instance with added EBS storage using Terraform. In the previous, many of the basic networking components provided by AWS like the Virtual Private Network (VPC), Internet Gateway, Routing Tables etc. were already configured.

In this article, we are going to look at how we can customize the setup of these networking components and launch our own EC2 instances.

Objectives

Our main objectives are as follows:

  1. Create a VPC, with 2 subnets — a public and private subnet.
  2. Create a public-facing Internet Gateway to connect the VPC to the outside world (Internet). Attach the gateway to the VPC we have created.
  3. Create a routing table for the Internet Gateway to allow instances launched in the public subnet to connect with the outside world. This is done by associating the routing table with the public subnet.
  4. Launch an AWS EC2 instance with WordPress setup on it on the public subnet. Also, attach a key to it. It must allow traffic through HTTP so that the clients can connect to the blog.
  5. Launch an AWS EC2 instance with MySQL setup on it on the private subnet. Also, attach a key to it. It must allow ingress only through port 3306 (default port to access MySQL database) and only the WordPress instances must be able to connect to the MySQL database.

Writing the Terraform Code

Firstly, we begin by making a workspace and writing the Terraform code in a file with .tf extension. In the Terraform code file, we first define the provider, which is AWS in our case, and the user profile through which we will be creating all of our resources. Then, we begin with the creation of the VPC. The VPC can be described as a space where we can launch AWS resources into a virtual network that we have defined. The VPC is created using the aws_vpc resource. In the resource, we assign a network range to the VPC using the cidr_block parameter.

Once the VPC is created, we configure the public and private subnets we need to use the aws_subnet resource. By setting the map_public_ip_on_launch parameter to true or false, we can set up a public or a private subnet. Setting this parameter to true means that any instance launched in this subnet will be automatically assigned a public address unless specified otherwise. If the parameter is set to false, then by default, the instances launched in the subnet will not be assigned a public IP.

provider "aws" {
region = "ap-south-1"
profile = "iam_akshaya"
}
resource "aws_vpc" "task3_vpc" {
cidr_block = "192.168.0.0/16"
instance_tenancy = "default"
tags = {
Name = "task3_vpc"
}
}
resource "aws_subnet" "subnet_pub_1a" {
vpc_id = aws_vpc.task3_vpc.id
cidr_block = "192.168.0.0/24"
availability_zone = "ap-south-1a"
map_public_ip_on_launch = true
tags = {
Name = "subnet_pub_1a"
}
}
resource "aws_subnet" "subnet_pri_1b" {
vpc_id = aws_vpc.task3_vpc.id
cidr_block = "192.168.1.0/24"
availability_zone = "ap-south-1b"
tags = {
Name = "subnet_pri_1b"
}
}

Next, we create the Internet Gateway(IGW). An internet gateway serves two purposes: to provide a target in your VPC route tables for internet-routable traffic and to perform network address translation (NAT) for instances that have been assigned public IPv4 addresses. We create the Internet Gateway using the aws_internet_gateway resource of Terraform.

After the IGW is created, we proceed with the creation of the route table, which is a sub-service of VPCs in AWS. It is created using the aws_route_table resource. The route table must be created in relation to the Internet Gateway we have just created. Finally, the route table is associated with the public subnet, as only the public subnet must be allowed to have access to the Internet and the resources created in the private subnet are restricted from accessing the Internet. The association is set using the aws_route_table_association resource.

resource "aws_internet_gateway" "task3_int_gw" {
vpc_id = aws_vpc.task3_vpc.id
tags = {
Name = "task3_int_gw"
}
}
resource "aws_route_table" "task3_route_table" {
depends_on = [aws_internet_gateway.task3_int_gw,]
vpc_id = aws_vpc.task3_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.task3_int_gw.id
}
tags = {
Name = "task3_route_table"
}
}
resource "aws_route_table_association" "rt_tbl_pri" {
depends_on = [aws_route_table.task3_route_table,]
subnet_id = aws_subnet.subnet_pub_1a.id
route_table_id = aws_route_table.task3_route_table.id
}

Now, we set the rules for the two types of instances we want to create. The rules for the WordPress instances allow access through port 80 for clients to access the blog. Also, to give ourselves more power in handling the instances, we can enable SSH access through port 22. Similarly, the MySQL instances must be accessible only by the WordPress instances and no user from the outside world (outside the subnet) must be able to access the MySQL instances.

Typically, it is hard to write rules for cases like those of the MySQL instances, as the security rules are often devised based on the IP addresses of the possible instances/users that can access them. However, in a dynamic setup, where the IP address can change (if we do not use Elastic IPs provided by AWS) and a variable number of instances themselves, using IP addresses is not feasible. Rather, we can write rules for the security group of the MySQL instances based on the security groups of the WordPress instances. Simply put, this means that only those EC2 instances with the security groups written for the WordPress instances will be allowed to access the MySQL instances.

resource "aws_security_group" "wp_sg" {
name = "wp_sg"
description = "SG rules for Wordpress instances"
vpc_id = aws_vpc.task3_vpc.id
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "ICMP-IPv4"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "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 = "wp_sg"
}
}
resource "aws_security_group" "mysql_sg" {

depends_on = [aws_security_group.wp_sg,]
name = "mysql_sg"
description = "SG rules for MySQL instances"
vpc_id = aws_vpc.task3_vpc.id
ingress {
description = "MySQL access"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.wp_sg.id]
}
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.wp_sg.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "mysql_sg"
}
}

Finally, we create the EC2 instances. Typically, the QuickStart AMIs of AWS comes with all the requisites needed for us to install WordPress and MYSQL, but the Community AMIs often come with WordPress and MySQL installed. In our case, we will be using an AMI with WordPress pre-installed, and separately install MySQL on an Amazon Linux AMI. The WordPress instance is launched on the public subnet, while the MySQL instance is launched on the private subnet.

resource "aws_instance"  "wp_os" {ami = "ami-004a955bfb611bf13" # bitnami-wordpress-4.9.5-1-linux-redhat-7.4-x86_64-hvm-ebs (ami-004a955bfb611bf13)
subnet_id = aws_subnet.subnet_pub_1a.id
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.wp_sg.id,]
key_name = "mykey123"
tags = {
Name = "wp_os"
}
}
resource "aws_instance" "mysql_os" {ami = "ami-08706cb5f68222d09" # Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type - ami-08706cb5f68222d09
subnet_id = aws_subnet.subnet_pri_1b.id
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.mysql_sg.id,]
key_name = "mykey123"
user_data = <<-EOF
#!bin/bash
sudo yum update
sudo yum install mysql157-server -y
sudo service mysqld start
sudo chkconfig mysqld on
EOF
tags = {
Name = "mysql_os"
}
}

Creating all the resources

To create all the resources, we first write all the code above in a file with .tf extension in our workspace. Then we configure the AWS profile using the following command

> aws configure

Finally, the resources can be created by the following set of commands.

> terraform init
> terraform plan
> terraform validate
> terraform apply --auto-approve

The terraform plan and terraform validate commands are executed even if we run terraform apply --auto-approve, so we can skip those commands if we wish to. Now, we can observe the resources created from both the CMD Window as well as the AWS Web UI.

Figure 1: Successful creation of the resources seen on the CMD Window
Figure 2: Custom VPC
Figure 3: Public and Private Subnets (After assigning IPs to the instances)
Figure 4: Internet Gateway
Figure 5: Route Table
Figure 6: WordPress (wp_os) and MySQL (mysql_os) Instances
Figure 7: WordPress is successfully launched using public IP of WordPress instance.

Typically, the WordPress AMIs come with built-in support for MySQL databases as they are launched on the same instance. If we wish to integrate the MySQL database in another instance, we can modify the configuration file of WordPress called wp-config.php.

This work has been done as a part of the Hybrid Multi-Cloud Computing Program conducted by Mr. Vimal Daga from LinuxWorld Informatics, Pvt., Ltd.

Do check out my other work with AWS!

  1. Integration of AWS, Terraform, and GitHub for Automated Deployment Infrastructure: https://medium.com/@akshayavb99/integration-of-aws-terraform-and-github-for-automated-deployment-infrastructure-da0a19ff4e86
  2. Working with AWS EKS — An Introduction: https://medium.com/@akshayavb99/working-with-aws-eks-an-introduction-8c1886407ec8

--

--

Akshaya Balaji

MEng EECS | ML, AI and Data Science Enthusiast | Avid Reader | Keen to explore different domains in Computer Science