-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathcompute-environment.ts
458 lines (402 loc) · 16.4 KB
/
compute-environment.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
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import { Aws, Construct, IResource, Resource, Stack, Tag } from '@aws-cdk/core';
import { CfnComputeEnvironment } from './batch.generated';
/**
* Property to specify if the compute environment
* uses On-Demand or SpotFleet compute resources.
*/
export enum ComputeResourceType {
/**
* Resources will be EC2 On-Demand resources.
*/
ON_DEMAND = 'EC2',
/**
* Resources will be EC2 SpotFleet resources.
*/
SPOT = 'SPOT',
}
/**
* Properties for how to prepare compute resources
* that are provisioned for a compute environment.
*/
export enum AllocationStrategy {
/**
* Batch will use the best fitting instance type will be used
* when assigning a batch job in this compute environment.
*/
BEST_FIT = 'BEST_FIT',
/**
* Batch will select additional instance types that are large enough to
* meet the requirements of the jobs in the queue, with a preference for
* instance types with a lower cost per unit vCPU.
*/
BEST_FIT_PROGRESSIVE = 'BEST_FIT_PROGRESSIVE',
/**
* This is only available for Spot Instance compute resources and will select
* additional instance types that are large enough to meet the requirements of
* the jobs in the queue, with a preference for instance types that are less
* likely to be interrupted.
*/
SPOT_CAPACITY_OPTIMIZED = 'SPOT_CAPACITY_OPTIMIZED',
}
/**
* Properties for defining the structure of the batch compute cluster.
*/
export interface ComputeResources {
/**
* The IAM role applied to EC2 resources in the compute environment.
*
* @default - a new role will be created.
*/
readonly instanceRole?: iam.IRole;
/**
* The types of EC2 instances that may be launched in the compute environment. You can specify instance
* families to launch any instance type within those families (for example, c4 or p3), or you can specify
* specific sizes within a family (such as c4.8xlarge). You can also choose optimal to pick instance types
* (from the C, M, and R instance families) on the fly that match the demand of your job queues.
*
* @default optimal
*/
readonly instanceTypes?: ec2.InstanceType[];
/**
* The EC2 security group(s) associated with instances launched in the compute environment.
*
* @default AWS default security group.
*/
readonly securityGroups?: ec2.ISecurityGroup[];
/**
* The VPC network that all compute resources will be connected to.
*/
readonly vpc: ec2.IVpc;
/**
* The VPC subnets into which the compute resources are launched.
*
* @default - private subnets of the supplied VPC.
*/
readonly vpcSubnets?: ec2.SubnetSelection;
/**
* The type of compute environment: ON_DEMAND or SPOT.
*
* @default ON_DEMAND
*/
readonly type?: ComputeResourceType;
/**
* This property will be ignored if you set the environment type to ON_DEMAND.
*
* The maximum percentage that a Spot Instance price can be when compared with the On-Demand price for
* that instance type before instances are launched. For example, if your maximum percentage is 20%,
* then the Spot price must be below 20% of the current On-Demand price for that EC2 instance. You always
* pay the lowest (market) price and never more than your maximum percentage. If you leave this field empty,
* the default value is 100% of the On-Demand price.
*
* @default 100
*/
readonly bidPercentage?: number;
/**
* The desired number of EC2 vCPUS in the compute environment.
*
* @default - no desired vcpu value will be used.
*/
readonly desiredvCpus?: number;
/**
* The maximum number of EC2 vCPUs that an environment can reach. Each vCPU is equivalent to
* 1,024 CPU shares. You must specify at least one vCPU.
*
* @default 256
*/
readonly maxvCpus?: number;
/**
* The minimum number of EC2 vCPUs that an environment should maintain (even if the compute environment state is DISABLED).
* Each vCPU is equivalent to 1,024 CPU shares. You must specify at least one vCPU.
*
* @default 1
*/
readonly minvCpus?: number;
/**
* The EC2 key pair that is used for instances launched in the compute environment.
* If no key is defined, then SSH access is not allowed to provisioned compute resources.
*
* @default - No SSH access will be possible.
*/
readonly ec2KeyPair?: string;
/**
* The Amazon Machine Image (AMI) ID used for instances launched in the compute environment.
*
* @default - no image will be used.
*/
readonly image?: ec2.IMachineImage;
/**
* This property will be ignored if you set the environment type to ON_DEMAND.
*
* The Amazon Resource Name (ARN) of the Amazon EC2 Spot Fleet IAM role applied to a SPOT compute environment.
* For more information, see Amazon EC2 Spot Fleet Role in the AWS Batch User Guide.
*
* @link https://docs.aws.amazon.com/batch/latest/userguide/spot_fleet_IAM_role.html
* @default - no fleet role will be used.
*/
readonly spotFleetRole?: iam.IRole;
/**
* Key-value pair tags to be applied to resources that are launched in the compute environment.
* For AWS Batch, these take the form of "String1": "String2", where String1 is the tag key and
* String2 is the tag value—for example, { "Name": "AWS Batch Instance - C4OnDemand" }.
*
* @default - no tags will be assigned on compute resources.
*/
readonly computeResourcesTags?: Tag;
}
/**
* Properties for creating a new Compute Environment
*/
export interface ComputeEnvironmentProps {
/**
* The allocation strategy to use for the compute resource in case not enough instances ofthe best
* fitting instance type can be allocated. This could be due to availability of the instance type in
* the region or Amazon EC2 service limits. If this is not specified, the default is BEST_FIT, which
* will use only the best fitting instance type, waiting for additional capacity if it's not available.
* This allocation strategy keeps costs lower but can limit scaling. If you are using Spot Fleets with
* BEST_FIT then the Spot Fleet IAM Role must be specified. BEST_FIT_PROGRESSIVE will select an additional
* instance type that is large enough to meet the requirements of the jobs in the queue, with a preference
* for an instance type with a lower cost. SPOT_CAPACITY_OPTIMIZED is only available for Spot Instance
* compute resources and will select an additional instance type that is large enough to meet the requirements
* of the jobs in the queue, with a preference for an instance type that is less likely to be interrupted.
*
* @default AllocationStrategy.BEST_FIT
*/
readonly allocationStrategy?: AllocationStrategy;
/**
* A name for the compute environment.
*
* Up to 128 letters (uppercase and lowercase), numbers, hyphens, and underscores are allowed.
*
* @default Cloudformation-generated name
*/
readonly computeEnvironmentName?: string;
/**
* The details of the compute resources managed by this environment.
*
* If specified, and this is an managed compute environment, the property will be ignored.
*
* By default, AWS Batch managed compute environments use a recent, approved version of the
* Amazon ECS-optimized AMI for compute resources.
*
* @default - AWS-managed compute resources
*/
readonly computeResources?: ComputeResources;
/**
* The state of the compute environment. If the state is set to true, then the compute
* environment accepts jobs from a queue and can scale out automatically based on queues.
*
* @default true
*/
readonly enabled?: boolean;
/**
* The IAM role used by Batch to make calls to other AWS services on your behalf for managing
* the resources that you use with the service. By default, this role is created for you using
* the AWS managed service policy for Batch.
*
* @link https://docs.aws.amazon.com/batch/latest/userguide/service_IAM_role.html
*
* @default - Role using the 'service-role/AWSBatchServiceRole' policy.
*/
readonly serviceRole?: iam.IRole,
/**
* Determines if AWS should manage the allocation of compute resources for processing jobs.
* If set to false, then you are in charge of providing the compute resource details.
*
* @default true
*/
readonly managed?: boolean;
}
/**
* Properties of a compute environment.
*/
export interface IComputeEnvironment extends IResource {
/**
* The ARN of this compute environment.
*
* @attribute
*/
readonly computeEnvironmentArn: string;
/**
* The name of this compute environment.
*
* @attribute
*/
readonly computeEnvironmentName: string;
}
/**
* Batch Compute Environment.
*
* Defines a batch compute environment to run batch jobs on.
*/
export class ComputeEnvironment extends Resource implements IComputeEnvironment {
/**
* Fetches an existing batch compute environment by its amazon resource name.
*
* @param scope
* @param id
* @param computeEnvironmentArn
*/
public static fromComputeEnvironmentArn(scope: Construct, id: string, computeEnvironmentArn: string): IComputeEnvironment {
const stack = Stack.of(scope);
const computeEnvironmentName = stack.parseArn(computeEnvironmentArn).resourceName!;
class Import extends Resource implements IComputeEnvironment {
public readonly computeEnvironmentArn = computeEnvironmentArn;
public readonly computeEnvironmentName = computeEnvironmentName;
}
return new Import(scope, id);
}
/**
* The ARN of this compute environment.
*
* @attribute
*/
public readonly computeEnvironmentArn: string;
/**
* The name of this compute environment.
*
* @attribute
*/
public readonly computeEnvironmentName: string;
constructor(scope: Construct, id: string, props: ComputeEnvironmentProps = { enabled: true, managed: true }) {
super(scope, id, {
physicalName: props.computeEnvironmentName,
});
this.validateProps(props);
const spotFleetRole = this.getSpotFleetRole(props);
let computeResources: CfnComputeEnvironment.ComputeResourcesProperty | undefined;
// Only allow compute resources to be set when using UNMANAGED type
if (props.computeResources && !this.isManaged(props)) {
computeResources = {
allocationStrategy: props.allocationStrategy || AllocationStrategy.BEST_FIT,
bidPercentage: props.computeResources.bidPercentage,
desiredvCpus: props.computeResources.desiredvCpus,
ec2KeyPair: props.computeResources.ec2KeyPair,
imageId: props.computeResources.image && props.computeResources.image.getImage(this).imageId,
instanceRole: props.computeResources.instanceRole
? props.computeResources.instanceRole.roleArn
: new iam.Role(this, 'Resource-Instance-Role', {
assumedBy: new iam.ServicePrincipal('batch.amazonaws.com'),
}).roleArn,
instanceTypes: this.buildInstanceTypes(props.computeResources.instanceTypes),
maxvCpus: props.computeResources.maxvCpus || 256,
minvCpus: props.computeResources.minvCpus || 0,
securityGroupIds: this.buildSecurityGroupIds(props.computeResources.vpc, props.computeResources.securityGroups),
spotIamFleetRole: spotFleetRole ? spotFleetRole.roleArn : undefined,
subnets: props.computeResources.vpc.selectSubnets(props.computeResources.vpcSubnets).subnetIds,
tags: props.computeResources.computeResourcesTags,
type: props.computeResources.type || ComputeResourceType.ON_DEMAND,
};
}
const computeEnvironment = new CfnComputeEnvironment(this, 'Resource', {
computeEnvironmentName: this.physicalName,
computeResources,
serviceRole: props.serviceRole
? props.serviceRole.roleArn
: new iam.Role(this, 'Resource-Service-Instance-Role', {
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSBatchServiceRole'),
],
assumedBy: new iam.ServicePrincipal('batch.amazonaws.com'),
}).roleArn,
state: props.enabled === undefined ? 'ENABLED' : (props.enabled ? 'ENABLED' : 'DISABLED'),
type: this.isManaged(props) ? 'UNMANAGED' : 'MANAGED',
});
if (props.computeResources && props.computeResources.vpc) {
this.node.addDependency(props.computeResources.vpc);
}
this.computeEnvironmentArn = this.getResourceArnAttribute(computeEnvironment.ref, {
service: 'batch',
resource: 'compute-environment',
resourceName: this.physicalName,
});
this.computeEnvironmentName = this.getResourceNameAttribute(computeEnvironment.ref);
}
private isManaged(props: ComputeEnvironmentProps): boolean {
return props.managed === undefined ? true : props.managed;
}
/**
* Validates the properties provided for a new batch compute environment.
*/
private validateProps(props: ComputeEnvironmentProps) {
if (props === undefined) {
return;
}
if (this.isManaged(props) && props.computeResources !== undefined) {
throw new Error('It is not allowed to set computeResources on an AWS managed compute environment');
}
if (!this.isManaged(props) && props.computeResources === undefined) {
throw new Error('computeResources is missing but required on an unmanaged compute environment');
}
// Setting a bid percentage is only allowed on SPOT resources +
// Cannot use SPOT_CAPACITY_OPTIMIZED when using ON_DEMAND
if (props.computeResources) {
if (props.computeResources.type === ComputeResourceType.ON_DEMAND) {
// VALIDATE FOR ON_DEMAND
// Bid percentage is not allowed
if (props.computeResources.bidPercentage !== undefined) {
throw new Error('Setting the bid percentage is only allowed for SPOT type resources on a batch compute environment');
}
// SPOT_CAPACITY_OPTIMIZED allocation is not allowed
if (props.allocationStrategy && props.allocationStrategy === AllocationStrategy.SPOT_CAPACITY_OPTIMIZED) {
throw new Error('The SPOT_CAPACITY_OPTIMIZED allocation strategy is only allowed if the environment is a SPOT type compute environment');
}
} else {
// VALIDATE FOR SPOT
// Bid percentage must be from 0 - 100
if (props.computeResources.bidPercentage !== undefined &&
(props.computeResources.bidPercentage < 0 || props.computeResources.bidPercentage > 100)) {
throw new Error('Bid percentage can only be a value between 0 and 100');
}
}
if (props.computeResources.minvCpus) {
// minvCpus cannot be less than 0
if (props.computeResources.minvCpus < 0) {
throw new Error('Minimum vCpus for a batch compute environment cannot be less than 0');
}
// minvCpus cannot exceed max vCpus
if (props.computeResources.maxvCpus &&
props.computeResources.minvCpus > props.computeResources.maxvCpus) {
throw new Error('Minimum vCpus cannot be greater than the maximum vCpus');
}
}
}
}
private buildInstanceTypes(instanceTypes?: ec2.InstanceType[]): string[] {
if (instanceTypes === undefined) {
return [
'optimal',
];
}
return instanceTypes.map((type: ec2.InstanceType) => type.toString());
}
private buildSecurityGroupIds(vpc: ec2.IVpc, securityGroups?: ec2.ISecurityGroup[]): string[] | undefined {
if (securityGroups === undefined) {
return [
new ec2.SecurityGroup(this, 'Resource-Security-Group', { vpc }).securityGroupId,
];
}
return securityGroups.map((group: ec2.ISecurityGroup) => group.securityGroupId);
}
/**
* Generates an AWS IAM role for provisioning spotfleet resources
* if the allocation strategy is set to BEST_FIT or not defined.
*
* @param props - the compute environment construct properties
*/
private getSpotFleetRole(props: ComputeEnvironmentProps): iam.IRole | undefined {
if (props.allocationStrategy && props.allocationStrategy !== AllocationStrategy.BEST_FIT) {
return undefined;
}
if (props.computeResources) {
if (props.computeResources.spotFleetRole) {
return props.computeResources.spotFleetRole;
} else if (props.computeResources.type === ComputeResourceType.SPOT) {
return iam.Role.fromRoleArn(this, 'Resource-SpotFleet-Role',
`arn${Aws.PARTITION}iam::${this.stack.account}:role/aws-service-role/spotfleet.amazonaws.com/AWSServiceRoleForEC2SpotFleet`);
}
}
return undefined;
}
}