Terraform for Newbs: Launching a Simple AWS Backend (Welcome to the Series!)
Tags: terraform, aws, infrastructureascode, tutorial
Published: 2024-04-17 10:25:26

In today's world, code can be used to achieve remarkable feats, from building complex applications to managing your infrastructure across cloud providers like AWS, Azure, and GCP. While manually provisioning resources through a cloud console might be feasible for small-scale projects, large production applications require a more robust and scalable approach. 

The following scenarios are just some of the many nuances of manual resource provisioning:


- **Configuration Drift**: Over time, configurations can diverge between development, testing, and production environments if managed manually. This inconsistency can lead to unexpected behavior and troubleshooting challenges.

- **Human Error**: Manual configuration is prone to typos and errors, potentially leading to security vulnerabilities, service outages, or wasted resources due to misconfiguration.

- **Limited Collaboration**: Sharing and collaborating on infrastructure configurations can be difficult with manual provisioning. Centralized code repositories become less practical, hindering collaboration between developers and infrastructure teams.


This is where **Infrastructure as Code (IaC)** comes into play. IaC allows developers to define and provision infrastructure resources using code. One such widely used IaC tool is **Terraform**.

This article is the first in a series of articles where I will discuss how to provision a simple backend architecture in AWS using Terraform.

Note: Terraform uses HCL (HashiCorp Configuration Language), a domain-specific language (DSL) to define and configure infrastructure resources.

## Prerequisites:
- AWS Account with necessary permissions to create resources in it.
- AWS IAM user with access credentials.
- VS Code or any other code editor.

## Architecture Diagram

![Architecture Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/70qb503h8dboch0j6pld.png)
- **Components**:
    - VPC
    - Subnets (One each for Public and Private)
    - Internet Gateway
    - Route Table
    - NAT Gateway
    - EC2 instances 
    - Application Load Balancer
    - Auto Scaling Groups
    

Here is a brief intro to the components. I will suggest referring to the AWS documentation to learn about them in detail.
- VPC(Virtual Private Cloud): The foundation of your isolated network. It is a virtual network that is hosted within a public cloud. It provides a level of isolation between different organizations that use the resources. 

- Public Subnet: A subnet within the VPC that resides in a public availability zone. It has a route table configured to allow access from the internet. This subnet typically houses the application load balancer.

- Private Subnet: A subnet within the VPC that resides in a private availability zone. It has a route table configured to restrict access from the internet. This subnet houses your EC2 backend servers for security reasons.

- Internet Gateway: An internet gateway attached to the VPC, enabling resources in the public subnet (like the load balancer) to access the internet

- NAT Gateway (Optional): A NAT Gateway in the public subnet can act as an internet gateway for the private subnet, allowing outbound traffic from your backend servers for updates or external service communication (e.g., downloading updates from a public repository).

- Application Load Balancer: Distributes incoming traffic from the internet across healthy backend servers in the private subnet.

- EC2 Instances: Backend servers running your application code. They reside in the private subnet for security purposes.

- Auto Scaling Groups: A group that manages backend servers. It can automatically scale the number of EC2 instances up or down based on pre-defined policies (e.g., CPU utilization).

In this article, I will focus on creating the infrastructure for a standalone VPC.

## Directory Setup

1. Step 1: Create a folder in your local environment named backend.
2. Step 2: Open the folder in a code editor of your choice. I will use VS Code for this project.
3. Step 3: Open the terminal in your code editor and run the command `mkdir vpc`
4. Step 4: In your terminal run the command `cd vpc`

Before we start with Terraform, we need to configure our AWS credentials so that Terraform can be deployed to AWS from our local environment.

In your terminal, run `aws configure`. Set the `AWS Access Key ID`, `AWS Secret Access Key`, `Default region name`, and `Default output format`. Press enter for the default values.

## Terraform Code to create a VPC in AWS

Step 1: Create **provider.tf**

Inside the vpc folder create a file named 'provider.tf'. Paste the following code block inside it.
```
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
        source = "hashicorp/aws"
        version = ">= 4.66.1"
    }
  }
}

provider "aws" {
  region              = "ap-south-1"
  allowed_account_ids = [local.aws_account_id]
}
```
A provider in Terraform is a plugin that enables interaction with an API. This file contains the details of all the providers used in the project.

<u>Terraform Provider Configuration Breakdown:
</u>
- required_version: This specifies the minimum Terraform version required to execute this configuration. Ensure you have Terraform version 1.0 or above installed to work with this code.

- required_providers: This block defines the required providers and their versions. Here, we specify the AWS provider with a minimum version of 4.66.1. A specific version ensures compatibility between your Terraform configuration and the provider's functionalities.

<u>AWS Provider Configuration Breakdown:</u>

- provider "aws" Block: This block configures the AWS provider itself.

- region: This specifies the AWS region where your infrastructure will be deployed. In this example, we're using "ap-south-1" which corresponds to the Asia Pacific South (Mumbai) region.

- allowed_account_ids: (Optional) This restricts Terraform to managing resources only within the specified account IDs. Here, we're referencing a variable named aws_account_id defined in a separate variable file. This helps ensure Terraform only manages resources within your authorized accounts.

We will get this error: "No declaration found for "local.aws_account_id". To solve this we have to create the locals.tf file

Step 2: Create **"locals.tf"**:

Terraform locals define and assign values within your terraform configuration. These values can be reused throughout your code, making your configuration more concise and easier to maintain.

Inside the vpc folder create a file named "locals.tf". Paste the following code in it.

```
locals {
  project = "demo"
  aws_account_id = xxxxxxxxxxxx
}
```
Replace the value of the  aws_account_id variable with your own AWS account ID.

<u>Locals Configuration Breakdown:</u>

- project: This local value assigns the string "demo" to the name project. You can use this value throughout your Terraform configuration wherever you need to reference your project name (e.g., naming resources).

- aws_account_id: This local value assigns the 12-digit AWS account ID to the name aws_account_id.

Step 3: Create **variables.tf** and **terraform.tfvars**:

Terraform variables are a powerful mechanism for defining configuration values outside your Terraform code. They act as placeholders that can be assigned values later, typically through a separate file named 'terraform.tfvars'.

<u>variables.tf</u>

```
variable "env" {
  type = string
}

variable "vpc_conf" {
  type = map(any)
}
```

<u>Explanation of variables.tf code:</u>

- env: This variable has a type of string, indicating it will hold a textual value.  You can use this variable throughout your Terraform configuration to reference the environment (e.g., "dev", "staging", "production").

- vpc_conf: This variable has a type of map(any), which essentially defines a key-value dictionary. This allows you to define multiple configuration options for your VPC in a structured manner.

<u>terraform.tfvars</u>

```
env = "dev"
vpc_conf = {
  cidr_block           = "10.0.0.0/16"
  instance_tenancy     = "default"
  enable_dns_hostnames = true
  enable_dns_support   = true
}
```

<u>Explanation of terraform.tfvars code:</u>

- env = "dev": This line assigns the string value "dev" to the env variable.

- vpc_conf block: This block defines key-value pairs for the vpc_conf variable:

    - cidr_block = "10.0.0.0/16": This defines the CIDR 
    block for your VPC.
    - instance_tenancy = "default": This defines the 
    tenancy for your VPC instances (Default value is "default". The only other option is "dedicated").
    - enable_dns_hostnames = (Optional) A boolean flag to enable/disable DNS hostnames in the VPC. Defaults to false.
    - enable_dns_support = (Optional) A boolean flag to enable/disable DNS support in the VPC. Defaults to true.

Refer to the official terraform documentation [here](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc) to learn more about the attributes used.

Step 4: Create **vpc.tf**:

The vpc.tf file defines an AWS VPC resource using the aws_vpc resource block provided by the Terraform AWS provider. This block creates a virtual private cloud (VPC) within your AWS account.

```
resource "aws_vpc" "vpc" {
  cidr_block           = var.vpc_conf.cidr_block
  instance_tenancy     = var.vpc_conf.instance_tenancy
  enable_dns_hostnames = var.vpc_conf.enable_dns_hostnames
  enable_dns_support   = var.vpc_conf.enable_dns_support
  tags = {
    Name          = "${var.env}-${local.project}-vpc"
    CreatedByTer  = true
  }
}
```
The code demonstrates how to leverage variables defined in variables.tf. Notice how it references the vpc_conf map and accesses specific configuration values using the dot notation (e.g., var.vpc_conf.cidr_block). This approach keeps your Terraform code clean and avoids hardcoding configuration details.

<u>Explanation of vpc.tf code:</u>

- cidr_block = var.vpc_conf.cidr_block: This line sets the CIDR block for the VPC using the value assigned to the cidr_block key within the vpc_conf variable map (defined in terraform.tfvars).

- instance_tenancy = var.vpc_conf.instance_tenancy: This line sets the instance tenancy for the VPC using the value assigned to the instance_tenancy key within the vpc_conf variable map.

- enable_dns_hostnames = var.vpc_conf.enable_dns_hostnames: This line enables DNS hostnames for the VPC resources based on the value assigned to the enable_dns_hostnames key within the vpc_conf variable map.

- enable_dns_support = var.vpc_conf.enable_dns_support: This line enables DNS support for the VPC based on the value assigned to the enable_dns_support key within the vpc_conf variable map.

<u>Adding tags:</u>

- Name = "${var.env}-${local.project}-vpc": This line dynamically creates a tag named "Name" using string interpolation.

- CreatedByTerraform = true: This line assigns a tag named "CreatedByTerraform" with a " true " value, indicating this VPC was created using Terraform.

Step 5: Create **backend.tf**:

This code snippet defines a Terraform backend using the backend block. By default, Terraform stores the state of your infrastructure (information about created resources) in a local file named terraform.tfstate within your project directory. This local state file is not ideal for production environments due to limitations like:

- Single Point of Failure: If the local file is lost or corrupted, you might lose track of your infrastructure state.

- Collaboration Challenges: When working with multiple collaborators, sharing and managing the state file becomes cumbersome.

<u>Using S3 Backend:</u>

To address these limitations, we are going to configure an S3 backend. This instructs Terraform to store the state information in an Amazon S3 bucket. Here's a breakdown of the configuration:

- backend "s3": This line declares the backend type as "s3", indicating we'll use an S3 bucket to store the state.

- bucket = "terraform-demo-state-files": This line specifies the name of the S3 bucket where Terraform will store the state file. It's recommended to choose a descriptive name for your bucket.

- region = "us-east-1": This line specifies the AWS region where the S3 bucket resides. It is generally recommended to have the S3 bucket where your Terraform state is stored.

- key = "state": This line defines the key (filename) within the S3 bucket where Terraform will store the state information. You can customize this key name if needed.

## Deploy infrastructure to AWS

Now that we've defined our infrastructure using Terraform code, it's time to deploy it to AWS! 
Here's a typical workflow for deploying Terraform code:

- Initialize Terraform: Open the terminal of code editor, and navigate to the 'vpc' directory. Run `terraform init`. This downloads and installs any required plugins (like the AWS provider) based on your configuration.

![Terraform init](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/evay8ylo1o7qq21lmlna.png)


- Review Changes: Before applying, run `terraform plan` to see a preview of the changes Terraform will make to your AWS environment. This allows you to verify the planned actions before actual creation.

![Terraform plan](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3811jd7ofw4ybz72wo9m.png)


- Apply the Configuration (Optional): Once you are satisfied with the plan, run `terraform apply` to create the resources in your AWS account. If you're using a basic Terraform setup, you might see a prompt like "Do you want to apply these changes?"  Carefully review the prompt and answer "yes" only if you intend to proceed.

![Terraform apply](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d279icjvzbltc55itnk1.png)

Congratulations! You have successfully created a vpc in AWS using Terraform. Open the console, and examine the infrastructure that we just created. Notice that a route table has been created. To ensure basic functionality within your VPC, AWS automatically creates a main route table when you create a VPC.

Up next: [Terraform 101: Creating Secure Subnets in Your](https://dev.to/ananyadasgupta/terraform-101-creating-secure-subnets-in-your-vpc-553m)! Stay tuned. :wave: