From 319b01168a5a68c0c1fbf9a27a544dea35b3c8fd Mon Sep 17 00:00:00 2001 From: Amir Szekely Date: Wed, 27 Mar 2024 10:17:34 -0400 Subject: [PATCH] feat: Windows fast launch (#521) Add an option for enabling [fast launch](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/win-ami-config-fast-launch.html) for Windows AMIs. This should speed up booting up of EC2 Windows runners. Note that it does come with [extra cost](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/win-fast-launch-manage-costs.html). ```typescript const ec2WindowsImageBuilder = Ec2RunnerProvider.imageBuilder(stack, 'Windows EC2 Builder', { os: Os.WINDOWS, vpc, awsImageBuilderOptions: { fastLaunchOptions: { enabled: true, }, }, }); const runners = new GitHubRunners(stack, 'runners', { providers: [ new Ec2RunnerProvider(stack, 'Fast Windows', { labels: ['windows'], imageBuilder: ec2WindowsImageBuilder, }), ], } ``` Before: image After: image Resolves #466 Closes #514 --- API.md | 83 ++++++++++++ .../aws-image-builder/builder.ts | 120 +++++++++++++++++- .../github-runners-test.assets.json | 4 +- .../github-runners-test.template.json | 9 +- 4 files changed, 206 insertions(+), 10 deletions(-) diff --git a/API.md b/API.md index 1cff6d5..befcccd 100644 --- a/API.md +++ b/API.md @@ -5118,10 +5118,26 @@ const awsImageBuilderRunnerImageBuilderProps: AwsImageBuilderRunnerImageBuilderP | **Name** | **Type** | **Description** | | --- | --- | --- | +| fastLaunchOptions | FastLaunchOptions | Options for fast launch. | | instanceType | aws-cdk-lib.aws_ec2.InstanceType | The instance type used to build the image. | --- +##### `fastLaunchOptions`Optional + +```typescript +public readonly fastLaunchOptions: FastLaunchOptions; +``` + +- *Type:* FastLaunchOptions +- *Default:* disabled + +Options for fast launch. + +This is only supported for Windows AMIs. + +--- + ##### `instanceType`Optional ```typescript @@ -6674,6 +6690,73 @@ VPC to launch the runners in. --- +### FastLaunchOptions + +Options for fast launch. + +#### Initializer + +```typescript +import { FastLaunchOptions } from '@cloudsnorkel/cdk-github-runners' + +const fastLaunchOptions: FastLaunchOptions = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| enabled | boolean | Enable fast launch for AMIs generated by this builder. | +| maxParallelLaunches | number | The maximum number of parallel instances that are launched for creating resources. | +| targetResourceCount | number | The number of pre-provisioned snapshots to keep on hand for a fast-launch enabled Windows AMI. | + +--- + +##### `enabled`Optional + +```typescript +public readonly enabled: boolean; +``` + +- *Type:* boolean +- *Default:* false + +Enable fast launch for AMIs generated by this builder. + +It creates a snapshot of the root volume and uses it to launch new instances faster. + +This is only supported for Windows AMIs. + +--- + +##### `maxParallelLaunches`Optional + +```typescript +public readonly maxParallelLaunches: number; +``` + +- *Type:* number +- *Default:* 6 + +The maximum number of parallel instances that are launched for creating resources. + +Must be at least 6. + +--- + +##### `targetResourceCount`Optional + +```typescript +public readonly targetResourceCount: number; +``` + +- *Type:* number +- *Default:* 1 + +The number of pre-provisioned snapshots to keep on hand for a fast-launch enabled Windows AMI. + +--- + ### GitHubRunnersProps Properties for GitHubRunners. diff --git a/src/image-builders/aws-image-builder/builder.ts b/src/image-builders/aws-image-builder/builder.ts index 8d9c74c..40050dc 100644 --- a/src/image-builders/aws-image-builder/builder.ts +++ b/src/image-builders/aws-image-builder/builder.ts @@ -35,6 +35,48 @@ export interface AwsImageBuilderRunnerImageBuilderProps { * @default m5.large */ readonly instanceType?: ec2.InstanceType; + + /** + * Options for fast launch. + * + * This is only supported for Windows AMIs. + * + * @default disabled + */ + readonly fastLaunchOptions?: FastLaunchOptions; +} + +/** + * Options for fast launch. + */ +export interface FastLaunchOptions { + /** + * Enable fast launch for AMIs generated by this builder. It creates a snapshot of the root volume and uses it to launch new instances faster. + * + * This is only supported for Windows AMIs. + * + * @note this feature comes with additional resource costs. See the documentation for more details. https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/win-fast-launch-manage-costs.html + * @note enabling fast launch on an existing builder will not enable it for existing AMIs. It will only affect new AMIs. If you want immediate effect, trigger a new image build. Alternatively, you can create a new builder with fast launch enabled and use it for new AMIs. + * + * @default false + */ + readonly enabled?: boolean; + + /** + * The maximum number of parallel instances that are launched for creating resources. + * + * Must be at least 6. + * + * @default 6 + */ + readonly maxParallelLaunches?: number; + + /** + * The number of pre-provisioned snapshots to keep on hand for a fast-launch enabled Windows AMI. + * + * @default 1 + */ + readonly targetResourceCount?: number; } /** @@ -266,6 +308,7 @@ export class AwsImageBuilderRunnerImageBuilder extends RunnerImageBuilderBase { private readonly instanceType: ec2.InstanceType; private infrastructure: imagebuilder.CfnInfrastructureConfiguration | undefined; private readonly role: iam.Role; + private readonly fastLaunchOptions?: FastLaunchOptions; constructor(scope: Construct, id: string, props?: RunnerImageBuilderProps) { super(scope, id, props); @@ -285,6 +328,7 @@ export class AwsImageBuilderRunnerImageBuilder extends RunnerImageBuilderBase { this.baseImage = props?.baseDockerImage ?? defaultBaseDockerImage(this.os); this.baseAmi = props?.baseAmi ?? defaultBaseAmi(this, this.os, this.architecture); this.instanceType = props?.awsImageBuilderOptions?.instanceType ?? ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE); + this.fastLaunchOptions = props?.awsImageBuilderOptions?.fastLaunchOptions; // confirm instance type if (!this.architecture.instanceTypeMatch(this.instanceType)) { @@ -587,6 +631,75 @@ export class AwsImageBuilderRunnerImageBuilder extends RunnerImageBuilderBase { requireImdsv2: true, }); + const launchTemplateConfigs: imagebuilder.CfnDistributionConfiguration.LaunchTemplateConfigurationProperty[] = [{ + launchTemplateId: launchTemplate.launchTemplateId, + setDefaultVersion: true, + }]; + const fastLaunchConfigs: imagebuilder.CfnDistributionConfiguration.FastLaunchConfigurationProperty[] = []; + + if (this.fastLaunchOptions?.enabled ?? false) { + if (!this.os.is(Os.WINDOWS)) { + throw new Error('Fast launch is only supported for Windows'); + } + + // create a separate launch template for fast launch so: + // - settings don't affect the runners + // - enabling fast launch on an existing builder works (without a new launch template, EC2 Image Builder will use the first version of the launch template, which doesn't have instance or VPC config) + // - setting vpc + subnet on the main launch template will cause RunInstances to fail + // - EC2 Image Builder seems to get confused with which launch template version to base any new version on, so a new template is always best + const fastLaunchTemplate = new ec2.CfnLaunchTemplate(this, 'Fast Launch Template', { + launchTemplateData: { + metadataOptions: { + httpTokens: 'required', + }, + instanceType: this.instanceType.toString(), + networkInterfaces: [{ + subnetId: this.vpc?.selectSubnets(this.subnetSelection).subnetIds[0], + deviceIndex: 0, + groups: this.securityGroups.map(sg => sg.securityGroupId), + }], + tagSpecifications: [ + { + resourceType: 'instance', + tags: [{ + key: 'Name', + value: `${this.node.path}/Fast Launch Instance`, + }], + }, + { + resourceType: 'volume', + tags: [{ + key: 'Name', + value: `${this.node.path}/Fast Launch Instance`, + }], + }, + ], + }, + tagSpecifications: [{ + resourceType: 'launch-template', + tags: [{ + key: 'Name', + value: `${this.node.path}/Fast Launch Template`, + }], + }], + }); + + launchTemplateConfigs.push({ + launchTemplateId: fastLaunchTemplate.attrLaunchTemplateId, + setDefaultVersion: true, + }); + fastLaunchConfigs.push({ + enabled: true, + launchTemplate: { + launchTemplateId: fastLaunchTemplate.attrLaunchTemplateId, + }, + maxParallelLaunches: this.fastLaunchOptions?.maxParallelLaunches ?? 6, + snapshotConfiguration: { + targetResourceCount: this.fastLaunchOptions?.targetResourceCount ?? 1, + }, + }); + } + const stackName = cdk.Stack.of(this).stackName; const builderName = this.node.path; @@ -608,11 +721,8 @@ export class AwsImageBuilderRunnerImageBuilder extends RunnerImageBuilderBase { 'GitHubRunners:Builder': builderName, }, }, - launchTemplateConfigurations: [ - { - launchTemplateId: launchTemplate.launchTemplateId, - }, - ], + launchTemplateConfigurations: launchTemplateConfigs, + fastLaunchConfigurations: fastLaunchConfigs.length > 0 ? fastLaunchConfigs : undefined, }, ], }); diff --git a/test/default.integ.snapshot/github-runners-test.assets.json b/test/default.integ.snapshot/github-runners-test.assets.json index 3762ba1..3ab5601 100644 --- a/test/default.integ.snapshot/github-runners-test.assets.json +++ b/test/default.integ.snapshot/github-runners-test.assets.json @@ -235,7 +235,7 @@ } } }, - "2aae55820f5b3b1bc6c23108a950d448c9f06303c2e15a71f6aed05080984ca5": { + "c778a1355556a7931c74bf18556e58cfff5dd5111b4157913931d869afcf4877": { "source": { "path": "github-runners-test.template.json", "packaging": "file" @@ -243,7 +243,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "2aae55820f5b3b1bc6c23108a950d448c9f06303c2e15a71f6aed05080984ca5.json", + "objectKey": "c778a1355556a7931c74bf18556e58cfff5dd5111b4157913931d869afcf4877.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/test/default.integ.snapshot/github-runners-test.template.json b/test/default.integ.snapshot/github-runners-test.template.json index 7888caf..427e399 100644 --- a/test/default.integ.snapshot/github-runners-test.template.json +++ b/test/default.integ.snapshot/github-runners-test.template.json @@ -3405,7 +3405,8 @@ { "LaunchTemplateId": { "Ref": "AMILinuxBuilderLaunchtemplateA29452C4" - } + }, + "SetDefaultVersion": true } ], "Region": { @@ -6416,7 +6417,8 @@ { "LaunchTemplateId": { "Ref": "AMILinuxarm64BuilderLaunchtemplate8F5EFF44" - } + }, + "SetDefaultVersion": true } ], "Region": { @@ -7529,7 +7531,8 @@ { "LaunchTemplateId": { "Ref": "WindowsEC2BuilderLaunchtemplate0A66E9C2" - } + }, + "SetDefaultVersion": true } ], "Region": {