From 8a73877b51774d00e58ee74ef5cb50a1619b06e3 Mon Sep 17 00:00:00 2001 From: Brian Quinn Date: Mon, 21 Jan 2019 18:53:49 +0000 Subject: [PATCH] feat(aws-ecs): ECS service scaling on ALB RequestCount (#1574) --- packages/@aws-cdk/aws-ecs/README.md | 7 ++- .../aws-ecs/lib/base/scalable-task-count.ts | 42 ++++++++++++-- .../test/fargate/test.fargate-service.ts | 56 +++++++++++++++++++ 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 6fa8e9f251116..6b68c2102ea4d 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -205,7 +205,7 @@ const service = new ecs.FargateService(this, 'Service', { /* ... */ }); const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, internetFacing: true }); const listener = lb.addListener('Listener', { port: 80 }); -listener.addTargets('ECS', { +const target = listener.addTargets('ECS', { port: 80, targets: [service] }); @@ -226,6 +226,11 @@ const scaling = service.autoScaleTaskCount({ maxCapacity: 10 }); scaling.scaleOnCpuUtilization('CpuScaling', { targetUtilizationPercent: 50 }); + +scaling.scaleOnRequestCount('RequestScaling', { + requestsPerTarget: 10000, + targetGroup: target +}) ``` Task AutoScaling is powered by *Application AutoScaling*. Refer to that for diff --git a/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts b/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts index 1796f7c1d2443..0f0b511053683 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts @@ -1,5 +1,6 @@ import appscaling = require('@aws-cdk/aws-applicationautoscaling'); import cloudwatch = require('@aws-cdk/aws-cloudwatch'); +import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); /** * Scalable attribute representing task count @@ -29,21 +30,39 @@ export class ScalableTaskCount extends appscaling.BaseScalableAttribute { disableScaleIn: props.disableScaleIn, targetValue: props.targetUtilizationPercent, scaleInCooldownSec: props.scaleInCooldownSec, - scaleOutCooldownSec: props.scaleOutCooldownSec, + scaleOutCooldownSec: props.scaleOutCooldownSec }); } /** - * Scale out or in to achieve a target memory utilization utilization + * Scale out or in to achieve a target memory utilization */ - public scaleOnMemoryUtilization(id: string, props: CpuUtilizationScalingProps) { + public scaleOnMemoryUtilization(id: string, props: MemoryUtilizationScalingProps) { return super.doScaleToTrackMetric(id, { predefinedMetric: appscaling.PredefinedMetric.ECSServiceAverageMemoryUtilization, targetValue: props.targetUtilizationPercent, policyName: props.policyName, disableScaleIn: props.disableScaleIn, scaleInCooldownSec: props.scaleInCooldownSec, - scaleOutCooldownSec: props.scaleOutCooldownSec, + scaleOutCooldownSec: props.scaleOutCooldownSec + }); + } + + /** + * Scale out or in to achieve a target ALB request count per target + */ + public scaleOnRequestCount(id: string, props: RequestCountScalingProps) { + const resourceLabel = props.targetGroup.firstLoadBalancerFullName + + '/' + props.targetGroup.targetGroupFullName; + + return super.doScaleToTrackMetric(id, { + predefinedMetric: appscaling.PredefinedMetric.ALBRequestCountPerTarget, + resourceLabel, + targetValue: props.requestsPerTarget, + policyName: props.policyName, + disableScaleIn: props.disableScaleIn, + scaleInCooldownSec: props.scaleInCooldownSec, + scaleOutCooldownSec: props.scaleOutCooldownSec }); } @@ -82,6 +101,21 @@ export interface MemoryUtilizationScalingProps extends appscaling.BaseTargetTrac targetUtilizationPercent: number; } +/** + * Properties for enabling scaling based on ALB request counts + */ +export interface RequestCountScalingProps extends appscaling.BaseTargetTrackingProps { + /** + * ALB requests per target + */ + requestsPerTarget: number; + + /** + * ALB Target Group + */ + targetGroup: elbv2.ApplicationTargetGroup; +} + /** * Properties to target track a custom metric */ diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts index a577150e5dc13..3eb953eb968b9 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts @@ -1,5 +1,6 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); +import elbv2 = require("@aws-cdk/aws-elasticloadbalancingv2"); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import ecs = require('../../lib'); @@ -156,4 +157,59 @@ export = { test.done(); }, }, + + "When adding an app load balancer": { + 'allows auto scaling by ALB request per target'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromDockerHub('hello'), + }); + container.addPortMappings({ containerPort: 8000 }); + const service = new ecs.FargateService(stack, 'Service', { cluster, taskDefinition }); + + const lb = new elbv2.ApplicationLoadBalancer(stack, "lb", { vpc }); + const listener = lb.addListener("listener", { port: 80 }); + const targetGroup = listener.addTargets("target", { + port: 80, + targets: [service] + }); + + // WHEN + const capacity = service.autoScaleTaskCount({ maxCapacity: 10, minCapacity: 1 }); + capacity.scaleOnRequestCount("ScaleOnRequests", { + requestsPerTarget: 1000, + targetGroup + }); + + // THEN + expect(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + MaxCapacity: 10, + MinCapacity: 1 + })); + + expect(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + TargetTrackingScalingPolicyConfiguration: { + PredefinedMetricSpecification: { + PredefinedMetricType: "ALBRequestCountPerTarget", + ResourceLabel: { + "Fn::Join": ["", [ + { "Fn::Select": [1, { "Fn::Split": ["/", { Ref: "lblistener657ADDEC" }] }] }, "/", + { "Fn::Select": [2, { "Fn::Split": ["/", { Ref: "lblistener657ADDEC" }] }] }, "/", + { "Fn::Select": [3, { "Fn::Split": ["/", { Ref: "lblistener657ADDEC" }] }] }, "/", + { "Fn::GetAtt": ["lblistenertargetGroupC7489D1E", "TargetGroupFullName"] } + ]] + } + }, + TargetValue: 1000 + } + })); + + test.done(); + } + + } };