Milu
Posted on January 5, 2023
It's been nearly two years since I first encountered Terraform. In my first week working with Terraform, I got myself into a bad local state that led to countless error messages, many trials and errors, and the exposure to numerous Stack Overflow threads. Ultimately I manually deleted all my AWS resources. To say the least, my introduction to Terraform was a total nightmare. After that, I didn’t want to touch Terraform again. Little did I know I would learn to appreciate and even enjoy working with this powerful tool.
On my journey, I struggled to find tutorials that use Terraform to set up AWS resources, so I decided to share the knowledge I acquired with the hopes you (dear reader) will be able to skip some of the troubles I encountered. This post is going to define Terraform, list the reasons why you might want to learn it and use it, and go through the set up process step by step.
The What
Terraform is an infrastructure as code tool (IAC) created by HashiCorp that lets you automate cloud and on-prem resources. It uses configuration files written in HashiCorp Configuration Language (HCL) to declare resources (infrastructure objects) and define dependencies between them. To put it simply, this tool allows you to write a few configuration files and build a whole system’s architecture on the cloud by running a couple of commands. I found this to be a more efficient alternative to clicking through a console to create resources manually.
Terraform State
Terraform keeps track of the infrastructure it created in a state file. Every time you run Terraform, it compares the latest status of your resources with the state configurations you declared and it determines what changes need to be applied.
When working with a team, you would need to manage shared storage for state files in a remote backend. This will prevent multiple team members from applying changes at the same time. Using a remote backend also provides access control management as data in the state files are stored in plain text, including passwords. A number of remote backends are supported. However, the example below will be using AWS S3 as the remote backend.
The Why
In my opinion, the following reasons explain why using Terraform in your project is a good idea:
Speed. As I mentioned above, the automation benefit of using this tool is truly the best part! By leveraging Terraform, you can configure and provision an entire infrastructure architecture for multiple environments (development, testing/staging, production) by running a few commands.
Collaboration. Terraform captures the desired project goal in a state and it keeps track of all state changes in the environment. This is perfect when working in a team, every member of the team is able to contribute in the same way they would with regular application code.
Agentless. Terraform is cloud-agnostic so you can use it with different providers such as AWS or Azure.
Reusability. Terraform is modular. If you are provisioning similar resources, you can abstract this code, create a module and reuse it. This saves you a lot of work and reduces repetition in your code.
Error reduction. Standardizing the creation of cloud resources using code minimizes the probability of errors when provisioning in multiple environments.
The How
It’s time to dive into the fun part! Let’s talk about how to set up Terraform in your project.
1) The first thing you need to do is download Terraform.
2) Create a directory dedicated to terraform in your project. I called mine tf
.
3) Create a main.tf
file in your newly created directory. Here is where you are going to define your providers. In this example, we are defining AWS as a provider.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
required_version = "~> 1.3.6"
}
4) Create a backend.tf
file. Here we are going to create and configure our remote state storage using a S3 bucket and a DynamoDB table. I’ve added comments above every resource block so you get a good idea of what these resources are doing. I encourage you to check the Terraform documentation for each resource as well. Also, here is an excellent post that explains each line in detail.
Essentially, the following code creates a S3 bucket called “remote_state” that cannot be deleted, keeps versioning of every change, uses encryption by default, and blocks public access.
# ----------------------------------------------------
# configure aws provider
# ----------------------------------------------------
provider "aws" {
region = "us-east-2"
}
# ----------------------------------------------------
# create S3 bucket (backend)
# ----------------------------------------------------
resource "aws_s3_bucket" "remote_state" {
bucket = "<YOUR-PROJECT-NAME>-remote-state"
# Prevent accidental deletion of this S3 bucket
lifecycle {
prevent_destroy = true
}
}
# Every update to a file in the bucket creates a new version of that file
resource "aws_s3_bucket_versioning" "enabled" {
bucket = aws_s3_bucket.remote_state.id
versioning_configuration {
status = "Enabled"
}
}
# Turn server-side encryption on by default
resource "aws_s3_bucket_server_side_encryption_configuration" "default" {
bucket = aws_s3_bucket.remote_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# Block all public access to the S3 bucket
resource "aws_s3_bucket_public_access_block" "public_access" {
bucket = aws_s3_bucket.remote_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
In addition, we create a DynamoDB table for locking. It uses a primary key called LockedID
and it has to match exactly (spelling and capitalization) for locking to work.
# ----------------------------------------------------
# create dynamo db table for state locking
# ----------------------------------------------------
resource "aws_dynamodb_table" "terraform_statelock" {
name = "<YOUR-PROJECT-NAME>-tf-statelock"
read_capacity = 20
write_capacity = 20
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
5) Set your AWS secret access key and access key id to access the AWS api on your terminal.
export AWS_ACCESS_KEY_ID=********
export AWS_SECRET_ACCESS_KEY=********
6) On your terminal, cd
into the tf
directory and run terraform init
. This command will download the provider code. Then, run terraform plan
to see a list of the changes that will be recorded on your state.
7) Run terraform apply
. This command will build the S3 bucket and the DynamoDB table we declared above.
8) Now that we have provisioned the resources needed, let's add a backend configuration to the terraform
configuration code under the main.tf
file. The following code is defining S3 as our backend.
terraform {
...
backend "s3" {
bucket = "<YOUR-PROJECT-NAME>-remote-state"
key = "global/s3/terraform.tfstate"
region = "us-east-2"
dynamodb_table = "<YOUR-PROJECT-NAME>-tf-statelock"
encrypt = true
}
}
9) Run terraform init
. This command will configure the Terraform backend to the S3 bucket as indicated above. Go to the AWS console and take a look at your DynamoDB table, you should see your first state file!
10) Run terraform apply
. After setting up our lock DynamoDB table, this command prompts Terraform to acquire a lock before running apply
and releases the lock after it’s done.
That’s it! Now your project is set up to use Terraform. From here, we can declare resources and provision them by running terraform plan
and terraform apply
. I’m planning to write a continuation post that uses this setup as a base to provision a simple REST api. Thank you for reading!
Give this post a reaction if you found it helpful and follow me on Twitter and Dev.to to keep up with new posts!
Resources:
https://developer.hashicorp.com/terraform/language
https://www.linode.com/docs/guides/introduction-to-hcl/
https://developer.hashicorp.com/terraform/language/state
https://zerotomastery.io/blog/benefits-of-using-terraform/
https://www.varonis.com/blog/what-is-terraform
Posted on January 5, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.