diff --git a/examples/complete/fixtures.us-east-2.tfvars b/examples/complete/fixtures.us-east-2.tfvars index ff5ca66c..e3f1a292 100644 --- a/examples/complete/fixtures.us-east-2.tfvars +++ b/examples/complete/fixtures.us-east-2.tfvars @@ -80,3 +80,19 @@ container_port_mappings = [ force_new_deployment = true redeploy_on_apply = true + +service_autoscaling_enabled = true +service_autoscaling_minimum_capacity = 1 +service_autoscaling_maximum_capacity = 3 +service_autoscaling_target_tracking_policies = { + cpu = { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + target_value = 50 + scale_out_cooldown = 60 + scale_in_cooldown = 60 + }, + memory = { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + target_value = 80 + } +} diff --git a/examples/complete/main.tf b/examples/complete/main.tf index d7ae6ff0..f72128c0 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -17,7 +17,7 @@ module "vpc" { module "subnets" { source = "cloudposse/dynamic-subnets/aws" - version = "2.1.0" + version = "2.4.2" availability_zones = var.availability_zones vpc_id = module.vpc.vpc_id @@ -77,29 +77,33 @@ module "test_policy" { } module "ecs_alb_service_task" { - source = "../.." - alb_security_group = module.vpc.vpc_default_security_group_id - container_definition_json = one(module.container_definition.*.json_map_encoded_list) - ecs_cluster_arn = one(aws_ecs_cluster.default.*.id) - launch_type = var.ecs_launch_type - vpc_id = module.vpc.vpc_id - security_group_ids = [module.vpc.vpc_default_security_group_id] - subnet_ids = module.subnets.public_subnet_ids - ignore_changes_task_definition = var.ignore_changes_task_definition - network_mode = var.network_mode - assign_public_ip = var.assign_public_ip - propagate_tags = var.propagate_tags - deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent - deployment_maximum_percent = var.deployment_maximum_percent - deployment_controller_type = var.deployment_controller_type - desired_count = var.desired_count - task_memory = var.task_memory - task_cpu = var.task_cpu - ecs_service_enabled = var.ecs_service_enabled - force_new_deployment = var.force_new_deployment - redeploy_on_apply = var.redeploy_on_apply - task_policy_arns = [module.test_policy.policy_arn] - task_exec_policy_arns_map = { test = module.test_policy.policy_arn } + source = "../.." + alb_security_group = module.vpc.vpc_default_security_group_id + container_definition_json = one(module.container_definition.*.json_map_encoded_list) + ecs_cluster_arn = one(aws_ecs_cluster.default.*.id) + launch_type = var.ecs_launch_type + vpc_id = module.vpc.vpc_id + security_group_ids = [module.vpc.vpc_default_security_group_id] + subnet_ids = module.subnets.public_subnet_ids + ignore_changes_task_definition = var.ignore_changes_task_definition + network_mode = var.network_mode + assign_public_ip = var.assign_public_ip + propagate_tags = var.propagate_tags + deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent + deployment_maximum_percent = var.deployment_maximum_percent + deployment_controller_type = var.deployment_controller_type + desired_count = var.desired_count + task_memory = var.task_memory + task_cpu = var.task_cpu + ecs_service_enabled = var.ecs_service_enabled + force_new_deployment = var.force_new_deployment + redeploy_on_apply = var.redeploy_on_apply + task_policy_arns = [module.test_policy.policy_arn] + task_exec_policy_arns_map = { test = module.test_policy.policy_arn } + service_autoscaling_enabled = var.service_autoscaling_enabled + service_autoscaling_minimum_capacity = var.service_autoscaling_minimum_capacity + service_autoscaling_maximum_capacity = var.service_autoscaling_maximum_capacity + service_autoscaling_target_tracking_policies = var.service_autoscaling_target_tracking_policies context = module.this.context } diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 421514d1..58a706cc 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -137,4 +137,40 @@ variable "redeploy_on_apply" { type = bool description = "Updates the service to the latest task definition on each apply" default = false -} \ No newline at end of file +} + +variable "service_autoscaling_enabled" { + type = bool + description = "Whether to enable autoscaling for the ECS service. This will cause the number of tasks to be adjusted based on the autoscaling policies defined." + default = false +} + +variable "service_autoscaling_minimum_capacity" { + type = number + description = "The minimum number of tasks to keep running in the service. This is used for service autoscaling policies." + nullable = true + default = null +} + +variable "service_autoscaling_maximum_capacity" { + type = number + description = "The maximum number of tasks to keep running in the service. This is used for service autoscaling policies." + nullable = true + default = null +} + +variable "service_autoscaling_target_tracking_policies" { + type = map(object({ + predefined_metric_type = string + resource_label = optional(string) + target_value = number + scale_out_cooldown = optional(number) + scale_in_cooldown = optional(number) + })) + description = <<-EOT + A map of target tracking policies to use for ECS service autoscaling. Valid metrics types are `ECSServiceAverageCPUUtilization`, `ECSServiceAverageMemoryUtilization`, and `ALBRequestCountPerTarget`. + Note: `resource_label` is required for `ALBRequestCountPerTarget` metric type. This will be the last part of the target group ARN: `targetgroup//`. + See the [documentation](https://docs.aws.amazon.com/autoscaling/application/APIReference/API_PredefinedMetricSpecification.html) for more details on predefined metrics. + EOT + default = {} +} diff --git a/main.tf b/main.tf index 76bebf50..a9a5a7f4 100644 --- a/main.tf +++ b/main.tf @@ -1,6 +1,8 @@ locals { enabled = module.this.enabled ecs_service_enabled = local.enabled && var.ecs_service_enabled + ecs_cluster_name = split("/", var.ecs_cluster_arn)[length(split("/", var.ecs_cluster_arn)) - 1] + autoscaling_enabled = local.enabled && var.ecs_service_enabled && var.service_autoscaling_enabled task_role_arn = try(var.task_role_arn[0], tostring(var.task_role_arn), "") create_task_role = local.enabled && length(var.task_role_arn) == 0 task_exec_role_arn = try(var.task_exec_role_arn[0], tostring(var.task_exec_role_arn), "") @@ -56,6 +58,15 @@ module "service_connect_label" { context = module.this.context } +module "service_autoscaling_label" { + source = "cloudposse/label/null" + version = "0.25.0" + enabled = local.autoscaling_enabled + attributes = ["service", "autoscaling"] + + context = module.this.context +} + resource "aws_ecs_task_definition" "default" { count = local.create_task_definition ? 1 : 0 family = module.this.id @@ -1012,3 +1023,32 @@ resource "aws_ecs_service" "default" { depends_on = [aws_iam_role.ecs_service, aws_iam_role_policy.ecs_service] } + +# Autoscaling +resource "aws_appautoscaling_target" "service" { + count = local.autoscaling_enabled ? 1 : 0 + min_capacity = var.service_autoscaling_minimum_capacity + max_capacity = var.service_autoscaling_maximum_capacity + resource_id = "service/${local.ecs_cluster_name}/${module.this.id}" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" + tags = module.service_autoscaling_label.tags +} + +resource "aws_appautoscaling_policy" "target" { + for_each = local.autoscaling_enabled ? var.service_autoscaling_target_tracking_policies : map() + name = "${module.service_autoscaling_label.id}-target-${each.key}" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.service[0].resource_id + scalable_dimension = aws_appautoscaling_target.service[0].scalable_dimension + service_namespace = aws_appautoscaling_target.service[0].service_namespace + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = each.value.predefined_metric_type + resource_label = each.value.resource_label + } + target_value = each.value.target_value + scale_out_cooldown = each.value.scale_out_cooldown + scale_in_cooldown = each.value.scale_in_cooldown + } +} diff --git a/variables.tf b/variables.tf index a171e73d..9f60f0d5 100644 --- a/variables.tf +++ b/variables.tf @@ -258,6 +258,43 @@ variable "desired_count" { default = 1 } +variable "service_autoscaling_enabled" { + type = bool + description = "Whether to enable autoscaling for the ECS service. This will cause the number of tasks to be adjusted based on the autoscaling policies defined." + default = false +} + +variable "service_autoscaling_minimum_capacity" { + type = number + description = "The minimum number of tasks to keep running in the service. This is used for service autoscaling policies." + nullable = true + default = null +} + +variable "service_autoscaling_maximum_capacity" { + type = number + description = "The maximum number of tasks to keep running in the service. This is used for service autoscaling policies." + nullable = true + default = null +} + +variable "service_autoscaling_target_tracking_policies" { + type = map(object({ + predefined_metric_type = string + resource_label = optional(string) + target_value = number + scale_out_cooldown = optional(number) + scale_in_cooldown = optional(number) + })) + description = <<-EOT + A map of target tracking policies to use for ECS service autoscaling. Valid metrics types are `ECSServiceAverageCPUUtilization`, `ECSServiceAverageMemoryUtilization`, and `ALBRequestCountPerTarget`. + Note: `resource_label` is required for `ALBRequestCountPerTarget` metric type. This will be the last part of the target group ARN: `targetgroup//`. + See the [documentation](https://docs.aws.amazon.com/autoscaling/application/APIReference/API_PredefinedMetricSpecification.html) for more details on predefined metrics. + EOT + default = {} +} + + variable "deployment_controller_type" { type = string description = "Type of deployment controller. Valid values are `CODE_DEPLOY` and `ECS`"