Skip to content

Commit

Permalink
feat: Storage options for EC2 and ECS (#624)
Browse files Browse the repository at this point in the history
Set volume type, IOPS and throughput with `storageOptions` for EC2 and ECS providers.

```
new Ec2RunnerProvider(stack, 'EC2 Linux', {
  labels: ['ec2', 'linux', 'x64'],
  storageSize: cdk.Size.gibibytes(500),
  storageOptions: {
    volumeType: EbsDeviceVolumeType.IO1,
    iops: 30000,
  },
});
```

Resolves #621
  • Loading branch information
kichik authored Sep 29, 2024
1 parent 4f9814c commit dd4eed1
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 113 deletions.
98 changes: 98 additions & 0 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions src/providers/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
CustomResource,
Duration,
} from 'aws-cdk-lib';
import { EbsDeviceVolumeType } from 'aws-cdk-lib/aws-ec2/lib/volume';
import { Construct, IConstruct } from 'constructs';
import { AmiRootDeviceFunction } from './ami-root-device-function';
import { singletonLambda, singletonLogGroup, SingletonLogType } from '../utils';
Expand Down Expand Up @@ -477,6 +478,41 @@ export interface IRunnerProvider extends ec2.IConnectable, iam.IGrantable, ICons
status(statusFunctionRole: iam.IGrantable): IRunnerProviderStatus;
}

/**
* Storage options for the runner instance.
*/
export interface StorageOptions {
/**
* The EBS volume type
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html
*
* @default `EbsDeviceVolumeType.GP2`
*/
readonly volumeType?: EbsDeviceVolumeType;

/**
* The number of I/O operations per second (IOPS) to provision for the volume.
*
* Must only be set for `volumeType`: `EbsDeviceVolumeType.IO1`
*
* The maximum ratio of IOPS to volume size (in GiB) is 50:1, so for 5,000 provisioned IOPS,
* you need at least 100 GiB storage on the volume.
*
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html
*
* @default - none, required for `EbsDeviceVolumeType.IO1`
*/
readonly iops?: number;

/**
* The throughput that the volume supports, in MiB/s
* Takes a minimum of 125 and maximum of 1000.
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-volume.html#cfn-ec2-volume-throughput
* @default - 125 MiB/s. Only valid on gp3 volumes.
*/
readonly throughput?: number;
}

/**
* Base class for all providers with common methods used by all providers.
*
Expand Down
11 changes: 11 additions & 0 deletions src/providers/ec2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
RunnerProviderProps,
RunnerRuntimeParameters,
RunnerVersion,
StorageOptions,
} from './common';
import { IRunnerImageBuilder, RunnerImageBuilder, RunnerImageBuilderProps, RunnerImageBuilderType, RunnerImageComponent } from '../image-builders';
import { MINIMAL_EC2_SSM_SESSION_MANAGER_POLICY_STATEMENT } from '../utils';
Expand Down Expand Up @@ -217,6 +218,11 @@ export interface Ec2RunnerProviderProps extends RunnerProviderProps {
*/
readonly storageSize?: cdk.Size;

/**
* Options for runner instance storage volume.
*/
readonly storageOptions?: StorageOptions;

/**
* Security Group to assign to launched runner instances.
*
Expand Down Expand Up @@ -340,6 +346,7 @@ export class Ec2RunnerProvider extends BaseProvider implements IRunnerProvider {
private readonly role: iam.Role;
private readonly instanceType: ec2.InstanceType;
private readonly storageSize: cdk.Size;
private readonly storageOptions?: StorageOptions;
private readonly spot: boolean;
private readonly spotMaxPrice: string | undefined;
private readonly vpc: ec2.IVpc;
Expand All @@ -355,6 +362,7 @@ export class Ec2RunnerProvider extends BaseProvider implements IRunnerProvider {
this.subnets = props?.subnet ? [props.subnet] : this.vpc.selectSubnets(props?.subnetSelection).subnets;
this.instanceType = props?.instanceType ?? ec2.InstanceType.of(ec2.InstanceClass.M6I, ec2.InstanceSize.LARGE);
this.storageSize = props?.storageSize ?? cdk.Size.gibibytes(30); // 30 is the minimum for Windows
this.storageOptions = props?.storageOptions;
this.spot = props?.spot ?? false;
this.spotMaxPrice = props?.spotMaxPrice;

Expand Down Expand Up @@ -471,6 +479,9 @@ export class Ec2RunnerProvider extends BaseProvider implements IRunnerProvider {
Ebs: {
DeleteOnTermination: true,
VolumeSize: this.storageSize.toGibibytes(),
VolumeType: this.storageOptions?.volumeType,
Iops: this.storageOptions?.iops,
Throughput: this.storageOptions?.throughput,
},
}],
InstanceMarketOptions: this.spot ? {
Expand Down
15 changes: 14 additions & 1 deletion src/providers/ecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
RunnerProviderProps,
RunnerRuntimeParameters,
RunnerVersion,
StorageOptions,
} from './common';
import { ecsRunCommand } from './fargate';
import { IRunnerImageBuilder, RunnerImageBuilder, RunnerImageBuilderProps, RunnerImageComponent } from '../image-builders';
Expand Down Expand Up @@ -151,6 +152,11 @@ export interface EcsRunnerProviderProps extends RunnerProviderProps {
*/
readonly storageSize?: cdk.Size;

/**
* Options for runner instance storage volume.
*/
readonly storageOptions?: StorageOptions;

/**
* Support building and running Docker images by enabling Docker-in-Docker (dind) and the required CodeBuild privileged mode. Disabling this can
* speed up provisioning of CodeBuild runners. If you don't intend on running or building Docker images, disable this for faster start-up times.
Expand Down Expand Up @@ -340,6 +346,10 @@ export class EcsRunnerProvider extends BaseProvider implements IRunnerProvider {
},
);

if (props?.storageOptions && !props?.storageSize) {
throw new Error('storageSize is required when storageOptions are specified');
}

const imageBuilder = props?.imageBuilder ?? EcsRunnerProvider.imageBuilder(this, 'Image Builder');
const image = this.image = imageBuilder.bindDockerImage();

Expand All @@ -360,8 +370,11 @@ export class EcsRunnerProvider extends BaseProvider implements IRunnerProvider {
deviceName: amiRootDevice(this, this.defaultClusterInstanceAmi().getImage(this).imageId).ref,
volume: {
ebsDevice: {
volumeSize: props.storageSize.toGibibytes(),
deleteOnTermination: true,
volumeSize: props.storageSize.toGibibytes(),
volumeType: props.storageOptions?.volumeType,
iops: props.storageOptions?.iops,
throughput: props.storageOptions?.throughput,
},
},
},
Expand Down
30 changes: 15 additions & 15 deletions test/default.integ.snapshot/github-runners-test.assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@
}
}
},
"88cc171bf3103a3b98ba0006fc7e5c6fdfd338c03591b344bb4cf60f1a9da18c": {
"source": {
"path": "asset.88cc171bf3103a3b98ba0006fc7e5c6fdfd338c03591b344bb4cf60f1a9da18c.lambda",
"packaging": "zip"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "88cc171bf3103a3b98ba0006fc7e5c6fdfd338c03591b344bb4cf60f1a9da18c.zip",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
},
"2fc3b84da69dcc5adb6dc4721b50c1166474fa7e5fd5f242e833d12ac28e09d9": {
"source": {
"path": "asset.2fc3b84da69dcc5adb6dc4721b50c1166474fa7e5fd5f242e833d12ac28e09d9.sh",
Expand Down Expand Up @@ -105,19 +118,6 @@
}
}
},
"88cc171bf3103a3b98ba0006fc7e5c6fdfd338c03591b344bb4cf60f1a9da18c": {
"source": {
"path": "asset.88cc171bf3103a3b98ba0006fc7e5c6fdfd338c03591b344bb4cf60f1a9da18c.lambda",
"packaging": "zip"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "88cc171bf3103a3b98ba0006fc7e5c6fdfd338c03591b344bb4cf60f1a9da18c.zip",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
},
"83c9fbef7e367049876156e1b2fd8fb89d8044003b26b693e2e7aca16fd4fb7f": {
"source": {
"path": "asset.83c9fbef7e367049876156e1b2fd8fb89d8044003b26b693e2e7aca16fd4fb7f.lambda",
Expand Down Expand Up @@ -209,15 +209,15 @@
}
}
},
"bdf474a164f615217e0702c26ada96931a06c49f175c267bfd2b35a006b799a8": {
"2958bcb16280e16db04d04050757197c299db756977117494e2cbcfcc78500c5": {
"source": {
"path": "github-runners-test.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "bdf474a164f615217e0702c26ada96931a06c49f175c267bfd2b35a006b799a8.json",
"objectKey": "2958bcb16280e16db04d04050757197c299db756977117494e2cbcfcc78500c5.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Loading

0 comments on commit dd4eed1

Please sign in to comment.