Efficiently Deploy Cloud Resources on AWS with Terraform
Tags: aws, terraform, cloudcomputing, devops
Published: 2024-05-02 23:52:46


Infrastructure as code(IaC) tools has become very popular and useful in cloud engineering. Tools like Chef, puppet, and Ansible are configuration management tools, focusing on installing and managing applications on existing servers. 

Provisioning tools like Pulumi, Crossplane and Terraform create new servers and other infrastructure components.

Terraform is a declarative coding tool that uses a high-level configuration language called Hashicorp configuration language(HCL) to describe the desired cloud or on-premise infrastructure for running an application.

In this article, we will build a simple cloud architecture with a virtual private cloud(VPC), subnets, route tables, security groups and an ec2 instance and deploy a web server all using a Terraform script. 

### Prerequisites
* Basic knowledge of cloud environments(AWS)
* An AWS account
* Terraform. You can download it from here
* VS Code with terraform plugin. VS code can be downloaded from its [official website](https://code.visualstudio.com/Download). Once installed navigate to the plugin marketplace and search for “terraform” and install.
![screenshot of vs code with the terraform plugin installation page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9l73vvhr839ur50mkqt1.png)

### Understanding the infrastructure
**VPC:** Virtual Private Cloud is an isolated section of the AWS cloud where you can launch AWS resources in a defined virtual network. It provides isolation and control over our network environment which includes IP address range selection, subnet creation and route table configurations.
**Subnets:** Subnets are a range of IP addresses in our VPC that you designate to group resources based on security requirements.
**Route Table:** is a set of rules, known as routes that are used to determine where traffic is directed within a VPC or to an external network
**Internet Gateway:** An Internet gateway enables communications between instances within a VPC and the Internet. It allows them to send data to and from the internet.
**Security groups:** Security groups act as virtual firewalls for your server instances. It controls inbound and outbound traffic. It allows us to define which traffic is allowed to reach our instances.
**Elastic IP:** Elastic IP is a static IPv4 address assigned to your instance. When an AWS instance is restarted, the IP address changes. An elastic IP enables us to attach a persistent IP address to our instance.
**EC2 Instance:** An EC2 instance is a virtual server in AWS that hosts our application.

### Setting Up Terraform configuration

To begin writing our terraform script:

* Create a folder, lets call it "terraform-project"
* Open this folder in vs code and create a file within this folder, we'll call it `main.tf` for the sake of this guide.

Let's write some scripts.

**AWS Provider**
The first step in any Terraform script is to define the provider. In our case, we're using AWS.

The provider block is used to specify the details of the cloud provider, such as Azure, GCP, Oracle Cloud, and even container orchestration platforms like Kubernetes, where all the resources will be created. Here's what our provider block would look like:
```HCL
provider "aws" {
  region     = "us-east-1"
  access_key = ""
  secret_key = ""
}
```
`provider "aws" ` - This line declares a provider block for AWS. Terraform uses a plugin-based architecture and “aws” is the plugin for Amazon Web Service.
`region = "us-east-1" ` - This line sets the AWS region where the resources will be created. In this case, it’s set to “us-east-1”, which corresponds to the N. Virginia region.
`access_key = “” ` - this line is where you should put your AWS access key. It is part of your AWS security credentials
`Secret_key = “” `- this line is where you should put your AWS access key.
To obtain your secret and access key, follow these steps:
* Navigate to the AWS Management Console.
* Click on your name in the top right corner and select “Security Credentials”.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1blt4xrmgbc737gx4fe3.png)
* Select “Create Access Key”. Your access and secret key will be provided. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9lubz9pkvjl4hebetpza.png)
* Don’t forget to download the CSV file for safekeeping.
Remember, these keys are sensitive information. Store them securely and do not share them publicly. 

**Virtual Private Cloud(VPC)**
```HCL
resource "aws_vpc" "prod-vpc" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "production"
  }
}
```
`resource ”aws vpc” “prod-vpc” `- this line declares a resource of type “aws vpc” (an AWS VPC) and gives it the name `"prod-vpc" `. This name is used to refer to this resource in other parts of our terraform script
`Cidr_block = “10.0.0.0/16” ` - This line sets the IP address range for the VPC in CIDR notation. The range “10.0.0.0/16” includes all IP addresses from 10.0.0.0 to 10.0.255.255.
`Tags{Name = production}` - Tags are key-value pairs that you can attach to AWS resources to easily organize, search, and identify them. In our case, our VPC has the tag “production”.

**Internet Gateway**
```HCL
resource "aws_internet_gateway" "gw" {
    vpc_id = aws_vpc.prod-vpc.id
}
```
`resource “aws_internet_gateway” “gw”` - This line declares a resource of type “aws_internet_gateway” (an AWS Internet Gateway), and gives it the name “gw”. This name is used to refer to this resource in other parts of our Terraform script
`vpc_id = aws_vpc.prod-vpc.id` - This line attaches the Internet Gateway to the VPC that was created earlier in the script. It does this by setting the vpc_id property of the Internet Gateway to the ID of the VPC. `The aws_vpc.prod-vpc.id` syntax is used to get the ID of the VPC named `prod-vpc`.

**Route Table**
```HCL
resource "aws_route_table" "prod-route-table" {
 vpc_id = aws_vpc.prod-vpc.id
 route {
   cidr_block = "0.0.0.0/0"
   gateway_id = aws_internet_gateway.gw.id
 }
  tags = {
   Name = "prod"
 }
}
```
`resource “aws_route_table” “prod-route-table”` - This line is declaring a resource of type “aws_route_table” and the name of this particular resource is “prod-route-table”.
`vpc_id = aws_vpc.prod_vpc.id` - this line associates the route table with the VPC that has been created earlier in the script with the name `prod-vpc`. The `.id` attribute fetches the ID of that VPC.
The route block within the resource defines a route for the route table.
`cidr_block = "0.0.0.0/0"`- This line specifies the destination for the route as 0.0.0.0/0, which represents all IPv4 addresses. In the context of a route table, this is often used to define the default route.
`gateway_id = aws_internet_gateway.gw.id` - This line is setting the Internet Gateway (gw which was declared earlier) as the target of the route, meaning that any traffic destined for 0.0.0.0/0 will be directed to this Internet Gateway.

**Subnet**
```HCL
resource "aws_subnet" "subnet-1" {
   vpc_id = aws_vpc.prod-vpc.id
   cidr_block = "10.0.1.0/24"
   availability_zone = "us-east-1a"
   tags = {
       Name = "prod"
   }
}
```
`resource “aws_subnet” “subnet-1” ` - This line declares a resource of type aws_subnet and the name of this particular resource is subnet-1.
`vpc_id = aws_vpc.prod-vpc.id` - This line associates the subnet with the VPC that has been created earlier in the script with the name `prod-vpc`. The `.id` attribute fetches the ID of that VPC.
`cidr_block = "10.0.1.0/24"` - This line specifies the IP address range for the subnet in CIDR notation. The range 10.0.1.0/24 includes all IP addresses from 10.0.1.0 to 10.0.1.255.
`availability_zone = "us-east-1a"` - This line specifies the availability zone in which to create the subnet. In this case, it’s us-east-1a.

**Route Table Association**
```HCL 
resource "aws_route_table_association" "a" {
   subnet_id = aws_subnet.subnet-1.id
   route_table_id = aws_route_table.prod-route-table.id
}
```
`resource “aws_route_table_association” ”a”` - This line is declaring a resource of type `aws_route_table_association` and the name of this particular resource is `a`.
 `subnet_id = aws_subnet.subnet-1.id` - This line associates the route table with the subnet that has been created earlier in the script with the name `subnet-1`. The `.id` attribute fetches the ID of that subnet.
`route_table_id = aws_route_table.prod-route-table.id` This line specifies the route table to associate with the subnet. It refers to the route table that was created earlier with the name `prod-route-table`.
This block means that the routing policies defined in the `prod-route-table` will now apply to any resources that are deployed within subnet-1

**Security Group**
```HCL
resource "aws_security_group" "allow-web" {
    name = "allow_web_traffic"
    description = "allow traffic"
    vpc_id = aws_vpc.prod-vpc.id

    ingress {
        description = "https"
        from_port        = 443
        to_port          = 443
        protocol         = "tcp"
        cidr_blocks      = ["0.0.0.0/0"]
    }

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

    tags = {
      Name = "allow web"
    }
}
```

`resource “aws_security_group” “allow-web”` - This line declares a resource of type `aws_security_group` and the name of this particular resource is "allow-web".
`name = "allow_web_traffic"` This line is setting the security group's name to "allow_web_traffic".
`description = "allow traffic"` - This line sets the description of the security group to allow traffic.
`vpc_id = aws_vpc.prod-vpc.id` - This line associates the security group with the VPC that has been created earlier in the script with the name "prod-vpc". The `.id` attribute fetches the ID of that VPC.

The ingress blocks within the resource define inbound rules for the security group
* The first ingress block allows inbound HTTPS traffic on port 443 from any IP address (0.0.0.0/0).
* The second ingress block allows inbound HTTP traffic on port 80 from any IP address (0.0.0.0/0).
* The third ingress block allows inbound SSH traffic on port 22 from any IP address (0.0.0.0/0).
** Network Interface**
```HCL
resource "aws_network_interface" "web-server-nic" {
  subnet_id       = aws_subnet.subnet-1.id
  private_ips     = ["10.0.1.50"]
  security_groups = [aws_security_group.allow-web.id]
}
```

`resource "aws_network_interface" "web-server-nic"` -  This line declares a resource of type `aws_network_interface` and the name of this particular resource is "web-server-nic".
`subnet_id = aws_subnet.subnet-1.id` - This line associates the network interface with the subnet that has been created earlier in the script with the name "subnet-1." The `.id` attribute fetches the ID of that subnet.
`private_ips = ["10.0.1.50"]` - This line assigns the private IP address 10.0.1.50 to the network interface.
`security_groups = [aws_security_group.allow-web.id]` This line associates the network interface with the security group that has been created earlier in the script with the name "allow-web" The `.id` attribute fetches the ID of that security group.

**Elastic IP**
```HCL
resource "aws_eip" "eip" {
    network_interface = aws_network_interface.web-server nic.id
    associate_with_private_ip = "10.0.1.50"
    depends_on = [ aws_internet_gateway.gw ]
}
```
`resource "aws_eip" "eip"` - This line declares a resource of type aws_eip and the name of this particular resource is true.
`network_interface = aws_network_interface.web-server-nic.id` - This line associates the EIP with the network interface that has been created earlier in the script with the name "web-server-nic". The `.id` attribute fetches the ID of that network interface.
`associate_with_private_ip = "10.0.1.50"` - This line specifies the private IP address with which the EIP should be associated. In this case, it’s 10.0.1.50, which is the same private IP address assigned to the network interface.
`depends_on = [ aws_internet_gateway.gw ]` This line specifies a dependency on the Internet Gateway resource named "gw". This means that Terraform will ensure the Internet Gateway is created before attempting to create the EIP.
**EC2 Instance**
```HCL
resource "aws_instance" "web-server" {
    ami = "ami-04b70fa74e45c3917"
    instance_type = "t2.micro"
    availability_zone = "us-east-1a"
    key_name = "main-key2"

    network_interface {
        device_index = 0
        network_interface_id = aws_network_interface.web-server-nic.id
      
    }

    user_data = <<-EOF
      #!/bin/bash
      sudo apt update -y
      sudo apt install apache2 -y
      sudo systemctl enable apache2
      sudo systemctl start apache2
      echo "<h1>Welcome to my server </h1>" > /var/www/html/index.html
    EOF

    tags = {
        Name = "web-server"
    }
  
}
```
`resource "aws_instance" "web-server"` - This line declares a resource of type aws_instance and the name of this particular resource is "web-server".
`ami = "ami-04b70fa74e45c3917"` - This line specifies the Amazon Machine Image (AMI) to use for the instance. The AMI determines the operating system and other software for the instance.
`instance_type = "t2.micro"` - This line specifies the type of instance to launch. In this case, it’s "t2.micro", which is a general-purpose instance type.
`availability_zone = "us-east-1a"` - This line specifies the availability zone in which to launch the instance.
`key_name = "main-key2"`- This line specifies the name of the key pair to use for the instance. This key pair is used for SSH access to the instance.
The `network_interface` block within the resource associates the instance with the network interface that has been created earlier in the script with the name "web-server-nic".
The user_data block within the resource specifies a startup script to run on the instance when it launches. This script updates the package lists for upgrades and new package installations, installs Apache2, enables and starts the Apache2 service, and creates a simple HTML file as the index page for the web server.


### Deploying the Infrastructure
Deploying the infrastructure to AWS is pretty straightforward. To do this:
* Open a terminal in Visual Studio Code. 
* Ensure that it points to the directory containing the Terraform file.
* Execute the following command:
`terraform apply`
If everything is set up correctly, the system will prompt you to enter “yes”. If there’s an error, it will point you to the location of the issue. 

Next, open the AWS console and locate the EC2 instance that we just deployed.

Copy the IPv4 public address and paste it into a new browser tab. The HTML file that we deployed on our web server will be displayed.

### Conclusion
So far, we’ve discussed how to deploy various resources on AWS using Terraform. We’ve also examined how these resources can be made dependent on each other, enabling them to form a unified infrastructure that serves a specific purpose in our case display a simple HTML page.

In an ideal situation, more than one instance would be deployed across different availability zones. Additional resources such as load balancers, S3 buckets, Content Delivery Networks (CDNs) like CloudFront, and Domain Name Services (DNS) like Route53, among others, would also be utilized.

All these components come together to create an infrastructure that is reliable, dependable, and operates smoothly.

I hope you won’t stop here. Continue exploring Terraform and even extend your knowledge beyond AWS to other cloud service providers. Good luck!