Skip to content

Commit

Permalink
feat(deadline): change SEP construct to use launch templates instead …
Browse files Browse the repository at this point in the history
…of launch specifications (#513)

* feat(deadline): change SEP construct to use launch templates instead of launch specifications

BREAKING CHANGE: SpotEventPluginFleet now uses EC2 Launch Templates instead of Launch Specifications.
See the [RFDK 0.39.x upgrade documentation](https://github.com/aws/aws-rfdk/blob/v0.39.0/packages/aws-rfdk/docs/upgrade/upgrading-0.39.md)
for more details and guidance on how to upgrade.

* update example app flag description, correct subnet ID code, and general cleanup

* update render queue SEP policy

* add upgrade documentation and general cleanup

* update architecture diagram for SpotEventPluginFleet
  • Loading branch information
jericht committed Nov 22, 2021
1 parent 7cdc8df commit 7c61c18
Show file tree
Hide file tree
Showing 19 changed files with 910 additions and 1,033 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def main():
sep_props = sep_stack.SEPStackProps(
docker_recipes_stage_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, 'stage'),
worker_machine_image=MachineImage.generic_linux(config.deadline_client_linux_ami_map),
create_resource_tracker_role=config.create_resource_tracker_role,
)
service = sep_stack.SEPStack(app, 'SEPStack', props=sep_props, env=env)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,13 @@ def __init__(self):
# should match the one used for staging the render queue and usage based licensing recipes.
self.deadline_client_linux_ami_map: Mapping[str, str] = {'us-west-2': 'ami-04ae356533dc07fb5'}

# Whether the DeadlineResourceTrackerAccessRole IAM role required by Deadline's Resource Tracker should be created in this CDK app.
#
# If you have previously used this same AWS account with either Deadline's AWS Portal feature or Spot Event Plugin and had used the
# Deadline Resource Tracker, then you likely have this IAM role in your account already unless you have removed it.
#
# Note: Deadline's Resource Tracker only supports being used by a single Deadline Repository per AWS account.
self.create_resource_tracker_role: bool = True


config: AppConfig = AppConfig()
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class SEPStackProps(StackProps):
docker_recipes_stage_path: str
# The IMachineImage to use for Workers (needs Deadline Client installed).
worker_machine_image: IMachineImage
# Whether the DeadlineResourceTrackerAccessRole IAM role required by Deadline's Resource Tracker should be created in this CDK app.
create_resource_tracker_role: bool


class SEPStack(Stack):
Expand Down Expand Up @@ -159,15 +161,15 @@ def __init__(self, scope: Construct, stack_id: str, *, props: SEPStackProps, **k
),
)

# Creates the Resource Tracker Access role. This role is required to exist in your account so the resource tracker will work properly
# Note: If you already have a Resource Tracker IAM role in your account you can remove this code.
Role(
self,
'ResourceTrackerRole',
assumed_by=ServicePrincipal('lambda.amazonaws.com'),
managed_policies= [ManagedPolicy.from_aws_managed_policy_name('AWSThinkboxDeadlineResourceTrackerAccessPolicy')],
role_name= 'DeadlineResourceTrackerAccessRole',
)
if props.create_resource_tracker_role:
# Creates the Resource Tracker Access role. This role is required to exist in your account so the resource tracker will work properly
Role(
self,
'ResourceTrackerRole',
assumed_by=ServicePrincipal('lambda.amazonaws.com'),
managed_policies= [ManagedPolicy.from_aws_managed_policy_name('AWSThinkboxDeadlineResourceTrackerAccessPolicy')],
role_name= 'DeadlineResourceTrackerAccessRole',
)

fleet = SpotEventPluginFleet(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ new SEPStack(app, 'SEPStack', {
env,
dockerRecipesStagePath: path.join(__dirname, '..', pkg.config.stage_path), // Stage directory in config is relative, make it absolute
workerMachineImage: MachineImage.genericLinux(config.deadlineClientLinuxAmiMap),
createResourceTrackerRole: config.createResourceTrackerRole,
});
10 changes: 10 additions & 0 deletions examples/deadline/All-In-AWS-Infrastructure-SEP/ts/bin/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ class AppConfig {
* is filled in. It can be used as-is, added to, or replaced.
*/
public readonly deadlineClientLinuxAmiMap: Record<string, string> = {['us-west-2']: 'ami-04ae356533dc07fb5'};

/**
* Whether the DeadlineResourceTrackerAccessRole IAM role required by Deadline's Resource Tracker should be created in this CDK app.
*
* If you have previously used this same AWS account with either Deadline's AWS Portal feature or Spot Event Plugin and had used the
* Deadline Resource Tracker, then you likely have this IAM role in your account already unless you have removed it.
*
* Note: Deadline's Resource Tracker only supports being used by a single Deadline Repository per AWS account.
*/
public readonly createResourceTrackerRole: boolean = true;
}

export const config = new AppConfig();
26 changes: 16 additions & 10 deletions examples/deadline/All-In-AWS-Infrastructure-SEP/ts/lib/sep-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ export interface SEPStackProps extends StackProps {
* The {@link IMachineImage} to use for Workers (needs Deadline Client installed).
*/
readonly workerMachineImage: IMachineImage;

/**
* Whether the DeadlineResourceTracker stack and supporting resources already exist or not.
*/
readonly createResourceTrackerRole: boolean;
}

export class SEPStack extends Stack {
Expand Down Expand Up @@ -126,15 +131,16 @@ export class SEPStack extends Stack {
trafficEncryption,
});

// Creates the Resource Tracker Access role. This role is required to exist in your account so the resource tracker will work properly
// Note: If you already have a Resource Tracker IAM role in your account you can remove this code.
new Role(this, 'ResourceTrackerRole', {
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName('AWSThinkboxDeadlineResourceTrackerAccessPolicy'),
],
roleName: 'DeadlineResourceTrackerAccessRole',
});
if (props.createResourceTrackerRole) {
// Creates the Resource Tracker Access role. This role is required to exist in your account so the resource tracker will work properly
new Role(this, 'ResourceTrackerRole', {
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName('AWSThinkboxDeadlineResourceTrackerAccessPolicy'),
],
roleName: 'DeadlineResourceTrackerAccessRole',
});
}

const fleet = new SpotEventPluginFleet(this, 'SpotEventPluginFleet', {
vpc,
Expand All @@ -154,7 +160,7 @@ export class SEPStack extends Stack {

new ConfigureSpotEventPlugin(this, 'ConfigureSpotEventPlugin', {
vpc,
renderQueue: renderQueue,
renderQueue,
spotFleets: [
fleet,
],
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/aws-rfdk/docs/upgrade/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ upgrading to (or beyond) a version listed below, you should consult the the link
* [`0.27.x`](./upgrading-0.27.md)
* [`0.37.x`](./upgrading-0.37.md)
* [`0.38.x`](./upgrading-0.38.md)
* [`0.39.x`](./upgrading-0.39.md)
13 changes: 13 additions & 0 deletions packages/aws-rfdk/docs/upgrade/upgrading-0.39.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Upgrading to RFDK v0.39.x or Newer

Starting in RFDK v0.39.0, the `SpotEventPluginFleet` construct now creates an [EC2 Launch Template](https://docs.aws.amazon.com/autoscaling/ec2/userguide/LaunchTemplates.html)
instead of using Launch Specifications. This change will reconfigure the Spot Event Plugin settings in Deadline to use a new Spot Fleet Request configuration. If you have active
Spot Fleet Requests created by the Spot Event Plugin, upgrading to RFDK v0.39.x and redeploying your render farm will orphan those Spot Fleet Requests. Therefore, we highly
recommend following these instructions to upgrade to RFDK v0.39.x:

1. Disable the Spot Event Plugin in Deadline. Refer to the [Spot Event Plugin "State" option in Deadline](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/event-spot-configuration-options.html)
for more information.
2. Cancel any Spot Fleet Requests created by the Spot Event Plugin, which you can do by following these [instructions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/work-with-spot-fleets.html#cancel-spot-fleet).
3. Upgrade to RFDK v0.39.x and redeploy your render farm.
4. Once the deployment is complete, re-enable the Spot Event Plugin in Deadline. Refer to the [Spot Event Plugin "State" option in Deadline](https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/event-spot-configuration-options.html)
for more information.
110 changes: 16 additions & 94 deletions packages/aws-rfdk/lib/deadline/lib/configure-spot-event-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@

import * as path from 'path';

import {
BlockDevice,
BlockDeviceVolume,
} from '@aws-cdk/aws-autoscaling';
import {
IVpc,
SubnetSelection,
Expand All @@ -30,7 +26,6 @@ import {
Construct,
CustomResource,
Duration,
Fn,
IResolvable,
Lazy,
Stack,
Expand All @@ -39,13 +34,9 @@ import {
import {
PluginSettings,
SEPConfiguratorResourceProps,
LaunchSpecification,
SpotFleetRequestConfiguration,
SpotFleetRequestProps,
SpotFleetSecurityGroupId,
SpotFleetTagSpecification,
BlockDeviceMappingProperty,
BlockDeviceProperty,
} from '../../lambdas/nodejs/configure-spot-event-plugin';
import {
IRenderQueue,
Expand Down Expand Up @@ -157,6 +148,12 @@ export interface SpotEventPluginSettings {

/**
* Determines whether the Deadline Resource Tracker should be used.
*
* In addition to this property, the Spot Instances deployed by the Spot Event Plugin must also be configured to be tracked by the Resource Tracker using the
* [`trackInstancesWithResourceTracker`](https://docs.aws.amazon.com/rfdk/api/latest/docs/aws-rfdk.deadline.SpotEventPluginFleet.html#trackinstanceswithresourcetracker)
* property of the `SpotEventPluginFleet` construct, which is `true` by default. You can set that property to `false` for fleets that you would like to opt out of the
* Resource Tracker.
*
* See https://docs.thinkboxsoftware.com/products/deadline/10.1/1_User%20Manual/manual/resource-tracker-overview.html
*
* @default true
Expand Down Expand Up @@ -344,6 +341,9 @@ export interface ConfigureSpotEventPluginProps {
* - A policy to pass a fleet and instance role
* - A policy to create tags for spot fleet requests
*
* The Spot Fleet Requests that this construct configures Deadline to create will always use the latest version of the
* corresponding EC2 Launch Template that was created for them.
*
* ![architecture diagram](/diagrams/deadline/ConfigureSpotEventPlugin.svg)
*
* Resources Deployed
Expand All @@ -352,6 +352,7 @@ export interface ConfigureSpotEventPluginProps {
* - A CloudFormation Custom Resource that triggers execution of the Lambda on stack deployment, update, and deletion.
* - An Amazon CloudWatch log group that records history of the AWS Lambda's execution.
* - An IAM Policy attached to Render Queue's Role.
* - EC2 Launch Templates for each Spot Event Plugin fleet.
*
* Security Considerations
* ------------------------
Expand Down Expand Up @@ -418,7 +419,10 @@ export class ConfigureSpotEventPlugin extends Construct {
actions: [
'ec2:CreateTags',
],
resources: ['arn:aws:ec2:*:*:spot-fleet-request/*'],
resources: [
'arn:aws:ec2:*:*:spot-fleet-request/*',
'arn:aws:ec2:*:*:volume/*',
],
}),
],
roles: [
Expand Down Expand Up @@ -545,54 +549,15 @@ export class ConfigureSpotEventPlugin extends Construct {

/**
* Construct Spot Fleet Configurations from the provided fleet.
* Each congiguration is a mapping between one Deadline Group and one Spot Fleet Request Configuration.
* Each configuration is a mapping between one Deadline Group and one Spot Fleet Request Configuration.
*/
private generateSpotFleetRequestConfig(fleet: SpotEventPluginFleet): SpotFleetRequestConfiguration[] {
const securityGroupsToken = Lazy.any({ produce: () => {
return fleet.securityGroups.map(sg => {
const securityGroupId: SpotFleetSecurityGroupId = {
GroupId: sg.securityGroupId,
};
return securityGroupId;
});
}});

const userDataToken = Lazy.string({ produce: () => Fn.base64(fleet.userData.render()) });

const blockDeviceMappings = (fleet.blockDevices !== undefined ?
this.synthesizeBlockDeviceMappings(fleet.blockDevices) : undefined);

const { subnetIds } = fleet.subnets;
const subnetId = subnetIds.join(',');

const instanceTagsToken = this.tagSpecifications(fleet, SpotFleetResourceType.INSTANCE);
const spotFleetRequestTagsToken = this.tagSpecifications(fleet, SpotFleetResourceType.SPOT_FLEET_REQUEST);

const launchSpecifications: LaunchSpecification[] = [];

fleet.instanceTypes.map(instanceType => {
const launchSpecification: LaunchSpecification = {
BlockDeviceMappings: blockDeviceMappings,
IamInstanceProfile: {
Arn: fleet.instanceProfile.attrArn,
},
ImageId: fleet.imageId,
KeyName: fleet.keyName,
// Need to convert from IResolvable to bypass TypeScript
SecurityGroups: (securityGroupsToken as unknown) as SpotFleetSecurityGroupId[],
SubnetId: subnetId,
// Need to convert from IResolvable to bypass TypeScript
TagSpecifications: (instanceTagsToken as unknown) as SpotFleetTagSpecification[],
UserData: userDataToken,
InstanceType: instanceType.toString(),
};
launchSpecifications.push(launchSpecification);
});

const spotFleetRequestProps: SpotFleetRequestProps = {
AllocationStrategy: fleet.allocationStrategy,
IamFleetRole: fleet.fleetRole.roleArn,
LaunchSpecifications: launchSpecifications,
LaunchTemplateConfigs: fleet._launchTemplateConfigs,
ReplaceUnhealthyInstances: true,
// In order to work with Deadline, the 'Target Capacity' of the Spot fleet Request is
// the maximum number of Workers that Deadline will start.
Expand All @@ -615,49 +580,6 @@ export class ConfigureSpotEventPlugin extends Construct {
return spotFleetRequestConfigurations;
}

/**
* Synthesize an array of block device mappings from a list of block devices
*
* @param blockDevices list of block devices
*/
private synthesizeBlockDeviceMappings(blockDevices: BlockDevice[]): BlockDeviceMappingProperty[] {
return blockDevices.map(({ deviceName, volume, mappingEnabled }) => {
const { virtualName, ebsDevice: ebs } = volume;

if (volume === BlockDeviceVolume._NO_DEVICE || mappingEnabled === false) {
return {
DeviceName: deviceName,
// To omit the device from the block device mapping, specify an empty string.
// See https://docs.aws.amazon.com/cli/latest/reference/ec2/request-spot-fleet.html
NoDevice: '',
};
}

let Ebs: BlockDeviceProperty | undefined;

if (ebs) {
const { iops, volumeType, volumeSize, snapshotId, deleteOnTermination } = ebs;

Ebs = {
DeleteOnTermination: deleteOnTermination,
Iops: iops,
SnapshotId: snapshotId,
VolumeSize: volumeSize,
VolumeType: volumeType,
// encrypted is not exposed as part of ebsDeviceProps so we need to access it via [].
// eslint-disable-next-line dot-notation
Encrypted: 'encrypted' in ebs ? ebs['encrypted'] : undefined,
};
}

return {
DeviceName: deviceName,
Ebs,
VirtualName: virtualName,
};
});
}

private mergeSpotFleetRequestConfigs(spotFleets?: SpotEventPluginFleet[]): SpotFleetRequestConfiguration | undefined {
if (!spotFleets || spotFleets.length === 0) {
return undefined;
Expand Down
Loading

0 comments on commit 7c61c18

Please sign in to comment.