Table of contents
Terraform might seem a bit daunting at first, but it quickly becomes addictive. As with any tool, the best way to achieve mastery is through consistent practice. Here is one such practice project that helped me gain deeper insights into Terraform.
Prerequisites
Install terraform.
Create an AWS account.
AWS CLI installed.
Task
Host a static website using AWS S3 and CloudFront Distribution.
Steps:
Configure AWS CLI on the system.
Create S3 bucket.
Create IAM policy to access the S3 bucket.
Create CloudFront Distribution.
Add static pages to S3.
Deploy and test website.
It is best to have some basic understanding of AWS S3 and CloudFront before creating this project. Here S3 bucket will only be used for storing the website files. The website will be hosted using CloudFront.
What is Terraform?
Terraform is an Infrastructure as Code (IaC) tool used to create cloud-based resources, such as AWS infrastructure. To get started with Terraform, there are a few essential files you need to be aware of:
main.tf
: Contains the main components to be created. While it can include all configurations, for readability, we use separate files as outlined below.provider.tf
: Specifies the cloud provider details, user credentials, etc.variables.tf
: Defines global variables used across the project.values.tfvars
: Specifies values for the global variables.output.tf
: Specifies output values to be returned.
The names of the files are irrelevant for this project, this is just a brief overview of the files. For more details, refer to this great article by Spacelift.
To create any resource in terraform we need to refer to the documentation.
How to access resource attributes:
Suppose we have the below resource a.
resource "a" "b"{
attribute1 = ""
attribute2 = ""
}
If we need to access attribute1 of a anywhere in the project we can use the syntax: a.b.attribute1
Short note on using variables:
To make it more challenging we will use variables instead of static values.
Syntax to declare variables: ( variables.tf file)
variable "variable-name" { type = variable-type default = default-value }
Syntax to access the variables:
var.variable-name
Syntax to specify values dynamically: ( *.tfvars file )
variable-name="test"
Terraform command to use the variables:
terraform apply -var-file=values.tfvars
Implementing the Task
Configure AWS CLI:
We need to configure AWS CLI so that terraform can communicate with AWS and create the resources. Here, I have updated my Access Keys in the ~/.aws/credentials
file on my system.
[test] #profile used in provider.tf
aws_access_key_id = YOUR_ACCESS_KEY_ID
aws_secret_access_key = YOUR_SECRET_ACCESS_KEY
region = YOUR_DEFAULT_REGION
output = json
The Terraform Configuration:
NOTE: You can first create the Terraform files with static hardcoded values, later you can convert these to variables as shown.
Example: Initially S3 resource could be created like so,
resource "aws_s3_bucket" "s3" {
bucket = "abc-test" #using static values instaed of variables
tags = {
Name = "abc-test" #using static values instaed of variables
Environment = "dev" #using static values instaed of variables
}
}
Let’s create the Terraform files:
Create
provider.tf
fileprovider "aws" { region = "us-east-2" profile = "test" }
Create S3 setup, use
s3.tf
fileCreates an S3 Bucket:
Uses variables for the bucket name and tags.
Tags include the bucket name and environment.
Sets Ownership Controls:
- Ensures the bucket owner has preferred ownership of objects.
Restricts Public Access:
Blocks public ACLs and policies.
Ignores public ACLs and restricts public bucket access.
Defines IAM Policy for CDN Access:
Allows only the CDN to access the S3 bucket contents.
Specifies actions and resources for the policy.
Attaches IAM Policy to S3 Bucket:
- Applies the defined IAM policy to the S3 bucket.
#Create an S3 bucket
resource "aws_s3_bucket" "s3" {
bucket = var.s3_name #using variables from variables.tf file
tags = {
Name = var.s3_name #using variables from variables.tf file
Environment = var.env #using variables from variables.tf file
}
}
#Sets Ownership Controls
resource "aws_s3_bucket_ownership_controls" "s3_owner" {
bucket = aws_s3_bucket.s3.id
rule {
object_ownership = "BucketOwnerPreferred"
}
}
#Restricts public access to the S3 bucket
resource "aws_s3_bucket_public_access_block" "s3_access" {
bucket = aws_s3_bucket.s3.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
#IAM policy that allows only the CDN to access the S3 bucket contents
data "aws_iam_policy_document" "s3_policy" {
statement {
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.s3.arn}/*"]
principals {
type = "AWS"
identifiers = [aws_cloudfront_origin_access_identity.s3_distribution.iam_arn]
}
}
}
#Attaches IAM Policy to S3 Bucket
resource "aws_s3_bucket_policy" "allow_access_from_another_account" {
bucket = aws_s3_bucket.s3.id
policy = data.aws_iam_policy_document.s3_policy.json
}
Create CloudFront setup, use
cdn.tf
fileFetch S3 bucket details into Local Variables:
s3_origin_id
: Sets the origin ID using the S3 bucket name froms3.tf
.s3_domain_name
: Retrieves the regional domain name of the S3 bucket froms3.tf
.
Creates CloudFront Origin Access Identity:
- Establishes an origin access identity (OAI) for CloudFront to access the S3 bucket. An OAI is a special CloudFront user that gives CloudFront permission to access private content in an S3 bucket. This is used in the s3_origin_config block.
Configures CloudFront Distribution:
Origin Settings:
Uses the S3 bucket’s domain name and origin ID.
Configures S3 origin with the CloudFront origin access identity.
The following settings can be left as default:
General Settings:
Enables the distribution and IPv6.
Sets the default root object to
index.html
.
Default Cache Behavior:
Allows various HTTP methods.
Specifies cached methods.
Sets target origin ID.
Configures forwarded values (no query strings, no cookies).
Enforces HTTPS by redirecting HTTP requests.
Sets TTL values to 0.
Price Class:
- Uses
PriceClass_200
for cost management.
- Uses
Restrictions:
- No geographical restrictions.
Tags:
- Tags the distribution with the environment variable.
Viewer Certificate:
- Uses the default CloudFront certificate.
- Handles Custom Error Responses:
Configures custom error responses for 403 and 400 errors.
Sets a minimum TTL for error caching.
Redirects to
index.html
with a 200 response code for these errors.
#Defines Local Variables
locals {
s3_origin_id = "${var.s3_name}-origin"
s3_domain_name = aws_s3_bucket.s3.bucket_regional_domain_name
}
#Creates CloudFront Origin Access Identity
resource "aws_cloudfront_origin_access_identity" "s3_distribution" {}
#Configures CloudFront Distribution
resource "aws_cloudfront_distribution" "s3_distribution" {
origin {
domain_name = local.s3_domain_name
origin_id = local.s3_origin_id
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.s3_distribution.cloudfront_access_identity_path
}
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
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 = "redirect-to-https"
min_ttl = 0
default_ttl = 0
max_ttl = 0
}
price_class = "PriceClass_200"
restrictions {
geo_restriction {
restriction_type = "none"
}
}
tags = {
Environment = var.env
}
viewer_certificate {
cloudfront_default_certificate = true
}
# Handles Custom Error Responses
custom_error_response {
error_code = 403
error_caching_min_ttl = 30
response_code = 200
response_page_path = "/index.html"
}
custom_error_response {
error_code = 400
error_caching_min_ttl = 30
response_code = 200
response_page_path = "/index.html"
}
}
Create
variables.tf
filevariable "s3_name" { type = string } variable "env" { type = string } variable "region" { type = string }
Create
values.tf
files3_name = "tftest" env = "dev" region = "us-east-2"
All the files created above are available here.
Deploy Terraform:
Run the terraform commands in the terminal.
terraform init terraform validate terraform plan -var-file=values.tfvars terraform apply -var-file=values.tfvars -auto-approve
Verify in AWS console:
S3
CloudFront
Upload static content into S3:
Create invalidation in CloudFront:
Test the website:
Access the website using the CloudFront URL from the terraform apply command output.
Clean up:
Delete resources using terraform destroy
command. Make sure to delete the contents in S3 before running terraform destroy.
Conclusion
Using Terraform to manage your AWS infrastructure can save you time and reduce errors. By using variables and separate configuration files, you can easily manage and scale your resources.
This GitHub repository contains Terraform templates I created for other AWS resources as part of my learning journey.
References: