From d5baf5dc5b6f588d61ba79443fcaa7ad9d8428ae Mon Sep 17 00:00:00 2001 From: Piradeep Kandasamy Date: Fri, 26 Apr 2019 15:10:16 -0700 Subject: [PATCH 1/3] Define the ECS/Fargate QueueWorkerService construct and fix Step Scaling implementation to add ScalingTargetId and default AdjustmentType --- design/aws-ecs-autoscaling-queue-worker.md | 232 ++++++++++++++++++ .../aws-ecs/lib/ecs-queue-worker-service.ts | 68 +++++ .../lib/fargate-queue-worker-service.ts | 76 ++++++ packages/@aws-cdk/aws-ecs/lib/index.ts | 4 + .../aws-ecs/lib/queue-worker-service-base.ts | 163 ++++++++++++ packages/@aws-cdk/aws-ecs/package.json | 2 + .../aws-ecs/test/test.ecs-worker-service.ts | 135 ++++++++++ .../test/test.fargate-worker-service.ts | 131 ++++++++++ 8 files changed, 811 insertions(+) create mode 100644 design/aws-ecs-autoscaling-queue-worker.md create mode 100644 packages/@aws-cdk/aws-ecs/lib/ecs-queue-worker-service.ts create mode 100644 packages/@aws-cdk/aws-ecs/lib/fargate-queue-worker-service.ts create mode 100644 packages/@aws-cdk/aws-ecs/lib/queue-worker-service-base.ts create mode 100644 packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts create mode 100644 packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts diff --git a/design/aws-ecs-autoscaling-queue-worker.md b/design/aws-ecs-autoscaling-queue-worker.md new file mode 100644 index 0000000000000..3de827f973c6c --- /dev/null +++ b/design/aws-ecs-autoscaling-queue-worker.md @@ -0,0 +1,232 @@ +# AWS ECS - L3 Construct for Autoscaling ECS/Fargate Service that Processes Items in a SQS Queue + +To address issue [#2396](https://github.com/awslabs/aws-cdk/issues/2396), the AWS ECS CDK construct library should provide a way for customers to create a queue worker service (an AWS ECS/Fargate service that processes items from an sqs queue). This would mean adding new ECS CDK constructs `Ec2QueueWorkerService` and `FargateQueryWorkerService`, that would take in the necessary properties required to create a task definition, an SQS queue as well as an ECS/Fargate service and enable autoscaling for the service based on cpu usage and the SQS queue's approximateNumberOfMessagesVisible metric. + +## General approach + +The new `ecs.QueueWorkerServiceBase`, `ecs.Ec2QueueWorkerService` and `ecs.FargateQueueWorkerService` classes will create L3 constructs for: + +* Ec2QueueWorkerService +* FargateQueueWorkerService + +A `QueueWorkerService` will create a task definition with the specified container (on both EC2 and Fargate). An AWS SQS `Queue` will be created and autoscaling of the ECS Service will be dependent on both CPU as well as the SQS queue's `ApproximateNumberOfMessagesVisible` metric. + +The `QueueWorkerService` constructs (for EC2 and Fargate) will use the following existing constructs: + +* Ec2TaskDefinition/FargateTaskDefinition - To create a Task Definition for the container to start +* SQSQueue - The queue that the worker is processing from +* Ec2Service/FargateService - The Service running the container + +## Code changes + +Given the above, we should make the following changes to support queue workers on ECS (for both EC2 and Fargate): +1. Create `QueueWorkerServiceBaseProps` interface and `QueueWorkerServiceBase` construct +2. Create `Ec2QueueWorkerServiceProps` interface and `Ec2QueueWorkerService` construct +3. Create `FargateQueueWorkerServiceProps` interface and `FargateQueueWorkerService` construct + +### Part 1: Create `QueueWorkerServiceBaseProps` interface and `QueueWorkerServiceBase` construct + +The `QueueWorkerServiceBaseProps` interface will contain common properties used to construct both the Ec2QueueWorkerService and the FargateQueueWorkerService: + +```ts +/** + * Properties to define a Query Worker service + */ +export interface QueueWorkerServiceBaseProps { + /** + * Cluster where service will be deployed + */ + readonly cluster: ICluster; + + /** + * The image to start. + */ + readonly image: ContainerImage; + + /** + * The CMD value to pass to the container. A string with commands delimited by commas. + * + * @default none + */ + readonly command?: string; + + /** + * Number of desired copies of running tasks + * + * @default 1 + */ + readonly desiredTaskCount?: number; + + /** + * Flag to indicate whether to enable logging + * + * @default true + */ + readonly enableLogging?: boolean; + + /** + * The environment variables to pass to the container. + * + * @default 'QUEUE_NAME: queue.queueName' + */ + readonly environment?: { [key: string]: string }; + + /** + * A queue for which to process items from. + * + * If specified and this is a FIFO queue, the queue name must end in the string '.fifo'. + * @see https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_CreateQueue.html + * + * @default 'SQSQueue with CloudFormation-generated name' + */ + readonly queue?: IQueue; + + /** + * Maximum capacity to scale to. + * + * @default (desiredTaskCount * 2) + */ + readonly maxScalingCapacity?: number + + /** + * The intervals for scaling based on the SQS queue's ApproximateNumberOfMessagesVisible metric. + * + * Maps a range of metric values to a particular scaling behavior. + * https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html + * + * @default [{ upper: 0, change: -1 },{ lower: 100, change: +1 },{ lower: 500, change: +5 }] + */ + readonly scalingSteps: autoScaling.ScalingInterval[]; +} +``` + +### Part 2: Create `Ec2QueueWorkerServiceProps` interface and `Ec2QueueWorkerService` construct + +The `Ec2QueueWorkerServiceProps` interface will contain properties to construct the Ec2TaskDefinition, SQSQueue and Ec2Service: + +```ts +/** + * Properties to define an ECS service + */ +export interface Ec2QueueWorkerServiceProps { + /** + * The minimum number of CPU units to reserve for the container. + * + * @default none + */ + readonly cpu?: number; + + /** + * The hard limit (in MiB) of memory to present to the container. + * + * If your container attempts to exceed the allocated memory, the container + * is terminated. + * + * At least one of memoryLimitMiB and memoryReservationMiB is required for non-Fargate services. + */ + readonly memoryLimitMiB?: number; + + /** + * The soft limit (in MiB) of memory to reserve for the container. + * + * When system memory is under contention, Docker attempts to keep the + * container memory within the limit. If the container requires more memory, + * it can consume up to the value specified by the Memory property or all of + * the available memory on the container instance—whichever comes first. + * + * At least one of memoryLimitMiB and memoryReservationMiB is required for non-Fargate services. + */ + readonly memoryReservationMiB?: number; +} +``` + +An example use case: +```ts +// Create the vpc and cluster used by the Queue Worker task +const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 1 }); +const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); +cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro') +}); +const queue = new sqs.Queue(stack, 'WorkerQueue', { + QueueName: 'EcsWorkerQueue' +}); + +// Create the Queue Worker task +new Ec2QueueWorkerService(stack, 'EcsQueueWorkerService', { + cluster, + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + desiredTaskCount: 2, + maxScalingCapacity: 5, + memoryReservationMiB: 512, + cpu: 256, + queue +}); +``` + +### Part 3: Create `FargateQueueWorkerServiceProps` interface and `FargateQueueWorkerService` construct + +The `FargateQueueWorkerServiceProps` interface will contain properties to construct the FargateTaskDefinition, SQSQueue and FargateService: + +```ts +/** + * Properties to define an Fargate service + */ +export interface FargateQueueWorkerServiceProps { + /** + * The number of cpu units used by the task. + * Valid values, which determines your range of valid values for the memory parameter: + * 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB + * 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB + * 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB + * 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments + * 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments + * + * This default is set in the underlying FargateTaskDefinition construct. + * + * @default 256 + */ + readonly cpu?: string; + + /** + * The amount (in MiB) of memory used by the task. + * + * This field is required and you must use one of the following values, which determines your range of valid values + * for the cpu parameter: + * + * 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU) + * + * 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU) + * + * 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU) + * + * Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU) + * + * Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU) + * + * This default is set in the underlying FargateTaskDefinition construct. + * + * @default 512 + */ + readonly memoryMiB?: string; +} +``` + +An example use case: +```ts +// Create the vpc and cluster used by the Queue Worker task +const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 2 }); +const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); +const queue = new sqs.Queue(stack, 'WorkerQueue', { + QueueName: 'FargateWorkerQueue' +}); + +// Create the Queue Worker task +new FargateQueueWorkerService(stack, 'FargateQueueWorkerService', { + cluster, + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + desiredTaskCount: 2, + maxScalingCapacity: 5, + queue +}); +``` diff --git a/packages/@aws-cdk/aws-ecs/lib/ecs-queue-worker-service.ts b/packages/@aws-cdk/aws-ecs/lib/ecs-queue-worker-service.ts new file mode 100644 index 0000000000000..5d33a54a37c62 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/lib/ecs-queue-worker-service.ts @@ -0,0 +1,68 @@ +import cdk = require('@aws-cdk/cdk'); +import { Ec2Service } from './ec2/ec2-service'; +import { Ec2TaskDefinition } from './ec2/ec2-task-definition'; +import { QueueWorkerServiceBase, QueueWorkerServiceBaseProps } from './queue-worker-service-base'; + +/** + * Properties to define an Ec2 query worker service + */ +export interface Ec2QueueWorkerServiceProps extends QueueWorkerServiceBaseProps { + /** + * The minimum number of CPU units to reserve for the container. + * + * @default none + */ + readonly cpu?: number; + + /** + * The hard limit (in MiB) of memory to present to the container. + * + * If your container attempts to exceed the allocated memory, the container + * is terminated. + * + * At least one of memoryLimitMiB and memoryReservationMiB is required for non-Fargate services. + */ + readonly memoryLimitMiB?: number; + + /** + * The soft limit (in MiB) of memory to reserve for the container. + * + * When system memory is under contention, Docker attempts to keep the + * container memory within the limit. If the container requires more memory, + * it can consume up to the value specified by the Memory property or all of + * the available memory on the container instance—whichever comes first. + * + * At least one of memoryLimitMiB and memoryReservationMiB is required for non-Fargate services. + */ + readonly memoryReservationMiB?: number; +} + +/** + * Class to create an Ec2 query worker service + */ +export class Ec2QueueWorkerService extends QueueWorkerServiceBase { + constructor(scope: cdk.Construct, id: string, props: Ec2QueueWorkerServiceProps) { + super(scope, id, props); + + // Create a Task Definition for the container to start + const taskDefinition = new Ec2TaskDefinition(this, 'QueueWorkerTaskDef'); + taskDefinition.addContainer('QueueWorkerContainer', { + image: props.image, + memoryLimitMiB: props.memoryLimitMiB, + memoryReservationMiB: props.memoryReservationMiB, + cpu: props.cpu, + command: props.command !== undefined ? cdk.Fn.split(",", props.command) : undefined, + environment: this.environment, + logging: this.logDriver + }); + + // Create an ECS service with the previously defined Task Definition and configure + // autoscaling based on cpu utilization and number of messages visible in the SQS queue. + const ecsService = new Ec2Service(this, 'QueueWorkerService', { + cluster: props.cluster, + desiredCount: this.desiredCount, + taskDefinition + }); + this.configureAutoscalingForService(ecsService); + } +} diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate-queue-worker-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate-queue-worker-service.ts new file mode 100644 index 0000000000000..a726e2b3a37ba --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/lib/fargate-queue-worker-service.ts @@ -0,0 +1,76 @@ +import cdk = require('@aws-cdk/cdk'); +import { FargateService } from './fargate/fargate-service'; +import { FargateTaskDefinition } from './fargate/fargate-task-definition'; +import { QueueWorkerServiceBase, QueueWorkerServiceBaseProps } from './queue-worker-service-base'; + +/** + * Properties to define a Fargate queue worker service + */ +export interface FargateQueueWorkerServiceProps extends QueueWorkerServiceBaseProps { + /** + * The number of cpu units used by the task. + * Valid values, which determines your range of valid values for the memory parameter: + * 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB + * 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB + * 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB + * 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments + * 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments + * + * This default is set in the underlying FargateTaskDefinition construct. + * + * @default 256 + */ + readonly cpu?: string; + + /** + * The amount (in MiB) of memory used by the task. + * + * This field is required and you must use one of the following values, which determines your range of valid values + * for the cpu parameter: + * + * 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU) + * + * 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU) + * + * 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU) + * + * Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU) + * + * Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU) + * + * This default is set in the underlying FargateTaskDefinition construct. + * + * @default 512 + */ + readonly memoryMiB?: string; +} + +/** + * Class to create a Fargate query worker service + */ +export class FargateQueueWorkerService extends QueueWorkerServiceBase { + constructor(scope: cdk.Construct, id: string, props: FargateQueueWorkerServiceProps) { + super(scope, id, props); + + // Create a Task Definition for the container to start + const taskDefinition = new FargateTaskDefinition(this, 'QueueWorkerTaskDef', { + memoryMiB: props.memoryMiB !== undefined ? props.memoryMiB : '512', + cpu: props.cpu !== undefined ? props.cpu : '256', + }); + taskDefinition.addContainer('QueueWorkerContainer', { + image: props.image, + command: props.command !== undefined ? cdk.Fn.split(",", props.command) : undefined, + environment: this.environment, + logging: this.logDriver + }); + + // Create a Fargate service with the previously defined Task Definition and configure + // autoscaling based on cpu utilization and number of messages visible in the SQS queue. + const fargateService = new FargateService(this, 'FargateQueueWorkerService', { + cluster: props.cluster, + desiredCount: this.desiredCount, + taskDefinition + }); + this.configureAutoscalingForService(fargateService); + } +} diff --git a/packages/@aws-cdk/aws-ecs/lib/index.ts b/packages/@aws-cdk/aws-ecs/lib/index.ts index 189f667461bbd..101ffc2d817e8 100644 --- a/packages/@aws-cdk/aws-ecs/lib/index.ts +++ b/packages/@aws-cdk/aws-ecs/lib/index.ts @@ -20,6 +20,10 @@ export * from './load-balanced-fargate-service'; export * from './load-balanced-ecs-service'; export * from './load-balanced-fargate-service-applet'; +export * from './queue-worker-service-base'; +export * from './ecs-queue-worker-service'; +export * from './fargate-queue-worker-service'; + export * from './images/asset-image'; export * from './images/repository'; export * from './images/ecr'; diff --git a/packages/@aws-cdk/aws-ecs/lib/queue-worker-service-base.ts b/packages/@aws-cdk/aws-ecs/lib/queue-worker-service-base.ts new file mode 100644 index 0000000000000..fc8e281c9ba5f --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/lib/queue-worker-service-base.ts @@ -0,0 +1,163 @@ +import autoscaling = require('@aws-cdk/aws-applicationautoscaling'); +import sqs = require('@aws-cdk/aws-sqs'); +import { IQueue } from '@aws-cdk/aws-sqs'; +import cdk = require('@aws-cdk/cdk'); +import { BaseService } from './base/base-service'; +import { ICluster } from './cluster'; +import { ContainerImage } from './container-image'; +import { AwsLogDriver } from './log-drivers/aws-log-driver'; +import { LogDriver } from './log-drivers/log-driver'; + +/** + * Properties to define a Query Worker service + */ +export interface QueueWorkerServiceBaseProps { + /** + * Cluster where service will be deployed + */ + readonly cluster: ICluster; + + /** + * The image to start. + */ + readonly image: ContainerImage; + + /** + * The CMD value to pass to the container. A string with commands delimited by commas. + * + * @default none + */ + readonly command?: string; + + /** + * Number of desired copies of running tasks + * + * @default 1 + */ + readonly desiredTaskCount?: number; + + /** + * Flag to indicate whether to enable logging + * + * @default true + */ + readonly enableLogging?: boolean; + + /** + * The environment variables to pass to the container. + * + * @default 'QUEUE_NAME: queue.queueName' + */ + readonly environment?: { [key: string]: string }; + + /** + * A queue for which to process items from. + * + * If specified and this is a FIFO queue, the queue name must end in the string '.fifo'. + * @see https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_CreateQueue.html + * + * @default 'SQSQueue with CloudFormation-generated name' + */ + readonly queue?: IQueue; + + /** + * Maximum capacity to scale to. + * + * @default (desiredTaskCount * 2) + */ + readonly maxScalingCapacity?: number + + /** + * The intervals for scaling based on the SQS queue's ApproximateNumberOfMessagesVisible metric. + * + * Maps a range of metric values to a particular scaling behavior. + * https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html + * + * @default [{ upper: 0, change: -1 },{ lower: 100, change: +1 },{ lower: 500, change: +5 }] + */ + readonly scalingSteps?: autoscaling.ScalingInterval[]; +} + +/** + * Base class for a Fargate and ECS queue worker service + */ +export abstract class QueueWorkerServiceBase extends cdk.Construct { + /** + * The SQS queue that the worker service will process from + */ + public readonly sqsQueue: IQueue; + + // Properties that have defaults defined. The Queue Worker will handle assigning undefined properties with default + // values so that derived classes do not need to maintain the same logic. + + /** + * Environment variables that will include the queue name + */ + public readonly environment: { [key: string]: string }; + /** + * The minimum number of tasks to run + */ + public readonly desiredCount: number; + /** + * The maximum number of instances for autoscaling to scale up to + */ + public readonly maxCapacity: number; + /** + * The scaling interval for autoscaling based off an SQS Queue size + */ + public readonly scalingSteps: autoscaling.ScalingInterval[]; + /** + * The AwsLogDriver to use for logging if logging is enabled. + */ + public readonly logDriver?: LogDriver; + + constructor(scope: cdk.Construct, id: string, props: QueueWorkerServiceBaseProps) { + super(scope, id); + + // Create the worker SQS queue if one is not provided + this.sqsQueue = props.queue !== undefined ? props.queue : new sqs.Queue(this, 'EcsWorkerServiceQueue', {}); + + // Setup autoscaling scaling intervals + const defaultScalingSteps = [{ upper: 0, change: -1 }, { lower: 100, change: +1 }, { lower: 500, change: +5 }]; + this.scalingSteps = props.scalingSteps !== undefined ? props.scalingSteps : defaultScalingSteps; + + // Create log driver if logging is enabled + const enableLogging = props.enableLogging !== undefined ? props.enableLogging : true; + this.logDriver = enableLogging ? this.createAwsLogDriver(this.node.id) : undefined; + + // Add the queue name to environment variables + this.environment = { ...(props.environment || {}), QUEUE_NAME: this.sqsQueue.queueName }; + + // Determine the desired task count (minimum) and maximum scaling capacity + this.desiredCount = props.desiredTaskCount || 1; + this.maxCapacity = props.maxScalingCapacity || (2 * this.desiredCount); + + new cdk.CfnOutput(this, 'SQSQueue', { value: this.sqsQueue.queueName }); + new cdk.CfnOutput(this, 'SQSQueueArn', { value: this.sqsQueue.queueArn }); + } + + /** + * Configure autoscaling based off of CPU utilization as well as the number of messages visible in the SQS queue + * + * @param service the ECS/Fargate service for which to apply the autoscaling rules to + */ + protected configureAutoscalingForService(service: BaseService) { + const scalingTarget = service.autoScaleTaskCount({ maxCapacity: this.maxCapacity, minCapacity: this.desiredCount }); + scalingTarget.scaleOnCpuUtilization('CpuScaling', { + targetUtilizationPercent: 50, + }); + scalingTarget.scaleOnMetric('QueueMessagesVisibleScaling', { + metric: this.sqsQueue.metricApproximateNumberOfMessagesVisible(), + scalingSteps: this.scalingSteps + }); + } + + /** + * Create an AWS Log Driver with the provided streamPrefix + * + * @param prefix the Cloudwatch logging prefix + */ + private createAwsLogDriver(prefix: string): AwsLogDriver { + return new AwsLogDriver(this, 'QueueWorkerLogging', { streamPrefix: prefix }); + } +} diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 92c57f69a2055..617e42d2cd6fe 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -86,6 +86,7 @@ "@aws-cdk/aws-secretsmanager": "^0.31.0", "@aws-cdk/aws-servicediscovery": "^0.31.0", "@aws-cdk/aws-sns": "^0.31.0", + "@aws-cdk/aws-sqs": "^0.31.0", "@aws-cdk/cdk": "^0.31.0", "@aws-cdk/cx-api": "^0.31.0" }, @@ -108,6 +109,7 @@ "@aws-cdk/aws-secretsmanager": "^0.31.0", "@aws-cdk/aws-servicediscovery": "^0.31.0", "@aws-cdk/aws-sns": "^0.31.0", + "@aws-cdk/aws-sqs": "^0.31.0", "@aws-cdk/cdk": "^0.31.0", "@aws-cdk/cx-api": "^0.31.0" }, diff --git a/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts b/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts new file mode 100644 index 0000000000000..249fb4f1804bf --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts @@ -0,0 +1,135 @@ +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); +import sqs = require('@aws-cdk/aws-sqs'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import ecs = require('../lib'); + +export = { + 'test ECS queue worker service construct - with only required props'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + // WHEN + new ecs.Ec2QueueWorkerService(stack, 'Service', { + cluster, + memoryLimitMiB: 512, + image: ecs.ContainerImage.fromRegistry('test') + }); + + // THEN - QueueWorker is of EC2 launch type, an SQS queue is created and all default properties are set. + expect(stack).to(haveResource("AWS::ECS::Service", { + DesiredCount: 1, + LaunchType: "EC2", + })); + + expect(stack).to(haveResource("AWS::SQS::Queue")); + + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Environment: [ + { + Name: "QUEUE_NAME", + Value: { + "Fn::GetAtt": [ + "ServiceEcsWorkerServiceQueue19BF278C", + "QueueName" + ] + } + } + ], + LogConfiguration: { + LogDriver: "awslogs", + Options: { + "awslogs-group": { + Ref: "ServiceQueueWorkerLoggingLogGroup5E11C73B" + }, + "awslogs-stream-prefix": "Service", + "awslogs-region": { + Ref: "AWS::Region" + } + } + }, + Image: "test", + Memory: 512 + } + ] + })); + + test.done(); + }, + + 'test ECS queue worker service construct - with optional props'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const queue = new sqs.Queue(stack, 'ecs-test-queue', { queueName: 'ecs-test-sqs-queue'}); + + // WHEN + new ecs.Ec2QueueWorkerService(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + image: ecs.ContainerImage.fromRegistry('test'), + command: "-c, 4, amazon.com", + enableLogging: false, + desiredTaskCount: 2, + environment: { + TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", + TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value" + }, + queue, + maxScalingCapacity: 5 + }); + + // THEN - QueueWorker is of EC2 launch type, an SQS queue is created and all optional properties are set. + expect(stack).to(haveResource("AWS::ECS::Service", { + DesiredCount: 2, + LaunchType: "EC2" + })); + + expect(stack).to(haveResource("AWS::SQS::Queue", { + QueueName: "ecs-test-sqs-queue" + })); + + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Command: [ + "-c", + " 4", + " amazon.com" + ], + Environment: [ + { + Name: "TEST_ENVIRONMENT_VARIABLE1", + Value: "test environment variable 1 value" + }, + { + Name: "TEST_ENVIRONMENT_VARIABLE2", + Value: "test environment variable 2 value" + }, + { + Name: "QUEUE_NAME", + Value: { + "Fn::GetAtt": [ + "ecstestqueueD1FDA34B", + "QueueName" + ] + } + } + ], + Image: "test", + Memory: 1024 + } + ] + })); + + test.done(); + } +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts b/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts new file mode 100644 index 0000000000000..44d113a63529e --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts @@ -0,0 +1,131 @@ +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); +import sqs = require('@aws-cdk/aws-sqs'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import ecs = require('../lib'); + +export = { + 'test fargate queue worker service construct - with only required props'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + // WHEN + new ecs.FargateQueueWorkerService(stack, 'Service', { + cluster, + memoryMiB: '512', + image: ecs.ContainerImage.fromRegistry('test') + }); + + // THEN - QueueWorker is of FARGATE launch type, an SQS queue is created and all default properties are set. + expect(stack).to(haveResource("AWS::ECS::Service", { + DesiredCount: 1, + LaunchType: "FARGATE", + })); + + expect(stack).to(haveResource("AWS::SQS::Queue")); + + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Environment: [ + { + Name: "QUEUE_NAME", + Value: { + "Fn::GetAtt": [ + "ServiceEcsWorkerServiceQueue19BF278C", + "QueueName" + ] + } + } + ], + LogConfiguration: { + LogDriver: "awslogs", + Options: { + "awslogs-group": { + Ref: "ServiceQueueWorkerLoggingLogGroup5E11C73B" + }, + "awslogs-stream-prefix": "Service", + "awslogs-region": { + Ref: "AWS::Region" + } + } + }, + Image: "test", + } + ] + })); + + test.done(); + }, + + 'test Fargate queue worker service construct - with optional props'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const queue = new sqs.Queue(stack, 'fargate-test-queue', { queueName: 'fargate-test-sqs-queue'}); + + // WHEN + new ecs.FargateQueueWorkerService(stack, 'Service', { + cluster, + memoryMiB: '512', + image: ecs.ContainerImage.fromRegistry('test'), + command: "-c, 4, amazon.com", + enableLogging: false, + desiredTaskCount: 2, + environment: { + TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value", + TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value" + }, + queue, + maxScalingCapacity: 5 + }); + + // THEN - QueueWorker is of FARGATE launch type, an SQS queue is created and all optional properties are set. + expect(stack).to(haveResource("AWS::ECS::Service", { + DesiredCount: 2, + LaunchType: "FARGATE" + })); + + expect(stack).to(haveResource("AWS::SQS::Queue", { QueueName: 'fargate-test-sqs-queue' })); + + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Command: [ + "-c", + " 4", + " amazon.com" + ], + Environment: [ + { + Name: "TEST_ENVIRONMENT_VARIABLE1", + Value: "test environment variable 1 value" + }, + { + Name: "TEST_ENVIRONMENT_VARIABLE2", + Value: "test environment variable 2 value" + }, + { + Name: "QUEUE_NAME", + Value: { + "Fn::GetAtt": [ + "fargatetestqueue28B43841", + "QueueName" + ] + } + } + ], + Image: "test", + } + ] + })); + + test.done(); + } +}; From f8eeb3ecef719abd7babb6daadd2da18b9736b04 Mon Sep 17 00:00:00 2001 From: Piradeep Kandasamy Date: Tue, 21 May 2019 10:16:20 -0700 Subject: [PATCH 2/3] Address feedback --- design/aws-ecs-autoscaling-queue-worker.md | 4 ++-- packages/@aws-cdk/aws-ecs/lib/ecs-queue-worker-service.ts | 2 +- packages/@aws-cdk/aws-ecs/lib/fargate-queue-worker-service.ts | 2 +- packages/@aws-cdk/aws-ecs/lib/queue-worker-service-base.ts | 2 +- packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts | 2 +- packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/design/aws-ecs-autoscaling-queue-worker.md b/design/aws-ecs-autoscaling-queue-worker.md index 3de827f973c6c..ca94a9d0c0a85 100644 --- a/design/aws-ecs-autoscaling-queue-worker.md +++ b/design/aws-ecs-autoscaling-queue-worker.md @@ -44,11 +44,11 @@ export interface QueueWorkerServiceBaseProps { readonly image: ContainerImage; /** - * The CMD value to pass to the container. A string with commands delimited by commas. + * The CMD value to pass to the container as a string array. * * @default none */ - readonly command?: string; + readonly command?: string[]; /** * Number of desired copies of running tasks diff --git a/packages/@aws-cdk/aws-ecs/lib/ecs-queue-worker-service.ts b/packages/@aws-cdk/aws-ecs/lib/ecs-queue-worker-service.ts index 5d33a54a37c62..e97d480b44e6d 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ecs-queue-worker-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ecs-queue-worker-service.ts @@ -51,7 +51,7 @@ export class Ec2QueueWorkerService extends QueueWorkerServiceBase { memoryLimitMiB: props.memoryLimitMiB, memoryReservationMiB: props.memoryReservationMiB, cpu: props.cpu, - command: props.command !== undefined ? cdk.Fn.split(",", props.command) : undefined, + command: props.command, environment: this.environment, logging: this.logDriver }); diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate-queue-worker-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate-queue-worker-service.ts index a726e2b3a37ba..cb5d361f2faed 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate-queue-worker-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate-queue-worker-service.ts @@ -59,7 +59,7 @@ export class FargateQueueWorkerService extends QueueWorkerServiceBase { }); taskDefinition.addContainer('QueueWorkerContainer', { image: props.image, - command: props.command !== undefined ? cdk.Fn.split(",", props.command) : undefined, + command: props.command, environment: this.environment, logging: this.logDriver }); diff --git a/packages/@aws-cdk/aws-ecs/lib/queue-worker-service-base.ts b/packages/@aws-cdk/aws-ecs/lib/queue-worker-service-base.ts index fc8e281c9ba5f..f59b54bad9509 100644 --- a/packages/@aws-cdk/aws-ecs/lib/queue-worker-service-base.ts +++ b/packages/@aws-cdk/aws-ecs/lib/queue-worker-service-base.ts @@ -27,7 +27,7 @@ export interface QueueWorkerServiceBaseProps { * * @default none */ - readonly command?: string; + readonly command?: string[]; /** * Number of desired copies of running tasks diff --git a/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts b/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts index 249fb4f1804bf..313df73132124 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts @@ -76,7 +76,7 @@ export = { cluster, memoryLimitMiB: 1024, image: ecs.ContainerImage.fromRegistry('test'), - command: "-c, 4, amazon.com", + command: ["-c", "4", "amazon.com"], enableLogging: false, desiredTaskCount: 2, environment: { diff --git a/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts b/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts index 44d113a63529e..8d6d1cbdcfb29 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts @@ -75,7 +75,7 @@ export = { cluster, memoryMiB: '512', image: ecs.ContainerImage.fromRegistry('test'), - command: "-c, 4, amazon.com", + command: ["-c", "4", "amazon.com"], enableLogging: false, desiredTaskCount: 2, environment: { From 222020dbb5876b2c1b447591f9f6273670821dc9 Mon Sep 17 00:00:00 2001 From: Piradeep Kandasamy Date: Tue, 21 May 2019 13:53:59 -0700 Subject: [PATCH 3/3] Fix merge issues --- packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts | 8 ++++---- .../@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts b/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts index 313df73132124..9da38e86c195f 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.ecs-worker-service.ts @@ -9,7 +9,7 @@ export = { 'test ECS queue worker service construct - with only required props'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); @@ -66,7 +66,7 @@ export = { 'test ECS queue worker service construct - with optional props'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); const queue = new sqs.Queue(stack, 'ecs-test-queue', { queueName: 'ecs-test-sqs-queue'}); @@ -102,8 +102,8 @@ export = { { Command: [ "-c", - " 4", - " amazon.com" + "4", + "amazon.com" ], Environment: [ { diff --git a/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts b/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts index 8d6d1cbdcfb29..f558212763b71 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.fargate-worker-service.ts @@ -9,7 +9,7 @@ export = { 'test fargate queue worker service construct - with only required props'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); @@ -65,7 +65,7 @@ export = { 'test Fargate queue worker service construct - with optional props'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); const queue = new sqs.Queue(stack, 'fargate-test-queue', { queueName: 'fargate-test-sqs-queue'}); @@ -99,8 +99,8 @@ export = { { Command: [ "-c", - " 4", - " amazon.com" + "4", + "amazon.com" ], Environment: [ {