Terraform: Learning Variables, Loops, Conditions, and Resource Creation

This article dives into essential Terraform concepts—variables, variable precedence, conditions, count-based loops, and for_each loops—and shows how to use them to create EC2 instances, security groups, and Route 53 records. Each section explains the concept in simple terms, why it’s needed, and provides a practical example with file structures. The examples are inspired by real-world use cases, such as provisioning multiple EC2 instances with associated DNS records.
1. Terraform Variables
What are Variables?
Variables in Terraform allow you to parameterize your configurations, making them reusable and flexible. Instead of hardcoding values (e.g., AMI IDs, instance types), you define variables that can be set dynamically through defaults, input files, or command-line arguments.
Why Use Variables?
Reusability: Define a configuration once and reuse it across environments (e.g., dev, prod) by changing variable values.
Maintainability: Centralize values like AMI IDs or tags in one place, making updates easier.
Flexibility: Allow users to customize infrastructure without modifying the core code.
Readability: Make configurations clearer by using descriptive variable names.
How Variables Work
Variables are defined in a variables.tf
file (or any .tf
file) using the variable
block. Each variable can have:
type: Specifies the data type (e.g.,
string
,number
,list
,map
).default: An optional default value.
description: A note explaining the variable’s purpose.
Example: Defining Variables
Here’s a variables.tf
file defining variables for EC2 instances, security groups, and Route 53 records.
# variables.tf
variable "ami_id" {
type = string
default = "ami-09c813fb71547fc4f"
description = "AMI ID for EC2 instances"
}
variable "instance_type" {
type = string
default = "t2.micro"
description = "EC2 instance type"
}
variable "ec2_tags" {
type = map(string)
default = {
Name = "HelloWorld"
Purpose = "variables_demo"
}
description = "Tags for EC2 instances"
}
variable "sg_name" {
type = string
default = "allow-all"
description = "Security group name"
}
variable "sg_description" {
type = string
default = "Allow all traffic for instance"
description = "Security group description"
}
variable "cidr_blocks" {
type = list(string)
default = ["0.0.0.0/0"]
description = "CIDR blocks for security group"
}
variable "instances" {
type = list(string)
default = ["mongodb", "mysql", "redis", "rabbitmq"]
description = "Names of EC2 instances"
}
variable "zone_id" {
type = string
default = "Z06785221SBGYOQ3RLYGM"
description = "Route 53 hosted zone ID"
}
variable "domain_name" {
type = string
default = "rshopdaws84s.site"
description = "Domain name for Route 53 records"
}
Using Variables
Variables are referenced in other .tf
files using var.<variable_name>
. For example, var.ami_id
refers to the ami_id
variable.
2. Variable Precedence
What is Variable Precedence?
Terraform allows you to set variable values in multiple ways (e.g., defaults, files, command-line flags). Variable precedence determines which value Terraform uses when a variable is defined in multiple places.
Why is Variable Precedence Important?
Flexibility: You can override default values for specific environments or use cases.
Control: Ensures the correct value is used when multiple sources provide values.
Debugging: Understanding precedence helps troubleshoot unexpected variable values.
Precedence Order (Highest to Lowest)
Command-line flags:
-var "ami_id=ami-12345678"
or-var-file=prod.tfvars
..tfvars
files: Files liketerraform.tfvars
orprod.tfvars
loaded explicitly.Environment variables: Variables prefixed with
TF_VAR_
(e.g.,TF_VAR_ami_id
).Default values: Defined in the
variable
block’sdefault
field.
Example: Setting Variable Values
Default in
variables.tf
:variable "instance_type" { type = string default = "t2.micro" }
Override in
prod.tfvars
:instance_type = "t3.medium"
Override via CLI:
terraform apply -var="instance_type=t3.large"
In this case, t3.large
(CLI) takes precedence over t3.medium
(prod.tfvars
), which takes precedence over t2.micro
(default).
Example File: prod.tfvars
# prod.tfvars
ami_id = "ami-98765432"
instance_type = "t3.medium"
ec2_tags = {
Name = "ProdServer"
Purpose = "production"
}
Run with:
terraform apply -var-file=prod.tfvars
3. Conditions
What are Conditions?
Conditions in Terraform allow you to make decisions in your configuration using conditional expressions. They enable you to apply different values or resources based on a condition, similar to if-else
logic in programming.
Why Use Conditions?
Dynamic Configurations: Apply different settings based on environment (e.g., dev vs. prod).
Cost Optimization: Use smaller instance types in dev but larger ones in prod.
Simplification: Avoid duplicating code for similar resources with slight differences.
Syntax
A conditional expression follows this format:
condition ? value_if_true : value_if_false
Example: Conditional Instance Type
Suppose you want to use t2.micro
for dev and t3.medium
for prod based on a variable environment
.
# variables.tf
variable "environment" {
type = string
default = "dev"
description = "Deployment environment (dev or prod)"
}
# ec2.tf
resource "aws_instance" "example" {
ami = var.ami_id
instance_type = var.environment == "prod" ? "t3.medium" : "t2.micro"
vpc_security_group_ids = [aws_security_group.allow_all.id]
tags = var.ec2_tags
}
Here, if var.environment
is "prod"
, the instance type is t3.medium
; otherwise, it’s t2.micro
.
4. Count-Based Loop
What is a Count-Based Loop?
The count
meta-argument in Terraform creates multiple instances of a resource based on a numeric value. It’s like a for
loop that repeats a resource block a specified number of times.
Why Use Count?
Scalability: Create multiple resources (e.g., 4 EC2 instances) without duplicating code.
Simplicity: Manage similar resources with a single block.
Dynamic Provisioning: Adjust the number of resources based on variables.
How It Works
Set
count = <number>
in a resource block.Access the index of each instance using
count.index
(starts at 0).Use
count
with lists to assign unique values (e.g., names) to each resource.
Example: Creating Multiple EC2 Instances
This example creates 4 EC2 instances, each named after an entry in the instances
variable list.
# ec2.tf
resource "aws_instance" "servers" {
count = 4
ami = var.ami_id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.allow_all.id]
tags = {
Name = var.instances[count.index]
}
}
Here:
count = 4
creates 4 EC2 instances.var.instances[count.index]
assigns names (mongodb
,mysql
,redis
,rabbitmq
) from theinstances
list.
5. For_Each Loop
What is a For_Each Loop?
The for_each
meta-argument creates resources based on a map or set of strings, where each resource is tied to a unique key. Unlike count
, which uses numeric indices, for_each
uses keys for more explicit control.
Why Use For_Each?
Key-Based Control: Ideal when resources need unique identifiers (e.g., names or IDs).
Flexibility: Works with maps or sets, allowing complex configurations.
Stability: Adding or removing items doesn’t affect unrelated resources (unlike
count
, where index shifts can cause issues).
Syntax
resource "type" "name" {
for_each = <map_or_set>
# Access key/value with each.key and each.value
}
Example: Creating EC2 Instances with For_Each
This example creates EC2 instances using a map of instance names and types.
# variables.tf
variable "instance_configs" {
type = map(object({
name = string
type = string
}))
default = {
mongodb = { name = "mongodb", type = "t2.micro" }
mysql = { name = "mysql", type = "t3.small" }
redis = { name = "redis", type = "t2.micro" }
}
}
# ec2.tf
resource "aws_instance" "servers" {
for_each = var.instance_configs
ami = var.ami_id
instance_type = each.value.type
vpc_security_group_ids = [aws_security_group.allow_all.id]
tags = {
Name = each.value.name
}
}
Here:
for_each
iterates over theinstance_configs
map.each.key
is the map key (e.g.,mongodb
).each.value.name
andeach.value.type
access the name and type for each instance.
6. Creating EC2 Instances, Security Groups, and Route 53 Records
Now, let’s combine the above concepts to create a complete setup with EC2 instances, a security group, and Route 53 DNS records.
File Structure
├── ec2.tf
├── output.tf
├── provider.tf
├── route53.tf
├── sg.tf
├── terraform.tfvars
└── variables.tf
Complete Example
provider.tf
Configures the AWS provider.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.98.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
variables.tf
Defines all variables (as shown in the Variables section).
sg.tf
Creates a security group allowing all traffic.
resource "aws_security_group" "allow_all" {
name = var.sg_name
description = var.sg_description
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = var.cidr_blocks
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = var.cidr_blocks
}
tags = var.sg_tags
}
ec2.tf
Creates EC2 instances using a count-based loop and conditions.
resource "aws_instance" "servers" {
count = length(var.instances)
ami = var.ami_id
instance_type = var.environment == "prod" ? "t3.medium" : var.instance_type
vpc_security_group_ids = [aws_security_group.allow_all.id]
tags = {
Name = var.instances[count.index]
}
}
route53.tf
Creates Route 53 DNS records for each EC2 instance.
resource "aws_route53_record" "dns" {
count = length(var.instances)
zone_id = var.zone_id
name = "${var.instances[count.index]}.${var.domain_name}"
type = "A"
ttl = 1
records = [aws_instance.servers[count.index].private_ip]
}
output.tf
Outputs instance details.
output "instance_ips" {
value = { for instance in aws_instance.servers : instance.tags.Name => instance.private_ip }
}
terraform.tfvars
Overrides defaults for a specific environment.
environment = "prod"
instance_type = "t3.medium"
Steps to Run
Initialize:
terraform init
Plan:
terraform plan
Apply:
terraform apply
Destroy:
terraform destroy
(to clean up)
Interview Questions and Answers
1. What are Terraform variables, and why are they used?
Answer: Terraform variables allow you to parameterize configurations, making them reusable and flexible. They’re used to avoid hardcoding values, centralize settings, and enable customization across environments. Variables are defined with type
, default
, and description
and referenced using var.<name>
.
2. Explain Terraform variable precedence with an example.
Answer: Variable precedence determines which value Terraform uses when a variable is set in multiple places. The order is:
Command-line flags (
-var
or-var-file
).tfvars
filesEnvironment variables (
TF_VAR_
)Default values
Example: Ifinstance_type
has a default oft2.micro
butprod.tfvars
sets it tot3.medium
and CLI uses-var="instance_type=t3.large"
, Terraform usest3.large
.
3. How do conditions work in Terraform? Provide an example.
Answer: Conditions use the syntax condition ? value_if_true : value_if_false
to apply values dynamically. Example: instance_type = var.environment == "prod" ? "t3.medium" : "t2.micro"
sets t3.medium
for prod and t2.micro
for other environments.
4. What is the difference between count
and for_each
in Terraform?
Answer:
Count: Creates multiple resources based on a number, using
count.index
. Best for ordered lists but can cause issues if the list changes.For_Each: Creates resources based on a map or set, using
each.key
andeach.value
. Ideal for key-based resources and stable configurations.
Example: Usecount
to create 4 EC2 instances with names from a list; usefor_each
to create instances from a map with specific names and types.
5. How would you create a security group in Terraform?
Answer: Define a security group using the aws_security_group
resource, specifying name
, description
, ingress
, and egress
rules. Example:
resource "aws_security_group" "allow_all" {
name = "allow-all"
description = "Allow all traffic"
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
6. What is the purpose of Route 53 records in Terraform?
Answer: Route 53 records map domain names to resources (e.g., EC2 instances) in AWS. In Terraform, the aws_route53_record
resource creates DNS records like A records. Example: Create an A record for mongodb.example.com
pointing to an EC2 instance’s IP.
7. How do you handle dependencies in Terraform?
Answer: Terraform automatically manages dependencies based on resource references. For example, if an EC2 instance references a security group’s ID (vpc_security_group_ids = [aws_security_group.allow_all.id]
), Terraform creates the security group first. Explicit dependencies can be defined using depends_on
.
Conclusion
This guide covered Terraform’s concepts—variables, variable precedence, conditions, count-based loops, and for_each loops—and demonstrated their use in creating EC2 instances, security groups, and Route 53 records. By using these features, you can write flexible, scalable, and maintainable infrastructure code. The provided file structure and examples make it easy to get started, and the interview questions prepare you for real-world discussions.
Subscribe to my newsletter
Read articles from ESHNITHIN YADAV directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
