Automated Website deployment using Terraform
By: Daksh Jain
Tags: Terraform, Cloudfront, AWS, Aws Ec2, Terraform Cloud

Clients connect to Google, Amazon, Flipkart more often because it is fast. Headquartered in any part of the world, how come at any place, the webpage shows up without any delay? The contents of the web page, even the images and all the graphics show up in less than a second.

How it happens?

All of this is possible with the advent of Cloud Technology. In this article I will be showing how to create a Terraform code from scratch for a website deployment.

Using Terraform we will do multiple things in a proper sequence:

Create an AWS Instance — EC2

Install required dependencies, modules, softwares

Create an EBS for persistent storage

for persistent storage Attach, Format and Mount it in a folder in the instance

Clone the code sent by developer on GitHub in the folder

in the folder Create an S3 Bucket for storage of static data

for storage of static data This will be sent to all the edge locations using CloudFront

Finally loading the webpage on your favourite browser automatically

Terraform

Terraform uses Infrastructure as Code to provision and manage any cloud, infrastructure, or service. This means that we can write the code in 1 single unified language- HCL(HashiCorp Configuration Language) that will work with any kind of Cloud: Public or Private.

NOTE: I am using Terraform on my local system running on Windows 10.

For this particular setup you should have the following things installed on your machine:

Git AWS Command Line Interface Terraform

Let’s start by building the code.

provider "aws" {

profile = "daksh"

region = "ap-south-1"

}

provider is a keyword used to tell Terraform which cloud platform we are using. I have specified aws, now this will help terraform to download the plugins required for AWS cloud platform.

profile is given so that you need not login through the code. The cloud engineer just provides the profile name and the terraform code will pick up credentials from the local system as shown in the figure:

How to Configure a Profile

aws configure --profile profilename

Using this command you can setup your profile.

resource "aws_security_group" "allow_traffic" {

name = "allow_traffic"

description = "Allow TLS inbound traffic"

vpc_id = "vpc-59766a31" ingress {

description = "http"

from_port = 80

to_port = 80

protocol = "tcp"

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"]

} ingress {

description = "ping"

from_port = -1

to_port = -1

protocol = "icmp"

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_traffic"

}

}

VPC ID can be found in your AWS account details.

Ingress means the traffic that is coming in to our website. We need to specify this, keeping in mind what ports we want to keep open. I have kept open 3 ports: ssh, http, icmp.

SSH for testing purpose so that remotely we can connect to the AWS EC2 instance.

for testing purpose so that remotely we can connect to the AWS EC2 instance. HTTP so that traffic can hit on the website.

so that traffic can hit on the website. ICMP to check ping connectivity.

Egress has been set to all ports so that outbound traffic originating from within a network can go.

resource "aws_instance" "webserver" {

ami = "ami-0447a12f28fddb066"

instance_type = "t2.micro"

security_groups = [ "allow_traffic" ]

key_name = "key1"



connection {

type = "ssh"

user = "ec2-user"

private_key = file("C:/Users/Daksh/Downloads/key1.pem")

host = aws_instance.webserver.public_ip

} provisioner "remote-exec" {

inline = [

"sudo yum install httpd php git -y",

"sudo systemctl restart httpd",

"sudo systemctl enable httpd"

]

}

tags = {

Name = "webserver"

}

}

Now as a next step we have to download the required tools, so that our webpage can be deployed, managed and can be viewed by the client.

So first we create an EC2 instance and specify all the required details.

Then we create a connection so that we can do SSH on the the instance, to install the tools.

Then using Provisioner we go to the remote system and using the inline method run multiple commands that are compatible with the Linux flavour I am using.

(If you are using some other Operating System, then you must know the equivalent commands to do the same.)

resource "aws_ebs_volume" "web_vol" {

availability_zone = aws_instance.webserver.availability_zone

size = 1

tags = {

Name = "web_vol"

}

}

Then create another EBS storage, to make the data persistent. Make sure that you make the EBS in the same availability zone in which the instance is launched. that is the reason I have used a better approach to tackle it by using the internal keywords that can be found on the Terraform docs.

resource "aws_volume_attachment" "web_vol" { depends_on = [

aws_ebs_volume.web_vol,

]

device_name = "/dev/xvdf"

volume_id = aws_ebs_volume.web_vol.id

instance_id = aws_instance.webserver.id

force_detach = true connection {

type = "ssh"

user = "ec2-user"

private_key = file("C:/Users/Daksh/Downloads/key1.pem")

host = aws_instance.webserver.public_ip

} provisioner "remote-exec" {

inline = [

"sudo mkfs.ext4 /dev/xvdf",

"sudo mount /dev/xvdf /var/www/html",

"sudo rm -rf /var/www/html/*",

"sudo git clone https://github.com/Dakshjain1/php-cloud.git /var/www/html/" ]

}

}

Now to use a new block storage, first we need to format it, then mount it.

Also it is not mandatory to create a Partition in a Storage device, without creating a partition also we can format. So, here we are going to use a similar kind of approach.

Again using provisioner we use the inline method to run the commands:

sudo mkfs.ext4 /dev/xvdf : create partition

: create partition sudo mount /dev/xvdf /var/www/html : mount the partition

: mount the partition sudo rm -rf /var/www/html/* : empty the folder because git clone works only in empty directory

: empty the folder because git clone works only in empty directory sudo git clone https://github.com/Dakshjain1/php-cloud.git /var/www/html/ : git clone to get the webpage files

resource "aws_s3_bucket" "s3bucket" {

bucket = "123mywebbucket321"

acl = "public-read"

region = "ap-south-1" tags = {

Name = "123mywebbucket321"

}

}

Next we create the S3 bucket. S3 bucket is used to store the static data like images, videos and other graphics. This is required so that anywhere from the world, if the website is opened, the static contents also get loaded without any delay or latency.

resource "aws_s3_bucket_object" "image-upload" { depends_on = [

aws_s3_bucket.s3bucket,

]

bucket = aws_s3_bucket.s3bucket.bucket

key = "flower.jpg"

source = "C:/Users/Daksh/Desktop/CLOUD/task1/pic.jpg"

acl = "public-read"

} output "bucketid" {

value = aws_s3_bucket.s3bucket.bucket

}

Now I have to upload an object into the S3 bucket created.

First I have used a keyword depends_on. This is used because terraform code doesn’t work in a sequential manner. So to prevent a condition where the bucket is not yet created but object is trying to get uploaded we are doing this.

Like this the object will start to upload only after the bucket creation is done.

key is the name of the file that will show in the bucket and source is the path of the file I want to upload in the bucket. This file/object can come from anywhere Google, local system or somewhere else.

variable "oid" {

type = string

default = "S3-"

} locals {

s3_origin_id = "${var.oid}${aws_s3_bucket.s3bucket.id}"

} resource "aws_cloudfront_distribution" "s3_distribution" {

depends_on = [

aws_s3_bucket_object.image-upload,

]

origin {

domain_name = "${aws_s3_bucket.s3bucket.bucket_regional_domain_name}"

origin_id = "${local.s3_origin_id}"

} enabled = true default_cache_behavior {

allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]

cached_methods = ["GET", "HEAD"]

target_origin_id = "${local.s3_origin_id}" forwarded_values {

query_string = false

cookies {

forward = "none"

}

} viewer_protocol_policy = "allow-all"

min_ttl = 0

default_ttl = 3600

max_ttl = 86400

} restrictions {

geo_restriction {

restriction_type = "none"

}

} viewer_certificate {

cloudfront_default_certificate = true

} connection {

type = "ssh"

user = "ec2-user"

private_key = file("C:/Users/Daksh jain/Downloads/CLOUD/key1.pem")

host = aws_instance.webserver.public_ip

} provisioner "remote-exec" {

inline = [

"sudo su <<END",

"echo \"<img src='http://${aws_cloudfront_distribution.s3_distribution.domain_name}/${aws_s3_bucket_object.image-upload.key}' height='200' width='200'>\" >> /var/www/html/index.php",

"END",

]

}

}

The main aim of creating an S3 Bucket is that there is no latency. So this can be achieved in AWS using Edge Locations. These are small data centres that are created by AWS all over the world. To use this mechanism, we use CloudFront service of AWS.

I have created a variable where I have set a value to “S3-”.