Introduction to Terraform
Terraform is an open-source tool developed by HashiCorp that allows you to define and provision infrastructure using declarative configuration files. It supports multiple cloud providers, including AWS, Azure, and Google Cloud Platform.
Terraform is a game-changer in the realm of infrastructure management. Here's why:
Infrastructure as Code (IaC): Terraform lets you define your infrastructure in code using a configuration language. This code becomes a single source of truth that can be version-controlled and shared, ensuring consistency and reproducibility across environments.
Declarative Approach: Instead of scripting every step, Terraform works declaratively. You specify the desired end state of your infrastructure, and Terraform figures out the steps needed to get there. This abstraction simplifies complex tasks and reduces human error.
Multi-Cloud and On-Premise Support: Terraform isn't limited to a single cloud provider. It supports multiple providers like AWS, Azure, GCP, and even on-premise setups. This flexibility allows you to manage diverse infrastructures using a unified workflow.
Workflow Efficiency: Terraform streamlines infrastructure management through a structured workflow:
Define: Write configuration files that describe what your infrastructure should look like.
Plan: Terraform analyzes the configuration and generates an execution plan, highlighting what changes will occur.
Apply: Once you approve the plan, Terraform implements the changes to bring your infrastructure to the desired state.
By automating these processes, Terraform boosts efficiency, ensures consistency, and minimizes manual intervention, making it a cornerstone in modern infrastructure orchestration.
Terraform Script for Public VPC with CDN, ALB, EC2, and Target Group
- Setting up AWS Provider
The first step is to configure the AWS provider in your Terraform configuration. Please specify your AWS credentials and the desired AWS region. Here's an example of how to define the AWS provider:
provider "aws" {
region = "us-east-1"
}
- Creating a VPC
Next, let's create a Virtual Private Cloud (VPC) to host our resources. The VPC will define the networking environment for our AWS infrastructure. Here's how to create a VPC with Terraform:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "deb-public-vpc"
}
}
- Configuring Subnets
After creating the VPC, we'll configure two public subnets across different availability zones to ensure high availability. Here's an example of how to define subnets:
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.3.0/24"
availability_zone = "us-east-1a"
map_public_ip_on_launch = true
tags = {
Name = "deb-public-subnet-a"
}
}
resource "aws_subnet" "public_b" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1b"
map_public_ip_on_launch = true
tags = {
Name = "deb-public-subnet-b"
}
}
- Internet Gateway
We'll attach an Internet Gateway to enable internet access for resources within the VPC. This will allow our instances to communicate with the internet. Here's how to create an Internet Gateway:
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "deb-internet-gateway"
}
}
- Route Table (for Internet Access)
We need to associate the Internet Gateway with the route table to enable internet access for resources in the public subnets. Here's how to define the route table:
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
tags = {
Name = "deb-public-route-table"
}
}
- Associate Subnets with Route Table
Finally, we'll associate the public subnets with the routeing table to enable internet access for instances deployed in those subnets. Here's how to do it:
resource "aws_route_table_association" "public_assoc_a" {
subnet_id = aws_subnet.public_a.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "public_assoc_b" {
subnet_id = aws_subnet.public_b.id
route_table_id = aws_route_table.public.id
}
- Setting Up Security Group
To control inbound and outbound traffic to our EC2 instance, we'll create a security group. This security group will allow HTTP, HTTPS, and SSH traffic.
# Security Group for EC2 (Allow HTTP, HTTPS, SSH)
resource "aws_security_group" "web_access" {
name = "allow_web"
description = "Allow HTTP/HTTPS and SSH traffic"
vpc_id = aws_vpc.main.id
# Allow inbound traffic on port 22 for SSH
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Allow HTTP traffic
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Allow HTTPS traffic
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Allow outbound traffic to anywhere
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
- Creating an EC2 Instance
Now, let's deploy an EC2 instance in one of the public subnets. This instance will serve as our web server. Here's how to define the EC2 instance in Terraform:
# EC2 Instance
resource "aws_instance" "web_server" {
ami = "your-instance-ami"
instance_type = "your-instance-type"
subnet_id = aws_subnet.public_a.id
# Add security group
vpc_security_group_ids = [aws_security_group.web_access.id]
tags = {
Name = "deb-web-server"
}
}
- Setting Up Target Group
To route traffic from the ALB to our EC2 instances, we need to define a target group. The ALB will forward incoming requests to this target group based on the configured rules.
# Target Group
resource "aws_lb_target_group" "web_tg" {
name = "my-web-target-group"
protocol = "HTTP"
port = 80
vpc_id = aws_vpc.main.id
health_check {
path = "/"
}
}
- Setting Up Application Load Balancer (ALB)
To distribute incoming application traffic across multiple targets, such as EC2 instances, we'll set up an Application Load Balancer (ALB). The ALB will intelligently route traffic to healthy instances based on configured rules.
# ALB
resource "aws_lb" "main" {
name = "my-application-lb"
internal = false
load_balancer_type = "application"
# Add both subnets
subnets = [aws_subnet.public_a.id, aws_subnet.public_b.id]
security_groups = [aws_security_group.web_access.id]
}
- Attaching EC2 Instance to Target Group
To route traffic from the ALB to our EC2 instance, we need to attach the instance to a target group. This allows the ALB to direct traffic to the instance based on the configured rules.
# Attach target to Target Group
resource "aws_lb_target_group_attachment" "web_server_attachment" {
target_group_arn = aws_lb_target_group.web_tg.arn
target_id = aws_instance.web_server.id
port = 80
}
- Configuring CloudFront Distribution
To improve the performance and availability of our application, we can use Amazon CloudFront as a content delivery network (CDN). CloudFront caches content at edge locations worldwide, reducing latency for users accessing our application.
# CloudFront Distribution
resource "aws_cloudfront_distribution" "my_cdn" {
origin {
domain_name = aws_lb.main.dns_name # Get the ALB's DNS name
origin_id = "my_alb_origin"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html" # If needed
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "my_alb_origin"
viewer_protocol_policy = "allow-all"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true # Use default CloudFront cert (for basic usage)
}
}
Terraform Script to set up an Amazon CloudFront Distribution for your Amazon S3 Static Website
Terraform Backend Configuration:
terraform { backend "s3" { bucket = "your-backend-bucket-name" key = "terraform.tfstate" region = "us-east-1" access_key = "ACCESS-KEY" secret_key = "SECRET-ACCESS-KEY" } }
This section configures Terraform to use an S3 bucket named "your-backend-bucket-name" to store its state file ("terraform.tfstate").
It also specifies the AWS region and the access keys (access_key and secret_key) for managing the state.
AWS Provider Configuration:
provider "aws" { region = var.aws_region # Change this to your desired AWS region }
- Configures the AWS provider for Terraform, specifying the AWS region to be used. It uses a variable
aws_region
, allowing flexibility to change regions easily.
- Configures the AWS provider for Terraform, specifying the AWS region to be used. It uses a variable
AWS S3 Bucket Resource:
resource "aws_s3_bucket" "static_website" { bucket = var.s3_bucket_name # Change this to your desired bucket name object_lock_enabled = false tags = { Name = "StaticWebsiteBucket" CreatedBy = "Rajdeep" } }
Defines an S3 bucket resource named "static_website" using the specified bucket name (via the variable
s3_bucket_name
).Sets
object_lock_enabled
to false and assign tags to the bucket for identification.
AWS IAM Policy Resource:
resource "aws_iam_policy" "s3_bucket_policy" { name = "S3BucketPolicy" description = "IAM policy to allow managing S3 buckets and ACLs" policy = jsonencode({ Version = "2012-10-17", Statement = [ { Effect = "Allow", Action = ["s3:*"], Resource = "arn:aws:s3:::terraform-cloudfront/*" } ] }) }
- Creates an IAM policy named "S3BucketPolicy" to allow managing S3 buckets and ACLs. The policy grants full access to all S3 actions for resources under "arn:aws:s3:::terraform-cloudfront/*".
AWS S3 Bucket ACL Resource:
resource "aws_s3_bucket_acl" "static_website_acl" { bucket = aws_s3_bucket.static_website.id acl = var.acl_permission depends_on = [aws_s3_bucket_ownership_controls.s3_bucket_acl_ownership] }
Sets the ACL (Access Control List) for the S3 bucket created earlier (
static_website
) using the specified ACL permissions from the variableacl_permission
.Depends on the completion of the
aws_s3_bucket_ownership_controls
resource.
AWS S3 Bucket Ownership Controls Resource:
resource "aws_s3_bucket_ownership_controls" "s3_bucket_acl_ownership" { bucket = aws_s3_bucket.static_website.id rule { object_ownership = "BucketOwnerPreferred" } depends_on = [aws_s3_bucket_public_access_block.static_website_access_block] }
Configures ownership controls for the S3 bucket, specifying that the preferred object ownership should be the bucket owner.
Depends on the completion of the
aws_s3_bucket_public_access_block
resource.
AWS S3 Bucket Public Access Block Resource:
resource "aws_s3_bucket_public_access_block" "static_website_access_block" { bucket = aws_s3_bucket.static_website.id block_public_acls = false block_public_policy = false ignore_public_acls = false restrict_public_buckets = false }
- Configures public access settings for the S3 bucket, explicitly allowing public ACLs, policies, and public buckets.
AWS S3 Bucket Website Configuration:
resource "aws_s3_bucket_website_configuration" "static_website_config" { bucket = aws_s3_bucket.static_website.id index_document { suffix = "index.html" } }
- Configures the S3 bucket for hosting a static website by specifying the index document ("index.html").
AWS CloudFront Distribution Resource:
resource "aws_cloudfront_distribution" "cdn_distribution" { origin { domain_name = aws_s3_bucket.static_website.bucket_regional_domain_name origin_id = "S3Origin" } ... }
Sets up a CloudFront distribution with the S3 bucket as the origin for serving content.
Configures caching behavior, viewer protocol policy, and other settings for the distribution.
AWS S3 Object Resource (Index HTML):
resource "aws_s3_object" "index_html" { bucket = aws_s3_bucket.static_website.id key = "index.html" source = var.index_html_path # Update this with the actual path to your index.html file acl = var.acl_permission content_type = "text/html" depends_on = [aws_s3_bucket.static_website, aws_cloudfront_distribution.cdn_distribution] }
- Uploads the index.html file to the S3 bucket for the static website, specifying ACL permissions, content type, and dependencies on other resources.
Here are the variable declarations provided in your Terraform configuration:
aws_region:
Description: Specifies the AWS region where resources will be created.
Default Value: "us-east-1"
Example usage:
var.aws
_region
(replacevar
with the appropriate block name)
s3_bucket_name:
Description: Defines the name of the S3 bucket used for the static website.
Default Value: "your-bucket-name"
Example usage:
var.s3_bucket_name
(replacevar
with the appropriate block name)
index_html_path:
Description: Specifies the local path to the index.html file.
Default Value: "index.html"
Example usage:
var.index_html_path
(replacevar
with the appropriate block name)
acl_permission:
Description: Sets the ACL (Access Control List) permission for the S3 bucket.
Default Value: "public-read"
Example usage:
var.acl_permission
(replacevar
with the appropriate block name)
variable "aws_region" {
description = "The AWS region where resources will be created."
default = "us-east-1"
}
variable "s3_bucket_name" {
description = "The name of the S3 bucket for the static website."
default = "your-bucket-name"
}
variable "index_html_path" {
description = "The local path to the index.html file."
default = "index.html"
}
variable "acl_permission" {
description = "The ACL permission for the S3 bucket."
default = "public-read"
}
When executed with Terraform, this configuration script will create the specified AWS resources, configure them accordingly, and deploy a static website accessible via CloudFront.
Conclusion
Terraform revolutionizes infrastructure management by treating it as code. This declarative approach, combined with multi-cloud support, simplifies and automates deployments. By adopting Infrastructure as Code (IaC) with Terraform, teams gain consistency, repeatability, and version control across their environments. The structured workflow streamlines the process, minimizes errors, and boosts overall efficiency.
In the provided Terraform script for setting up a public VPC with various AWS resources like CDN, ALB, EC2 instances, and more, we see firsthand how Terraform simplifies complex infrastructure configurations into manageable code blocks. Terraform offers a comprehensive solution for orchestrating modern infrastructure deployments from configuring AWS providers to deploying CloudFront distributions.
Ultimately, Terraform empowers organizations to embrace agile practices, accelerate deployment cycles, and maintain robust infrastructure configurations, making it an indispensable tool for modern DevOps and cloud-native environments.
Lastly, I would like to thank my colleague Debjyoti Koner for his contribution to this blog.