ECS Orchestration Part 3: Autoscaling

dbanieles

Daniele Baggio

Posted on November 13, 2024

ECS Orchestration Part 3: Autoscaling

This post describes the main types of autoscaling ECS and how to configure them via terraform , providing some examples from which to take inspiration. If you want to learn more about ECS container orchestration you can look at previous articles (Part 1, Part 2).

Amazon ECS offers several autoscaling mechanisms to handle varying workloads for containerized applications. Each type of scaling targets different aspects of the infrastructure to ensure your application remains responsive under load. The primary types of autoscaling in ECS include:

  • Service Autoscaling: Adjusts the number of task instances within a specific ECS service.
  • Cluster Autoscaling (Managed Scaling): Modifies the number of EC2 instances (hosts) in an ECS cluster when running EC2-backed clusters.
  • Target Tracking and Step Scaling Policies: Offers two main policy types to control scaling behavior.

Service Autoscaling
Service Autoscaling automatically scales the number of tasks in an ECS service to meet demand. This is helpful for applications with variable workloads where you want the service to scale automatically based on CPU, memory, or custom CloudWatch metrics.

Terraform Configuration for Service Autoscaling

  1. Define the ECS Service: First, define your ECS service with its properties.

  2. Create Autoscaling Policies: Use Terraform to define the target tracking or step scaling policies for your ECS service.

  3. Attach Autoscaling to ECS Service: Link the ECS service to the autoscaling policy.

resource "aws_ecs_cluster" "ecs_cluster" {
  name = "example-cluster"
}

resource "aws_ecs_service" "ecs_service" {
  name            = "my-service"
  cluster         = aws_ecs_cluster.example.id
  task_definition = aws_ecs_task_definition.ecs_service_task.arn
  desired_count   = 1

  autoscale {
    min_capacity = 1
    max_capacity = 10
  }
}

resource "aws_appautoscaling_target" "app_scaling" {
  max_capacity       = 10
  min_capacity       = 1
  resource_id        = "service/my-cluster/my-service"
  scalable_dimension = "ecs:service:DesiredCount"
  service_namespace  = "ecs"
}

resource "aws_appautoscaling_policy" "cpu_scaling_policy" {
  name               = "cpu-policy"
  policy_type        = "TargetTrackingScaling"
  resource_id        = aws_appautoscaling_target.app_scaling.resource_id
  scalable_dimension = aws_appautoscaling_target.app_scaling.scalable_dimension
  service_namespace  = aws_appautoscaling_target.app_scaling.service_namespace

  target_tracking_scaling_policy_configuration {
        target_value = 50
        customized_metric_specification {
            metric_name = "CPUUtilization"
            namespace   = "AWS/ECS"
            statistic   = "Average"
            unit        = "Percent"

            dimensions {
                name  = "ClusterName"
                value = "my-cluster"
            }

            dimensions {
                name  = "ServiceName"
                value = "my-service"
            }
        }
    }
}

resource "aws_appautoscaling_policy" "memory_scaling_policy" {
  name               = "memory-policy"
  policy_type        = "TargetTrackingScaling"
  resource_id        = aws_appautoscaling_target.app_scaling.resource_id
  scalable_dimension = aws_appautoscaling_target.app_scaling.scalable_dimension
  service_namespace  = aws_appautoscaling_target.app_scaling.service_namespace

 target_tracking_scaling_policy_configuration {
        target_value       = 50
        scale_in_cooldown  = 240
        scale_out_cooldown = 240

        customized_metric_specification {
            metric_name = "MemoryUtilization"
            namespace   = "AWS/ECS"
            statistic   = "Average"
            unit        = "Percent"

            dimensions {
                name  = "ClusterName"
                value = "my-cluster"
            }

            dimensions {
                name  = "ServiceName"
                value = "my-service"
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

In this example, the ECS service scales based on CPU and memory usage, maintaining CPU and memory utilization around 50%.
To scale up on memory metrics , look first at this post.

Cluster Autoscaling
Cluster Autoscaling automatically manages the number of EC2 instances within an ECS cluster. This is essential for EC2-backed clusters where additional hosts may be required based on task placement and resource needs.

  1. Define an Auto Scaling Group (ASG): Specify an ASG for your EC2 instances. The ASG handles the scaling of the ECS cluster itself.

  2. Enable Managed Scaling for the ECS Cluster: Use the aws_ecs_cluster resource to define the cluster with managed scaling.

  3. Configure CloudWatch Alarms: CloudWatch alarms are necessary for scaling based on memory or CPU usage thresholds.

resource "aws_ecs_cluster" "my_cluster" {
  name = "example-ecs-cluster"
}

data "aws_ami" "ecs_optimized" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["amzn2-ami-ecs-hvm-*-x86_64-ebs"]
  }
}

resource "aws_iam_role" "ecs_instance_role" {
  name = "ecsInstanceRole"
  assume_role_policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Principal" : {
          "Service" : "ec2.amazonaws.com"
        },
        "Action" : "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "ecs_instance_policy" {
  role       = aws_iam_role.ecs_instance_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
}

resource "aws_iam_instance_profile" "ecs_instance_profile" {
  name = "ecsInstanceProfile"
  role = aws_iam_role.ecs_instance_role.name
}

resource "aws_launch_template" "ecs_launch_template" {
  name_prefix   = "ecs-instance-"
  image_id      = data.aws_ami.ecs_optimized.id
  instance_type = "t3.small"


  user_data = base64encode(<<EOF
#!/bin/bash
echo "ECS_CLUSTER=${aws_ecs_cluster.my_cluster.name}" >> /etc/ecs/ecs.config
EOF
  )

  iam_instance_profile {
    name = aws_iam_instance_profile.ecs_instance_profile.name
  }
}

resource "aws_autoscaling_group" "ecs_asg" {
  desired_capacity     = 1
  min_size             = 1
  max_size             = 5
  launch_template      = {
    id      = aws_launch_template.ecs_launch_template.id
    version = "$Latest"
  }
  vpc_zone_identifier  = ["subnet-1", "subnet-2"]

  tag {
    key                 = "Name"
    value               = "ECS Instance"
    propagate_at_launch = true
  }

  tag {
      key                 = "AmazonECSManaged"
      value               = true
      propagate_at_launch = true
  }
}

resource "aws_ecs_capacity_provider" "ecs_capacity_provider" {
  name = "example-ecs-capacity-provider"

  auto_scaling_group_provider {
    auto_scaling_group_arn         = aws_autoscaling_group.ecs_asg.arn
    managed_scaling {
      status                    = "ENABLED"
      target_capacity           = 75
      minimum_scaling_step_size = 1
      maximum_scaling_step_size = 4
    }
    managed_termination_protection = "ENABLED"  # Protects tasks from ASG scale-in
  }
}

resource "aws_ecs_cluster_capacity_providers" "ecs_cluster_capacity_providers" {
  cluster_name = aws_ecs_cluster.my_cluster.name
  capacity_providers = [
    aws_ecs_capacity_provider.ecs_capacity_provider.name
  ]
}

Enter fullscreen mode Exit fullscreen mode

This configuration sets up an EC2-backed ECS cluster with cluster autoscaling. Modify the desired_capacity, max_size, and min_size to define how the ASG scales based on resource demands.

Target Tracking vs Step Scaling Policies
Both Target Tracking and Step Scaling Policies control how scaling occurs based on specific metrics.

  1. Target Tracking tries to keep a specified metric at a defined target level. AWS will automatically adjust resources up or down to maintain this target.

  2. Step Scaling adjusts the capacity in steps based on specified thresholds. For example, you can set multiple alarms that trigger different scaling actions based on how far the current value is from the threshold.

resource "aws_appautoscaling_target" "autoscaling_target" {
    max_capacity       = 1
    min_capacity       = 5
    resource_id        = "service/my-service/${aws_ecs_service.service.name}"
    scalable_dimension = "ecs:service:DesiredCount"
    service_namespace = "ecs"
}

# Use this configuration for Step scaling
resource "aws_appautoscaling_policy" "step_scaling_policy" {
  name               = "step-scaling-policy"
  policy_type        = "StepScaling"
  resource_id        = aws_appautoscaling_target.autoscaling_target.resource_id
  scalable_dimension = aws_appautoscaling_target.autoscaling_target.scalable_dimension
  service_namespace  = aws_appautoscaling_target.autoscaling_target.service_namespace

  step_scaling_policy_configuration {
    adjustment_type = "ChangeInCapacity"
    cooldown        = 60

    step_adjustment {
      metric_interval_lower_bound = 0
      scaling_adjustment          = 1
    }

    step_adjustment {
      metric_interval_upper_bound = 0
      scaling_adjustment          = -1
    }
  }
}

# Use this configuration for Target tracking scaling
resource "aws_appautoscaling_policy" "autoscaling_policy" {
    name               = "my-service-policy"
    policy_type        = "TargetTrackingScaling"
    resource_id        = aws_appautoscaling_target.autoscaling_target.resource_id
    scalable_dimension = aws_appautoscaling_target.autoscaling_target.scalable_dimension
    service_namespace  = aws_appautoscaling_target.autoscaling_target.service_namespace

    target_tracking_scaling_policy_configuration {
        target_value = 50
        customized_metric_specification {
            metric_name = "CPUUtilization"
            namespace   = "AWS/ECS"
            statistic   = "Average"
            unit        = "Percent"

            dimensions {
                name  = "ClusterName"
                value = "my-cluster"
            }

            dimensions {
                name  = "ServiceName"
                value = "my-service"
            }
        }
    }

}

Enter fullscreen mode Exit fullscreen mode

Summary

  • Service Autoscaling: Manages tasks within an ECS service. Use Terraform's aws_appautoscaling_target to set up target tracking and policy definitions.
  • Cluster Autoscaling: Expands or shrinks the number of EC2 instances. Set up an Auto Scaling Group and link it to the ECS cluster.
  • Scaling Policies: TargetTrackingScaling for maintaining a target metric or StepScaling for responding to metric thresholds.

Using these configurations, you can control and automate your ECS services' behavior based on resource needs, ensuring efficient, cost-effective scaling across various AWS resources.

💖 💪 🙅 🚩
dbanieles
Daniele Baggio

Posted on November 13, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related