Table of contents
- Why Use Terraform for Infrastructure as Code (IaC)?
- Why Terraform?
- How to Configure AWS CLI with Access Keys
- Create an EC2 instance using terraform
- Terraform Providers
- Multiple Region Setup
- Multi-Cloud Setup
- Terraform Variables
- Organizing Terraform Files
- Conditional Expressions
- Terraform Built-in Functions
- Module in Terraform
- Provisioners in Terraform
Why Use Terraform for Infrastructure as Code (IaC)?
Imagine you’re a DevOps engineer and you’re assigned a simple task to create an S3 bucket in AWS. Normally, you would log in to your AWS account, search for the S3 service, and manually create the bucket by filling in the necessary details. This works fine if you're only creating one bucket.
But what if you need to create 100 or even 1,000 S3 buckets? Doing this manually would take a lot of time and effort. In situations like this, you’d want a more programmatic approach—using the AWS CLI or scripting to interact with AWS APIs. With these tools, you could create all the required buckets in seconds. However, this requires good programming knowledge, and things get complicated when you need to create multiple resources together, like a VPC, EC2 instances, and S3 buckets.
To address these challenges, cloud providers offer Infrastructure as Code (IaC) tools. These tools let you define your infrastructure in code, using formats like YAML or JSON, and automate the provisioning of resources. AWS, for example, provides CloudFormation, which allows you to define AWS resources in templates.
Here are some examples of IaC tools:
AWS CloudFormation (for AWS)
Azure Resource Manager (for Azure)
Heat Template (for OpenStack)
Why Terraform?
With so many IaC tools available, why use Terraform? The answer lies in its flexibility and universal approach.
Let’s say you’re working with AWS and using CloudFormation, but later you switch to an organization that uses Azure. You would then need to learn Azure’s IaC tool. While you can certainly learn these tools, it can be a complex and time-consuming process.
Terraform solves this problem by providing a cloud-agnostic IaC tool. It allows you to manage infrastructure across multiple cloud platforms using a single language—HashiCorp Configuration Language (HCL). This means you don’t need to learn different IaC tools for different clouds. Just learn Terraform, and you can work with any cloud provider.
This is why Terraform has become such a popular and essential tool for DevOps and cloud engineers.
Install
Terraform
&aws cli
on your system using official documentation
How to Configure AWS CLI with Access Keys
Go to your AWS account and find the
secuirity credentials
optionInside
Security Credentials
, create a newAccess Key
. This will provide you with anAccess Key ID
and aSecret Access Key
Open your terminal and run the following command
aws configure
You will be prompted to enter the
Access Key ID
andSecret Access Key
. Paste the keys you just created to connect your AWS account to theCLI
To ensure everything is working, run
aws s3 ls
This command will list all the S3 buckets in your AWS account
Create an EC2 instance using terraform
Create a Terraform Configuration File (
main.tf
)provider "aws" { region = "us-east-1" # Set your desired AWS region } resource "aws_instance" "example" { ami = "ami-0c55b159cbfafe1f0" # Specify an appropriate AMI ID instance_type = "t2.micro"
Run the following command to initialize your Terraform project
terraform init
This command prepares the working directory by downloading the necessary provider plugins (in this case, for AWS) and setting up the environment
-
Preview Changes Using
terraform plan
terraform plan
This command will show you the "execution plan"—detailing what resources Terraform will create, modify, or destroy. It helps you confirm that everything looks correct before applying changes
Apply Changes Using
terraform apply
terraform apply
It is used to execute the changes described in your configuration files and actually create, modify, or delete infrastructure resources. After running
terraform plan
to preview the changes,terraform apply
makes those changes happenIf you encounter an error like this
This happens because the
AMI ID
provided may not be valid in your AWS regionTo resolve this Error
Go to your AWS account
In the EC2 Dashboard, search for a valid AMI ID
Copy the correct AMI ID
Replace the invalid AMI ID in the
main.tf
file with the correct one
Once you have the correct AMI ID, run the
terraform apply
command again to create the EC2 instanceIf the command runs successfully, an EC2 instance will be created in your AWS account
Run
terraform destroy
to delete all infrastructure resources that Terraform has created and is managing. This command will remove everything that is defined in your state file, essentially tearing down your entire infrastructure
Terraform State
Terraform keeps a file called a "state file" that stores the current setup of your infrastructure. This file helps Terraform figure out what changes need to be made by comparing the setup you want with what already exists. It uses this information to apply updates correctly.
Terraform Providers
In Terraform, providers
are plugins that allow Terraform to communicate with cloud platforms, services, or other APIs. Providers essentially tell Terraform which services to interact with, such as AWS, Azure, Google Cloud, etc.
When setting up infrastructure, you define providers
in your configuration file using the provider
block. This tells Terraform what cloud services you are going to use and sets necessary parameters such as region or authentication information.
Some examples of providers:
azurerm
- for Azuregoogle
- for Google Cloud Platformkubernetes
- for Kubernetesopenstack
- for OpenStackvsphere
- for VMware vSphere
Single Provider Example (AWS)
If you want to use Terraform to manage infrastructure on AWS, you first define the AWS provider
provider "aws" { region = "us-east-1" # Specify the AWS region }
Here, the region
us-east-1
is specified, which tells Terraform that resources should be created in the US East region.
Multiple Region Setup
Terraform supports setting up resources across multiple regions within the same cloud provider by using the alias
keyword. This allows you to define multiple instances
of the same provider, each targeting a different region.
provider "aws" {
alias = "us-east-1" # Alias to identify this provider instance
region = "us-east-1"
}
provider "aws" {
alias = "us-west-2" # Alias for another region
region = "us-west-2"
}
resource "aws_instance" "east_instance" {
ami = "ami-0123456789abcdef0"
instance_type = "t2.micro"
provider = "aws.us-east-1" # Use the east region provider
}
resource "aws_instance" "west_instance" {
ami = "ami-0123456789abcdef0"
instance_type = "t2.micro"
provider = "aws.us-west-2" # Use the west region provider
}
You can check the
ec2 instance
is created in bothus-east-1
&us-west-2
region
In this example
Two
provider
blocks are created for AWS, one for theUS East
region
and one for theUS West region
.The
alias
keyword helps Terraform distinguish between different instances of the same provider.Each resource (EC2 instance) is tied to a specific provider using the
provider
attribute.ami-0123456789abcdef0
Multi-Cloud Setup
Terraform allows you to use multiple providers in one project, enabling you to manage infrastructure across different cloud platforms simultaneously. Here's how you can set this up ?
Create a
providers.tf
FileBegin by creating a
providers.tf
file in the root directory of your Terraform project. This file will define the cloud providers that you want to use.Define Providers in
providers.tf
In the
providers.tf
file, define the providers for AWS and Azure.provider "aws" { region = "us-east-1" } provider "azurerm" { subscription_id = "your-azure-subscription-id" client_id = "your-azure-client-id" client_secret = "your-azure-client-secret" tenant_id = "your-azure-tenant-id" }
This configuration sets up AWS and Azure as providers in your project. Replace the placeholder values with your actual credentials.
Use Providers in Resource Definitions
Once you've configured the providers, you can create resources in AWS and Azure within the same project.
resource "aws_instance" "example" { ami = "ami-0123456789abcdef0" instance_type = "t2.micro" } resource "azurerm_virtual_machine" "example" { name = "example-vm" location = "eastus" size = "Standard_A1" }
In this example
An EC2 instance is provisioned in AWS using the
aws_instance
resource.A virtual machine is provisioned in Azure using the
azurerm_virtual_machine
resource.
Terraform Variables
Terraform variables make your configuration more dynamic, reusable, and flexible. Instead of hardcoding values directly in your code, you define variables that can be reused across your configuration. This makes it easier to adapt the same infrastructure for different environments or teams.
Types of Variables
Input Variables
It allow you to parameterize your Terraform configurations. This means you can pass values into your modules or configurations from the outside (for example, when running
terraform apply
). Input variables can be defined at both the module level and the root level.Defining Input Variables
You can define an input variable in Terraform like this
variable "instance_type" { description = "EC2 instance type" # A description for documentation type = string # The expected type (string, number, list, etc.) default = "t2.micro" # Default value if not provided }
This block defines an input variable
instance_type
, which can be set when running the configuration, or it will use the default value of"t2.micro"
if not providedUsing Input Variables
After defining a variable, you can reference it using the
var
keyword inside your configurationresource "aws_instance" "example_instance" { ami = var.ami_id # Referencing the variable value instance_type = var.instance_type # Referencing another variable }
When you run
terraform apply
, you can pass the value of the input variables through the command line or in a.tfvars
fileOutput Variables
Output variables are used to display values after the execution of a configuration. These are useful for retrieving key information from your infrastructure, such as the IP address of a created instance or resource IDs.
Defining Output Variables
You can define an output variable like this
output "public_ip" { description = "Public IP address of the EC2 instance" value = aws_instance.example_instance.public_ip # The value to output }
In this case, after Terraform finishes creating the EC2 instance, it will display the public IP address of the instance as part of the output
Organizing Terraform Files
When working on large projects, it’s important to organize your Terraform files for better readability and maintenance. A typical structure might look like this:
providers.tf
: Contains provider configurations.variables.tf
: Defines all input variables.outputs.tf
: Defines output variables.main.tf
: Contains the core infrastructure resources.
For projects involving multiple environments (development, production, staging), use a terraform.tfvars file to separate the actual values of the variables
Conditional Expressions
Conditional expressions in Terraform allow you to define dynamic behavior based on conditions. They work similarly to ternary operators (? :
) in programming languages. You can use them to decide whether resources should be created or how they should be configured based on variable values or other conditions.
Syntax of Conditional Expressions
The syntax of a conditional expression in Terraform is
condition ? true_value : false_value
This evaluates the condition
. If it’s true
, it returns the true_value
, otherwise, it returns the false_value
.
Conditional Resource Creation
You can conditionally create resources using the count
attribute:
resource "aws_instance" "example" {
count = var.create_instance ? 1 : 0 # Create instance if create_instance is true
ami = "ami-0123456789abcdef0"
instance_type = "t2.micro"
}
In this example, the instance will only be created if the variable create_instance
is set to true
. If false
, Terraform will create 0 instances.
Conditional Resource Configuration
You can use conditional expressions within resource configuration blocks to dynamically adjust settings:
resource "aws_security_group" "example" {
name = "example-sg"
description = "Example security group"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.enable_ssh ? ["0.0.0.0/0"] : [] # Conditionally allow SSH
}
}
In this example, SSH access is only allowed if the variable enable_ssh
is set to true
. Otherwise, the security group won’t allow any incoming SSH connections.
Terraform Built-in Functions
Terraform has a set of built-in functions that make it easier to manipulate and transform data in your configuration. These functions allow you to perform operations on lists, strings, and maps
Common Built-in Functions
concat(list1, list2, ...) Combines multiple lists into a single list.
variable "list1" { type = list default = ["a", "b"] } variable "list2" { type = list default = ["c", "d"] } output "combined_list" { value = concat(var.list1, var.list2) # Returns ["a", "b", "c", "d"] }
element(list, index) Retrieves an element from a list based on the given index.
variable "my_list" { type = list default = ["apple", "banana", "cherry"] } output "selected_element" { value = element(var.my_list, 1) # Returns "banana" }
length(list) Returns the number of elements in a list.
variable "my_list" { type = list default = ["apple", "banana", "cherry"] } output "list_length" { value = length(var.my_list) # Returns 3 }
lookup(map, key) Retrieves the value from a map by the specified key.
variable "my_map" { type = map default = { name = "Alice" age = 25 } } output "value" { value = lookup(var.my_map, "name") # Returns "Alice" }
join(separator, list) Joins the elements of a list into a single string, separated by the specified separator.
variable "my_list" { type = list default = ["apple", "banana", "cherry"] } output "joined_string" { value = join(", ", var.my_list) # Returns "apple, banana, cherry" }
Module in Terraform
In Terraform, modules
are like building blocks that help organize and reuse your infrastructure code. Instead of writing the same code over and over, you can group related resources into a module and use it multiple times. It make your Terraform setup easier to manage, reuse, and scale without duplicating code.