From 0afdaeb2fe64ed820b935ca38072ead6775a5467 Mon Sep 17 00:00:00 2001 From: Alex Kokachev Date: Mon, 15 Mar 2021 14:43:59 +1100 Subject: [PATCH 1/5] feat(aws-batch): fargate support for batch jobs closes: #13590 --- packages/@aws-cdk/aws-batch/README.md | 17 +- .../aws-batch/lib/compute-environment.ts | 117 ++++++++++--- .../@aws-cdk/aws-batch/lib/job-definition.ts | 68 ++++++++ .../test/compute-environment.test.ts | 160 +++++++++++++++++- .../aws-batch/test/job-definition.test.ts | 42 +++++ 5 files changed, 378 insertions(+), 26 deletions(-) diff --git a/packages/@aws-cdk/aws-batch/README.md b/packages/@aws-cdk/aws-batch/README.md index 48d5b7edf65d8..77557e5ea15d1 100644 --- a/packages/@aws-cdk/aws-batch/README.md +++ b/packages/@aws-cdk/aws-batch/README.md @@ -37,7 +37,7 @@ For more information on **AWS Batch** visit the [AWS Docs for Batch](https://doc ## Compute Environment -At the core of AWS Batch is the compute environment. All batch jobs are processed within a compute environment, which uses resource like OnDemand or Spot EC2 instances. +At the core of AWS Batch is the compute environment. All batch jobs are processed within a compute environment, which uses resource like OnDemand/Spot EC2 instances or Fargate. In **MANAGED** mode, AWS will handle the provisioning of compute resources to accommodate the demand. Otherwise, in **UNMANAGED** mode, you will need to manage the provisioning of those resources. @@ -74,6 +74,21 @@ const spotEnvironment = new batch.ComputeEnvironment(stack, 'MySpotEnvironment', }); ``` +### Fargate Compute Environment + +It is possible to have AWS Batch submit jobs to be run on Fargate compute resources. Below is an example of how this can be done: + +```ts +const vpc = new ec2.Vpc(this, 'VPC'); + +const spotEnvironment = new batch.ComputeEnvironment(stack, 'MyFargateEnvironment', { + computeResources: { + type: batch.ComputeResourceType.FARGATE_SPOT, + vpc, + }, +}); +``` + ### Understanding Progressive Allocation Strategies AWS Batch uses an [allocation strategy](https://docs.aws.amazon.com/batch/latest/userguide/allocation-strategies.html) to determine what compute resource will efficiently handle incoming job requests. By default, **BEST_FIT** will pick an available compute instance based on vCPU requirements. If none exist, the job will wait until resources become available. However, with this strategy, you may have jobs waiting in the queue unnecessarily despite having more powerful instances available. Below is an example of how that situation might look like: diff --git a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts index 18a2d1a446325..c0bc605e22995 100644 --- a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts +++ b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts @@ -18,6 +18,16 @@ export enum ComputeResourceType { * Resources will be EC2 SpotFleet resources. */ SPOT = 'SPOT', + + /** + * Resources will be Fargate resources. + */ + FARGATE = 'FARGATE', + + /** + * Resources will be Fargate resources. + */ + FARGATE_SPOT = 'FARGATE_SPOT', } /** @@ -427,41 +437,100 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment throw new Error('computeResources is missing but required on a managed 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 + if (props.computeResources.type === ComputeResourceType.FARGATE || props.computeResources.type === ComputeResourceType.FARGATE_SPOT) { + // VALIDATE FOR FARGATE - // Bid percentage is not allowed + // Bid percentage cannot be set for Fargate evnvironments if (props.computeResources.bidPercentage !== undefined) { - throw new Error('Setting the bid percentage is only allowed for SPOT type resources on a batch compute environment'); + throw new Error('Bid percentage must not be set for Fargate compute environments'); } - // SPOT_CAPACITY_OPTIMIZED allocation is not allowed - if (props.computeResources.allocationStrategy && props.computeResources.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'); + // Allocation strategy cannot be set for Fargate evnvironments + if (props.computeResources.allocationStrategy !== undefined) { + throw new Error('Allocation strategy must not be set for Fargate compute environments'); } - } 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'); + // Desired vCPUs cannot be set for Fargate evnvironments + if (props.computeResources.desiredvCpus !== undefined) { + throw new Error('Desired vCPUs must not be set for Fargate compute environments'); + } + + // Image ID cannot be set for Fargate evnvironments + if (props.computeResources.image !== undefined) { + throw new Error('Image must not be set for Fargate compute environments'); + } + + // Instance types cannot be set for Fargate evnvironments + if (props.computeResources.instanceTypes !== undefined) { + throw new Error('Instance types must not be set for Fargate compute environments'); } - } - 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'); + // EC2 key pair cannot be set for Fargate evnvironments + if (props.computeResources.ec2KeyPair !== undefined) { + throw new Error('EC2 key pair must not be set for Fargate compute environments'); } - // 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'); + // Instance role cannot be set for Fargate evnvironments + if (props.computeResources.instanceRole !== undefined) { + throw new Error('Instance role must not be set for Fargate compute environments'); + } + + // Launch template cannot be set for Fargate evnvironments + if (props.computeResources.launchTemplate !== undefined) { + throw new Error('Launch template must not be set for Fargate compute environments'); + } + + // Min vCPUs cannot be set for Fargate evnvironments + if (props.computeResources.minvCpus !== undefined) { + throw new Error('Min vCPUs must not be set for Fargate compute environments'); + } + + // Placement group cannot be set for Fargate evnvironments + if (props.computeResources.placementGroup !== undefined) { + throw new Error('Placement group must not be set for Fargate compute environments'); + } + + // Spot fleet role cannot be set for Fargate evnvironments + if (props.computeResources.spotFleetRole !== undefined) { + throw new Error('Spot fleet role must not be set for Fargate compute environments'); + } + } else { + // VALIDATE FOR ON_DEMAND AND SPOT + 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'); + } + } + // Setting a bid percentage is only allowed on SPOT resources + + // Cannot use SPOT_CAPACITY_OPTIMIZED when using ON_DEMAND + 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.computeResources.allocationStrategy && props.computeResources.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 if (props.computeResources.type === ComputeResourceType.SPOT) { + // 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'); + } } } } diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index 88107b0266615..aee4a10c7657e 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -52,6 +52,21 @@ export enum LogDriver { SYSLOG = 'syslog' } +/** + * Platform capabilities + */ +export enum PlatformCapabilities { + /** + * Specifies EC2 environment. + */ + EC2 = 'EC2', + + /** + * Specifies Fargate environment. + */ + FARGATE = 'FARGATE' +} + /** * Log configuration options to send to a custom log driver for the container. */ @@ -77,6 +92,17 @@ export interface LogConfiguration { readonly secretOptions?: ExposedSecret[]; } + +/** + * Fargate platform configuration + */ +export interface FargatePlatformConfiguration { + /** + * Fargate platform version + */ + readonly platformVersion: ecs.FargatePlatformVersion +} + /** * Properties of a job definition container. */ @@ -197,6 +223,20 @@ export interface JobDefinitionContainer { * @default - No data volumes will be used. */ readonly volumes?: ecs.Volume[]; + + /** + * The platform configuration for jobs that are running on Fargate resources. + * + * @default - LATEST platform version will be used + */ + readonly fargatePlatformConfiguration?: FargatePlatformConfiguration; + + /** + * The IAM role that AWS Batch can assume. + * + * @default - None + */ + readonly executionRole?: iam.IRole; } /** @@ -252,6 +292,13 @@ export interface JobDefinitionProps { * @default - undefined */ readonly timeout?: Duration; + + /** + * The platform capabilities required by the job definition. + * + * @default - undefined + */ + readonly platformCapabilities?: PlatformCapabilities[]; } /** @@ -382,6 +429,8 @@ export class JobDefinition extends Resource implements IJobDefinition { physicalName: props.jobDefinitionName, }); + this.validateProps(props); + this.imageConfig = new JobDefinitionImageConfig(this, props.container); const jobDef = new CfnJobDefinition(this, 'Resource', { @@ -402,6 +451,7 @@ export class JobDefinition extends Resource implements IJobDefinition { timeout: { attemptDurationSeconds: props.timeout ? props.timeout.toSeconds() : undefined, }, + platformCapabilities: props.platformCapabilities || undefined, }); this.jobDefinitionArn = this.getResourceArnAttribute(jobDef.ref, { @@ -426,6 +476,20 @@ export class JobDefinition extends Resource implements IJobDefinition { return vars; } + /** + * Validates the properties provided for a new job definition. + */ + private validateProps(props: JobDefinitionProps) { + if (props === undefined) { + return; + } + + if (props.platformCapabilities !== undefined && props.platformCapabilities.includes(PlatformCapabilities.FARGATE) + && props.container.executionRole === undefined) { + throw new Error('Fargate job must have executionRole set'); + } + } + private buildJobContainer(container?: JobDefinitionContainer): CfnJobDefinition.ContainerPropertiesProperty | undefined { if (container === undefined) { return undefined; @@ -437,6 +501,7 @@ export class JobDefinition extends Resource implements IJobDefinition { image: this.imageConfig.imageName, instanceType: container.instanceType && container.instanceType.toString(), jobRoleArn: container.jobRole && container.jobRole.roleArn, + executionRoleArn: container.executionRole && container.executionRole.roleArn, linuxParameters: container.linuxParams ? { devices: container.linuxParams.renderLinuxParameters().devices } : undefined, @@ -458,6 +523,9 @@ export class JobDefinition extends Resource implements IJobDefinition { user: container.user, vcpus: container.vcpus || 1, volumes: container.volumes, + fargatePlatformConfiguration: container.fargatePlatformConfiguration ? { + platformVersion: container.fargatePlatformConfiguration.platformVersion, + } : undefined, }; } diff --git a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts index c7ead4cd47de8..4cd446eec3774 100644 --- a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts +++ b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts @@ -6,7 +6,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as batch from '../lib'; -describe('Batch Compute Evironment', () => { +describe('Batch Compute Environment', () => { let expectedManagedDefaultComputeProps: any; let defaultServiceRole: any; @@ -81,6 +81,164 @@ describe('Batch Compute Evironment', () => { }); }); }); + describe('using fargate resources', () => { + test('should deny setting bid percentage', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + bidPercentage: -1, + }, + }); + }); + }); + test('should deny setting allocation strategy', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + allocationStrategy: batch.AllocationStrategy.BEST_FIT, + }, + }); + }); + }); + test('should deny setting desired vCPUs', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + desiredvCpus: 1, + }, + }); + }); + }); + test('should deny setting min vCPUs', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + minvCpus: 1, + }, + }); + }); + }); + test('should deny setting image', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + image: ec2.MachineImage.latestAmazonLinux(), + }, + }); + }); + }); + test('should deny setting instance types', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + instanceTypes: [], + }, + }); + }); + }); + test('should deny setting EC2 key pair', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + ec2KeyPair: 'test', + }, + }); + }); + }); + test('should deny setting instance role', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + instanceRole: 'test', + }, + }); + }); + }); + test('should deny setting launch template', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + launchTemplate: { + launchTemplateName: 'test-template', + }, + }, + }); + }); + }); + test('should deny setting placement group', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + placementGroup: 'test', + }, + }); + }); + }); + test('should deny setting spot fleet role', () => { + // THEN + throws(() => { + // WHEN + new batch.ComputeEnvironment(stack, 'test-compute-env', { + managed: true, + computeResources: { + vpc, + type: batch.ComputeResourceType.FARGATE, + spotFleetRole: iam.Role.fromRoleArn(stack, 'test-role-arn', 'test-role'), + }, + }); + }); + }); + }); describe('using spot resources', () => { test('should provide a spot fleet role if one is not given and allocationStrategy is BEST_FIT', () => { diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts index ed9bffb7a90bc..781ee06fd3c1c 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts +++ b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts @@ -1,3 +1,4 @@ +import { throws } from 'assert'; import { Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecr from '@aws-cdk/aws-ecr'; @@ -61,6 +62,7 @@ describe('Batch Job Definition', () => { }, retryAttempts: 2, timeout: cdk.Duration.seconds(30), + platformCapabilities: [batch.PlatformCapabilities.EC2], }; }); @@ -112,6 +114,7 @@ describe('Batch Job Definition', () => { AttemptDurationSeconds: jobDefProps.timeout ? jobDefProps.timeout.toSeconds() : -1, }, Type: 'container', + PlatformCapabilities: ['EC2'], }); }); test('can use an ecr image', () => { @@ -286,4 +289,43 @@ describe('Batch Job Definition', () => { }, }); }); + describe('using fargate job definition', () => { + test('can configure platform configuration properly', () => { + // GIVEN + const platformConfiguration: batch.FargatePlatformConfiguration = { + platformVersion: ecs.FargatePlatformVersion.LATEST, + }; + const executionRole = new iam.Role(stack, 'execution-role', { + assumedBy: new iam.ServicePrincipal('batch.amazonaws.com'), + }); + // WHEN + new batch.JobDefinition(stack, 'job-def', { + platformCapabilities: [batch.PlatformCapabilities.FARGATE], + container: { + image: ecs.EcrImage.fromRegistry('docker/whalesay'), + fargatePlatformConfiguration: platformConfiguration, + executionRole: executionRole, + }, + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Batch::JobDefinition', { + ContainerProperties: { + FargatePlatformConfiguration: { + PlatformVersion: 'LATEST', + }, + }, + }); + }); + test('must require executionRole', () => { + throws(() => { + // WHEN + new batch.JobDefinition(stack, 'job-def', { + platformCapabilities: [batch.PlatformCapabilities.FARGATE], + container: { + image: ecs.EcrImage.fromRegistry('docker/whalesay'), + }, + }); + }); + }); + }); }); From a346b7e614f7066a61f7ffc283bc41fe76bf3ebd Mon Sep 17 00:00:00 2001 From: Dylan Seidt Date: Sun, 1 Aug 2021 21:20:42 -0500 Subject: [PATCH 2/5] add integ tests, more docs, and MR feedback --- packages/@aws-cdk/aws-batch/README.md | 2 +- .../aws-batch/lib/compute-environment.ts | 58 +- .../@aws-cdk/aws-batch/lib/job-definition.ts | 89 ++- .../aws-batch/test/integ.batch.expected.json | 710 ++++++++++++++---- .../@aws-cdk/aws-batch/test/integ.batch.ts | 40 + .../aws-batch/test/job-definition.test.ts | 69 ++ 6 files changed, 797 insertions(+), 171 deletions(-) diff --git a/packages/@aws-cdk/aws-batch/README.md b/packages/@aws-cdk/aws-batch/README.md index 77557e5ea15d1..f2900da8cda0f 100644 --- a/packages/@aws-cdk/aws-batch/README.md +++ b/packages/@aws-cdk/aws-batch/README.md @@ -81,7 +81,7 @@ It is possible to have AWS Batch submit jobs to be run on Fargate compute resour ```ts const vpc = new ec2.Vpc(this, 'VPC'); -const spotEnvironment = new batch.ComputeEnvironment(stack, 'MyFargateEnvironment', { +const fargateSpotEnvironment = new batch.ComputeEnvironment(stack, 'MyFargateEnvironment', { computeResources: { type: batch.ComputeResourceType.FARGATE_SPOT, vpc, diff --git a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts index c0bc605e22995..d5fc59cb5ac12 100644 --- a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts +++ b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts @@ -6,7 +6,7 @@ import { CfnComputeEnvironment } from './batch.generated'; /** * Property to specify if the compute environment - * uses On-Demand or SpotFleet compute resources. + * uses On-Demand, SpotFleet, Fargate, or Fargate Spot compute resources. */ export enum ComputeResourceType { /** @@ -25,7 +25,11 @@ export enum ComputeResourceType { FARGATE = 'FARGATE', /** - * Resources will be Fargate resources. + * Resources will be Fargate Spot resources. + * + * Fargate Spot uses spare capacity in the AWS cloud to run your fault-tolerant, + * time-flexible jobs at up to a 70% discount. If AWS needs the resources back, + * jobs running on Fargate Spot will be interrupted with two minutes of notification. */ FARGATE_SPOT = 'FARGATE_SPOT', } @@ -145,7 +149,7 @@ export interface ComputeResources { readonly vpcSubnets?: ec2.SubnetSelection; /** - * The type of compute environment: ON_DEMAND or SPOT. + * The type of compute environment: ON_DEMAND, SPOT, FARGATE, or FARGATE_SPOT. * * @default ON_DEMAND */ @@ -358,30 +362,12 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment // Only allow compute resources to be set when using MANAGED type if (props.computeResources && this.isManaged(props)) { computeResources = { - allocationStrategy: props.computeResources.allocationStrategy - || ( - props.computeResources.type === ComputeResourceType.SPOT - ? AllocationStrategy.SPOT_CAPACITY_OPTIMIZED - : 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 - : new iam.CfnInstanceProfile(this, 'Instance-Profile', { - roles: [new iam.Role(this, 'Ecs-Instance-Role', { - assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonEC2ContainerServiceforEC2Role'), - ], - }).roleName], - }).attrArn, - instanceTypes: this.buildInstanceTypes(props.computeResources.instanceTypes), launchTemplate: props.computeResources.launchTemplate, maxvCpus: props.computeResources.maxvCpus || 256, - minvCpus: props.computeResources.minvCpus || 0, placementGroup: props.computeResources.placementGroup, securityGroupIds: this.buildSecurityGroupIds(props.computeResources.vpc, props.computeResources.securityGroups), spotIamFleetRole: spotFleetRole?.roleArn, @@ -389,6 +375,32 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment tags: props.computeResources.computeResourcesTags, type: props.computeResources.type || ComputeResourceType.ON_DEMAND, }; + + const isFargate = ComputeResourceType.FARGATE === props.computeResources.type + || ComputeResourceType.FARGATE_SPOT === props.computeResources.type; + + if (!isFargate) { + Object.assign(computeResources, { + allocationStrategy: props.computeResources.allocationStrategy + || ( + props.computeResources.type === ComputeResourceType.SPOT + ? AllocationStrategy.SPOT_CAPACITY_OPTIMIZED + : AllocationStrategy.BEST_FIT + ), + instanceRole: props.computeResources.instanceRole + ? props.computeResources.instanceRole + : new iam.CfnInstanceProfile(this, 'Instance-Profile', { + roles: [new iam.Role(this, 'Ecs-Instance-Role', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonEC2ContainerServiceforEC2Role'), + ], + }).roleName], + }).attrArn, + instanceTypes: this.buildInstanceTypes(props.computeResources.instanceTypes), + minvCpus: props.computeResources.minvCpus || 0, + }); + } } const computeEnvironment = new CfnComputeEnvironment(this, 'Resource', { @@ -505,7 +517,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment // minvCpus cannot exceed max vCpus if (props.computeResources.maxvCpus && - props.computeResources.minvCpus > props.computeResources.maxvCpus) { + props.computeResources.minvCpus > props.computeResources.maxvCpus) { throw new Error('Minimum vCpus cannot be greater than the maximum vCpus'); } } @@ -528,7 +540,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment // Bid percentage must be from 0 - 100 if (props.computeResources.bidPercentage !== undefined && - (props.computeResources.bidPercentage < 0 || props.computeResources.bidPercentage > 100)) { + (props.computeResources.bidPercentage < 0 || props.computeResources.bidPercentage > 100)) { throw new Error('Bid percentage can only be a value between 0 and 100'); } } diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index aee4a10c7657e..2c030527c6912 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -103,6 +103,33 @@ export interface FargatePlatformConfiguration { readonly platformVersion: ecs.FargatePlatformVersion } +/** + * Whether or not to assign a public IP to the job + */ +export enum AssignPublicIp { + /** + * Assign public IP address to job + */ + ENABLED = 'ENABLED', + + /** + * Do not assign public IP address to job + */ + DISABLED = 'DISABLED', +} + +/** + * Fargate network configuration + */ +export interface NetworkConfiguration { + /** + * Whether or not to assign a public IP to the job + * + * @default - DISABLED + */ + readonly assignPublicIp?: AssignPublicIp +} + /** * Properties of a job definition container. */ @@ -163,7 +190,7 @@ export interface JobDefinitionContainer { * The hard limit (in MiB) of memory to present to the container. If your container attempts to exceed * the memory specified here, the container is killed. You must specify at least 4 MiB of memory for a job. * - * @default 4 + * @default - 4 for EC2, 512 for Fargate */ readonly memoryLimitMiB?: number; @@ -211,9 +238,9 @@ export interface JobDefinitionContainer { /** * The number of vCPUs reserved for the container. Each vCPU is equivalent to - * 1,024 CPU shares. You must specify at least one vCPU. + * 1,024 CPU shares. You must specify at least one vCPU for EC2 and 0.25 for Fargate. * - * @default 1 + * @default - 1 for EC2, 0.25 for Fargate */ readonly vcpus?: number; @@ -237,6 +264,14 @@ export interface JobDefinitionContainer { * @default - None */ readonly executionRole?: iam.IRole; + + /** + * The network configuration for jobs that are running on Fargate resources. + * Jobs that are running on EC2 resources must not specify this parameter. + * + * @default - None + */ + readonly networkConfiguration?: NetworkConfiguration } /** @@ -433,14 +468,16 @@ export class JobDefinition extends Resource implements IJobDefinition { this.imageConfig = new JobDefinitionImageConfig(this, props.container); + const isFargate = !!props.platformCapabilities?.includes(PlatformCapabilities.FARGATE); + const jobDef = new CfnJobDefinition(this, 'Resource', { jobDefinitionName: props.jobDefinitionName, - containerProperties: this.buildJobContainer(props.container), + containerProperties: this.buildJobContainer(props.container, isFargate), type: 'container', nodeProperties: props.nodeProps ? { mainNode: props.nodeProps.mainNode, - nodeRangeProperties: this.buildNodeRangeProps(props.nodeProps), + nodeRangeProperties: this.buildNodeRangeProps(props.nodeProps, isFargate), numNodes: props.nodeProps.count, } : undefined, @@ -462,7 +499,7 @@ export class JobDefinition extends Resource implements IJobDefinition { this.jobDefinitionName = this.getResourceNameAttribute(jobDef.ref); } - private deserializeEnvVariables(env?: { [name: string]: string}): CfnJobDefinition.EnvironmentProperty[] | undefined { + private deserializeEnvVariables(env?: { [name: string]: string }): CfnJobDefinition.EnvironmentProperty[] | undefined { const vars = new Array(); if (env === undefined) { @@ -485,17 +522,22 @@ export class JobDefinition extends Resource implements IJobDefinition { } if (props.platformCapabilities !== undefined && props.platformCapabilities.includes(PlatformCapabilities.FARGATE) - && props.container.executionRole === undefined) { + && props.container.executionRole === undefined) { throw new Error('Fargate job must have executionRole set'); } + + if (props.platformCapabilities !== undefined && props.platformCapabilities.includes(PlatformCapabilities.EC2) + && props.container.networkConfiguration !== undefined) { + throw new Error('EC2 job must not have networkConfiguration set'); + } } - private buildJobContainer(container?: JobDefinitionContainer): CfnJobDefinition.ContainerPropertiesProperty | undefined { + private buildJobContainer(container: JobDefinitionContainer, isFargate: boolean): CfnJobDefinition.ContainerPropertiesProperty | undefined { if (container === undefined) { return undefined; } - return { + const containerProperties = { command: container.command, environment: this.deserializeEnvVariables(container.environment), image: this.imageConfig.imageName, @@ -512,29 +554,44 @@ export class JobDefinition extends Resource implements IJobDefinition { ? this.buildLogConfigurationSecretOptions(container.logConfiguration.secretOptions) : undefined, } : undefined, - memory: container.memoryLimitMiB || 4, mountPoints: container.mountPoints, privileged: container.privileged || false, - resourceRequirements: container.gpuCount - ? [{ type: 'GPU', value: String(container.gpuCount) }] - : undefined, + networkConfiguration: container.networkConfiguration, readonlyRootFilesystem: container.readOnly || false, ulimits: container.ulimits, user: container.user, - vcpus: container.vcpus || 1, volumes: container.volumes, fargatePlatformConfiguration: container.fargatePlatformConfiguration ? { platformVersion: container.fargatePlatformConfiguration.platformVersion, } : undefined, }; + + if (!isFargate) { + Object.assign(containerProperties, { + memory: container.memoryLimitMiB || 4, + vcpus: container.vcpus || 1, + resourceRequirements: container.gpuCount + ? [{ type: 'GPU', value: String(container.gpuCount) }] + : undefined, + }); + } else { + Object.assign(containerProperties, { + resourceRequirements: [ + { type: 'VCPU', value: String(container.vcpus || 0.25) }, + { type: 'MEMORY', value: String(container.memoryLimitMiB || 512) }, + ], + }); + } + + return containerProperties; } - private buildNodeRangeProps(multiNodeProps: IMultiNodeProps): CfnJobDefinition.NodeRangePropertyProperty[] { + private buildNodeRangeProps(multiNodeProps: IMultiNodeProps, isFargate: boolean): CfnJobDefinition.NodeRangePropertyProperty[] { const rangeProps = new Array(); for (const prop of multiNodeProps.rangeProps) { rangeProps.push({ - container: this.buildJobContainer(prop.container), + container: this.buildJobContainer(prop.container, isFargate), targetNodes: `${prop.fromNodeIndex || 0}:${prop.toNodeIndex || multiNodeProps.count}`, }); } diff --git a/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json b/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json index 09d021e49bd9d..b4a11bc0026e1 100644 --- a/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json +++ b/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json @@ -95,15 +95,15 @@ "vpcPublicSubnet1NATGateway9C16659E": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + }, "AllocationId": { "Fn::GetAtt": [ "vpcPublicSubnet1EIPDA49DCBE", "AllocationId" ] }, - "SubnetId": { - "Ref": "vpcPublicSubnet1Subnet2E65531E" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "vpcPublicSubnet2NATGateway9B8AE11A": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "vpcPublicSubnet2Subnet009B674F" + }, "AllocationId": { "Fn::GetAtt": [ "vpcPublicSubnet2EIP9B3743B1", "AllocationId" ] }, - "SubnetId": { - "Ref": "vpcPublicSubnet2Subnet009B674F" - }, "Tags": [ { "Key": "Name", @@ -289,15 +289,15 @@ "vpcPublicSubnet3NATGateway82F6CA9E": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "vpcPublicSubnet3Subnet11B92D7C" + }, "AllocationId": { "Fn::GetAtt": [ "vpcPublicSubnet3EIP2C3B9D91", "AllocationId" ] }, - "SubnetId": { - "Ref": "vpcPublicSubnet3Subnet11B92D7C" - }, "Tags": [ { "Key": "Name", @@ -566,55 +566,30 @@ "batchunmanagedcomputeenvED550298": { "Type": "AWS::Batch::ComputeEnvironment", "Properties": { + "Type": "UNMANAGED", "ServiceRole": { "Fn::GetAtt": [ "batchunmanagedcomputeenvResourceServiceInstanceRoleCA40AF77", "Arn" ] }, - "Type": "UNMANAGED", "State": "ENABLED" } }, - "batchdemandcomputeenvlaunchtemplateEcsInstanceRole24D4E799": { - "Type": "AWS::IAM::Role", + "batchdemandcomputeenvlaunchtemplateResourceSecurityGroup23599B84": { + "Type": "AWS::EC2::SecurityGroup", "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "ec2.", - { - "Ref": "AWS::URLSuffix" - } - ] - ] - } - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ + "GroupDescription": "batch-stack/batch-demand-compute-env-launch-template/Resource-Security-Group", + "SecurityGroupEgress": [ { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" - ] - ] + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" } - ] + ], + "VpcId": { + "Ref": "vpcA2121C38" + } }, "DependsOn": [ "vpcIGWE57CBDCA", @@ -652,12 +627,43 @@ "vpcVPCGW7984C166" ] }, - "batchdemandcomputeenvlaunchtemplateInstanceProfile2DEC3A97": { - "Type": "AWS::IAM::InstanceProfile", + "batchdemandcomputeenvlaunchtemplateEcsInstanceRole24D4E799": { + "Type": "AWS::IAM::Role", "Properties": { - "Roles": [ + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ { - "Ref": "batchdemandcomputeenvlaunchtemplateEcsInstanceRole24D4E799" + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" + ] + ] } ] }, @@ -697,20 +703,14 @@ "vpcVPCGW7984C166" ] }, - "batchdemandcomputeenvlaunchtemplateResourceSecurityGroup23599B84": { - "Type": "AWS::EC2::SecurityGroup", + "batchdemandcomputeenvlaunchtemplateInstanceProfile2DEC3A97": { + "Type": "AWS::IAM::InstanceProfile", "Properties": { - "GroupDescription": "batch-stack/batch-demand-compute-env-launch-template/Resource-Security-Group", - "SecurityGroupEgress": [ + "Roles": [ { - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" + "Ref": "batchdemandcomputeenvlaunchtemplateEcsInstanceRole24D4E799" } - ], - "VpcId": { - "Ref": "vpcA2121C38" - } + ] }, "DependsOn": [ "vpcIGWE57CBDCA", @@ -817,12 +817,6 @@ "batchdemandcomputeenvlaunchtemplateF8A5B233": { "Type": "AWS::Batch::ComputeEnvironment", "Properties": { - "ServiceRole": { - "Fn::GetAtt": [ - "batchdemandcomputeenvlaunchtemplateResourceServiceInstanceRole76AD99CC", - "Arn" - ] - }, "Type": "MANAGED", "ComputeResources": { "AllocationStrategy": "BEST_FIT", @@ -864,6 +858,12 @@ }, "Type": "EC2" }, + "ServiceRole": { + "Fn::GetAtt": [ + "batchdemandcomputeenvlaunchtemplateResourceServiceInstanceRole76AD99CC", + "Arn" + ] + }, "State": "ENABLED" }, "DependsOn": [ @@ -902,45 +902,20 @@ "vpcVPCGW7984C166" ] }, - "batchspotcomputeenvEcsInstanceRoleE976826B": { - "Type": "AWS::IAM::Role", + "batchspotcomputeenvResourceSecurityGroup07B09BF9": { + "Type": "AWS::EC2::SecurityGroup", "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "ec2.", - { - "Ref": "AWS::URLSuffix" - } - ] - ] - } - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ + "GroupDescription": "batch-stack/batch-spot-compute-env/Resource-Security-Group", + "SecurityGroupEgress": [ { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" - ] - ] + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" } - ] + ], + "VpcId": { + "Ref": "vpcA2121C38" + } }, "DependsOn": [ "vpcIGWE57CBDCA", @@ -978,12 +953,43 @@ "vpcVPCGW7984C166" ] }, - "batchspotcomputeenvInstanceProfileFA613AC2": { - "Type": "AWS::IAM::InstanceProfile", + "batchspotcomputeenvEcsInstanceRoleE976826B": { + "Type": "AWS::IAM::Role", "Properties": { - "Roles": [ + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ { - "Ref": "batchspotcomputeenvEcsInstanceRoleE976826B" + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" + ] + ] } ] }, @@ -1023,20 +1029,14 @@ "vpcVPCGW7984C166" ] }, - "batchspotcomputeenvResourceSecurityGroup07B09BF9": { - "Type": "AWS::EC2::SecurityGroup", + "batchspotcomputeenvInstanceProfileFA613AC2": { + "Type": "AWS::IAM::InstanceProfile", "Properties": { - "GroupDescription": "batch-stack/batch-spot-compute-env/Resource-Security-Group", - "SecurityGroupEgress": [ + "Roles": [ { - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" + "Ref": "batchspotcomputeenvEcsInstanceRoleE976826B" } - ], - "VpcId": { - "Ref": "vpcA2121C38" - } + ] }, "DependsOn": [ "vpcIGWE57CBDCA", @@ -1143,12 +1143,6 @@ "batchspotcomputeenv2CE4DFD9": { "Type": "AWS::Batch::ComputeEnvironment", "Properties": { - "ServiceRole": { - "Fn::GetAtt": [ - "batchspotcomputeenvResourceServiceInstanceRole8B0DF5A7", - "Arn" - ] - }, "Type": "MANAGED", "ComputeResources": { "AllocationStrategy": "SPOT_CAPACITY_OPTIMIZED", @@ -1201,6 +1195,12 @@ ], "Type": "SPOT" }, + "ServiceRole": { + "Fn::GetAtt": [ + "batchspotcomputeenvResourceServiceInstanceRole8B0DF5A7", + "Arn" + ] + }, "State": "ENABLED" }, "DependsOn": [ @@ -1266,13 +1266,410 @@ "State": "ENABLED" } }, - "batchjobrepo4C508C51": { - "Type": "AWS::ECR::Repository", - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, - "batchjobdeffromecrE0E30DAD": { - "Type": "AWS::Batch::JobDefinition", + "batchfargatecomputeenvResourceSecurityGroupE2963776": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "batch-stack/batch-fargate-compute-env/Resource-Security-Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "vpcA2121C38" + } + }, + "DependsOn": [ + "vpcIGWE57CBDCA", + "vpcPrivateSubnet1DefaultRoute1AA8E2E5", + "vpcPrivateSubnet1RouteTableB41A48CC", + "vpcPrivateSubnet1RouteTableAssociation67945127", + "vpcPrivateSubnet1Subnet934893E8", + "vpcPrivateSubnet2DefaultRouteB0E07F99", + "vpcPrivateSubnet2RouteTable7280F23E", + "vpcPrivateSubnet2RouteTableAssociation007E94D3", + "vpcPrivateSubnet2Subnet7031C2BA", + "vpcPrivateSubnet3DefaultRoute30C45F47", + "vpcPrivateSubnet3RouteTable24DA79A0", + "vpcPrivateSubnet3RouteTableAssociationC58B3C2C", + "vpcPrivateSubnet3Subnet985AC459", + "vpcPublicSubnet1DefaultRoute10708846", + "vpcPublicSubnet1EIPDA49DCBE", + "vpcPublicSubnet1NATGateway9C16659E", + "vpcPublicSubnet1RouteTable48A2DF9B", + "vpcPublicSubnet1RouteTableAssociation5D3F4579", + "vpcPublicSubnet1Subnet2E65531E", + "vpcPublicSubnet2DefaultRouteA1EC0F60", + "vpcPublicSubnet2EIP9B3743B1", + "vpcPublicSubnet2NATGateway9B8AE11A", + "vpcPublicSubnet2RouteTableEB40D4CB", + "vpcPublicSubnet2RouteTableAssociation21F81B59", + "vpcPublicSubnet2Subnet009B674F", + "vpcPublicSubnet3DefaultRoute3F356A11", + "vpcPublicSubnet3EIP2C3B9D91", + "vpcPublicSubnet3NATGateway82F6CA9E", + "vpcPublicSubnet3RouteTableA3C00665", + "vpcPublicSubnet3RouteTableAssociationD102D1C4", + "vpcPublicSubnet3Subnet11B92D7C", + "vpcA2121C38", + "vpcVPCGW7984C166" + ] + }, + "batchfargatecomputeenvResourceServiceInstanceRole94D7AA5F": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "batch.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSBatchServiceRole" + ] + ] + } + ] + }, + "DependsOn": [ + "vpcIGWE57CBDCA", + "vpcPrivateSubnet1DefaultRoute1AA8E2E5", + "vpcPrivateSubnet1RouteTableB41A48CC", + "vpcPrivateSubnet1RouteTableAssociation67945127", + "vpcPrivateSubnet1Subnet934893E8", + "vpcPrivateSubnet2DefaultRouteB0E07F99", + "vpcPrivateSubnet2RouteTable7280F23E", + "vpcPrivateSubnet2RouteTableAssociation007E94D3", + "vpcPrivateSubnet2Subnet7031C2BA", + "vpcPrivateSubnet3DefaultRoute30C45F47", + "vpcPrivateSubnet3RouteTable24DA79A0", + "vpcPrivateSubnet3RouteTableAssociationC58B3C2C", + "vpcPrivateSubnet3Subnet985AC459", + "vpcPublicSubnet1DefaultRoute10708846", + "vpcPublicSubnet1EIPDA49DCBE", + "vpcPublicSubnet1NATGateway9C16659E", + "vpcPublicSubnet1RouteTable48A2DF9B", + "vpcPublicSubnet1RouteTableAssociation5D3F4579", + "vpcPublicSubnet1Subnet2E65531E", + "vpcPublicSubnet2DefaultRouteA1EC0F60", + "vpcPublicSubnet2EIP9B3743B1", + "vpcPublicSubnet2NATGateway9B8AE11A", + "vpcPublicSubnet2RouteTableEB40D4CB", + "vpcPublicSubnet2RouteTableAssociation21F81B59", + "vpcPublicSubnet2Subnet009B674F", + "vpcPublicSubnet3DefaultRoute3F356A11", + "vpcPublicSubnet3EIP2C3B9D91", + "vpcPublicSubnet3NATGateway82F6CA9E", + "vpcPublicSubnet3RouteTableA3C00665", + "vpcPublicSubnet3RouteTableAssociationD102D1C4", + "vpcPublicSubnet3Subnet11B92D7C", + "vpcA2121C38", + "vpcVPCGW7984C166" + ] + }, + "batchfargatecomputeenvE9C3FCA4": { + "Type": "AWS::Batch::ComputeEnvironment", + "Properties": { + "Type": "MANAGED", + "ComputeResources": { + "MaxvCpus": 256, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "batchfargatecomputeenvResourceSecurityGroupE2963776", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "vpcPrivateSubnet1Subnet934893E8" + }, + { + "Ref": "vpcPrivateSubnet2Subnet7031C2BA" + }, + { + "Ref": "vpcPrivateSubnet3Subnet985AC459" + } + ], + "Type": "FARGATE" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "batchfargatecomputeenvResourceServiceInstanceRole94D7AA5F", + "Arn" + ] + }, + "State": "ENABLED" + }, + "DependsOn": [ + "vpcIGWE57CBDCA", + "vpcPrivateSubnet1DefaultRoute1AA8E2E5", + "vpcPrivateSubnet1RouteTableB41A48CC", + "vpcPrivateSubnet1RouteTableAssociation67945127", + "vpcPrivateSubnet1Subnet934893E8", + "vpcPrivateSubnet2DefaultRouteB0E07F99", + "vpcPrivateSubnet2RouteTable7280F23E", + "vpcPrivateSubnet2RouteTableAssociation007E94D3", + "vpcPrivateSubnet2Subnet7031C2BA", + "vpcPrivateSubnet3DefaultRoute30C45F47", + "vpcPrivateSubnet3RouteTable24DA79A0", + "vpcPrivateSubnet3RouteTableAssociationC58B3C2C", + "vpcPrivateSubnet3Subnet985AC459", + "vpcPublicSubnet1DefaultRoute10708846", + "vpcPublicSubnet1EIPDA49DCBE", + "vpcPublicSubnet1NATGateway9C16659E", + "vpcPublicSubnet1RouteTable48A2DF9B", + "vpcPublicSubnet1RouteTableAssociation5D3F4579", + "vpcPublicSubnet1Subnet2E65531E", + "vpcPublicSubnet2DefaultRouteA1EC0F60", + "vpcPublicSubnet2EIP9B3743B1", + "vpcPublicSubnet2NATGateway9B8AE11A", + "vpcPublicSubnet2RouteTableEB40D4CB", + "vpcPublicSubnet2RouteTableAssociation21F81B59", + "vpcPublicSubnet2Subnet009B674F", + "vpcPublicSubnet3DefaultRoute3F356A11", + "vpcPublicSubnet3EIP2C3B9D91", + "vpcPublicSubnet3NATGateway82F6CA9E", + "vpcPublicSubnet3RouteTableA3C00665", + "vpcPublicSubnet3RouteTableAssociationD102D1C4", + "vpcPublicSubnet3Subnet11B92D7C", + "vpcA2121C38", + "vpcVPCGW7984C166" + ] + }, + "batchfargatespotcomputeenvResourceSecurityGroup923D2390": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "batch-stack/batch-fargate-spot-compute-env/Resource-Security-Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "vpcA2121C38" + } + }, + "DependsOn": [ + "vpcIGWE57CBDCA", + "vpcPrivateSubnet1DefaultRoute1AA8E2E5", + "vpcPrivateSubnet1RouteTableB41A48CC", + "vpcPrivateSubnet1RouteTableAssociation67945127", + "vpcPrivateSubnet1Subnet934893E8", + "vpcPrivateSubnet2DefaultRouteB0E07F99", + "vpcPrivateSubnet2RouteTable7280F23E", + "vpcPrivateSubnet2RouteTableAssociation007E94D3", + "vpcPrivateSubnet2Subnet7031C2BA", + "vpcPrivateSubnet3DefaultRoute30C45F47", + "vpcPrivateSubnet3RouteTable24DA79A0", + "vpcPrivateSubnet3RouteTableAssociationC58B3C2C", + "vpcPrivateSubnet3Subnet985AC459", + "vpcPublicSubnet1DefaultRoute10708846", + "vpcPublicSubnet1EIPDA49DCBE", + "vpcPublicSubnet1NATGateway9C16659E", + "vpcPublicSubnet1RouteTable48A2DF9B", + "vpcPublicSubnet1RouteTableAssociation5D3F4579", + "vpcPublicSubnet1Subnet2E65531E", + "vpcPublicSubnet2DefaultRouteA1EC0F60", + "vpcPublicSubnet2EIP9B3743B1", + "vpcPublicSubnet2NATGateway9B8AE11A", + "vpcPublicSubnet2RouteTableEB40D4CB", + "vpcPublicSubnet2RouteTableAssociation21F81B59", + "vpcPublicSubnet2Subnet009B674F", + "vpcPublicSubnet3DefaultRoute3F356A11", + "vpcPublicSubnet3EIP2C3B9D91", + "vpcPublicSubnet3NATGateway82F6CA9E", + "vpcPublicSubnet3RouteTableA3C00665", + "vpcPublicSubnet3RouteTableAssociationD102D1C4", + "vpcPublicSubnet3Subnet11B92D7C", + "vpcA2121C38", + "vpcVPCGW7984C166" + ] + }, + "batchfargatespotcomputeenvResourceServiceInstanceRole6462BFB0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "batch.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSBatchServiceRole" + ] + ] + } + ] + }, + "DependsOn": [ + "vpcIGWE57CBDCA", + "vpcPrivateSubnet1DefaultRoute1AA8E2E5", + "vpcPrivateSubnet1RouteTableB41A48CC", + "vpcPrivateSubnet1RouteTableAssociation67945127", + "vpcPrivateSubnet1Subnet934893E8", + "vpcPrivateSubnet2DefaultRouteB0E07F99", + "vpcPrivateSubnet2RouteTable7280F23E", + "vpcPrivateSubnet2RouteTableAssociation007E94D3", + "vpcPrivateSubnet2Subnet7031C2BA", + "vpcPrivateSubnet3DefaultRoute30C45F47", + "vpcPrivateSubnet3RouteTable24DA79A0", + "vpcPrivateSubnet3RouteTableAssociationC58B3C2C", + "vpcPrivateSubnet3Subnet985AC459", + "vpcPublicSubnet1DefaultRoute10708846", + "vpcPublicSubnet1EIPDA49DCBE", + "vpcPublicSubnet1NATGateway9C16659E", + "vpcPublicSubnet1RouteTable48A2DF9B", + "vpcPublicSubnet1RouteTableAssociation5D3F4579", + "vpcPublicSubnet1Subnet2E65531E", + "vpcPublicSubnet2DefaultRouteA1EC0F60", + "vpcPublicSubnet2EIP9B3743B1", + "vpcPublicSubnet2NATGateway9B8AE11A", + "vpcPublicSubnet2RouteTableEB40D4CB", + "vpcPublicSubnet2RouteTableAssociation21F81B59", + "vpcPublicSubnet2Subnet009B674F", + "vpcPublicSubnet3DefaultRoute3F356A11", + "vpcPublicSubnet3EIP2C3B9D91", + "vpcPublicSubnet3NATGateway82F6CA9E", + "vpcPublicSubnet3RouteTableA3C00665", + "vpcPublicSubnet3RouteTableAssociationD102D1C4", + "vpcPublicSubnet3Subnet11B92D7C", + "vpcA2121C38", + "vpcVPCGW7984C166" + ] + }, + "batchfargatespotcomputeenv374749B0": { + "Type": "AWS::Batch::ComputeEnvironment", + "Properties": { + "Type": "MANAGED", + "ComputeResources": { + "MaxvCpus": 256, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "batchfargatespotcomputeenvResourceSecurityGroup923D2390", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "vpcPrivateSubnet1Subnet934893E8" + }, + { + "Ref": "vpcPrivateSubnet2Subnet7031C2BA" + }, + { + "Ref": "vpcPrivateSubnet3Subnet985AC459" + } + ], + "Type": "FARGATE_SPOT" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "batchfargatespotcomputeenvResourceServiceInstanceRole6462BFB0", + "Arn" + ] + }, + "State": "ENABLED" + }, + "DependsOn": [ + "vpcIGWE57CBDCA", + "vpcPrivateSubnet1DefaultRoute1AA8E2E5", + "vpcPrivateSubnet1RouteTableB41A48CC", + "vpcPrivateSubnet1RouteTableAssociation67945127", + "vpcPrivateSubnet1Subnet934893E8", + "vpcPrivateSubnet2DefaultRouteB0E07F99", + "vpcPrivateSubnet2RouteTable7280F23E", + "vpcPrivateSubnet2RouteTableAssociation007E94D3", + "vpcPrivateSubnet2Subnet7031C2BA", + "vpcPrivateSubnet3DefaultRoute30C45F47", + "vpcPrivateSubnet3RouteTable24DA79A0", + "vpcPrivateSubnet3RouteTableAssociationC58B3C2C", + "vpcPrivateSubnet3Subnet985AC459", + "vpcPublicSubnet1DefaultRoute10708846", + "vpcPublicSubnet1EIPDA49DCBE", + "vpcPublicSubnet1NATGateway9C16659E", + "vpcPublicSubnet1RouteTable48A2DF9B", + "vpcPublicSubnet1RouteTableAssociation5D3F4579", + "vpcPublicSubnet1Subnet2E65531E", + "vpcPublicSubnet2DefaultRouteA1EC0F60", + "vpcPublicSubnet2EIP9B3743B1", + "vpcPublicSubnet2NATGateway9B8AE11A", + "vpcPublicSubnet2RouteTableEB40D4CB", + "vpcPublicSubnet2RouteTableAssociation21F81B59", + "vpcPublicSubnet2Subnet009B674F", + "vpcPublicSubnet3DefaultRoute3F356A11", + "vpcPublicSubnet3EIP2C3B9D91", + "vpcPublicSubnet3NATGateway82F6CA9E", + "vpcPublicSubnet3RouteTableA3C00665", + "vpcPublicSubnet3RouteTableAssociationD102D1C4", + "vpcPublicSubnet3Subnet11B92D7C", + "vpcA2121C38", + "vpcVPCGW7984C166" + ] + }, + "batchjobfargatequeue5A12983E": { + "Type": "AWS::Batch::JobQueue", + "Properties": { + "ComputeEnvironmentOrder": [ + { + "ComputeEnvironment": { + "Ref": "batchfargatecomputeenvE9C3FCA4" + }, + "Order": 1 + }, + { + "ComputeEnvironment": { + "Ref": "batchfargatespotcomputeenv374749B0" + }, + "Order": 2 + } + ], + "Priority": 1, + "State": "ENABLED" + } + }, + "batchjobrepo4C508C51": { + "Type": "AWS::ECR::Repository", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "batchjobdeffromecrE0E30DAD": { + "Type": "AWS::Batch::JobDefinition", "Properties": { "Type": "container", "ContainerProperties": { @@ -1352,6 +1749,57 @@ }, "Timeout": {} } + }, + "executionroleD9A39BE6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "batch.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "batchjobdeffargate7FE30059": { + "Type": "AWS::Batch::JobDefinition", + "Properties": { + "Type": "container", + "ContainerProperties": { + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "executionroleD9A39BE6", + "Arn" + ] + }, + "Image": "docker/whalesay", + "Privileged": false, + "ReadonlyRootFilesystem": false, + "ResourceRequirements": [ + { + "Type": "VCPU", + "Value": "0.25" + }, + { + "Type": "MEMORY", + "Value": "512" + } + ] + }, + "PlatformCapabilities": [ + "FARGATE" + ], + "RetryStrategy": { + "Attempts": 1 + }, + "Timeout": {} + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-batch/test/integ.batch.ts b/packages/@aws-cdk/aws-batch/test/integ.batch.ts index 4e19da37ca897..4430cda4a7bf3 100644 --- a/packages/@aws-cdk/aws-batch/test/integ.batch.ts +++ b/packages/@aws-cdk/aws-batch/test/integ.batch.ts @@ -1,6 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecr from '@aws-cdk/aws-ecr'; import * as ecs from '@aws-cdk/aws-ecs'; +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as batch from '../lib/'; @@ -64,6 +65,33 @@ new batch.JobQueue(stack, 'batch-job-queue', { ], }); +// Split out into two job queues because each queue +// supports a max of 3 compute environments +new batch.JobQueue(stack, 'batch-job-fargate-queue', { + computeEnvironments: [ + { + computeEnvironment: new batch.ComputeEnvironment(stack, 'batch-fargate-compute-env', { + managed: true, + computeResources: { + type: batch.ComputeResourceType.FARGATE, + vpc, + }, + }), + order: 1, + }, + { + computeEnvironment: new batch.ComputeEnvironment(stack, 'batch-fargate-spot-compute-env', { + managed: true, + computeResources: { + type: batch.ComputeResourceType.FARGATE_SPOT, + vpc, + }, + }), + order: 2, + }, + ], +}); + const repo = new ecr.Repository(stack, 'batch-job-repo'); new batch.JobDefinition(stack, 'batch-job-def-from-ecr', { @@ -77,3 +105,15 @@ new batch.JobDefinition(stack, 'batch-job-def-from-', { image: ecs.ContainerImage.fromRegistry('docker/whalesay'), }, }); + +const executionRole = new iam.Role(stack, 'execution-role', { + assumedBy: new iam.ServicePrincipal('batch.amazonaws.com'), +}); + +new batch.JobDefinition(stack, 'batch-job-def-fargate', { + platformCapabilities: [batch.PlatformCapabilities.FARGATE], + container: { + image: ecs.ContainerImage.fromRegistry('docker/whalesay'), + executionRole, + }, +}); diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts index 781ee06fd3c1c..7a71d46d3401e 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts +++ b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts @@ -8,6 +8,7 @@ import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as ssm from '@aws-cdk/aws-ssm'; import * as cdk from '@aws-cdk/core'; import * as batch from '../lib'; +import { PlatformCapabilities } from '../lib'; describe('Batch Job Definition', () => { let stack: cdk.Stack; @@ -117,6 +118,74 @@ describe('Batch Job Definition', () => { PlatformCapabilities: ['EC2'], }); }); + + test('renders the correct cloudformation properties for a Fargate job definition', () => { + // WHEN + const executionRole = new iam.Role(stack, 'execution-role', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + + new batch.JobDefinition(stack, 'job-def', { + ...jobDefProps, + container: { ...jobDefProps.container, executionRole }, + platformCapabilities: [PlatformCapabilities.FARGATE], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Batch::JobDefinition', { + JobDefinitionName: jobDefProps.jobDefinitionName, + ContainerProperties: jobDefProps.container ? { + Command: jobDefProps.container.command, + Environment: [ + { + Name: 'foo', + Value: 'bar', + }, + ], + ExecutionRoleArn: { + 'Fn::GetAtt': [ + 'executionroleD9A39BE6', + 'Arn', + ], + }, + InstanceType: jobDefProps.container.instanceType ? jobDefProps.container.instanceType.toString() : '', + LinuxParameters: {}, + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-region': 'us-east-1', + }, + }, + MountPoints: [], + Privileged: jobDefProps.container.privileged, + ReadonlyRootFilesystem: jobDefProps.container.readOnly, + ResourceRequirements: [ + { Type: 'VCPU', Value: String(jobDefProps.container.vcpus) }, + { Type: 'MEMORY', Value: String(jobDefProps.container.memoryLimitMiB) }, + ], + Ulimits: [], + User: jobDefProps.container.user, + Volumes: [], + } : undefined, + NodeProperties: jobDefProps.nodeProps ? { + MainNode: jobDefProps.nodeProps.mainNode, + NodeRangeProperties: [], + NumNodes: jobDefProps.nodeProps.count, + } : undefined, + Parameters: { + foo: 'bar', + }, + RetryStrategy: { + Attempts: jobDefProps.retryAttempts, + }, + Timeout: { + AttemptDurationSeconds: jobDefProps.timeout ? jobDefProps.timeout.toSeconds() : -1, + }, + Type: 'container', + PlatformCapabilities: ['FARGATE'], + }); + }); + test('can use an ecr image', () => { // WHEN const repo = new ecr.Repository(stack, 'image-repo'); From 4691cedb410c2681309163b0b0be7afbb1e1121f Mon Sep 17 00:00:00 2001 From: Dylan Seidt Date: Mon, 2 Aug 2021 18:27:07 -0500 Subject: [PATCH 3/5] compute values before initing computeEnv and containerProperties, simplify network config --- .../aws-batch/lib/compute-environment.ts | 15 +++--- .../@aws-cdk/aws-batch/lib/job-definition.ts | 48 ++++++------------- 2 files changed, 20 insertions(+), 43 deletions(-) diff --git a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts index d5fc59cb5ac12..1b545b00f26d4 100644 --- a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts +++ b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts @@ -361,6 +361,9 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment // Only allow compute resources to be set when using MANAGED type if (props.computeResources && this.isManaged(props)) { + const isFargate = ComputeResourceType.FARGATE === props.computeResources.type + || ComputeResourceType.FARGATE_SPOT === props.computeResources.type; + computeResources = { bidPercentage: props.computeResources.bidPercentage, desiredvCpus: props.computeResources.desiredvCpus, @@ -374,13 +377,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment subnets: props.computeResources.vpc.selectSubnets(props.computeResources.vpcSubnets).subnetIds, tags: props.computeResources.computeResourcesTags, type: props.computeResources.type || ComputeResourceType.ON_DEMAND, - }; - - const isFargate = ComputeResourceType.FARGATE === props.computeResources.type - || ComputeResourceType.FARGATE_SPOT === props.computeResources.type; - - if (!isFargate) { - Object.assign(computeResources, { + ...(!isFargate ? { allocationStrategy: props.computeResources.allocationStrategy || ( props.computeResources.type === ComputeResourceType.SPOT @@ -399,8 +396,8 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment }).attrArn, instanceTypes: this.buildInstanceTypes(props.computeResources.instanceTypes), minvCpus: props.computeResources.minvCpus || 0, - }); - } + } : {}), + }; } const computeEnvironment = new CfnComputeEnvironment(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index 2c030527c6912..74f5a1573195d 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -103,21 +103,6 @@ export interface FargatePlatformConfiguration { readonly platformVersion: ecs.FargatePlatformVersion } -/** - * Whether or not to assign a public IP to the job - */ -export enum AssignPublicIp { - /** - * Assign public IP address to job - */ - ENABLED = 'ENABLED', - - /** - * Do not assign public IP address to job - */ - DISABLED = 'DISABLED', -} - /** * Fargate network configuration */ @@ -125,9 +110,9 @@ export interface NetworkConfiguration { /** * Whether or not to assign a public IP to the job * - * @default - DISABLED + * @default - false */ - readonly assignPublicIp?: AssignPublicIp + readonly assignPublicIp?: boolean } /** @@ -537,7 +522,7 @@ export class JobDefinition extends Resource implements IJobDefinition { return undefined; } - const containerProperties = { + return { command: container.command, environment: this.deserializeEnvVariables(container.environment), image: this.imageConfig.imageName, @@ -556,7 +541,9 @@ export class JobDefinition extends Resource implements IJobDefinition { } : undefined, mountPoints: container.mountPoints, privileged: container.privileged || false, - networkConfiguration: container.networkConfiguration, + networkConfiguration: container.networkConfiguration ? { + assignPublicIp: container.networkConfiguration.assignPublicIp ? 'ENABLED' : 'DISABLED', + } : undefined, readonlyRootFilesystem: container.readOnly || false, ulimits: container.ulimits, user: container.user, @@ -564,26 +551,19 @@ export class JobDefinition extends Resource implements IJobDefinition { fargatePlatformConfiguration: container.fargatePlatformConfiguration ? { platformVersion: container.fargatePlatformConfiguration.platformVersion, } : undefined, - }; - - if (!isFargate) { - Object.assign(containerProperties, { + ...(isFargate ? { + resourceRequirements: [ + { type: 'VCPU', value: String(container.vcpus || 0.25) }, + { type: 'MEMORY', value: String(container.memoryLimitMiB || 512) }, + ], + } : { memory: container.memoryLimitMiB || 4, vcpus: container.vcpus || 1, resourceRequirements: container.gpuCount ? [{ type: 'GPU', value: String(container.gpuCount) }] : undefined, - }); - } else { - Object.assign(containerProperties, { - resourceRequirements: [ - { type: 'VCPU', value: String(container.vcpus || 0.25) }, - { type: 'MEMORY', value: String(container.memoryLimitMiB || 512) }, - ], - }); - } - - return containerProperties; + }), + }; } private buildNodeRangeProps(multiNodeProps: IMultiNodeProps, isFargate: boolean): CfnJobDefinition.NodeRangePropertyProperty[] { From 5d7ec9a14d922aa5458e27a6a453ff0c8c2aad03 Mon Sep 17 00:00:00 2001 From: DDynamic Date: Thu, 5 Aug 2021 21:37:52 -0500 Subject: [PATCH 4/5] Remove redundant nesting and validate gpuCount and assignPublicIp --- .../aws-batch/lib/compute-environment.ts | 12 ++-- .../@aws-cdk/aws-batch/lib/job-definition.ts | 58 +++++++------------ .../aws-batch/test/integ.batch.expected.json | 6 ++ .../aws-batch/test/job-definition.test.ts | 7 +-- 4 files changed, 34 insertions(+), 49 deletions(-) diff --git a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts index 1b545b00f26d4..408e16c7bb98a 100644 --- a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts +++ b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts @@ -354,16 +354,16 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment physicalName: props.computeEnvironmentName, }); - this.validateProps(props); + const isFargate = ComputeResourceType.FARGATE === props.computeResources?.type + || ComputeResourceType.FARGATE_SPOT === props.computeResources?.type;; + + this.validateProps(props, isFargate); const spotFleetRole = this.getSpotFleetRole(props); let computeResources: CfnComputeEnvironment.ComputeResourcesProperty | undefined; // Only allow compute resources to be set when using MANAGED type if (props.computeResources && this.isManaged(props)) { - const isFargate = ComputeResourceType.FARGATE === props.computeResources.type - || ComputeResourceType.FARGATE_SPOT === props.computeResources.type; - computeResources = { bidPercentage: props.computeResources.bidPercentage, desiredvCpus: props.computeResources.desiredvCpus, @@ -433,7 +433,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment /** * Validates the properties provided for a new batch compute environment. */ - private validateProps(props: ComputeEnvironmentProps) { + private validateProps(props: ComputeEnvironmentProps, isFargate: boolean) { if (props === undefined) { return; } @@ -447,7 +447,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment } if (props.computeResources) { - if (props.computeResources.type === ComputeResourceType.FARGATE || props.computeResources.type === ComputeResourceType.FARGATE_SPOT) { + if (isFargate) { // VALIDATE FOR FARGATE // Bid percentage cannot be set for Fargate evnvironments diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index 74f5a1573195d..da77fa0075702 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -92,29 +92,6 @@ export interface LogConfiguration { readonly secretOptions?: ExposedSecret[]; } - -/** - * Fargate platform configuration - */ -export interface FargatePlatformConfiguration { - /** - * Fargate platform version - */ - readonly platformVersion: ecs.FargatePlatformVersion -} - -/** - * Fargate network configuration - */ -export interface NetworkConfiguration { - /** - * Whether or not to assign a public IP to the job - * - * @default - false - */ - readonly assignPublicIp?: boolean -} - /** * Properties of a job definition container. */ @@ -237,26 +214,26 @@ export interface JobDefinitionContainer { readonly volumes?: ecs.Volume[]; /** - * The platform configuration for jobs that are running on Fargate resources. + * Fargate platform version * * @default - LATEST platform version will be used */ - readonly fargatePlatformConfiguration?: FargatePlatformConfiguration; + readonly platformVersion?: ecs.FargatePlatformVersion /** * The IAM role that AWS Batch can assume. + * Required when using Fargate. * * @default - None */ readonly executionRole?: iam.IRole; /** - * The network configuration for jobs that are running on Fargate resources. - * Jobs that are running on EC2 resources must not specify this parameter. + * Whether or not to assign a public IP to the job * - * @default - None + * @default - false */ - readonly networkConfiguration?: NetworkConfiguration + readonly assignPublicIp?: boolean } /** @@ -316,7 +293,7 @@ export interface JobDefinitionProps { /** * The platform capabilities required by the job definition. * - * @default - undefined + * @default - EC2 */ readonly platformCapabilities?: PlatformCapabilities[]; } @@ -473,7 +450,7 @@ export class JobDefinition extends Resource implements IJobDefinition { timeout: { attemptDurationSeconds: props.timeout ? props.timeout.toSeconds() : undefined, }, - platformCapabilities: props.platformCapabilities || undefined, + platformCapabilities: props.platformCapabilities ?? [PlatformCapabilities.EC2], }); this.jobDefinitionArn = this.getResourceArnAttribute(jobDef.ref, { @@ -511,9 +488,14 @@ export class JobDefinition extends Resource implements IJobDefinition { throw new Error('Fargate job must have executionRole set'); } - if (props.platformCapabilities !== undefined && props.platformCapabilities.includes(PlatformCapabilities.EC2) - && props.container.networkConfiguration !== undefined) { - throw new Error('EC2 job must not have networkConfiguration set'); + if (props.platformCapabilities !== undefined && props.platformCapabilities.includes(PlatformCapabilities.FARGATE) + && props.container.gpuCount !== undefined) { + throw new Error('Fargate job must not have gpuCount set'); + } + + if ((props.platformCapabilities === undefined || props.platformCapabilities.includes(PlatformCapabilities.EC2)) + && props.container.assignPublicIp !== undefined) { + throw new Error('EC2 job must not have assignPublicIp set'); } } @@ -541,15 +523,15 @@ export class JobDefinition extends Resource implements IJobDefinition { } : undefined, mountPoints: container.mountPoints, privileged: container.privileged || false, - networkConfiguration: container.networkConfiguration ? { - assignPublicIp: container.networkConfiguration.assignPublicIp ? 'ENABLED' : 'DISABLED', + networkConfiguration: container.assignPublicIp ? { + assignPublicIp: container.assignPublicIp ? 'ENABLED' : 'DISABLED', } : undefined, readonlyRootFilesystem: container.readOnly || false, ulimits: container.ulimits, user: container.user, volumes: container.volumes, - fargatePlatformConfiguration: container.fargatePlatformConfiguration ? { - platformVersion: container.fargatePlatformConfiguration.platformVersion, + fargatePlatformConfiguration: container.platformVersion ? { + platformVersion: container.platformVersion, } : undefined, ...(isFargate ? { resourceRequirements: [ diff --git a/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json b/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json index b4a11bc0026e1..ac99fab5630f6 100644 --- a/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json +++ b/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json @@ -1727,6 +1727,9 @@ "ReadonlyRootFilesystem": false, "Vcpus": 1 }, + "PlatformCapabilities": [ + "EC2" + ], "RetryStrategy": { "Attempts": 1 }, @@ -1744,6 +1747,9 @@ "ReadonlyRootFilesystem": false, "Vcpus": 1 }, + "PlatformCapabilities": [ + "EC2" + ], "RetryStrategy": { "Attempts": 1 }, diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts index 7a71d46d3401e..d472d7b18d672 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts +++ b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts @@ -127,7 +127,7 @@ describe('Batch Job Definition', () => { new batch.JobDefinition(stack, 'job-def', { ...jobDefProps, - container: { ...jobDefProps.container, executionRole }, + container: { ...jobDefProps.container, executionRole, gpuCount: undefined }, platformCapabilities: [PlatformCapabilities.FARGATE], }); @@ -361,9 +361,6 @@ describe('Batch Job Definition', () => { describe('using fargate job definition', () => { test('can configure platform configuration properly', () => { // GIVEN - const platformConfiguration: batch.FargatePlatformConfiguration = { - platformVersion: ecs.FargatePlatformVersion.LATEST, - }; const executionRole = new iam.Role(stack, 'execution-role', { assumedBy: new iam.ServicePrincipal('batch.amazonaws.com'), }); @@ -372,7 +369,7 @@ describe('Batch Job Definition', () => { platformCapabilities: [batch.PlatformCapabilities.FARGATE], container: { image: ecs.EcrImage.fromRegistry('docker/whalesay'), - fargatePlatformConfiguration: platformConfiguration, + platformVersion: ecs.FargatePlatformVersion.LATEST, executionRole: executionRole, }, }); From ba3fe8671252eaa27b63ccfc7e4ed878cefb9bed Mon Sep 17 00:00:00 2001 From: DDynamic Date: Thu, 5 Aug 2021 22:05:28 -0500 Subject: [PATCH 5/5] Swap to ResourceRequirements --- .../@aws-cdk/aws-batch/lib/job-definition.ts | 18 +++++------------ .../aws-batch/test/integ.batch.expected.json | 12 +++++++---- .../aws-batch/test/job-definition.test.ts | 20 ++++++++++++------- .../integ.job-definition-events.expected.json | 9 +++++++-- .../batch/integ.run-batch-job.expected.json | 9 +++++++-- .../test/batch/integ.submit-job.expected.json | 9 +++++++-- 6 files changed, 47 insertions(+), 30 deletions(-) diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index da77fa0075702..dab8515acb6d1 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -150,7 +150,7 @@ export interface JobDefinitionContainer { /** * The hard limit (in MiB) of memory to present to the container. If your container attempts to exceed - * the memory specified here, the container is killed. You must specify at least 4 MiB of memory for a job. + * the memory specified here, the container is killed. You must specify at least 4 MiB of memory for EC2 and 512 MiB for Fargate. * * @default - 4 for EC2, 512 for Fargate */ @@ -533,18 +533,10 @@ export class JobDefinition extends Resource implements IJobDefinition { fargatePlatformConfiguration: container.platformVersion ? { platformVersion: container.platformVersion, } : undefined, - ...(isFargate ? { - resourceRequirements: [ - { type: 'VCPU', value: String(container.vcpus || 0.25) }, - { type: 'MEMORY', value: String(container.memoryLimitMiB || 512) }, - ], - } : { - memory: container.memoryLimitMiB || 4, - vcpus: container.vcpus || 1, - resourceRequirements: container.gpuCount - ? [{ type: 'GPU', value: String(container.gpuCount) }] - : undefined, - }), + resourceRequirements: [ + { type: 'VCPU', value: String(container.vcpus || (isFargate ? 0.25 : 1)) }, + { type: 'MEMORY', value: String(container.memoryLimitMiB || (isFargate ? 512 : 4)) }, + ].concat(container.gpuCount ? [{ type: 'GPU', value: String(container.gpuCount) }] : []), }; } diff --git a/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json b/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json index ac99fab5630f6..7624200d45321 100644 --- a/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json +++ b/packages/@aws-cdk/aws-batch/test/integ.batch.expected.json @@ -1722,10 +1722,12 @@ ] ] }, - "Memory": 4, "Privileged": false, "ReadonlyRootFilesystem": false, - "Vcpus": 1 + "ResourceRequirements": [ + { "Type": "VCPU", "Value": "1" }, + { "Type": "MEMORY", "Value": "4" } + ] }, "PlatformCapabilities": [ "EC2" @@ -1742,10 +1744,12 @@ "Type": "container", "ContainerProperties": { "Image": "docker/whalesay", - "Memory": 4, "Privileged": false, "ReadonlyRootFilesystem": false, - "Vcpus": 1 + "ResourceRequirements": [ + { "Type": "VCPU", "Value": "1" }, + { "Type": "MEMORY", "Value": "4" } + ] }, "PlatformCapabilities": [ "EC2" diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts index d472d7b18d672..13926b6b80788 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts +++ b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts @@ -90,14 +90,16 @@ describe('Batch Job Definition', () => { 'awslogs-region': 'us-east-1', }, }, - Memory: jobDefProps.container.memoryLimitMiB, MountPoints: [], Privileged: jobDefProps.container.privileged, ReadonlyRootFilesystem: jobDefProps.container.readOnly, - ResourceRequirements: [{ Type: 'GPU', Value: String(jobDefProps.container.gpuCount) }], + ResourceRequirements: [ + { Type: 'VCPU', Value: String(jobDefProps.container.vcpus) }, + { Type: 'MEMORY', Value: String(jobDefProps.container.memoryLimitMiB) }, + { Type: 'GPU', Value: String(jobDefProps.container.gpuCount) }, + ], Ulimits: [], User: jobDefProps.container.user, - Vcpus: jobDefProps.container.vcpus, Volumes: [], } : undefined, NodeProperties: jobDefProps.nodeProps ? { @@ -248,10 +250,12 @@ describe('Batch Job Definition', () => { ], ], }, - Memory: 4, Privileged: false, ReadonlyRootFilesystem: false, - Vcpus: 1, + ResourceRequirements: [ + { Type: 'VCPU', Value: '1' }, + { Type: 'MEMORY', Value: '4' }, + ], }, }); }); @@ -268,10 +272,12 @@ describe('Batch Job Definition', () => { Template.fromStack(stack).hasResourceProperties('AWS::Batch::JobDefinition', { ContainerProperties: { Image: 'docker/whalesay', - Memory: 4, Privileged: false, ReadonlyRootFilesystem: false, - Vcpus: 1, + ResourceRequirements: [ + { Type: 'VCPU', Value: '1' }, + { Type: 'MEMORY', Value: '4' }, + ], }, }); }); diff --git a/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.expected.json b/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.expected.json index 77a8854041e1f..f4dfe0408f63e 100644 --- a/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.expected.json @@ -65,11 +65,16 @@ "Type": "container", "ContainerProperties": { "Image": "test-repo", - "Memory": 4, "Privileged": false, "ReadonlyRootFilesystem": false, - "Vcpus": 1 + "ResourceRequirements": [ + { "Type": "VCPU", "Value": "1" }, + { "Type": "MEMORY", "Value": "4" } + ] }, + "PlatformCapabilities": [ + "EC2" + ], "RetryStrategy": { "Attempts": 1 }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.expected.json index 97eea60b24dcc..f37bcd6e520f6 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.expected.json @@ -873,11 +873,16 @@ ] ] }, - "Memory": 4, "Privileged": false, "ReadonlyRootFilesystem": false, - "Vcpus": 1 + "ResourceRequirements": [ + { "Type": "VCPU", "Value": "1" }, + { "Type": "MEMORY", "Value": "4" } + ] }, + "PlatformCapabilities": [ + "EC2" + ], "RetryStrategy": { "Attempts": 1 }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.expected.json index 2026e45ae3c4e..a3851fd3fb5b7 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.expected.json @@ -873,11 +873,16 @@ ] ] }, - "Memory": 4, "Privileged": false, "ReadonlyRootFilesystem": false, - "Vcpus": 1 + "ResourceRequirements": [ + { "Type": "VCPU", "Value": "1" }, + { "Type": "MEMORY", "Value": "4" } + ] }, + "PlatformCapabilities": [ + "EC2" + ], "RetryStrategy": { "Attempts": 1 },