TechLead
Lesson 20 of 25
5 min read
Cloud & Kubernetes

Infrastructure as Code

Deep dive into IaC principles, Terraform modules, workspaces, state management, and advanced patterns for managing infrastructure at scale

Infrastructure as Code (IaC) Principles

Infrastructure as Code is the practice of managing and provisioning infrastructure through machine-readable definition files rather than manual processes or interactive configuration tools. IaC brings software engineering practices — version control, code review, testing, and CI/CD — to infrastructure.

IaC Core Principles

  • Idempotency: Applying the same configuration multiple times produces the same result
  • Immutability: Replace resources rather than modifying them in place
  • Declarative: Describe the desired end state, not the steps to get there
  • Version Control: All infrastructure code lives in Git alongside application code
  • Automation: Infrastructure changes are applied through CI/CD, not manual commands

Terraform Modules

Modules are the key building block for reusable, composable Terraform code. A module is a container for multiple resources that are used together. Instead of copying infrastructure code, you define it once as a module and instantiate it multiple times with different parameters.

# Module directory structure:
# modules/
#   vpc/
#     main.tf
#     variables.tf
#     outputs.tf
#   eks-cluster/
#     main.tf
#     variables.tf
#     outputs.tf
#   rds/
#     main.tf
#     variables.tf
#     outputs.tf

# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "${var.project}-${var.environment}-vpc"
  }
}

resource "aws_subnet" "public" {
  count             = length(var.public_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.public_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project}-${var.environment}-public-${count.index + 1}"
    "kubernetes.io/role/elb" = "1"
  }
}

resource "aws_subnet" "private" {
  count             = length(var.private_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "${var.project}-${var.environment}-private-${count.index + 1}"
    "kubernetes.io/role/internal-elb" = "1"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.project}-${var.environment}-igw"
  }
}

resource "aws_nat_gateway" "main" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id

  tags = {
    Name = "${var.project}-${var.environment}-nat"
  }
}

resource "aws_eip" "nat" {
  domain = "vpc"
}

Using Modules

# main.tf - Using modules
module "vpc" {
  source = "./modules/vpc"

  project              = var.project_name
  environment          = var.environment
  vpc_cidr             = "10.0.0.0/16"
  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  private_subnet_cidrs = ["10.0.10.0/24", "10.0.20.0/24", "10.0.30.0/24"]
  availability_zones   = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

module "eks" {
  source = "./modules/eks-cluster"

  cluster_name    = "${var.project_name}-${var.environment}"
  vpc_id          = module.vpc.vpc_id
  subnet_ids      = module.vpc.private_subnet_ids
  node_count      = var.environment == "production" ? 3 : 1
  instance_types  = ["t3.medium"]
}

module "rds" {
  source = "./modules/rds"

  identifier    = "${var.project_name}-${var.environment}-db"
  vpc_id        = module.vpc.vpc_id
  subnet_ids    = module.vpc.private_subnet_ids
  instance_class = var.environment == "production" ? "db.r6g.large" : "db.t3.micro"
  multi_az      = var.environment == "production"
}

Terraform Workspaces

# Create and manage workspaces
terraform workspace list
terraform workspace new staging
terraform workspace new production
terraform workspace select production

# Use workspace name in configuration
# main.tf
locals {
  environment = terraform.workspace
  instance_count = {
    development = 1
    staging     = 2
    production  = 3
  }
}

resource "aws_instance" "web" {
  count         = local.instance_count[local.environment]
  instance_type = local.environment == "production" ? "t3.large" : "t3.micro"
  # ...
}

State Management Best Practices

# Remote state with locking
terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "projects/my-app/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-state-locks"
    encrypt        = true
  }
}

# State commands
terraform state list                       # List all resources
terraform state show aws_s3_bucket.website # Show resource details
terraform state mv aws_s3_bucket.old aws_s3_bucket.new  # Rename/move
terraform state rm aws_s3_bucket.website   # Remove from state (not from cloud)
terraform import aws_s3_bucket.website my-existing-bucket  # Import existing resource

IaC Tool Comparison

Tool Language State Best For
TerraformHCLStatefulMulti-cloud infrastructure
PulumiTypeScript, Python, GoStatefulDevelopers who prefer real languages
CloudFormationYAML/JSONAWS-managedAWS-only infrastructure
CDKTypeScript, PythonAWS-managedAWS with programming languages
AnsibleYAMLStatelessConfiguration management, provisioning

Key Takeaways

  • IaC brings software engineering practices to infrastructure management
  • Terraform modules enable reusable, composable infrastructure components
  • Always store state remotely with locking for team collaboration
  • Use workspaces or separate state files to manage multiple environments
  • Apply infrastructure changes through CI/CD pipelines, not manual commands

Continue Learning