This Blog demonstrates the usage of various Terraform functions such as lookup
, count
, and condition
, along with implementing file provisioners (remote-exec
, local-exec
). The goal is to dynamically manage infrastructure using variables, conditional logic, and provisioning tasks.
clone repository https://github.com/imkiran13/Mastering-Terraform.git
Project Structure
ec2.tf
: Main file to create EC2 instances.variables.tf
: Define variables such as AMIs, instance type, keyname, and environment.terraform.tfvars
: Assign values to variables such as AMI IDs for different regions and the environment.null.tf
: Implementsnull_resource
to run scripts without recreating instances.userdata.sh
: Script to install software on EC2 instances after they are created.
Terraform Functions Overview
1. AMI Lookup
The lookup
function helps dynamically retrieve AMI IDs based on the region.
Example:
variable "amis" {
type = map(string)
}
# In terraform.tfvars
amis = {
us-east-1 = "ami-0abcd1234efgh5678"
us-east-2 = "ami-0wxyz1234mnop5678"
}
# In ec2.tf
ami = lookup(var.amis, var.aws_region)
This setup allows us to deploy EC2 instances using region-specific AMIs. For example, AMIs in us-east-1
may not work in us-east-2
.
2. Instance Count with Subnet Mapping
We declare three subnets, and each subnet must map to one EC2 instance. By using count
, we can define how many instances to create based on the length of subnets.
count = length(var.public_cidr_block)
subnet_id = element(var.subnets, count.index)
3. Conditional Deployment
Using a condition, we can decide how many instances to create based on the environment.
count = var.environment == "prod" ? 3 : 1
This means if the environment is prod
, 3 instances are created; otherwise, 1 instance is created.
Provisioners
File
Provisioning with remote-exec
We use provisioners to apply scripts after EC2 instances are created without recreating the instances.
User Data: Initially, the user data script is passed during instance creation.
Provisioners: To avoid recreating instances for every change, we use
null_resource
to run scripts or commands on existing instances.
Verify the Private Key File
Ensure the
devops.pem
file exists at the path specified and has the correct permissions (read-only, typicallychmod 400 devops.pem
).Ensure the private key in
devops.pem
corresponds to the public key added to the instances during their creation (via the AWS key pair or manually).
Example: create null.tf
file and create file provisioner
resource "null_resource" "cluster" {
count = length(var.private_cird_block)
provisioner "file" {
source = "user-data.sh"
destination = "/tmp/user-data.sh"
connection {
type = "ssh"
user = "ubuntu"
private_key = file("devops.pem")
host = element(aws_instance.public-server.*.public_ip, count.index)
}
}
}
Run the terraform command terraform apply —auto-approve
SSH into public instance 1
before adding null.tf
resource
After adding null.tf
resource
We have successfully copied the user-data.sh
file from local to remote.
Tainting Resources
If we need to recreate a resource, we can use Terraform's taint
feature. Marking a resource as "tainted" forces Terraform to recreate it during the next apply.
Open the user-data.sh
file, make your changes, and then apply them.
Apply the changes and check if they are reflected.
To make sure the changes are applied, we need to taint the resource. This way, only the script is updated on our EC2 server, and the entire EC2 instance is not restarted.
Taint Example
terraform taint null_resource.cluster[0]
terraform apply
Check if the changes are applied now without restarting the entire EC2 instance.
ssh into public ec2 server
Create a remote-exec
provisioner to install all the packages on the EC2 server without restarting it.
update null.tf
file
resource "null_resource" "cluster" {
count = length(var.private_cird_block)
provisioner "file" {
source = "user-data.sh"
destination = "/tmp/user-data.sh"
connection {
type = "ssh"
user = "ubuntu"
private_key = file("devops.pem")
host = element(aws_instance.public-server.*.public_ip, count.index)
}
}
provisioner "remote-exec" {
inline = [
"sudo chmod 400 /tmp/user-data.sh",
"sudo /tmp/user-data.sh",
"sudo apt update",
"sudo apt install jq unzip -y",
]
connection {
type = "ssh"
user = "ubuntu"
private_key = file("devops.pem")
host = element(aws_instance.public-server.*.public_ip, count.index)
}
}
}
This marks the resource as needing recreation, allowing the new script to be applied without affecting the rest of the infrastructure
.
Copy the EC2 public IP http://ec2-public-ip/
and paste it into your browser.
Destroy resources using terraform destroy --auto-approve
Next Steps
- Explore Terraform Modules for better structuring and reuse of code.
Interview Tips
What is taint in Terraform? Taint marks a resource for recreation. You can manually taint a resource using the terraform taint
command, causing Terraform to destroy and recreate it during the next apply
. Conversely, you can "untaint" a resource to prevent it from being recreated.