100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Infrastructure as Code — Terraform & Ansible
55 minintermediate

Terraform Modules — VPC, ALB, EC2 ASG, RDS Multi-AZ and S3

What You'll Build

You will write the Terraform configuration for all five infrastructure components of the capstone project, composing the VPC module from M2 with new ALB, ASG, RDS and S3 configurations. Each component uses production patterns: the ALB with HTTPS listener and ACM certificate, the ASG with launch template and health check, the RDS with encryption and Multi-AZ, and the S3 bucket with all security settings. You will also write the module composition in the root configuration and verify that 'terraform plan' shows the expected resources and 'Checkov' passes all HIGH severity checks.

Analogy🏏Cricket
🏏 Think of it like cricket: You are designing the BCCI's standard cricket ground specification module — a parameterisable blueprint that can be instantiated for any venue in the country without redrawing it from scratch. The blueprint fixes the non-negotiables every certified ground shares: a boundary perimeter (the VPC and its CIDR), spectator zones with public access (public subnets with an Internet Gateway route), and restricted player-and-officials zones behind accreditation checks (private subnets routing through NAT). What varies per venue arrives as parameters: how many stands to build (var.azs and subnet counts), whether this is a full international stadium or a modest district ground (var.single_nat_gateway as the cost lever — one shared service corridor instead of one per stand), and the venue's name and signage (tags). Wankhede and a Ranchi district ground are instantiated from the same drawing with different inputs — and when the board later improves the blueprint (adds flow logs, tightens NACLs), every venue inherits the improvement on its next renovation (module version bump) instead of each ground hand-patching its own architecture. That is the entire economics of module authorship: design once, rigorously, then stamp out consistent grounds forever.
hcl
# environments/production/main.tf — Capstone infrastructure

terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws    = { source = "hashicorp/aws",    version = "~> 5.0" }
    random = { source = "hashicorp/random", version = "~> 3.5" }
  }
  backend "s3" {
    bucket         = var.state_bucket
    key            = "cricket-analytics/production/terraform.tfstate"
    region         = "ap-south-1"
    dynamodb_table = "cricket-terraform-locks"
    encrypt        = true
  }
}

provider "aws" {
  region = var.aws_region
  default_tags {
    tags = {
      Project     = "cricket-analytics"
      Environment = var.environment
      ManagedBy   = "terraform"
    }
  }
}

# ── VPC Module (reused from M2) ───────────────────────────────────────────────
module "vpc" {
  source = "../../modules/vpc"
  name               = "cricket-${var.environment}"
  vpc_cidr           = var.vpc_cidr
  azs                = var.azs
  enable_nat_gateway = true
  single_nat_gateway = !local.is_production
  enable_flow_logs   = local.is_production
  tags               = local.common_tags
}

# ── ALB with HTTPS listener ───────────────────────────────────────────────────
resource "aws_lb" "api" {
  name                       = "cricket-${var.environment}-alb"
  internal                   = false
  load_balancer_type         = "application"
  subnets                    = module.vpc.public_subnet_ids
  security_groups            = [aws_security_group.alb.id]
  drop_invalid_header_fields = true
  # checkov:skip=CKV_AWS_91:Access logging requires additional S3 bucket configuration
  tags = { Name = "cricket-${var.environment}-alb" }
}

resource "aws_lb_target_group" "api" {
  name        = "cricket-${var.environment}-tg"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = module.vpc.vpc_id
  target_type = "instance"
  health_check {
    path                = "/health"
    healthy_threshold   = 2
    unhealthy_threshold = 3
    interval            = 30
    timeout             = 10
  }
}

data "aws_acm_certificate" "cricket" {
  domain      = "*.cricket-analytics.io"
  statuses    = ["ISSUED"]
  most_recent = true
}

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.api.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  certificate_arn   = data.aws_acm_certificate.cricket.arn
  default_action { type = "forward"; target_group_arn = aws_lb_target_group.api.arn }
}

resource "aws_lb_listener" "http_redirect" {
  load_balancer_arn = aws_lb.api.arn
  port              = 80
  protocol          = "HTTP"
  default_action { type = "redirect"; redirect { port = "443"; protocol = "HTTPS"; status_code = "HTTP_301" } }
}

# ── EC2 Auto Scaling Group ────────────────────────────────────────────────────
resource "aws_launch_template" "api" {
  name_prefix            = "cricket-${var.environment}-api-"
  image_id               = data.aws_ami.al2023.id
  instance_type          = local.is_production ? "t3.medium" : "t3.micro"
  vpc_security_group_ids = [aws_security_group.app.id]
  iam_instance_profile   { arn = aws_iam_instance_profile.app.arn }
  metadata_options { http_tokens = "required"; http_put_response_hop_limit = 1 }
  ebs_optimized = true
  block_device_mappings {
    device_name = "/dev/xvda"
    ebs { volume_type = "gp3"; volume_size = 20; encrypted = true; delete_on_termination = true }
  }
  tag_specifications {
    resource_type = "instance"
    tags = merge(local.common_tags, {
      Name           = "cricket-${var.environment}-api"
      Role           = "api-server"
      AnsibleManaged = "true"
    })
  }
  lifecycle { create_before_destroy = true }
}

resource "aws_autoscaling_group" "api" {
  name                      = "cricket-${var.environment}-asg"
  vpc_zone_identifier        = module.vpc.private_subnet_ids
  target_group_arns          = [aws_lb_target_group.api.arn]
  health_check_type          = "ELB"
  health_check_grace_period  = 120
  min_size                   = local.is_production ? 2 : 1
  max_size                   = local.is_production ? 6 : 2
  desired_capacity           = local.is_production ? 2 : 1
  launch_template { id = aws_launch_template.api.id; version = "$Latest" }
  lifecycle {
    ignore_changes  = [desired_capacity]  # Managed by scaling policies
    create_before_destroy = true
  }
  tag { key = "Name"; value = "cricket-${var.environment}-asg"; propagate_at_launch = false }
}

# ── RDS PostgreSQL Multi-AZ ───────────────────────────────────────────────────
resource "aws_db_instance" "analytics" {
  identifier              = "cricket-${var.environment}-db"
  engine                  = "postgres"
  engine_version          = "16.1"
  instance_class          = local.is_production ? "db.r5.large" : "db.t3.micro"
  allocated_storage       = local.is_production ? 200 : 20
  storage_encrypted       = true
  kms_key_id              = aws_kms_key.rds.arn
  db_subnet_group_name    = aws_db_subnet_group.analytics.name
  vpc_security_group_ids  = [aws_security_group.rds.id]
  multi_az                = local.is_production
  publicly_accessible     = false
  username                = "cricket_admin"
  password                = random_password.db.result
  backup_retention_period = local.is_production ? 14 : 1
  deletion_protection     = local.is_production
  skip_final_snapshot     = !local.is_production
  performance_insights_enabled = local.is_production
  tags = { Name = "cricket-${var.environment}-db" }
  lifecycle {
    prevent_destroy = true  # REQUIRED for capstone grading
    ignore_changes  = [password]
  }
}

# ── S3 data bucket ────────────────────────────────────────────────────────────
resource "aws_s3_bucket" "data" {
  bucket = "cricket-data-${var.environment}-${data.aws_caller_identity.current.account_id}"
  lifecycle { prevent_destroy = true }  # REQUIRED for capstone grading
}
# [versioning, encryption, public_access_block, lifecycle_configuration resources omitted for brevity]

# ── Supporting resources ───────────────────────────────────────────────────────
resource "aws_kms_key" "rds" {
  description             = "Cricket ${var.environment} RDS encryption"
  enable_key_rotation     = true
  deletion_window_in_days = 14
}

resource "random_password" "db" {
  length = 32; special = true; override_special = "!#$%&*-_=+<>?"
}

resource "aws_secretsmanager_secret" "db_password" {
  name = "/cricket/${var.environment}/db-password"
}

resource "aws_secretsmanager_secret_version" "db_password" {
  secret_id     = aws_secretsmanager_secret.db_password.id
  secret_string = random_password.db.result
  lifecycle { ignore_changes = [secret_string] }
}

data "aws_ami" "al2023" {
  most_recent = true; owners = ["amazon"]
  filter { name = "name";  values = ["al2023-ami-*-x86_64"] }
  filter { name = "state"; values = ["available"] }
}

data "aws_caller_identity" "current" {}

locals {
  is_production = var.environment == "production"
  common_tags   = { Environment = var.environment }
}
Analogy🏏Cricket
🏏 Think of it like cricket: Notice what the composition in main.tf actually does with the modules: nothing is built twice. The VPC module you authored in M2 is instantiated here unchanged — the rehearsed powerplay routine executed again in a bigger match, not re-invented for it — and every new component plugs into its published outputs: the ALB takes module.vpc.public_subnet_ids the way the fielding plan takes the ground dimensions as given, the ASG takes the private subnet IDs, the RDS subnet group takes the database tier's. That chain of references IS the architecture — Terraform reads it and derives the entire build order (the graph knows subnets precede the ALB, and the ALB's target group precedes the ASG that registers into it) with no explicit sequencing from you, the way a competent operations team derives the setup schedule from the venue drawings rather than being told step by step. The production patterns are non-negotiable for the same reasons their cricket counterparts are: Multi-AZ RDS is the synchronised duplicate record room in a second building (a lost AZ loses no data), create_before_destroy on the launch template is the replacement keeper drilled before the incumbent leaves (no capacity gap during updates), and prevent_destroy on the database is heritage protection on the trophy room — the one demolition that must never be a side effect of a routine renovation.
  • Compose infrastructure from focused modules (VPC module) and direct resource blocks (ALB, ASG, RDS) — modules for reusable patterns, direct resources for component-specific configuration.
  • The ASG's desired_capacity has 'ignore_changes = [desired_capacity]' — Auto Scaling manages runtime count; Terraform manages the min/max bounds and launch template.
  • RDS and S3 have 'lifecycle { prevent_destroy = true }' — this is the mandatory capstone requirement demonstrating stateful resource protection; the grader will verify terraform destroy fails for these resources.
  • The ALB listener uses the most current TLS policy ('ELBSecurityPolicy-TLS13-1-2-2021-06') — always check the AWS documentation for the current recommended policy as it changes when older TLS versions are deprecated.
Lesson 30 of 33
0% complete