-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
task-definition.ts
478 lines (417 loc) · 12.6 KB
/
task-definition.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
import iam = require('@aws-cdk/aws-iam');
import cdk = require('@aws-cdk/cdk');
import { ContainerDefinition, ContainerDefinitionOptions } from '../container-definition';
import { CfnTaskDefinition } from '../ecs.generated';
import { isEc2Compatible, isFargateCompatible } from '../util';
/**
* Properties common to all Task definitions
*/
export interface CommonTaskDefinitionProps {
/**
* Namespace for task definition versions
*
* @default Automatically generated name
*/
family?: string;
/**
* The IAM role assumed by the ECS agent.
*
* The role will be used to retrieve container images from ECR and
* create CloudWatch log groups.
*
* @default An execution role will be automatically created if you use ECR images in your task definition
*/
executionRole?: iam.Role;
/**
* The IAM role assumable by your application code running inside the container
*
* @default A task role is automatically created for you
*/
taskRole?: iam.Role;
/**
* See: https://docs.aws.amazon.com/AmazonECS/latest/developerguide//task_definition_parameters.html#volumes
*/
volumes?: Volume[];
}
/**
* Properties for generic task definitions
*/
export interface TaskDefinitionProps extends CommonTaskDefinitionProps {
/**
* The Docker networking mode to use for the containers in the task.
*
* On Fargate, the only supported networking mode is AwsVpc.
*
* @default NetworkMode.Bridge for EC2 tasks, AwsVpc for Fargate tasks.
*/
networkMode?: NetworkMode;
/**
* An array of placement constraint objects to use for the task. You can
* specify a maximum of 10 constraints per task (this limit includes
* constraints in the task definition and those specified at run time).
*
* Not supported in Fargate.
*/
placementConstraints?: PlacementConstraint[];
/**
* What launch types this task definition should be compatible with.
*/
compatibility: Compatibility;
/**
* 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
*/
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)
*/
memoryMiB?: string;
}
/**
* Base class for Ecs and Fargate task definitions
*/
export class TaskDefinition extends cdk.Construct {
/**
* The family name of this task definition
*/
public readonly family: string;
/**
* ARN of this task definition
*/
public readonly taskDefinitionArn: string;
/**
* Task role used by this task definition
*/
public readonly taskRole: iam.Role;
/**
* Network mode used by this task definition
*/
public readonly networkMode: NetworkMode;
/**
* Default container for this task
*
* Load balancers will send traffic to this container. The first
* essential container that is added to this task will become the default
* container.
*/
public defaultContainer?: ContainerDefinition;
/**
* What launching modes this task is compatible with
*/
public compatibility: Compatibility;
/**
* Execution role for this task definition
*
* May not exist, will be created as needed.
*/
public executionRole?: iam.IRole;
/**
* All containers
*/
protected readonly containers = new Array<ContainerDefinition>();
/**
* All volumes
*/
private readonly volumes: Volume[] = [];
/**
* Placement constraints for task instances
*/
private readonly placementConstraints = new Array<CfnTaskDefinition.TaskDefinitionPlacementConstraintProperty>();
constructor(scope: cdk.Construct, id: string, props: TaskDefinitionProps) {
super(scope, id);
this.family = props.family || this.node.uniqueId;
this.compatibility = props.compatibility;
if (props.volumes) {
props.volumes.forEach(v => this.addVolume(v));
}
this.networkMode = props.networkMode !== undefined ? props.networkMode :
isFargateCompatible(this.compatibility) ? NetworkMode.AwsVpc : NetworkMode.Bridge;
if (isFargateCompatible(this.compatibility) && this.networkMode !== NetworkMode.AwsVpc) {
throw new Error(`Fargate tasks can only have AwsVpc network mode, got: ${this.networkMode}`);
}
if (props.placementConstraints && props.placementConstraints.length > 0 && isFargateCompatible(this.compatibility)) {
throw new Error('Cannot set placement constraints on tasks that run on Fargate');
}
if (isFargateCompatible(this.compatibility) && (!props.cpu || !props.memoryMiB)) {
throw new Error(`Fargate-compatible tasks require both CPU (${props.cpu}) and memory (${props.memoryMiB}) specifications`);
}
this.executionRole = props.executionRole;
this.taskRole = props.taskRole || new iam.Role(this, 'TaskRole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
});
const taskDef = new CfnTaskDefinition(this, 'Resource', {
containerDefinitions: new cdk.Token(() => this.containers.map(x => x.renderContainerDefinition())),
volumes: new cdk.Token(() => this.volumes),
executionRoleArn: new cdk.Token(() => this.executionRole && this.executionRole.roleArn).toString(),
family: this.family,
taskRoleArn: this.taskRole.roleArn,
requiresCompatibilities: [
...(isEc2Compatible(props.compatibility) ? ["EC2"] : []),
...(isFargateCompatible(props.compatibility) ? ["FARGATE"] : []),
],
networkMode: this.networkMode,
placementConstraints: !isFargateCompatible(this.compatibility) ? new cdk.Token(this.placementConstraints) : undefined,
cpu: props.cpu,
memory: props.memoryMiB,
});
if (props.placementConstraints) {
props.placementConstraints.forEach(pc => this.addPlacementConstraint(pc));
}
this.taskDefinitionArn = taskDef.taskDefinitionArn;
}
/**
* Add a policy statement to the Task Role
*/
public addToTaskRolePolicy(statement: iam.PolicyStatement) {
this.taskRole.addToPolicy(statement);
}
/**
* Add a policy statement to the Execution Role
*/
public addToExecutionRolePolicy(statement: iam.PolicyStatement) {
this.obtainExecutionRole().addToPolicy(statement);
}
/**
* Create a new container to this task definition
*/
public addContainer(id: string, props: ContainerDefinitionOptions) {
return new ContainerDefinition(this, id, { taskDefinition: this, ...props });
}
/**
* (internal) Links a container to this task definition.
*/
public _linkContainer(container: ContainerDefinition) {
this.containers.push(container);
if (this.defaultContainer === undefined && container.essential) {
this.defaultContainer = container;
}
}
/**
* Add a volume to this task definition
*/
public addVolume(volume: Volume) {
this.volumes.push(volume);
}
/**
* Constrain where tasks can be placed
*/
public addPlacementConstraint(constraint: PlacementConstraint) {
if (isFargateCompatible(this.compatibility)) {
throw new Error('Cannot set placement constraints on tasks that run on Fargate');
}
const pc = this.renderPlacementConstraint(constraint);
this.placementConstraints.push(pc);
}
/**
* Extend this TaskDefinition with the given extension
*
* Extension can be used to apply a packaged modification to
* a task definition.
*/
public addExtension(extension: ITaskDefinitionExtension) {
extension.extend(this);
}
/**
* Create the execution role if it doesn't exist
*/
public obtainExecutionRole(): iam.IRole {
if (!this.executionRole) {
this.executionRole = new iam.Role(this, 'ExecutionRole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
});
}
return this.executionRole;
}
/**
* Validate this task definition
*/
protected validate(): string[] {
const ret = super.validate();
if (isEc2Compatible(this.compatibility)) {
// EC2 mode validations
// Container sizes
for (const container of this.containers) {
if (!container.memoryLimitSpecified) {
ret.push(`ECS Container ${container.node.id} must have at least one of 'memoryLimitMiB' or 'memoryReservationMiB' specified`);
}
}
}
return ret;
}
/**
* Render the placement constraints
*/
private renderPlacementConstraint(pc: PlacementConstraint): CfnTaskDefinition.TaskDefinitionPlacementConstraintProperty {
return {
type: pc.type,
expression: pc.expression
};
}
}
/**
* The Docker networking mode to use for the containers in the task.
*/
export enum NetworkMode {
/**
* The task's containers do not have external connectivity and port mappings can't be specified in the container definition.
*/
None = 'none',
/**
* The task utilizes Docker's built-in virtual network which runs inside each container instance.
*/
Bridge = 'bridge',
/**
* The task is allocated an elastic network interface.
*/
AwsVpc = 'awsvpc',
/**
* The task bypasses Docker's built-in virtual network and maps container ports directly to the EC2 instance's network interface directly.
*
* In this mode, you can't run multiple instantiations of the same task on a
* single container instance when port mappings are used.
*/
Host = 'host',
}
/**
* Volume definition
*/
export interface Volume {
/**
* Path on the host
*/
host?: Host;
/**
* A name for the volume
*/
name: string;
/**
* Specifies this configuration when using Docker volumes
*/
dockerVolumeConfiguration?: DockerVolumeConfiguration;
}
/**
* A volume host
*/
export interface Host {
/**
* Source path on the host
*/
sourcePath?: string;
}
/**
* A configuration of a Docker volume
*/
export interface DockerVolumeConfiguration {
/**
* If true, the Docker volume is created if it does not already exist
*
* @default false
*/
autoprovision?: boolean;
/**
* The Docker volume driver to use
*/
driver: string;
/**
* A map of Docker driver specific options passed through
*
* @default No options
*/
driverOpts?: string[];
/**
* Custom metadata to add to your Docker volume
*
* @default No labels
*/
labels?: string[];
/**
* The scope for the Docker volume which determines it's lifecycle
*/
scope: Scope;
}
export enum Scope {
/**
* Docker volumes are automatically provisioned when the task starts and destroyed when the task stops
*/
Task = "task",
/**
* Docker volumes are persist after the task stops
*/
Shared = "shared"
}
/**
* A constraint on how instances should be placed
*/
export interface PlacementConstraint {
/**
* The type of constraint
*/
type: PlacementConstraintType;
/**
* Additional information for the constraint
*/
expression?: string;
}
/**
* A placement constraint type
*/
export enum PlacementConstraintType {
/**
* Place each task on a different instance
*/
DistinctInstance = "distinctInstance",
/**
* Place tasks only on instances matching the expression in 'expression'
*/
MemberOf = "memberOf"
}
/**
* Task compatibility
*/
export enum Compatibility {
/**
* Task should be launchable on EC2 clusters
*/
Ec2,
/**
* Task should be launchable on Fargate clusters
*/
Fargate,
/**
* Task should be launchable on both types of clusters
*/
Ec2AndFargate
}
/**
* An extension for Task Definitions
*
* Classes that want to make changes to a TaskDefinition (such as
* adding helper containers) can implement this interface, and can
* then be "added" to a TaskDefinition like so:
*
* taskDefinition.addExtension(new MyExtension("some_parameter"));
*/
export interface ITaskDefinitionExtension {
/**
* Apply the extension to the given TaskDefinition
*/
extend(taskDefinition: TaskDefinition): void;
}