Scaling Applications Automatically with AWS Auto Scaling and Terraform

Learn how to implement auto scaling for your applications using AWS Auto Scaling Groups, Launch Templates, and Terraform

Scaling Applications Automatically with AWS Auto Scaling and Terraform

Auto Scaling helps ensure your application can handle varying loads efficiently while optimizing costs. This guide demonstrates how to implement auto scaling using AWS Auto Scaling Groups and Launch Templates with Terraform.

Video Tutorial

Learn more about implementing Auto Scaling with Terraform in AWS in this comprehensive video tutorial:

Prerequisites

  • AWS CLI configured with appropriate permissions
  • Terraform installed (version 1.0.0 or later)
  • Basic understanding of EC2 and networking concepts
  • Existing VPC with public and private subnets

Project Structure

terraform-autoscaling/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── autoscaling/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── user-data/
    └── init.sh

Launch Template Configuration

Create modules/autoscaling/main.tf:

# Launch Template
resource "aws_launch_template" "main" {
  name_prefix   = "${var.project_name}-template"
  image_id      = var.ami_id
  instance_type = var.instance_type

  network_interfaces {
    associate_public_ip_address = false
    security_groups            = [aws_security_group.instance.id]
  }

  iam_instance_profile {
    name = aws_iam_instance_profile.instance.name
  }

  user_data = base64encode(templatefile("${path.module}/../../user-data/init.sh", {
    environment = var.environment
    region      = data.aws_region.current.name
  }))

  block_device_mappings {
    device_name = "/dev/xvda"
    ebs {
      volume_size           = 30
      volume_type           = "gp3"
      delete_on_termination = true
      encrypted            = true
    }
  }

  monitoring {
    enabled = true
  }

  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"
    http_put_response_hop_limit = 1
  }

  tag_specifications {
    resource_type = "instance"
    tags = merge(
      var.tags,
      {
        Name = "${var.project_name}-instance"
      }
    )
  }

  lifecycle {
    create_before_destroy = true
  }
}

# Auto Scaling Group
resource "aws_autoscaling_group" "main" {
  name                = "${var.project_name}-asg"
  desired_capacity    = var.desired_capacity
  max_size            = var.max_size
  min_size            = var.min_size
  target_group_arns   = [aws_lb_target_group.main.arn]
  vpc_zone_identifier = var.private_subnet_ids
  health_check_type   = "ELB"
  health_check_grace_period = 300

  launch_template {
    id      = aws_launch_template.main.id
    version = "$Latest"
  }

  instance_refresh {
    strategy = "Rolling"
    preferences {
      min_healthy_percentage = 50
    }
  }

  dynamic "tag" {
    for_each = merge(
      var.tags,
      {
        Name = "${var.project_name}-instance"
      }
    )
    content {
      key                 = tag.key
      value               = tag.value
      propagate_at_launch = true
    }
  }

  lifecycle {
    create_before_destroy = true
    ignore_changes        = [desired_capacity]
  }
}

# Target Tracking Scaling Policy (CPU)
resource "aws_autoscaling_policy" "cpu" {
  name                   = "${var.project_name}-cpu-policy"
  autoscaling_group_name = aws_autoscaling_group.main.name
  policy_type           = "TargetTrackingScaling"

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ASGAverageCPUUtilization"
    }
    target_value = var.cpu_target
  }
}

# Target Tracking Scaling Policy (Request Count Per Target)
resource "aws_autoscaling_policy" "request" {
  name                   = "${var.project_name}-request-policy"
  autoscaling_group_name = aws_autoscaling_group.main.name
  policy_type           = "TargetTrackingScaling"

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ALBRequestCountPerTarget"
      resource_label        = "${aws_lb.main.arn_suffix}/${aws_lb_target_group.main.arn_suffix}"
    }
    target_value = var.request_target
  }
}

# Step Scaling Policy (Memory)
resource "aws_autoscaling_policy" "memory" {
  name                   = "${var.project_name}-memory-policy"
  autoscaling_group_name = aws_autoscaling_group.main.name
  policy_type           = "StepScaling"
  adjustment_type       = "ChangeInCapacity"
  metric_aggregation_type = "Average"

  step_adjustment {
    scaling_adjustment          = 1
    metric_interval_lower_bound = 10
  }

  step_adjustment {
    scaling_adjustment          = 2
    metric_interval_lower_bound = 20
  }
}

# Application Load Balancer
resource "aws_lb" "main" {
  name               = "${var.project_name}-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = var.public_subnet_ids

  enable_deletion_protection = true

  access_logs {
    bucket  = aws_s3_bucket.logs.id
    prefix  = "alb-logs"
    enabled = true
  }

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-alb"
    }
  )
}

# Target Group
resource "aws_lb_target_group" "main" {
  name     = "${var.project_name}-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id

  health_check {
    enabled             = true
    healthy_threshold   = 3
    interval            = 30
    matcher            = "200"
    path               = "/health"
    port               = "traffic-port"
    protocol           = "HTTP"
    timeout            = 5
    unhealthy_threshold = 2
  }

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-tg"
    }
  )
}

# Scheduled Actions
resource "aws_autoscaling_schedule" "scale_up" {
  scheduled_action_name  = "${var.project_name}-scale-up"
  min_size              = var.peak_min_size
  max_size              = var.peak_max_size
  desired_capacity      = var.peak_desired_capacity
  recurrence           = "0 8 * * MON-FRI"
  autoscaling_group_name = aws_autoscaling_group.main.name
}

resource "aws_autoscaling_schedule" "scale_down" {
  scheduled_action_name  = "${var.project_name}-scale-down"
  min_size              = var.min_size
  max_size              = var.max_size
  desired_capacity      = var.desired_capacity
  recurrence           = "0 18 * * MON-FRI"
  autoscaling_group_name = aws_autoscaling_group.main.name
}

User Data Script

Create user-data/init.sh:

#!/bin/bash
set -e

# Install CloudWatch agent
yum install -y amazon-cloudwatch-agent

# Configure CloudWatch agent
cat > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json <<EOF
{
  "agent": {
    "metrics_collection_interval": 60,
    "run_as_user": "root"
  },
  "metrics": {
    "metrics_collected": {
      "mem": {
        "measurement": [
          "mem_used_percent"
        ]
      }
    }
  }
}
EOF

# Start CloudWatch agent
systemctl enable amazon-cloudwatch-agent
systemctl start amazon-cloudwatch-agent

# Install application dependencies
yum update -y
yum install -y nginx

# Configure application
cat > /etc/nginx/conf.d/default.conf <<EOF
server {
    listen 80;
    server_name _;

    location /health {
        access_log off;
        return 200 'healthy\n';
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host \$host;
        proxy_cache_bypass \$http_upgrade;
    }
}
EOF

# Start application
systemctl enable nginx
systemctl start nginx

Variables Configuration

Create variables.tf:

variable "project_name" {
  description = "Name of the project"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
}

variable "vpc_id" {
  description = "VPC ID"
  type        = string
}

variable "private_subnet_ids" {
  description = "List of private subnet IDs"
  type        = list(string)
}

variable "public_subnet_ids" {
  description = "List of public subnet IDs"
  type        = list(string)
}

variable "ami_id" {
  description = "AMI ID for instances"
  type        = string
}

variable "instance_type" {
  description = "Instance type"
  type        = string
  default     = "t3.micro"
}

variable "min_size" {
  description = "Minimum size of ASG"
  type        = number
  default     = 1
}

variable "max_size" {
  description = "Maximum size of ASG"
  type        = number
  default     = 5
}

variable "desired_capacity" {
  description = "Desired capacity of ASG"
  type        = number
  default     = 2
}

variable "cpu_target" {
  description = "Target CPU utilization"
  type        = number
  default     = 70
}

variable "request_target" {
  description = "Target request count per instance"
  type        = number
  default     = 1000
}

variable "peak_min_size" {
  description = "Minimum size during peak hours"
  type        = number
  default     = 2
}

variable "peak_max_size" {
  description = "Maximum size during peak hours"
  type        = number
  default     = 10
}

variable "peak_desired_capacity" {
  description = "Desired capacity during peak hours"
  type        = number
  default     = 4
}

Auto Scaling Strategies

  1. Target Tracking Scaling

    • CPU utilization
    • Request count per target
    • Custom metrics
  2. Step Scaling

    • Memory utilization
    • Network throughput
    • Custom alarms
  3. Scheduled Scaling

    • Business hours
    • Peak periods
    • Maintenance windows

Best Practices

  1. Instance Configuration

    • Use Launch Templates
    • Enable detailed monitoring
    • Implement proper tagging
    • Configure instance refresh
  2. Health Checks

    • Use ELB health checks
    • Configure grace periods
    • Implement custom health checks
    • Monitor instance health
  3. Scaling Policies

    • Set appropriate thresholds
    • Use multiple metrics
    • Implement cooldown periods
    • Configure step adjustments
  4. Cost Optimization

    • Use appropriate instance types
    • Configure scaling thresholds

Conclusion

You’ve learned how to implement Auto Scaling using Terraform in AWS. This setup provides:

  • Automatic scaling based on demand
  • Cost optimization
  • High availability
  • Performance optimization

Remember to:

  • Monitor scaling activities
  • Optimize scaling policies
  • Test scaling scenarios
  • Review cost implications