Terraform Day 7: Terraform Functions Part: 2

Terraform Day 7: Terraform Functions Part: 2

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: Implements null_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, typically chmod 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.