From 2bc2bff04c408f67141cf6ae8ec02b721dc0ca6d Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Thu, 21 Oct 2021 10:47:48 -0700 Subject: [PATCH 1/6] Add desired count and target tracking policies to extensions --- .../lib/extensions/http-load-balancer.ts | 23 +++++- .../ecs-service-extensions/lib/service.ts | 77 ++++++++++++++++++- .../test/http-load-balancer.test.ts | 63 +++++++++++++++ 3 files changed, 160 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts index e0390c0ab617c..66c8c9d379f70 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts @@ -8,6 +8,12 @@ import { ServiceExtension, ServiceBuild } from './extension-interfaces'; // eslint-disable-next-line no-duplicate-imports, import/order import { Construct } from '@aws-cdk/core'; +export interface HttpLoadBalancerProps { + /** + * The number of ALB requests per target. + */ + readonly requestsPerTarget?: number; +} /** * This extension add a public facing load balancer for sending traffic * to one or more replicas of the application container. @@ -15,9 +21,11 @@ import { Construct } from '@aws-cdk/core'; export class HttpLoadBalancerExtension extends ServiceExtension { private loadBalancer!: alb.IApplicationLoadBalancer; private listener!: alb.IApplicationListener; + private requestsPerTarget?: number; - constructor() { + constructor(props: HttpLoadBalancerProps = {}) { super('load-balancer'); + this.requestsPerTarget = props.requestsPerTarget; } // Before the service is created, go ahead and create the load balancer itself. @@ -55,10 +63,21 @@ export class HttpLoadBalancerExtension extends ServiceExtension { // After the service is created add the service to the load balancer's listener public useService(service: ecs.Ec2Service | ecs.FargateService) { - this.listener.addTargets(this.parentService.id, { + const targetGroup = this.listener.addTargets(this.parentService.id, { deregistrationDelay: cdk.Duration.seconds(10), port: 80, targets: [service], }); + + if (this.requestsPerTarget) { + if (!this.parentService.scalableTaskCount) { + throw Error(`Auto scaling target for the service '${this.parentService.id}' hasn't been configured. Please use Service construct to configure min and max task count.`); + } else { + this.parentService.scalableTaskCount.scaleOnRequestCount(`${this.parentService.id}-target-request-count-${this.requestsPerTarget}`, { + requestsPerTarget: this.requestsPerTarget, + targetGroup, + }); + } + } } } diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts index 4fdc8590c1dc0..c41e33a3bc249 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts @@ -30,6 +30,42 @@ export interface ServiceProps { * @default - A task role is automatically created for you. */ readonly taskRole?: iam.IRole; + + /** + * The desired number of instantiations of the task definition to keep running on the service. + * + * @default - 1 + */ + readonly desiredCount?: number; + + /** + * The options for configuring the auto scaling target. + */ + readonly autoScaleTaskCount?: AutoScalingOptions; +} + +export interface AutoScalingOptions { + /** + * The minimum number of tasks when scaling in. + * + * @default - 1 + */ + readonly minTaskCount?: number; + + /** + * The maximum number of tasks when scaling out. + */ + readonly maxTaskCount: number; + + /** + * The target value for CPU utilization across all tasks in the service. + */ + readonly targetCpuUtilization?: number; + + /** + * The target value for memory utilization across all tasks in the service. + */ + readonly targetMemoryUtilization?: number; } /** @@ -75,6 +111,11 @@ export class Service extends Construct { */ public readonly environment: IEnvironment; + /** + * The scalable attribute representing task count. + */ + public readonly scalableTaskCount?: ecs.ScalableTaskCount; + /** * The generated task definition for this service. It is only * generated after .prepare() has been executed. @@ -88,6 +129,14 @@ export class Service extends Construct { private readonly scope: cdk.Construct; + /** + * The desired number of instantiations of the task definition to keep running on the service. + * + * @default - When creating the service, default is 1; when updating the service, default uses + * the current task number. + */ + private readonly desiredCount: number; + constructor(scope: Construct, id: string, props: ServiceProps) { super(scope, id); @@ -98,6 +147,7 @@ export class Service extends Construct { this.cluster = props.environment.cluster; this.capacityType = props.environment.capacityType; this.serviceDescription = props.serviceDescription; + this.desiredCount = props.desiredCount || 1; // Check to make sure that the user has actually added a container const containerextension = this.serviceDescription.get('service-container'); @@ -160,6 +210,9 @@ export class Service extends Construct { } } + // Set desiredCount to `undefined` if auto scaling is configured for the service + const desiredCount = props.autoScaleTaskCount ? undefined : this.desiredCount; + // Give each extension a chance to mutate the service props before // service creation let serviceProps = { @@ -167,7 +220,7 @@ export class Service extends Construct { taskDefinition: this.taskDefinition, minHealthyPercent: 100, maxHealthyPercent: 200, - desiredCount: 1, + desiredCount, } as ServiceBuild; for (const extensions in this.serviceDescription.extensions) { @@ -219,6 +272,28 @@ export class Service extends Construct { throw new Error(`Unknown capacity type for service ${this.id}`); } + // Create the auto scaling target and configure target tracking policies after the service is created + if (props.autoScaleTaskCount) { + this.scalableTaskCount = this.ecsService.autoScaleTaskCount({ + maxCapacity: props.autoScaleTaskCount.maxTaskCount, + minCapacity: props.autoScaleTaskCount.minTaskCount, + }); + + if (props.autoScaleTaskCount.targetCpuUtilization) { + const targetUtilizationPercent = props.autoScaleTaskCount.targetCpuUtilization; + this.scalableTaskCount.scaleOnCpuUtilization(`${this.id}-target-cpu-utilization-${targetUtilizationPercent}`, { + targetUtilizationPercent, + }); + } + + if (props.autoScaleTaskCount.targetMemoryUtilization) { + const targetUtilizationPercent = props.autoScaleTaskCount.targetMemoryUtilization; + this.scalableTaskCount.scaleOnMemoryUtilization(`${this.id}-target-memory-utilization-${targetUtilizationPercent}`, { + targetUtilizationPercent, + }); + } + } + // Now give all extensions a chance to use the service for (const extensions in this.serviceDescription.extensions) { if (this.serviceDescription.extensions[extensions]) { diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts index 5e0d0e36ff554..24ddd011e142e 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts @@ -72,4 +72,67 @@ describe('http load balancer', () => { }); + test('allows scaling on request count for the HTTP load balancer', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + serviceDescription.add(new HttpLoadBalancerExtension({ requestsPerTarget: 100 })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + autoScaleTaskCount: { + maxTaskCount: 5, + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', { + PolicyType: 'TargetTrackingScaling', + TargetTrackingScalingPolicyConfiguration: { + PredefinedMetricSpecification: { + PredefinedMetricType: 'ALBRequestCountPerTarget', + }, + TargetValue: 100, + }, + }); + }); + + test('should error when adding scaling policy if scaling target has not been configured', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + serviceDescription.add(new HttpLoadBalancerExtension({ requestsPerTarget: 100 })); + + // THEN + expect(() => { + new Service(stack, 'my-service', { + environment, + serviceDescription, + }); + }).toThrow(/Auto scaling target for the service 'my-service' hasn't been configured. Please use Service construct to configure min and max task count./); + }); + }); \ No newline at end of file From 3d55ebd380f94f8ec62cda0ea1f1fe084cfd0555 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Thu, 21 Oct 2021 10:48:43 -0700 Subject: [PATCH 2/6] Add service tests --- .../test/service.test.ts | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts index eb21eec01b5f5..1fa6d9e8b68ea 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts @@ -1,3 +1,4 @@ +import { ABSENT } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; @@ -102,4 +103,90 @@ describe('service', () => { }); + test('allows scaling on a target CPU utilization', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + desiredCount: 3, + autoScaleTaskCount: { + maxTaskCount: 5, + targetCpuUtilization: 70, + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::Service', { + DesiredCount: ABSENT, + }); + + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', { + MaxCapacity: 5, + MinCapacity: 1, + }); + + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + PolicyType: 'TargetTrackingScaling', + TargetTrackingScalingPolicyConfiguration: { + PredefinedMetricSpecification: { PredefinedMetricType: 'ECSServiceAverageCPUUtilization' }, + TargetValue: 70, + }, + }); + }); + + test('allows scaling on a target memory utilization', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + new Service(stack, 'my-service', { + environment, + serviceDescription, + desiredCount: 3, + autoScaleTaskCount: { + maxTaskCount: 5, + targetMemoryUtilization: 70, + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::Service', { + DesiredCount: ABSENT, + }); + + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', { + MaxCapacity: 5, + MinCapacity: 1, + }); + + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + PolicyType: 'TargetTrackingScaling', + TargetTrackingScalingPolicyConfiguration: { + PredefinedMetricSpecification: { PredefinedMetricType: 'ECSServiceAverageMemoryUtilization' }, + TargetValue: 70, + }, + }); + }); + }); \ No newline at end of file From fed2265b4888ac708660f6d41821a79ac4bbf470 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Thu, 21 Oct 2021 11:24:32 -0700 Subject: [PATCH 3/6] Updated README --- .../ecs-service-extensions/README.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/README.md b/packages/@aws-cdk-containers/ecs-service-extensions/README.md index 0556debfc705f..3a0e931e23ead 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/README.md +++ b/packages/@aws-cdk-containers/ecs-service-extensions/README.md @@ -154,6 +154,57 @@ const nameService = new Service(stack, 'name', { }); ``` +## Task Auto-Scaling + +You can configure the task count of a service to match demand. The recommended way of achieving this is to configure target tracking policies for your service which scale in or out the service in order to keep a metric around a target value. + +You need to configure an auto scaling target for the service by configuring the `minTaskCount` (defaults to 1) and `maxTaskCount` in the `Service` construct. Then you can specify target values for CPU or Memory Utilization across all tasks in your service. Note that the `desiredCount` value will be set to `undefined` whenever the auto scaling target is configured for the service. + +```ts +const nameService = new Service(stack, 'name', { + environment: environment, + serviceDescription: nameDescription, + desiredCount: 5, + // Task auto-scaling construct for the service + autoScaleTaskCount: { + maxTaskCount: 10, + targetCpuUtilization: 70, + targetMemoryUtilization: 50, + }, +}); +``` + +If you want to configure auto-scaling policies for other resources like Application Load Balancer or SQS Queues, you can configure the auto scaling target for the service by setting the `minTaskCount` and `maxTaskCount` as shown above and set the respective resource-specific fields in the extension. + +For example, you can enable target tracking scaling based on Application Load Balancer request count as follows: + +```ts +const stack = new cdk.Stack(); +const environment = new Environment(stack, 'production'); +const serviceDescription = new ServiceDescription(); + +serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('my-alb'), +})); + +// Add the extension with target `requestsPerTarget` value set +serviceDescription.add(new HttpLoadBalancerExtension({ requestsPerTarget: 100 })); + +// Configure the auto scaling target +new Service(stack, 'my-service', { + environment, + serviceDescription, + autoScaleTaskCount: { + maxTaskCount: 5, + }, +}); +``` + +You can also define your own service extensions for [other auto-scaling policies](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-auto-scaling.html) for your service by making use of the `scalableTaskCount` attribute of the `Service` class. + ## Creating your own custom `ServiceExtension` In addition to using the default service extensions that come with this module, you From 8f4960763b5e3763133a40d1fe6c5524413f75be Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Thu, 21 Oct 2021 11:52:51 -0700 Subject: [PATCH 4/6] README updated --- .../ecs-service-extensions/README.md | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/README.md b/packages/@aws-cdk-containers/ecs-service-extensions/README.md index 3a0e931e23ead..fe5bf4c44ff4e 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/README.md +++ b/packages/@aws-cdk-containers/ecs-service-extensions/README.md @@ -211,37 +211,23 @@ In addition to using the default service extensions that come with this module, can choose to implement your own custom service extensions. The `ServiceExtension` class is an abstract class you can implement yourself. The following example implements a custom service extension that could be added to a service in order to -autoscale it based on CPU: +autoscale it based on scaling intervals of SQS Queue size: ```ts export class MyCustomAutoscaling extends ServiceExtension { constructor() { super('my-custom-autoscaling'); - } - - // This function modifies properties of the service prior - // to construct creation. - public modifyServiceProps(props: ServiceBuild) { - return { - ...props, - - // Initially launch 10 copies of the service - desiredCount: 10 - } as ServiceBuild; + // Scaling intervals for the step scaling policy + this.scalingSteps = [{ upper: 0, change: -1 }, { lower: 100, change: +1 }, { lower: 500, change: +5 }]; + this.sqsQueue = new sqs.Queue(this.scope, 'my-queue'); } // This hook utilizes the resulting service construct // once it is created public useService(service: ecs.Ec2Service | ecs.FargateService) { - const scalingTarget = service.autoScaleTaskCount({ - minCapacity: 5, // Min 5 tasks - maxCapacity: 20 // Max 20 tasks - }); - - scalingTarget.scaleOnCpuUtilization('TargetCpuUtilization50', { - targetUtilizationPercent: 50, - scaleInCooldown: cdk.Duration.seconds(60), - scaleOutCooldown: cdk.Duration.seconds(60), + this.parentService.scalableTaskCount.scaleOnMetric('QueueMessagesVisibleScaling', { + metric: this.sqsQueue.metricApproximateNumberOfMessagesVisible(), + scalingSteps: this.scalingSteps, }); } } From 2218fb2342b379fb6c5becfe4e3d12c5bf1edf6b Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Fri, 22 Oct 2021 11:31:38 -0700 Subject: [PATCH 5/6] Updates after review --- .../ecs-service-extensions/README.md | 30 ++++++------------- .../lib/extensions/http-load-balancer.ts | 11 ++++--- .../ecs-service-extensions/lib/service.ts | 14 ++------- .../test/http-load-balancer.test.ts | 2 +- 4 files changed, 18 insertions(+), 39 deletions(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/README.md b/packages/@aws-cdk-containers/ecs-service-extensions/README.md index fe5bf4c44ff4e..b80f1539f4447 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/README.md +++ b/packages/@aws-cdk-containers/ecs-service-extensions/README.md @@ -156,27 +156,11 @@ const nameService = new Service(stack, 'name', { ## Task Auto-Scaling -You can configure the task count of a service to match demand. The recommended way of achieving this is to configure target tracking policies for your service which scale in or out the service in order to keep a metric around a target value. +You can configure the task count of a service to match demand. The recommended way of achieving this is to configure target tracking policies for your service which scales in and out in order to keep metrics around target values. -You need to configure an auto scaling target for the service by configuring the `minTaskCount` (defaults to 1) and `maxTaskCount` in the `Service` construct. Then you can specify target values for CPU or Memory Utilization across all tasks in your service. Note that the `desiredCount` value will be set to `undefined` whenever the auto scaling target is configured for the service. +You need to configure an auto scaling target for the service by setting the `minTaskCount` (defaults to 1) and `maxTaskCount` in the `Service` construct. Then you can specify target values for "CPU Utilization" or "Memory Utilization" across all tasks in your service. Note that the `desiredCount` value will be set to `undefined` if the auto scaling target is configured. -```ts -const nameService = new Service(stack, 'name', { - environment: environment, - serviceDescription: nameDescription, - desiredCount: 5, - // Task auto-scaling construct for the service - autoScaleTaskCount: { - maxTaskCount: 10, - targetCpuUtilization: 70, - targetMemoryUtilization: 50, - }, -}); -``` - -If you want to configure auto-scaling policies for other resources like Application Load Balancer or SQS Queues, you can configure the auto scaling target for the service by setting the `minTaskCount` and `maxTaskCount` as shown above and set the respective resource-specific fields in the extension. - -For example, you can enable target tracking scaling based on Application Load Balancer request count as follows: +If you want to configure auto-scaling policies based on resources like Application Load Balancer or SQS Queues, you can set the corresponding resource-specific fields in the extension. For example, you can enable target tracking scaling based on Application Load Balancer request count as follows: ```ts const stack = new cdk.Stack(); @@ -191,14 +175,18 @@ serviceDescription.add(new Container({ })); // Add the extension with target `requestsPerTarget` value set -serviceDescription.add(new HttpLoadBalancerExtension({ requestsPerTarget: 100 })); +serviceDescription.add(new HttpLoadBalancerExtension({ requestsPerTarget: 10 })); // Configure the auto scaling target new Service(stack, 'my-service', { environment, serviceDescription, + desiredCount: 5, + // Task auto-scaling constuct for the service autoScaleTaskCount: { - maxTaskCount: 5, + maxTaskCount: 10, + targetCpuUtilization: 70, + targetMemoryUtilization: 50, }, }); ``` diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts index 66c8c9d379f70..4a15aa62efe85 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts @@ -71,13 +71,12 @@ export class HttpLoadBalancerExtension extends ServiceExtension { if (this.requestsPerTarget) { if (!this.parentService.scalableTaskCount) { - throw Error(`Auto scaling target for the service '${this.parentService.id}' hasn't been configured. Please use Service construct to configure min and max task count.`); - } else { - this.parentService.scalableTaskCount.scaleOnRequestCount(`${this.parentService.id}-target-request-count-${this.requestsPerTarget}`, { - requestsPerTarget: this.requestsPerTarget, - targetGroup, - }); + throw Error(`Auto scaling target for the service '${this.parentService.id}' hasn't been configured. Please use Service construct to configure 'minTaskCount' and 'maxTaskCount'.`); } + this.parentService.scalableTaskCount.scaleOnRequestCount(`${this.parentService.id}-target-request-count-${this.requestsPerTarget}`, { + requestsPerTarget: this.requestsPerTarget, + targetGroup, + }); } } } diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts index c41e33a3bc249..5727ee6ae5f2b 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts @@ -34,7 +34,8 @@ export interface ServiceProps { /** * The desired number of instantiations of the task definition to keep running on the service. * - * @default - 1 + * @default - When creating the service, default is 1; when updating the service, default uses + * the current task number. */ readonly desiredCount?: number; @@ -129,14 +130,6 @@ export class Service extends Construct { private readonly scope: cdk.Construct; - /** - * The desired number of instantiations of the task definition to keep running on the service. - * - * @default - When creating the service, default is 1; when updating the service, default uses - * the current task number. - */ - private readonly desiredCount: number; - constructor(scope: Construct, id: string, props: ServiceProps) { super(scope, id); @@ -147,7 +140,6 @@ export class Service extends Construct { this.cluster = props.environment.cluster; this.capacityType = props.environment.capacityType; this.serviceDescription = props.serviceDescription; - this.desiredCount = props.desiredCount || 1; // Check to make sure that the user has actually added a container const containerextension = this.serviceDescription.get('service-container'); @@ -211,7 +203,7 @@ export class Service extends Construct { } // Set desiredCount to `undefined` if auto scaling is configured for the service - const desiredCount = props.autoScaleTaskCount ? undefined : this.desiredCount; + const desiredCount = props.autoScaleTaskCount ? undefined : (props.desiredCount || 1); // Give each extension a chance to mutate the service props before // service creation diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts index 24ddd011e142e..ccb54433830f0 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts @@ -132,7 +132,7 @@ describe('http load balancer', () => { environment, serviceDescription, }); - }).toThrow(/Auto scaling target for the service 'my-service' hasn't been configured. Please use Service construct to configure min and max task count./); + }).toThrow(/Auto scaling target for the service 'my-service' hasn't been configured. Please use Service construct to configure 'minTaskCount' and 'maxTaskCount'./); }); }); \ No newline at end of file From da105c8303071d0acd27b9b8d71c6ef2cdb3b830 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Wed, 27 Oct 2021 12:02:12 -0700 Subject: [PATCH 6/6] Error when no policies configured --- .../lib/extensions/http-load-balancer.ts | 1 + .../ecs-service-extensions/lib/service.ts | 21 +++++++++++++++ .../test/http-load-balancer.test.ts | 5 ++++ .../test/service.test.ts | 27 +++++++++++++++++++ 4 files changed, 54 insertions(+) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts index 4a15aa62efe85..6db6a6c9616d4 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/http-load-balancer.ts @@ -77,6 +77,7 @@ export class HttpLoadBalancerExtension extends ServiceExtension { requestsPerTarget: this.requestsPerTarget, targetGroup, }); + this.parentService.enableAutoScalingPolicy(); } } } diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts index 5727ee6ae5f2b..957dcef280cd7 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts @@ -117,6 +117,12 @@ export class Service extends Construct { */ public readonly scalableTaskCount?: ecs.ScalableTaskCount; + /** + * The flag to track if auto scaling policies have been configured + * for the service. + */ + private autoScalingPoliciesEnabled: boolean = false; + /** * The generated task definition for this service. It is only * generated after .prepare() has been executed. @@ -276,6 +282,7 @@ export class Service extends Construct { this.scalableTaskCount.scaleOnCpuUtilization(`${this.id}-target-cpu-utilization-${targetUtilizationPercent}`, { targetUtilizationPercent, }); + this.enableAutoScalingPolicy(); } if (props.autoScaleTaskCount.targetMemoryUtilization) { @@ -283,6 +290,7 @@ export class Service extends Construct { this.scalableTaskCount.scaleOnMemoryUtilization(`${this.id}-target-memory-utilization-${targetUtilizationPercent}`, { targetUtilizationPercent, }); + this.enableAutoScalingPolicy(); } } @@ -292,6 +300,11 @@ export class Service extends Construct { this.serviceDescription.extensions[extensions].useService(this.ecsService); } } + + // Error out if the auto scaling target is created but no scaling policies have been configured + if (this.scalableTaskCount && !this.autoScalingPoliciesEnabled) { + throw Error(`The auto scaling target for the service '${this.id}' has been created but no auto scaling policies have been configured.`); + } } /** @@ -333,4 +346,12 @@ export class Service extends Construct { return this.urls[urlName]; } + + /** + * This helper method is used to set the `autoScalingPoliciesEnabled` attribute + * whenever an auto scaling policy is configured for the service. + */ + public enableAutoScalingPolicy() { + this.autoScalingPoliciesEnabled = true; + } } diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts index ccb54433830f0..e06c1632d93b5 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/http-load-balancer.test.ts @@ -98,6 +98,11 @@ describe('http load balancer', () => { }); // THEN + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', { + MaxCapacity: 5, + MinCapacity: 1, + }); + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingScalingPolicyConfiguration: { diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts index 1fa6d9e8b68ea..65807231679b7 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts @@ -189,4 +189,31 @@ describe('service', () => { }); }); + test('should error when no auto scaling policies have been configured after creating the auto scaling target', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const environment = new Environment(stack, 'production'); + const serviceDescription = new ServiceDescription(); + + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ecs.ContainerImage.fromRegistry('nathanpeck/name'), + })); + + // THEN + expect(() => { + new Service(stack, 'my-service', { + environment, + serviceDescription, + autoScaleTaskCount: { + maxTaskCount: 5, + }, + }); + }).toThrow(/The auto scaling target for the service 'my-service' has been created but no auto scaling policies have been configured./); + }); + }); \ No newline at end of file