Saturday, 28 June 2025

Terraform -2

 ✅ 1. Terraform Basics 

a. What is Terraform? 

Terraform is an open-source Infrastructure as Code (IaC) tool by HashiCorp to provision and manage cloud infrastructure using declarative configuration files. 

 

✅ 2. Core Terraform Concepts 

Concept 

Description 

Providers 

Interface to cloud platforms (e.g., AWS, Azure, GCP). 

Resources 

The actual components created (e.g., aws_instance, aws_s3_bucket). 

Variables 

Used to parameterize values for reusability and environment config. 

Modules 

Collection of .tf files that encapsulate reusable logic. 

State 

Tracks the real-world infrastructure (terraform.tfstate). 

Backend 

Where state is stored (e.g., local, S3, Terraform Cloud). 

Data 

Read-only lookup to fetch existing resources (data "aws_vpc" ...). 

 


✅ 3. Folder Structure with Multiple Environments 

 

terraform-project/ 
├── modules/ 
│   ├── s3/ 
│   │   └── main.tf 
│   │   └── variables.tf 
│   │   └── outputs.tf 
│   ├── vpc/ 
│       └── main.tf 
│       └── variables.tf 
│       └── outputs.tf 
├── environments/ 
│   ├── dev/ 
│   │   └── main.tf 
│   │   └── variables.tf 
│   │   └── backend.tf 
│   │   └── terraform.tfvars 
│   ├── prod/ 
│       └── main.tf 
│       └── variables.tf 
│       └── backend.tf 
│       └── terraform.tfvars 
├── provider.tf 
└── versions.tf 
 

 

✅ 4. Provider Configuration 

# versions.tf terraform { required_version = ">= 1.4.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } }

# provider.tf 
provider "aws" { 
 region  = var.region 
 profile = var.profile 
} 
 

 

✅ 5. Reusable Modules Example 

a. S3 Module 

# modules/s3/main.tf 
resource "aws_s3_bucket" "this" { 
 bucket = var.bucket_name 
 acl    = var.acl 
 
 tags = { 
   Environment = var.environment 
 } 
} 
 

# modules/s3/variables.tf 
variable "bucket_name" {} 
variable "acl" { default = "private" } 
variable "environment" {} 
 

# modules/s3/outputs.tf 
output "bucket_name" { 
 value = aws_s3_bucket.this.bucket 
} 
 

b. Using Module in Dev/Prod 

# environments/dev/main.tf 
module "s3" { 
 source      = "../../modules/s3" 
 bucket_name = "myapp-dev-bucket" 
 environment = "dev" 
} 
 

 

✅ 6. Conditional Resource Creation 

Use the count or for_each meta-argument. 

Example: Create only in prod 

resource "aws_s3_bucket" "only_in_prod" { 
 count  = var.environment == "prod" ? 1 : 0 
 bucket = "prod-only-bucket" 
} 
 

 

✅ 7. Dynamic Blocks 

Used when nested blocks need to be created conditionally. 

resource "aws_security_group" "example" { 
 name = "example" 
 
 dynamic "ingress" { 
   for_each = var.ingress_rules 
   content { 
     from_port   = ingress.value.from_port 
     to_port     = ingress.value.to_port 
     protocol    = ingress.value.protocol 
     cidr_blocks = ingress.value.cidr_blocks 
   } 
 } 
} 
 

 

✅ 8. State Management 

  • Local (default): Creates terraform.tfstate in working dir. 

  • Remote (recommended for teams): 

# backend.tf 
terraform { 
 backend "s3" { 
   bucket = "my-terraform-state" 
   key    = "dev/terraform.tfstate" 
   region = "us-east-1" 
 } 
} 
 

 

✅ 9. Using Data Sources 

Fetch existing resources instead of creating new ones. 

data "aws_vpc" "default" { 
 default = true 
} 
 
output "vpc_id" { 
 value = data.aws_vpc.default.id 
} 
 

 

✅ 10. Terratest for Testing Terraform 

What is Terratest? 

Terratest is a Go-based testing framework to automate testing for Terraform deployments. 

a. Folder Structure for Terratest 

terraform-project/ 
├── test/ 
│   ├── s3_test.go 
 

b. Sample Test (s3_test.go) 

go 

package test 
 
import ( 
 "testing" 
 "github.com/gruntwork-io/terratest/modules/terraform" 
 "github.com/stretchr/testify/assert" 
) 
 
func TestS3Bucket(t *testing.T) { 
 t.Parallel() 
 
 terraformOptions := &terraform.Options{ 
   TerraformDir: "../environments/dev", 
   Vars: map[string]interface{}{ 
     "bucket_name": "myapp-dev-bucket", 
   }, 
 } 
 
 defer terraform.Destroy(t, terraformOptions) 
 terraform.InitAndApply(t, terraformOptions) 
 
 bucketName := terraform.Output(t, terraformOptions, "bucket_name") 
 assert.Equal(t, "myapp-dev-bucket", bucketName) 
} 
 

c. Run the Test 

bash 

go test test/s3_test.go 
 

 

✅ 11. Best Practices 

  • Use remote state with locking (S3 + DynamoDB). 

  • Separate environments (dev, stage, prod). 

  • Parameterize everything with terraform.tfvars. 

  • Store secrets in Vault or SSM Parameter Store, not plain text. 

  • Always run terraform plan before apply. 

  • Use pre-commit hooks and CI/CD (GitHub Actions, GitLab CI) to test on PRs. 

 

✅ 12. How to Run Terraform (Dev Environment) 

cd environments/dev 
terraform init 
terraform plan -var-file="terraform.tfvars" 
terraform apply -var-file="terraform.tfvars" 
 

 

Would you like a ready-made Terraform+Terratest GitHub repo, or should I help you scaffold a sample project for S3+EC2+ALB with conditional logic and test cases? 


✅ 1. String 

📌 Declare: 

variable "region" { 
 type    = string 
 default = "us-east-1" 
} 
 

✅ Use: 

provider "aws" { 
 region = var.region 
} 
 

 

✅ 2. Number 

📌 Declare: 

variable "instance_count" { 
 type    = number 
 default = 2 
} 
 

✅ Use: 

resource "aws_instance" "example" { 
 count = var.instance_count 
 ... 
} 
 

 

✅ 3. Boolean 

📌 Declare: 

variable "enable_s3" { 
 type    = bool 
 default = true 
} 
 

✅ Use (with condition): 

resource "aws_s3_bucket" "test" { 
 count  = var.enable_s3 ? 1 : 0 
 bucket = "my-terraform-bucket" 
} 
 

 

✅ 4. List of Strings 

📌 Declare: 

variable "azs" { 
 type    = list(string) 
 default = ["us-east-1a", "us-east-1b"] 
} 
 

✅ Use: 

resource "aws_subnet" "public" { 
 count             = length(var.azs) 
 availability_zone = var.azs[count.index] 
} 
 

 

✅ 5. Map (Key/Value Pairs) 

📌 Declare: 

variable "tags" { 
 type = map(string) 
 default = { 
   Owner   = "Mitesh" 
   Project = "Terraform" 
 } 
} 
 

✅ Use: 

resource "aws_instance" "web" { 
 tags = var.tags 
} 
 

 

✅ 6. Object 

📌 Declare: 

variable "instance_config" { 
 type = object({ 
   ami           = string 
   instance_type = string 
 }) 
 
 default = { 
   ami           = "ami-0c55b159cbfafe1f0" 
   instance_type = "t2.micro" 
 } 
} 
 

✅ Use: 

resource "aws_instance" "web" { 
 ami           = var.instance_config.ami 
 instance_type = var.instance_config.instance_type 
} 
 

 

✅ 7. List of Objects 

📌 Declare: 

variable "ebs_volumes" { 
 type = list(object({ 
   device_name = string 
   volume_size = number 
   volume_type = string 
 })) 
} 
 

✅ Use: 

dynamic "ebs_block_device" { 
 for_each = var.ebs_volumes 
 content { 
   device_name = ebs_block_device.value.device_name 
   volume_size = ebs_block_device.value.volume_size 
   volume_type = ebs_block_device.value.volume_type 
 } 
} 
 

 

✅ 8. Set 

Similar to list, but all values must be unique (no duplicates). 

📌 Declare: 

variable "availability_zones" { 
 type = set(string) 
 default = ["us-east-1a", "us-east-1b"] 
} 
 

 

✅ 9. Tuple (fixed-length list of different types) 

📌 Declare: 

variable "example_tuple" { 
 type = tuple([string, number, bool]) 
 default = ["example", 42, true] 
} 
 

✅ Use: 

h 

CopyEdit 

output "tuple_value" { 
 value = var.example_tuple[1] # outputs 42 
} 
 

 

🧪 Summary Table 

Type 

Syntax 

Example Use Case 

string 

string 

Region, names 

number 

number 

Instance count 

bool 

bool 

Conditional resources 

list(string) 

list(string) 

Subnet CIDRs, AZs 

map(string) 

map(string) 

Tags 

object({...}) 

object 

EC2 config, complex settings 

list(object) 

list(object) 

Multiple EBS volumes 

set(string) 

set(string) 

Unique AZs or names 

tuple([...]) 

tuple([string, number]) 

Mixed values 

 

✅ How to Pass Values 

1. In .tfvars File 

region = "us-east-1" 
 
tags = { 
 Owner   = "esh" 
 Project = "Terraform Hands-on" 
} 
 

Then run: 

terraform apply -var-file="terraform.tfvars" 
 

2. Via CLI (Quick Test) 

terraform apply -var="region=us-east-1" 

 

 

6. Locals and Variable Precedence 

locals { 

environment = "dev" 
 app_name    = "myapp" 
 full_name   = "${local.app_name}-${local.environment}" 
} 
 

Variable precedence order (high to low): 

  1. CLI flags (-var) 

  1. Environment variables 

  1. terraform.tfvars 

  1. *.auto.tfvars 

  1. Default values in variable blocks 

 

 

✅ 1. local-exec Provisioner Example 

This runs a local command on your machine after the resource is created. 

🔧 Use Case: Print EC2 instance ID to console 

resource "aws_instance" "example" { 
 ami           = "ami-0c55b159cbfafe1f0" 
 instance_type = "t2.micro" 
 
 provisioner "local-exec" { 
   command = "echo EC2 instance ${self.id} created" 
 } 
 
 tags = { 
   Name = "LocalExecExample" 
 } 
} 
 

 

✅ 2. remote-exec Provisioner Example 

This connects to the EC2 instance via SSH and runs commands inside the instance. 

🔧 Use Case: Install nginx on EC2 after creation 

resource "aws_instance" "web" { 
 ami           = "ami-0c55b159cbfafe1f0" 
 instance_type = "t2.micro" 
 key_name      = "my-key" # Ensure your EC2 key pair is available 
 
 provisioner "remote-exec" { 
   inline = [ 
     "sudo yum update -y", 
     "sudo yum install -y nginx", 
     "sudo systemctl start nginx" 
   ] 
 } 
 
 connection { 
   type        = "ssh" 
   user        = "ec2-user" 
   private_key = file("~/.ssh/my-key.pem") 
   host        = self.public_ip 
 } 
 
 tags = { 
   Name = "RemoteExecExample" 
 } 
} 
 

 

🚫 When to Avoid Provisioners 

Terraform recommends avoiding provisioners unless absolutely necessary, because: 

  • They break idempotency 

  • They can fail for networking/SSH issues 

🔁 Preferred alternatives: 

  • Use user_data to bootstrap EC2 

  • Use tools like Ansible, Chef, or SSM documents 

 

user_data Alternative (Better for EC2 bootstrap) 

resource "aws_instance" "web" { 
 ami           = "ami-0c55b159cbfafe1f0" 
 instance_type = "t2.micro" 
 
 user_data = <<-EOF 
             #!/bin/bash 
             yum update -y 
             yum install -y nginx 
             systemctl start nginx 
           EOF 
 
 tags = { 
   Name = "UserDataExample" 
 } 
} 



-----------------------------------


count vs for_each in Terraform 

Feature 

count 

for_each 

Input Type 

number 

set, list, or map 

Key Access 

count.index 

each.key, each.value 

Use Case 

Simple repetition 

Loop over maps, sets, or lists 

Limitations 

Index-based only, no custom keys 

Keys and values available for use 

 

✅ 1. Using count – Simple Repeat 

🔧 Use Case: Create 3 EC2 instances 

resource "aws_instance" "web" { 
 count         = 3 
 ami           = "ami-0c55b159cbfafe1f0" 
 instance_type = "t2.micro" 
 
 tags = { 
   Name = "WebServer-${count.index}" 
 } 
} 
 

🎯 Use when you just need N copies of the same thing. 

 

✅ 2. Using for_each – Loop over Named Items 

🔧 Use Case: Create resources with different names or configs 

variable "server_names" { 
 default = ["app", "db", "cache"] 
} 
 
resource "aws_instance" "named" { 
 for_each      = toset(var.server_names) 
 ami           = "ami-0c55b159cbfafe1f0" 
 instance_type = "t2.micro" 
 
 tags = { 
   Name = each.key 
 } 
} 
 

✅ Better for tracking by name and deleting one item without affecting others. 

 

✅ 3. Using for_each with Map (Key → Config) 

hcl 

CopyEdit 

variable "instance_map" { 
 default = { 
   "app1" = "t2.micro" 
   "app2" = "t3.micro" 
 } 
} 
 
resource "aws_instance" "multi" { 
 for_each      = var.instance_map 
 ami           = "ami-0c55b159cbfafe1f0" 
 instance_type = each.value 
 
 tags = { 
   Name = each.key 
 } 
} 
 

 

❗Important Notes 

Concept 

count 

for_each 

Deletion impact 

Deleting one may reindex others 

Deleting one doesn't affect others 

Dynamic keys 

❌ No 

✅ Yes (via each.key) 

Best use case 

Identical repeat 

Named resources / dynamic map or set 


 



Alias for provider


provider "aws" { alias = "dev" region = "us-west-2" profile = "dev-profile" } provider "aws" { alias = "prod" region = "us-east-1" profile = "prod-profile" } resource "aws_s3_bucket" "dev_bucket" { provider = aws.dev bucket = "my-dev-bucket" } resource "aws_s3_bucket" "prod_bucket" { provider = aws.prod bucket = "my-prod-bucket" }

 


Tool Latest Stable Version
Terraform 1.8.1
AWS CLI 2.16.8
Sentinel 0.23.3
Bitbucket Server 8.20.1
Vault 1.20.0


 

Benefits of locals in Terraform 

Benefit 

Description 

Avoid Duplication 

Store commonly used expressions (e.g., tags, naming formats, regions) in one place. 

Improve Readability 

Give complex expressions meaningful names for better understanding. 

Easier Maintenance 

Change a value in one place instead of updating it in multiple locations. 

Reusable Logic 

Encapsulate derived values or computed logic that are reused across resources. 

 

Picture 

 

Notes 

  • locals are evaluated at plan time. 

  • They are read-only once defined. 

  • They do not persist between Terraform runs (not like variables stored in state). 

 

No comments:

Post a Comment