I have seen many articles where people shared information regarding how to use terraform, and spin up infrastructure but I saw not everyone talking about Terraform configuration blocks that are the foundation of Terraform script. So, I decided to talk about different configuration blocks.
In this article, I have covered What is terraform, why organizations are opting for terraform above cloud-native tools, how terraform works, and a high-level overview of different Terraform blocks that we can use while writing our terraform scripts.
What is Terraform?
Terraform is an open-source infrastructure as code (IAC) software tool developed by HashiCorp. It allows users to define, provision, and manage cloud infrastructure using a declarative configuration language.
With Terraform, users can create and manage infrastructure across multiple cloud providers such as AWS, Azure, Google Cloud Platform, and others. The configuration language used by Terraform is called HashiCorp Configuration Language (HCL), which provides a simple and easy-to-read syntax for describing infrastructure resources and their dependencies.
Why Organizations are adopting Terraform over other IaC tools?
Multi-Cloud Support: Terraform supports multiple cloud providers such as AWS, Azure, Google Cloud Platform, and others, making it easy to manage infrastructure across multiple cloud environments. This allows you to create a consistent infrastructure deployment process, regardless of the underlying cloud provider.
Declarative Configuration Language: Terraform uses a declarative configuration language (HCL) to define infrastructure resources and their dependencies. This allows you to define your infrastructure in a simple and easy-to-read format, which makes it easy to understand and maintain your infrastructure code.
Version Control: Terraform code can be version controlled using tools like Git, which means you can track changes to your infrastructure over time, collaborate with others, and roll back changes if necessary.
Modularity and Reusability: Terraform allows you to create reusable modules that can be used to provision infrastructure resources across multiple deployments. This modularity makes it easy to create and maintain complex infrastructure deployments.
Community Support: Terraform has a large and active community, which means that there are many resources available to help you learn and use the tool effectively. There are also many third-party modules and plugins available that can be used to extend the functionality of Terraform.
State Management: Terraform provides a built-in state management system that tracks the state of your infrastructure and allows you to make changes to it incrementally. This helps to reduce the risk of infrastructure failures and makes it easy to roll back changes if necessary.
Ecosystem Integration: Terraform integrates well with other tools in the DevOps ecosystem, such as Ansible, Jenkins, and Kubernetes. This makes it easy to create end-to-end deployment pipelines and automate the entire software delivery process.
How does Terraform work?
Terraform uses APIs to interact with platforms. Using API terraform client interacts with the platform endpoint. Shown in the image below.
Target API can be all available providers on Terraform
Let’s get into the Practical aspects of Terraform
Most Basic Terraform Workflow
Write: Define your infrastructure in configuration files
Plan: See the changes terraform will make to your infrastructure.
Apply: Terraform provision infrastructure and update state file.
Terraform Syntax:
Terraform is written in HCL language and it typically follows the following syntax:
"Block Type" "Block Name" "Block Name"{
# Body where you define Block requirement by providing argument
"Options"= "Name" #argument
}
# AWS Provider and VPC Resource block exampple
provider "aws" { #Block name
region = "us-east-2" #Body with arguments
}
resource "aws_vpc" "myvpc" { # Block name
cidr_block = "192.168.2.0/24" # Body with arguments
}
Block types in Terraform:
Different block types can be used based on the requirement:
- Provider Block: It is a block that is necessary to interact with the provider's API.
provider "aws" {
access_key = "ACCESS_KEY"
secret_key = "SECRET_KEY"
region = "us-west-2"
}
2. Resource Block: It is a block that specifies the type of resources you want to create as well as its configuration and dependencies.
resource "aws_instance" "prod-ec2" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
subnet_id = "subnet-0297e27970cdcc48d"
}
3. Data Block: It is a block that is used to define data sources and retrieve information from your data sources to use within your terraform configuration.
# checking for AMI on on aws ami
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["your account id"]
}
# using retrived AMI to be used in Ec2
resource "aws_instance" "prod-ec2" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
}
}
4. Input Variables Block: It is a block that is used to define input variables that can be used to pass values into your terraform configuration. It allows you to specify values at runtime rather than hardcoding values.
“It is best to have multiple variable files, different for prod, dev and test and use it based on your requirement.”
variable "subnet_id" {
type = string
description = "The ID of the VPC subnet"
default = "your subnet id"
}
resource "aws_instance" "prod-ec2" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
subnet_id = "var.subnet_id"
}
5. Local Variables Block: It can be used throughout your configuration to simplify complex expressions, avoid repeating values, or make your configuration more readable.
# we define two local variables: subnet_cidr_blocks and subnet_count.
locals {
subnet_cidr_blocks = [
"10.0.1.0/24",
"10.0.2.0/24",
"10.0.3.0/24",
]
subnet_count = length(local.subnet_cidr_blocks)
}
resource "aws_vpc" "example" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "example-vpc"
}
}
# Use these local variables in the aws_subnet resource block.
# It will create multiple subnets within the VPC.
# The count argument creates one subnet resource for each element in the subnet_cidr_blocks list.
resource "aws_subnet" "example" {
count = local.subnet_count
vpc_id = aws_vpc.example.id
# access corresponding element of the subnet_cidr_blocks list using the count.index expression.
cidr_block = local.subnet_cidr_blocks[count.index]
tags = {
Name = "example-subnet-${count.index}"
}
}
5. Output Values Block: An output
the block is used to define values that should be made available for other Terraform configurations or for human consumption after the current configuration is applied. output
blocks allow you to export values from your configuration, such as the IP address of a newly created server or the URL of a load balancer, and so much more.
output "public_ip" {
value = aws_instance.example.public_ip
}
6. Modules Block: It is used to call a reusable Terraform configuration from within another configuration. Modules allow you to encapsulate and reuse common configurations and simplify complex infrastructure setups.
Modules are used prominently consumed and used in every organization and are the most important area that everyone should master in order to rescue and simplify complex infrastructure.
## We are calling Module that are store in our central git repository.
provider "aws" {
region = "us-west-2"
}
module "web_servers" {
source = "github.com/example/web-servers"
server_count = 3
instance_type = "t2.micro"
key_name = "example-keypair"
}
## Now let's look at the configuration file that we have stored in github location.3
variable "server_count" {
description = "Number of web servers to create"
type = number
}
variable "instance_type" {
description = "Type of EC2 instance to create"
type = string
}
variable "key_name" {
description = "Name of the SSH key pair to use for authentication"
type = string
}
resource "aws_instance" "web_server" {
count = var.server_count
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.instance_type
key_name = var.key_name
tags = {
Name = "web-server-${count.index + 1}"
}
}