Skip to content

Commit 7dd0e1a

Browse files
piradeepkrix0rrr
authored andcommitted
feat(aws-ecs): add ECS/Fargate QueueWorkerService constructs (#2568)
Define a higher-level construct to model a worker queue.
1 parent d7b0324 commit 7dd0e1a

8 files changed

+811
-0
lines changed
+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# AWS ECS - L3 Construct for Autoscaling ECS/Fargate Service that Processes Items in a SQS Queue
2+
3+
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.
4+
5+
## General approach
6+
7+
The new `ecs.QueueWorkerServiceBase`, `ecs.Ec2QueueWorkerService` and `ecs.FargateQueueWorkerService` classes will create L3 constructs for:
8+
9+
* Ec2QueueWorkerService
10+
* FargateQueueWorkerService
11+
12+
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.
13+
14+
The `QueueWorkerService` constructs (for EC2 and Fargate) will use the following existing constructs:
15+
16+
* Ec2TaskDefinition/FargateTaskDefinition - To create a Task Definition for the container to start
17+
* SQSQueue - The queue that the worker is processing from
18+
* Ec2Service/FargateService - The Service running the container
19+
20+
## Code changes
21+
22+
Given the above, we should make the following changes to support queue workers on ECS (for both EC2 and Fargate):
23+
1. Create `QueueWorkerServiceBaseProps` interface and `QueueWorkerServiceBase` construct
24+
2. Create `Ec2QueueWorkerServiceProps` interface and `Ec2QueueWorkerService` construct
25+
3. Create `FargateQueueWorkerServiceProps` interface and `FargateQueueWorkerService` construct
26+
27+
### Part 1: Create `QueueWorkerServiceBaseProps` interface and `QueueWorkerServiceBase` construct
28+
29+
The `QueueWorkerServiceBaseProps` interface will contain common properties used to construct both the Ec2QueueWorkerService and the FargateQueueWorkerService:
30+
31+
```ts
32+
/**
33+
* Properties to define a Query Worker service
34+
*/
35+
export interface QueueWorkerServiceBaseProps {
36+
/**
37+
* Cluster where service will be deployed
38+
*/
39+
readonly cluster: ICluster;
40+
41+
/**
42+
* The image to start.
43+
*/
44+
readonly image: ContainerImage;
45+
46+
/**
47+
* The CMD value to pass to the container as a string array.
48+
*
49+
* @default none
50+
*/
51+
readonly command?: string[];
52+
53+
/**
54+
* Number of desired copies of running tasks
55+
*
56+
* @default 1
57+
*/
58+
readonly desiredTaskCount?: number;
59+
60+
/**
61+
* Flag to indicate whether to enable logging
62+
*
63+
* @default true
64+
*/
65+
readonly enableLogging?: boolean;
66+
67+
/**
68+
* The environment variables to pass to the container.
69+
*
70+
* @default 'QUEUE_NAME: queue.queueName'
71+
*/
72+
readonly environment?: { [key: string]: string };
73+
74+
/**
75+
* A queue for which to process items from.
76+
*
77+
* If specified and this is a FIFO queue, the queue name must end in the string '.fifo'.
78+
* @see https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_CreateQueue.html
79+
*
80+
* @default 'SQSQueue with CloudFormation-generated name'
81+
*/
82+
readonly queue?: IQueue;
83+
84+
/**
85+
* Maximum capacity to scale to.
86+
*
87+
* @default (desiredTaskCount * 2)
88+
*/
89+
readonly maxScalingCapacity?: number
90+
91+
/**
92+
* The intervals for scaling based on the SQS queue's ApproximateNumberOfMessagesVisible metric.
93+
*
94+
* Maps a range of metric values to a particular scaling behavior.
95+
* https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html
96+
*
97+
* @default [{ upper: 0, change: -1 },{ lower: 100, change: +1 },{ lower: 500, change: +5 }]
98+
*/
99+
readonly scalingSteps: autoScaling.ScalingInterval[];
100+
}
101+
```
102+
103+
### Part 2: Create `Ec2QueueWorkerServiceProps` interface and `Ec2QueueWorkerService` construct
104+
105+
The `Ec2QueueWorkerServiceProps` interface will contain properties to construct the Ec2TaskDefinition, SQSQueue and Ec2Service:
106+
107+
```ts
108+
/**
109+
* Properties to define an ECS service
110+
*/
111+
export interface Ec2QueueWorkerServiceProps {
112+
/**
113+
* The minimum number of CPU units to reserve for the container.
114+
*
115+
* @default none
116+
*/
117+
readonly cpu?: number;
118+
119+
/**
120+
* The hard limit (in MiB) of memory to present to the container.
121+
*
122+
* If your container attempts to exceed the allocated memory, the container
123+
* is terminated.
124+
*
125+
* At least one of memoryLimitMiB and memoryReservationMiB is required for non-Fargate services.
126+
*/
127+
readonly memoryLimitMiB?: number;
128+
129+
/**
130+
* The soft limit (in MiB) of memory to reserve for the container.
131+
*
132+
* When system memory is under contention, Docker attempts to keep the
133+
* container memory within the limit. If the container requires more memory,
134+
* it can consume up to the value specified by the Memory property or all of
135+
* the available memory on the container instance—whichever comes first.
136+
*
137+
* At least one of memoryLimitMiB and memoryReservationMiB is required for non-Fargate services.
138+
*/
139+
readonly memoryReservationMiB?: number;
140+
}
141+
```
142+
143+
An example use case:
144+
```ts
145+
// Create the vpc and cluster used by the Queue Worker task
146+
const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 1 });
147+
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
148+
cluster.addCapacity('DefaultAutoScalingGroup', {
149+
instanceType: new ec2.InstanceType('t2.micro')
150+
});
151+
const queue = new sqs.Queue(stack, 'WorkerQueue', {
152+
QueueName: 'EcsWorkerQueue'
153+
});
154+
155+
// Create the Queue Worker task
156+
new Ec2QueueWorkerService(stack, 'EcsQueueWorkerService', {
157+
cluster,
158+
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
159+
desiredTaskCount: 2,
160+
maxScalingCapacity: 5,
161+
memoryReservationMiB: 512,
162+
cpu: 256,
163+
queue
164+
});
165+
```
166+
167+
### Part 3: Create `FargateQueueWorkerServiceProps` interface and `FargateQueueWorkerService` construct
168+
169+
The `FargateQueueWorkerServiceProps` interface will contain properties to construct the FargateTaskDefinition, SQSQueue and FargateService:
170+
171+
```ts
172+
/**
173+
* Properties to define an Fargate service
174+
*/
175+
export interface FargateQueueWorkerServiceProps {
176+
/**
177+
* The number of cpu units used by the task.
178+
* Valid values, which determines your range of valid values for the memory parameter:
179+
* 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB
180+
* 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB
181+
* 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB
182+
* 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments
183+
* 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments
184+
*
185+
* This default is set in the underlying FargateTaskDefinition construct.
186+
*
187+
* @default 256
188+
*/
189+
readonly cpu?: string;
190+
191+
/**
192+
* The amount (in MiB) of memory used by the task.
193+
*
194+
* This field is required and you must use one of the following values, which determines your range of valid values
195+
* for the cpu parameter:
196+
*
197+
* 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU)
198+
*
199+
* 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU)
200+
*
201+
* 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU)
202+
*
203+
* Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU)
204+
*
205+
* Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU)
206+
*
207+
* This default is set in the underlying FargateTaskDefinition construct.
208+
*
209+
* @default 512
210+
*/
211+
readonly memoryMiB?: string;
212+
}
213+
```
214+
215+
An example use case:
216+
```ts
217+
// Create the vpc and cluster used by the Queue Worker task
218+
const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 2 });
219+
const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc });
220+
const queue = new sqs.Queue(stack, 'WorkerQueue', {
221+
QueueName: 'FargateWorkerQueue'
222+
});
223+
224+
// Create the Queue Worker task
225+
new FargateQueueWorkerService(stack, 'FargateQueueWorkerService', {
226+
cluster,
227+
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
228+
desiredTaskCount: 2,
229+
maxScalingCapacity: 5,
230+
queue
231+
});
232+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import cdk = require('@aws-cdk/cdk');
2+
import { Ec2Service } from './ec2/ec2-service';
3+
import { Ec2TaskDefinition } from './ec2/ec2-task-definition';
4+
import { QueueWorkerServiceBase, QueueWorkerServiceBaseProps } from './queue-worker-service-base';
5+
6+
/**
7+
* Properties to define an Ec2 query worker service
8+
*/
9+
export interface Ec2QueueWorkerServiceProps extends QueueWorkerServiceBaseProps {
10+
/**
11+
* The minimum number of CPU units to reserve for the container.
12+
*
13+
* @default none
14+
*/
15+
readonly cpu?: number;
16+
17+
/**
18+
* The hard limit (in MiB) of memory to present to the container.
19+
*
20+
* If your container attempts to exceed the allocated memory, the container
21+
* is terminated.
22+
*
23+
* At least one of memoryLimitMiB and memoryReservationMiB is required for non-Fargate services.
24+
*/
25+
readonly memoryLimitMiB?: number;
26+
27+
/**
28+
* The soft limit (in MiB) of memory to reserve for the container.
29+
*
30+
* When system memory is under contention, Docker attempts to keep the
31+
* container memory within the limit. If the container requires more memory,
32+
* it can consume up to the value specified by the Memory property or all of
33+
* the available memory on the container instance—whichever comes first.
34+
*
35+
* At least one of memoryLimitMiB and memoryReservationMiB is required for non-Fargate services.
36+
*/
37+
readonly memoryReservationMiB?: number;
38+
}
39+
40+
/**
41+
* Class to create an Ec2 query worker service
42+
*/
43+
export class Ec2QueueWorkerService extends QueueWorkerServiceBase {
44+
constructor(scope: cdk.Construct, id: string, props: Ec2QueueWorkerServiceProps) {
45+
super(scope, id, props);
46+
47+
// Create a Task Definition for the container to start
48+
const taskDefinition = new Ec2TaskDefinition(this, 'QueueWorkerTaskDef');
49+
taskDefinition.addContainer('QueueWorkerContainer', {
50+
image: props.image,
51+
memoryLimitMiB: props.memoryLimitMiB,
52+
memoryReservationMiB: props.memoryReservationMiB,
53+
cpu: props.cpu,
54+
command: props.command,
55+
environment: this.environment,
56+
logging: this.logDriver
57+
});
58+
59+
// Create an ECS service with the previously defined Task Definition and configure
60+
// autoscaling based on cpu utilization and number of messages visible in the SQS queue.
61+
const ecsService = new Ec2Service(this, 'QueueWorkerService', {
62+
cluster: props.cluster,
63+
desiredCount: this.desiredCount,
64+
taskDefinition
65+
});
66+
this.configureAutoscalingForService(ecsService);
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import cdk = require('@aws-cdk/cdk');
2+
import { FargateService } from './fargate/fargate-service';
3+
import { FargateTaskDefinition } from './fargate/fargate-task-definition';
4+
import { QueueWorkerServiceBase, QueueWorkerServiceBaseProps } from './queue-worker-service-base';
5+
6+
/**
7+
* Properties to define a Fargate queue worker service
8+
*/
9+
export interface FargateQueueWorkerServiceProps extends QueueWorkerServiceBaseProps {
10+
/**
11+
* The number of cpu units used by the task.
12+
* Valid values, which determines your range of valid values for the memory parameter:
13+
* 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB
14+
* 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB
15+
* 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB
16+
* 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments
17+
* 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments
18+
*
19+
* This default is set in the underlying FargateTaskDefinition construct.
20+
*
21+
* @default 256
22+
*/
23+
readonly cpu?: string;
24+
25+
/**
26+
* The amount (in MiB) of memory used by the task.
27+
*
28+
* This field is required and you must use one of the following values, which determines your range of valid values
29+
* for the cpu parameter:
30+
*
31+
* 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU)
32+
*
33+
* 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU)
34+
*
35+
* 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU)
36+
*
37+
* Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU)
38+
*
39+
* Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU)
40+
*
41+
* This default is set in the underlying FargateTaskDefinition construct.
42+
*
43+
* @default 512
44+
*/
45+
readonly memoryMiB?: string;
46+
}
47+
48+
/**
49+
* Class to create a Fargate query worker service
50+
*/
51+
export class FargateQueueWorkerService extends QueueWorkerServiceBase {
52+
constructor(scope: cdk.Construct, id: string, props: FargateQueueWorkerServiceProps) {
53+
super(scope, id, props);
54+
55+
// Create a Task Definition for the container to start
56+
const taskDefinition = new FargateTaskDefinition(this, 'QueueWorkerTaskDef', {
57+
memoryMiB: props.memoryMiB !== undefined ? props.memoryMiB : '512',
58+
cpu: props.cpu !== undefined ? props.cpu : '256',
59+
});
60+
taskDefinition.addContainer('QueueWorkerContainer', {
61+
image: props.image,
62+
command: props.command,
63+
environment: this.environment,
64+
logging: this.logDriver
65+
});
66+
67+
// Create a Fargate service with the previously defined Task Definition and configure
68+
// autoscaling based on cpu utilization and number of messages visible in the SQS queue.
69+
const fargateService = new FargateService(this, 'FargateQueueWorkerService', {
70+
cluster: props.cluster,
71+
desiredCount: this.desiredCount,
72+
taskDefinition
73+
});
74+
this.configureAutoscalingForService(fargateService);
75+
}
76+
}

packages/@aws-cdk/aws-ecs/lib/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export * from './load-balanced-fargate-service';
2020
export * from './load-balanced-ecs-service';
2121
export * from './load-balanced-fargate-service-applet';
2222

23+
export * from './queue-worker-service-base';
24+
export * from './ecs-queue-worker-service';
25+
export * from './fargate-queue-worker-service';
26+
2327
export * from './images/asset-image';
2428
export * from './images/repository';
2529
export * from './images/ecr';

0 commit comments

Comments
 (0)