Skip to content

Commit

Permalink
Upgrade module with dynamic step adjustments (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcincuber authored Apr 2, 2020
1 parent 9702b5c commit 8392384
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 69 deletions.
32 changes: 22 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,41 @@ Terraform module to configure ECS Service autoscaling using CloudWatch metrics

## Terraform versions

Terraform 0.12. Pin module version to `~> v1.0`. Submit pull-requests to `master` branch.
Terraform 0.12. Pin module version to `~> v2.0`. Submit pull-requests to `master` branch.

## Usage

```hcl
module "ecs-service-autoscaling-cloudwatch" {
source = "umotif-public/ecs-service-autoscaling-cloudwatch/aws"
version = "~> 1.0.0"
version = "~> 2.0.0"
enabled = true
name_prefix = "test-sqs-scalling"
min_capacity = 2
max_capacity = 22
min_capacity = 1
max_capacity = 20
cluster_name = "dev-ecs"
service_name = "dev-actions"
scale_up_step_adjustment = [
{
scaling_adjustment = 2
metric_interval_lower_bound = 0
metric_interval_upper_bound = "" # indicates inifinity
}
]
scale_down_step_adjustment = [
{
scaling_adjustment = -4
metric_interval_upper_bound = 0
metric_interval_lower_bound = ""
}
]
metric_query = [
{
id = "visible"
Expand Down Expand Up @@ -79,15 +95,11 @@ Module managed by [Marcin Cuber](https://github.com/marcincuber) [LinkedIn](http
| min\_capacity | Minimum number of tasks to scale to | `string` | `"2"` | no |
| name\_prefix | A prefix used for naming resources. | `string` | n/a | yes |
| scale\_down\_cooldown | The amount of time, in seconds, after a scaling down completes and before the next scaling activity can start | `string` | `"60"` | no |
| scale\_down\_lower\_bound | The lower bound for the difference between the alarm threshold and the CloudWatch metric. Without a value, AWS will treat this bound as negative infinity | `string` | `""` | no |
| scale\_down\_min\_adjustment\_magnitude | Minimum number of tasks to scale down at a time | `string` | `"0"` | no |
| scale\_down\_scaling\_adjustment | The number of members by which to scale down, when the adjustment bounds are breached. Should always be negative value | `string` | `"-2"` | no |
| scale\_down\_upper\_bound | The upper bound for the difference between the alarm threshold and the CloudWatch metric. Without a value, AWS will treat this bound as infinity | `string` | `"0"` | no |
| scale\_down\_step\_adjustment | A set of adjustments that manage scaling. Requires at least one object inside the list containing: `metric_interval_lower_bound`, `metric_interval_upper_bound`, `scaling_adjustment`. | `list(object({ metric_interval_lower_bound = string, metric_interval_upper_bound = string, scaling_adjustment = string }))` | `[]` | no |
| scale\_up\_cooldown | The amount of time, in seconds, after a scaling up completes and before the next scaling up can start | `string` | `"60"` | no |
| scale\_up\_lower\_bound | The lower bound for the difference between the alarm threshold and the CloudWatch metric. Without a value, AWS will treat this bound as negative infinity | `string` | `"0"` | no |
| scale\_up\_min\_adjustment\_magnitude | Minimum number of tasks to scale up at a time | `string` | `"0"` | no |
| scale\_up\_scaling\_adjustment | The number of members by which to scale up, when the adjustment bounds are breached. Should always be positive value | `string` | `"4"` | no |
| scale\_up\_upper\_bound | The upper bound for the difference between the alarm threshold and the CloudWatch metric. Without a value, AWS will treat this bound as infinity | `string` | `""` | no |
| scale\_up\_step\_adjustment | A set of adjustments that manage scaling. Requires at least one object inside the list containing: `metric_interval_lower_bound`, `metric_interval_upper_bound`, `scaling_adjustment`. | `list(object({ metric_interval_lower_bound = string, metric_interval_upper_bound = string, scaling_adjustment = string }))` | `[]` | no |
| service\_name | Name of ECS service to autoscale | `string` | n/a | yes |
| tags | A mapping of tags to assign to all resources | `map(string)` | `{}` | no |

Expand Down
135 changes: 128 additions & 7 deletions examples/core/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,139 @@ provider "aws" {
region = "eu-west-1"
}

#####
# VPC and subnets
#####
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 2.21"

name = "simple-vpc"

cidr = "10.0.0.0/16"

azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

enable_nat_gateway = false
}

#####
# ALB
#####
module "alb" {
source = "umotif-public/alb/aws"
version = "~> 1.1"

name_prefix = "alb-example"
load_balancer_type = "application"
internal = false
vpc_id = module.vpc.vpc_id
subnets = module.vpc.public_subnets
}

resource "aws_lb_listener" "alb_80" {
load_balancer_arn = module.alb.arn
port = "80"
protocol = "HTTP"

default_action {
type = "forward"
target_group_arn = module.fargate.target_group_arn
}
}

#####
# Security Group Config
#####
resource "aws_security_group_rule" "alb_ingress_80" {
security_group_id = module.alb.security_group_id
type = "ingress"
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}

resource "aws_security_group_rule" "task_ingress_80" {
security_group_id = module.fargate.service_sg_id
type = "ingress"
protocol = "tcp"
from_port = 80
to_port = 80
source_security_group_id = module.alb.security_group_id
}

#####
# ECS cluster and fargate
#####
resource "aws_ecs_cluster" "cluster" {
name = "example-ecs-cluster"
}

module "fargate" {
source = "umotif-public/ecs-fargate/aws"
version = "1.1.1"

name_prefix = "ecs-fargate-example"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.public_subnets
lb_arn = module.alb.arn
cluster_id = aws_ecs_cluster.cluster.id

task_container_image = "marcincuber/2048-game:latest"
task_definition_cpu = 256
task_definition_memory = 512

task_container_port = 80
task_container_assign_public_ip = true

health_check = {
port = "traffic-port"
path = "/"
}
}

resource "aws_sqs_queue" "queue" {
name = "sqs-example-queue"
delay_seconds = 90
}

module "ecs-service-scaling" {
source = "../.."

enabled = true

name_prefix = "test-sqs-scalling"
name_prefix = "ecs-fargate-example-sqs"

min_capacity = 1
max_capacity = 10

min_capacity = 2
max_capacity = 22
cluster_name = aws_ecs_cluster.cluster.name
service_name = module.fargate.service_name

cluster_name = "dev-ecs"
service_name = "dev-triggers-action"
scale_up_step_adjustment = [
{
scaling_adjustment = 2
metric_interval_lower_bound = 0
metric_interval_upper_bound = 5
},
{
scaling_adjustment = 1
metric_interval_lower_bound = 5
metric_interval_upper_bound = "" # indicates inifinity
}
]

scale_down_step_adjustment = [
{
scaling_adjustment = -4
metric_interval_upper_bound = 0
metric_interval_lower_bound = ""
}
]

metric_query = [
{
Expand All @@ -32,7 +153,7 @@ module "ecs-service-scaling" {
stat = "Maximum"

dimensions = {
QueueName = "dev-triggers-action"
QueueName = aws_sqs_queue.queue.name
}
}
]
Expand All @@ -48,7 +169,7 @@ module "ecs-service-scaling" {
stat = "Maximum"

dimensions = {
QueueName = "dev-triggers-action"
QueueName = aws_sqs_queue.queue.name
}
}
]
Expand Down
38 changes: 22 additions & 16 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ resource "aws_appautoscaling_target" "target" {
resource "aws_appautoscaling_policy" "scale_up" {
count = var.enabled ? 1 : 0

name = "sqs-scale-up"
name = "${var.name_prefix}-scale-up"
policy_type = "StepScaling"
resource_id = "service/${var.cluster_name}/${var.service_name}"
scalable_dimension = "ecs:service:DesiredCount"
Expand All @@ -29,10 +29,13 @@ resource "aws_appautoscaling_policy" "scale_up" {
metric_aggregation_type = "Average"
min_adjustment_magnitude = var.scale_up_min_adjustment_magnitude

step_adjustment {
metric_interval_lower_bound = var.scale_up_lower_bound
metric_interval_upper_bound = var.scale_up_upper_bound
scaling_adjustment = var.scale_up_scaling_adjustment
dynamic "step_adjustment" {
for_each = var.scale_up_step_adjustment
content {
metric_interval_lower_bound = lookup(step_adjustment.value, "metric_interval_lower_bound")
metric_interval_upper_bound = lookup(step_adjustment.value, "metric_interval_upper_bound")
scaling_adjustment = lookup(step_adjustment.value, "scaling_adjustment")
}
}
}

Expand All @@ -42,7 +45,7 @@ resource "aws_appautoscaling_policy" "scale_up" {
resource "aws_appautoscaling_policy" "scale_down" {
count = var.enabled ? 1 : 0

name = "sqs-scale-down"
name = "${var.name_prefix}-scale-down"
policy_type = "StepScaling"
resource_id = "service/${var.cluster_name}/${var.service_name}"
scalable_dimension = "ecs:service:DesiredCount"
Expand All @@ -54,10 +57,13 @@ resource "aws_appautoscaling_policy" "scale_down" {
metric_aggregation_type = "Average"
min_adjustment_magnitude = var.scale_down_min_adjustment_magnitude

step_adjustment {
metric_interval_lower_bound = var.scale_down_lower_bound
metric_interval_upper_bound = var.scale_down_upper_bound
scaling_adjustment = var.scale_down_scaling_adjustment
dynamic "step_adjustment" {
for_each = var.scale_down_step_adjustment
content {
metric_interval_lower_bound = lookup(step_adjustment.value, "metric_interval_lower_bound")
metric_interval_upper_bound = lookup(step_adjustment.value, "metric_interval_upper_bound")
scaling_adjustment = lookup(step_adjustment.value, "scaling_adjustment")
}
}
}

Expand All @@ -68,11 +74,11 @@ resource "aws_appautoscaling_policy" "scale_down" {
# CloudWatch Alerts
#####

resource "aws_cloudwatch_metric_alarm" "service_queue_high" {
resource "aws_cloudwatch_metric_alarm" "high" {
count = var.enabled ? 1 : 0

alarm_name = "${var.name_prefix}-sqs-queue-high"
alarm_description = "Alarm monitors SQS Queue count utilization for scaling up"
alarm_name = "${var.name_prefix}-alarm-high"
alarm_description = "Alarm monitors high utilization for scaling up"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = var.high_evaluation_periods
threshold = var.high_threshold
Expand Down Expand Up @@ -105,11 +111,11 @@ resource "aws_cloudwatch_metric_alarm" "service_queue_high" {
depends_on = [aws_appautoscaling_policy.scale_up]
}

resource "aws_cloudwatch_metric_alarm" "service_queue_low" {
resource "aws_cloudwatch_metric_alarm" "low" {
count = var.enabled ? 1 : 0

alarm_name = "${var.name_prefix}-sqs-queue-low"
alarm_description = "Alarm monitors SQS Queue count utilization for scaling down."
alarm_name = "${var.name_prefix}-alarm-low"
alarm_description = "Alarm monitors low utilization for scaling down."
comparison_operator = "LessThanOrEqualToThreshold"
evaluation_periods = var.low_evaluation_periods
threshold = var.low_threshold
Expand Down
49 changes: 13 additions & 36 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -80,42 +80,6 @@ variable "scale_up_min_adjustment_magnitude" {
default = "0"
}

variable "scale_down_scaling_adjustment" {
type = string
description = "The number of members by which to scale down, when the adjustment bounds are breached. Should always be negative value"
default = "-2"
}

variable "scale_down_lower_bound" {
type = string
description = "The lower bound for the difference between the alarm threshold and the CloudWatch metric. Without a value, AWS will treat this bound as negative infinity"
default = ""
}

variable "scale_down_upper_bound" {
type = string
description = "The upper bound for the difference between the alarm threshold and the CloudWatch metric. Without a value, AWS will treat this bound as infinity"
default = "0"
}

variable "scale_up_scaling_adjustment" {
type = string
description = "The number of members by which to scale up, when the adjustment bounds are breached. Should always be positive value"
default = "4"
}

variable "scale_up_lower_bound" {
type = string
description = "The lower bound for the difference between the alarm threshold and the CloudWatch metric. Without a value, AWS will treat this bound as negative infinity"
default = "0"
}

variable "scale_up_upper_bound" {
type = string
description = "The upper bound for the difference between the alarm threshold and the CloudWatch metric. Without a value, AWS will treat this bound as infinity"
default = ""
}

variable "high_evaluation_periods" {
type = string
description = "The number of periods over which data is compared to the high threshold"
Expand All @@ -139,3 +103,16 @@ variable "low_threshold" {
description = "The value against which the low statistic is compared"
default = "10"
}

variable "scale_up_step_adjustment" {
type = list(object({ metric_interval_lower_bound = string, metric_interval_upper_bound = string, scaling_adjustment = string }))
description = "A set of adjustments that manage scaling. Requires at least one object inside the list containing: `metric_interval_lower_bound`, `metric_interval_upper_bound`, `scaling_adjustment`."
default = []
}

variable "scale_down_step_adjustment" {
type = list(object({ metric_interval_lower_bound = string, metric_interval_upper_bound = string, scaling_adjustment = string }))
description = "A set of adjustments that manage scaling. Requires at least one object inside the list containing: `metric_interval_lower_bound`, `metric_interval_upper_bound`, `scaling_adjustment`."
default = []
}

0 comments on commit 8392384

Please sign in to comment.