Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

horizontal pod scaling, v1 #154

Merged
merged 4 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions tf/envs/staging/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions tf/envs/staging/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,42 @@ module "app" {
module "env_defns" {
source = "../../modules/env_defns"
}

module "autoscaling" {
source = "../../modules/autoscale"

env = module.envconfig.env
ecs_cluster = module.envconfig.ecs_cluster
service_name = module.app.service_name

min_capacity = 2
max_capacity = 10

metrics = {
CPUAverage = {
target = 60
predefined_metric = [{
type = "ECSServiceAverageCPUUtilization"
}]
}
MemoryAverage = {
target = 60
predefined_metric = [{
type = "ECSServiceAverageMemoryUtilization"
}]
}
CPUSpike = {
target = 85
customized_metric = [{
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
statistic = "Maximum"
unit = "Percent"
dimensions = {
"ClusterName" = module.envconfig.ecs_cluster
"ServiceName" = module.app.service_name
}
}]
}
}
}
9 changes: 7 additions & 2 deletions tf/modules/app/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ resource "aws_ecs_service" "api" {
name = "capp-api"
cluster = var.ecs_cluster
task_definition = aws_ecs_task_definition.api.arn
desired_count = 1
desired_count = 2
health_check_grace_period_seconds = 30
enable_ecs_managed_tags = true
propagate_tags = "SERVICE"
Expand Down Expand Up @@ -107,10 +107,15 @@ resource "aws_ecs_service" "api" {
# could explicitly set the strategy to be the default strategy, which would be acceptable.
lifecycle {
ignore_changes = [
capacity_provider_strategy
capacity_provider_strategy,
desired_count
]
}
}
output service_name {
description = "ECS service name"
value = aws_ecs_service.api.name
}

data "aws_region" "current" {}

Expand Down
88 changes: 88 additions & 0 deletions tf/modules/autoscale/autoscale.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Originally inspired by: https://github.com/techservicesillinois/terraform-aws-ecs-service/blob/main/autoscale.tf

data "aws_partition" "current" {}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}

resource "aws_iam_role" "ecs-autoscale-role" {
name = format("%s-%s-app-scaling", var.env, var.service_name)

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "application-autoscaling.amazonaws.com"
},
"Effect": "Allow"
}
]
}
EOF
}

resource "aws_iam_role_policy_attachment" "ecs-autoscale" {
role = aws_iam_role.ecs-autoscale-role.id
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole"
}

resource "aws_appautoscaling_target" "default" {
max_capacity = var.max_capacity
min_capacity = var.min_capacity
resource_id = format("service/%s/%s", var.ecs_cluster, var.service_name)
role_arn = aws_iam_role.ecs-autoscale-role.arn
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}

# Target tracking policy
resource "aws_appautoscaling_policy" "default" {
for_each = var.metrics

name = format("ecs-target-%s-%s", var.service_name, lower(each.key))
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.default.resource_id
scalable_dimension = aws_appautoscaling_target.default.scalable_dimension
service_namespace = aws_appautoscaling_target.default.service_namespace

target_tracking_scaling_policy_configuration {
target_value = each.value.target
disable_scale_in = each.value.disable_scale_in
scale_in_cooldown = each.value.scale_in_cooldown
scale_out_cooldown = each.value.scale_out_cooldown

dynamic predefined_metric_specification {
for_each = each.value.predefined_metric
iterator = metric

content {
predefined_metric_type = metric.value.type
}
}

dynamic customized_metric_specification {
for_each = each.value.customized_metric
iterator = metric

content {
metric_name = metric.value.metric_name
namespace = metric.value.namespace
statistic = metric.value.statistic
unit = metric.value.unit

dynamic dimensions {
for_each = metric.value.dimensions
iterator = dim

content {
name = dim.key
value = dim.value
}
}
}
}
}
}

51 changes: 51 additions & 0 deletions tf/modules/autoscale/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
variable env {
type = string
description = "Environment name"
}

variable ecs_cluster {
type = string
}

variable service_name {
type = string
}

variable min_capacity {
type = number
default = 1
}

variable max_capacity {
type = number
default = 1
}

variable "metrics" {
description = "Autoscaling metrics configuration"
type = map(
object({
target = string
disable_scale_in = optional(bool, false)
scale_in_cooldown = optional(number, 120)
scale_out_cooldown = optional(number, 120)

predefined_metric = optional(list(object({
type = string
resource_label = optional(string, null)
})), [])
customized_metric = optional(list(object({
metric_name = string
namespace = string
statistic = optional(string, "Average")
unit = optional(string, null)
dimensions = optional(map(string), {})
})), [])
})
)
default = null
validation {
condition = var.metrics == null || try(length(var.metrics) > 0, true)
error_message = "The 'metrics' block must have one or more metrics"
}
}