From dd4eed15605951d7e469b0428ad50d22de89fb07 Mon Sep 17 00:00:00 2001 From: Amir Szekely Date: Sun, 29 Sep 2024 17:38:02 -0400 Subject: [PATCH] feat: Storage options for EC2 and ECS (#624) 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 --- API.md | 98 ++++++++ src/providers/common.ts | 36 +++ src/providers/ec2.ts | 11 + src/providers/ecs.ts | 15 +- .../github-runners-test.assets.json | 30 +-- .../github-runners-test.template.json | 222 ++++++++++-------- test/default.integ.ts | 14 ++ 7 files changed, 313 insertions(+), 113 deletions(-) diff --git a/API.md b/API.md index 1e9bc515..a7792d85 100644 --- a/API.md +++ b/API.md @@ -5891,6 +5891,7 @@ const ec2RunnerProviderProps: Ec2RunnerProviderProps = { ... } | securityGroups | aws-cdk-lib.aws_ec2.ISecurityGroup[] | Security groups to assign to launched runner instances. | | spot | boolean | Use spot instances to save money. | | spotMaxPrice | string | Set a maximum price for spot instances. | +| storageOptions | StorageOptions | Options for runner instance storage volume. | | storageSize | aws-cdk-lib.Size | Size of volume available for launched runner instances. | | subnet | aws-cdk-lib.aws_ec2.ISubnet | Subnet where the runner instances will be launched. | | subnetSelection | aws-cdk-lib.aws_ec2.SubnetSelection | Where to place the network interfaces within the VPC. | @@ -6040,6 +6041,18 @@ Set a maximum price for spot instances. --- +##### `storageOptions`Optional + +```typescript +public readonly storageOptions: StorageOptions; +``` + +- *Type:* StorageOptions + +Options for runner instance storage volume. + +--- + ##### `storageSize`Optional ```typescript @@ -6131,6 +6144,7 @@ const ecsRunnerProviderProps: EcsRunnerProviderProps = { ... } | securityGroups | aws-cdk-lib.aws_ec2.ISecurityGroup[] | Security groups to assign to the task. | | spot | boolean | Use spot capacity. | | spotMaxPrice | string | Maximum price for spot instances. | +| storageOptions | StorageOptions | Options for runner instance storage volume. | | storageSize | aws-cdk-lib.Size | Size of volume available for launched cluster instances. | | subnetSelection | aws-cdk-lib.aws_ec2.SubnetSelection | Subnets to run the runners in. | | vpc | aws-cdk-lib.aws_ec2.IVpc | VPC to launch the runners in. | @@ -6381,6 +6395,18 @@ Maximum price for spot instances. --- +##### `storageOptions`Optional + +```typescript +public readonly storageOptions: StorageOptions; +``` + +- *Type:* StorageOptions + +Options for runner instance storage volume. + +--- + ##### `storageSize`Optional ```typescript @@ -8295,6 +8321,78 @@ Path to runner token used to register token. --- +### StorageOptions + +Storage options for the runner instance. + +#### Initializer + +```typescript +import { StorageOptions } from '@cloudsnorkel/cdk-github-runners' + +const storageOptions: StorageOptions = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| iops | number | The number of I/O operations per second (IOPS) to provision for the volume. | +| throughput | number | The throughput that the volume supports, in MiB/s Takes a minimum of 125 and maximum of 1000. | +| volumeType | aws-cdk-lib.aws_ec2.EbsDeviceVolumeType | The EBS volume type. | + +--- + +##### `iops`Optional + +```typescript +public readonly iops: number; +``` + +- *Type:* number +- *Default:* none, required for `EbsDeviceVolumeType.IO1` + +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. + +> [https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html) + +--- + +##### `throughput`Optional + +```typescript +public readonly throughput: number; +``` + +- *Type:* number +- *Default:* 125 MiB/s. Only valid on gp3 volumes. + +The throughput that the volume supports, in MiB/s Takes a minimum of 125 and maximum of 1000. + +> [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-volume.html#cfn-ec2-volume-throughput](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-volume.html#cfn-ec2-volume-throughput) + +--- + +##### `volumeType`Optional + +```typescript +public readonly volumeType: EbsDeviceVolumeType; +``` + +- *Type:* aws-cdk-lib.aws_ec2.EbsDeviceVolumeType +- *Default:* `EbsDeviceVolumeType.GP2` + +The EBS volume type. + +> [https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html) + +--- + ## Classes ### Architecture diff --git a/src/providers/common.ts b/src/providers/common.ts index 0c1a50e6..ff53121a 100644 --- a/src/providers/common.ts +++ b/src/providers/common.ts @@ -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'; @@ -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. * diff --git a/src/providers/ec2.ts b/src/providers/ec2.ts index de53191c..e5f0f3e4 100644 --- a/src/providers/ec2.ts +++ b/src/providers/ec2.ts @@ -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'; @@ -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. * @@ -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; @@ -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; @@ -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 ? { diff --git a/src/providers/ecs.ts b/src/providers/ecs.ts index 87db90df..af5a0f24 100644 --- a/src/providers/ecs.ts +++ b/src/providers/ecs.ts @@ -25,6 +25,7 @@ import { RunnerProviderProps, RunnerRuntimeParameters, RunnerVersion, + StorageOptions, } from './common'; import { ecsRunCommand } from './fargate'; import { IRunnerImageBuilder, RunnerImageBuilder, RunnerImageBuilderProps, RunnerImageComponent } from '../image-builders'; @@ -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. @@ -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(); @@ -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, }, }, }, diff --git a/test/default.integ.snapshot/github-runners-test.assets.json b/test/default.integ.snapshot/github-runners-test.assets.json index 18b0a3ff..0477e140 100644 --- a/test/default.integ.snapshot/github-runners-test.assets.json +++ b/test/default.integ.snapshot/github-runners-test.assets.json @@ -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", @@ -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", @@ -209,7 +209,7 @@ } } }, - "bdf474a164f615217e0702c26ada96931a06c49f175c267bfd2b35a006b799a8": { + "2958bcb16280e16db04d04050757197c299db756977117494e2cbcfcc78500c5": { "source": { "path": "github-runners-test.template.json", "packaging": "file" @@ -217,7 +217,7 @@ "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}" } } diff --git a/test/default.integ.snapshot/github-runners-test.template.json b/test/default.integ.snapshot/github-runners-test.template.json index d428812e..04862d9f 100644 --- a/test/default.integ.snapshot/github-runners-test.template.json +++ b/test/default.integ.snapshot/github-runners-test.template.json @@ -9632,6 +9632,20 @@ "DefaultCapacityProviderStrategy": [] } }, + "ECSAMIRootDevice35683156": { + "Type": "Custom::AmiRootDevice", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1C64D247C", + "Arn" + ] + }, + "Ami": "resolve:ssm:/aws/service/ecs/optimized-ami/amazon-linux-2023/recommended/image_id" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "ECSLaunchTemplateRole3D54417B": { "Type": "AWS::IAM::Role", "Properties": { @@ -9758,6 +9772,20 @@ "Type": "AWS::EC2::LaunchTemplate", "Properties": { "LaunchTemplateData": { + "BlockDeviceMappings": [ + { + "DeviceName": { + "Ref": "ECSAMIRootDevice35683156" + }, + "Ebs": { + "DeleteOnTermination": true, + "Iops": 1500, + "Throughput": 150, + "VolumeSize": 40, + "VolumeType": "gp3" + } + } + ], "IamInstanceProfile": { "Arn": { "Fn::GetAtt": [ @@ -10229,6 +10257,99 @@ ] } }, + "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRole69906A36": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRoleDefaultPolicyDC1762B6": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ssm:GetParameter", + "ec2:DescribeImages", + "ec2:DescribeLaunchTemplateVersions", + "imagebuilder:GetImage" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRoleDefaultPolicyDC1762B6", + "Roles": [ + { + "Ref": "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRole69906A36" + } + ] + } + }, + "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1C64D247C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "88cc171bf3103a3b98ba0006fc7e5c6fdfd338c03591b344bb4cf60f1a9da18c.zip" + }, + "Description": "Custom resource handler that discovers the boot drive device name for a given AMI", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + }, + "Handler": "index.handler", + "LoggingConfig": { + "LogFormat": "JSON", + "LogGroup": { + "Ref": "RunnerImageBuildHelpersLog13186633" + } + }, + "Role": { + "Fn::GetAtt": [ + "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRole69906A36", + "Arn" + ] + }, + "Runtime": "nodejs18.x", + "Timeout": 60 + }, + "DependsOn": [ + "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRoleDefaultPolicyDC1762B6", + "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRole69906A36" + ] + }, "ECSARM64securitygroup281D94B2": { "Type": "AWS::EC2::SecurityGroup", "Properties": { @@ -13878,99 +13999,6 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRole69906A36": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRoleDefaultPolicyDC1762B6": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameter", - "ec2:DescribeImages", - "ec2:DescribeLaunchTemplateVersions", - "imagebuilder:GetImage" - ], - "Effect": "Allow", - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRoleDefaultPolicyDC1762B6", - "Roles": [ - { - "Ref": "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRole69906A36" - } - ] - } - }, - "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1C64D247C": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" - }, - "S3Key": "88cc171bf3103a3b98ba0006fc7e5c6fdfd338c03591b344bb4cf60f1a9da18c.zip" - }, - "Description": "Custom resource handler that discovers the boot drive device name for a given AMI", - "Environment": { - "Variables": { - "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" - } - }, - "Handler": "index.handler", - "LoggingConfig": { - "LogFormat": "JSON", - "LogGroup": { - "Ref": "RunnerImageBuildHelpersLog13186633" - } - }, - "Role": { - "Fn::GetAtt": [ - "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRole69906A36", - "Arn" - ] - }, - "Runtime": "nodejs18.x", - "Timeout": 60 - }, - "DependsOn": [ - "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRoleDefaultPolicyDC1762B6", - "AMIRootDeviceReaderdcc036c8876b451ea2c1552f9e06e9e1ServiceRole69906A36" - ] - }, "deleteamidcc036c8876b451ea2c1552f9e06e9e1ServiceRole1CC58A6F": { "Type": "AWS::IAM::Role", "Properties": { @@ -16843,7 +16871,7 @@ { "Ref": "EC2LinuxAMIRootDevice26D5E56E" }, - "\",\"Ebs\":{\"DeleteOnTermination\":true,\"VolumeSize\":30}}],\"TagSpecifications\":[{\"ResourceType\":\"instance\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Linux\"}]},{\"ResourceType\":\"volume\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Linux\"}]}]}},\"ec2, linux, x64 subnet2\":{\"End\":true,\"Type\":\"Task\",\"Comment\":\"", + "\",\"Ebs\":{\"DeleteOnTermination\":true,\"VolumeSize\":40,\"VolumeType\":\"gp3\",\"Iops\":3000,\"Throughput\":200}}],\"TagSpecifications\":[{\"ResourceType\":\"instance\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Linux\"}]},{\"ResourceType\":\"volume\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Linux\"}]}]}},\"ec2, linux, x64 subnet2\":{\"End\":true,\"Type\":\"Task\",\"Comment\":\"", { "Ref": "VpcPublicSubnet2Subnet691E08A3" }, @@ -16881,7 +16909,7 @@ { "Ref": "EC2LinuxAMIRootDevice26D5E56E" }, - "\",\"Ebs\":{\"DeleteOnTermination\":true,\"VolumeSize\":30}}],\"TagSpecifications\":[{\"ResourceType\":\"instance\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Linux\"}]},{\"ResourceType\":\"volume\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Linux\"}]}]}},\"ec2-spot, linux, x64 data\":{\"Type\":\"Pass\",\"ResultPath\":\"$.ec2\",\"Parameters\":{\"userdataTemplate\":\"#!/bin/bash -x\\nTASK_TOKEN=\\\"{}\\\"\\nlogGroupName=\\\"{}\\\"\\nrunnerNamePath=\\\"{}\\\"\\ngithubDomainPath=\\\"{}\\\"\\nownerPath=\\\"{}\\\"\\nrepoPath=\\\"{}\\\"\\nrunnerTokenPath=\\\"{}\\\"\\nlabels=\\\"{}\\\"\\nregistrationURL=\\\"{}\\\"\\n\\nheartbeat () \\\\{\\n while true; do\\n aws stepfunctions send-task-heartbeat --task-token \\\"$TASK_TOKEN\\\"\\n sleep 60\\n done\\n\\\\}\\nsetup_logs () \\\\{\\n cat < /tmp/log.conf || exit 1\\n \\\\{\\n \\\"logs\\\": \\\\{\\n \\\"log_stream_name\\\": \\\"unknown\\\",\\n \\\"logs_collected\\\": \\\\{\\n \\\"files\\\": \\\\{\\n \\\"collect_list\\\": [\\n \\\\{\\n \\\"file_path\\\": \\\"/var/log/runner.log\\\",\\n \\\"log_group_name\\\": \\\"$logGroupName\\\",\\n \\\"log_stream_name\\\": \\\"$runnerNamePath\\\",\\n \\\"timezone\\\": \\\"UTC\\\"\\n \\\\}\\n ]\\n \\\\}\\n \\\\}\\n \\\\}\\n \\\\}\\nEOF\\n /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/tmp/log.conf || exit 2\\n\\\\}\\naction () \\\\{\\n # Determine the value of RUNNER_FLAGS\\n if [ \\\"$(< RUNNER_VERSION)\\\" = \\\"latest\\\" ]; then\\n RUNNER_FLAGS=\\\"\\\"\\n else\\n RUNNER_FLAGS=\\\"--disableupdate\\\"\\n fi\\n\\n labelsTemplate=\\\"$labels,cdkghr:started:$(date +%s)\\\"\\n\\n # Execute the configuration command for runner registration\\n sudo -Hu runner /home/runner/config.sh --unattended --url \\\"$registrationURL\\\" --token \\\"$runnerTokenPath\\\" --ephemeral --work _work --labels \\\"$labelsTemplate\\\" $RUNNER_FLAGS --name \\\"$runnerNamePath\\\" || exit 1\\n\\n # Execute the run command\\n sudo --preserve-env=AWS_REGION -Hu runner /home/runner/run.sh || exit 2\\n\\n # Retrieve the status\\n STATUS=$(grep -Phors \\\"finish job request for job [0-9a-f\\\\-]+ with result: K.*\\\" /home/runner/_diag/ | tail -n1)\\n\\n # Check and print the job status\\n [ -n \\\"$STATUS\\\" ] && echo CDKGHA JOB DONE \\\"$labels\\\" \\\"$STATUS\\\"\\n\\\\}\\nheartbeat &\\nif setup_logs && action | tee /var/log/runner.log 2>&1; then\\n aws stepfunctions send-task-success --task-token \\\"$TASK_TOKEN\\\" --task-output '\\\\{\\\"ok\\\": true\\\\}'\\nelse\\n aws stepfunctions send-task-failure --task-token \\\"$TASK_TOKEN\\\"\\nfi\\nsleep 10 # give cloudwatch agent its default 5 seconds buffer duration to upload logs\\npoweroff\\n\"},\"Next\":\"ec2-spot, linux, x64 subnet1\"},\"ec2-spot, linux, x64 subnet1\":{\"End\":true,\"Catch\":[{\"ErrorEquals\":[\"Ec2.Ec2Exception\",\"States.Timeout\"],\"ResultPath\":\"$.lastSubnetError\",\"Next\":\"ec2-spot, linux, x64 subnet2\"}],\"Type\":\"Task\",\"Comment\":\"", + "\",\"Ebs\":{\"DeleteOnTermination\":true,\"VolumeSize\":40,\"VolumeType\":\"gp3\",\"Iops\":3000,\"Throughput\":200}}],\"TagSpecifications\":[{\"ResourceType\":\"instance\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Linux\"}]},{\"ResourceType\":\"volume\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Linux\"}]}]}},\"ec2-spot, linux, x64 data\":{\"Type\":\"Pass\",\"ResultPath\":\"$.ec2\",\"Parameters\":{\"userdataTemplate\":\"#!/bin/bash -x\\nTASK_TOKEN=\\\"{}\\\"\\nlogGroupName=\\\"{}\\\"\\nrunnerNamePath=\\\"{}\\\"\\ngithubDomainPath=\\\"{}\\\"\\nownerPath=\\\"{}\\\"\\nrepoPath=\\\"{}\\\"\\nrunnerTokenPath=\\\"{}\\\"\\nlabels=\\\"{}\\\"\\nregistrationURL=\\\"{}\\\"\\n\\nheartbeat () \\\\{\\n while true; do\\n aws stepfunctions send-task-heartbeat --task-token \\\"$TASK_TOKEN\\\"\\n sleep 60\\n done\\n\\\\}\\nsetup_logs () \\\\{\\n cat < /tmp/log.conf || exit 1\\n \\\\{\\n \\\"logs\\\": \\\\{\\n \\\"log_stream_name\\\": \\\"unknown\\\",\\n \\\"logs_collected\\\": \\\\{\\n \\\"files\\\": \\\\{\\n \\\"collect_list\\\": [\\n \\\\{\\n \\\"file_path\\\": \\\"/var/log/runner.log\\\",\\n \\\"log_group_name\\\": \\\"$logGroupName\\\",\\n \\\"log_stream_name\\\": \\\"$runnerNamePath\\\",\\n \\\"timezone\\\": \\\"UTC\\\"\\n \\\\}\\n ]\\n \\\\}\\n \\\\}\\n \\\\}\\n \\\\}\\nEOF\\n /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/tmp/log.conf || exit 2\\n\\\\}\\naction () \\\\{\\n # Determine the value of RUNNER_FLAGS\\n if [ \\\"$(< RUNNER_VERSION)\\\" = \\\"latest\\\" ]; then\\n RUNNER_FLAGS=\\\"\\\"\\n else\\n RUNNER_FLAGS=\\\"--disableupdate\\\"\\n fi\\n\\n labelsTemplate=\\\"$labels,cdkghr:started:$(date +%s)\\\"\\n\\n # Execute the configuration command for runner registration\\n sudo -Hu runner /home/runner/config.sh --unattended --url \\\"$registrationURL\\\" --token \\\"$runnerTokenPath\\\" --ephemeral --work _work --labels \\\"$labelsTemplate\\\" $RUNNER_FLAGS --name \\\"$runnerNamePath\\\" || exit 1\\n\\n # Execute the run command\\n sudo --preserve-env=AWS_REGION -Hu runner /home/runner/run.sh || exit 2\\n\\n # Retrieve the status\\n STATUS=$(grep -Phors \\\"finish job request for job [0-9a-f\\\\-]+ with result: K.*\\\" /home/runner/_diag/ | tail -n1)\\n\\n # Check and print the job status\\n [ -n \\\"$STATUS\\\" ] && echo CDKGHA JOB DONE \\\"$labels\\\" \\\"$STATUS\\\"\\n\\\\}\\nheartbeat &\\nif setup_logs && action | tee /var/log/runner.log 2>&1; then\\n aws stepfunctions send-task-success --task-token \\\"$TASK_TOKEN\\\" --task-output '\\\\{\\\"ok\\\": true\\\\}'\\nelse\\n aws stepfunctions send-task-failure --task-token \\\"$TASK_TOKEN\\\"\\nfi\\nsleep 10 # give cloudwatch agent its default 5 seconds buffer duration to upload logs\\npoweroff\\n\"},\"Next\":\"ec2-spot, linux, x64 subnet1\"},\"ec2-spot, linux, x64 subnet1\":{\"End\":true,\"Catch\":[{\"ErrorEquals\":[\"Ec2.Ec2Exception\",\"States.Timeout\"],\"ResultPath\":\"$.lastSubnetError\",\"Next\":\"ec2-spot, linux, x64 subnet2\"}],\"Type\":\"Task\",\"Comment\":\"", { "Ref": "VpcPublicSubnet1Subnet5C2D37C4" }, @@ -16919,7 +16947,7 @@ { "Ref": "EC2SpotLinuxAMIRootDeviceC8A06843" }, - "\",\"Ebs\":{\"DeleteOnTermination\":true,\"VolumeSize\":30}}],\"InstanceMarketOptions\":{\"MarketType\":\"spot\",\"SpotOptions\":{\"SpotInstanceType\":\"one-time\"}},\"TagSpecifications\":[{\"ResourceType\":\"instance\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Spot Linux\"}]},{\"ResourceType\":\"volume\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Spot Linux\"}]}]}},\"ec2-spot, linux, x64 subnet2\":{\"End\":true,\"Type\":\"Task\",\"Comment\":\"", + "\",\"Ebs\":{\"DeleteOnTermination\":true,\"VolumeSize\":40}}],\"InstanceMarketOptions\":{\"MarketType\":\"spot\",\"SpotOptions\":{\"SpotInstanceType\":\"one-time\"}},\"TagSpecifications\":[{\"ResourceType\":\"instance\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Spot Linux\"}]},{\"ResourceType\":\"volume\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Spot Linux\"}]}]}},\"ec2-spot, linux, x64 subnet2\":{\"End\":true,\"Type\":\"Task\",\"Comment\":\"", { "Ref": "VpcPublicSubnet2Subnet691E08A3" }, @@ -16957,7 +16985,7 @@ { "Ref": "EC2SpotLinuxAMIRootDeviceC8A06843" }, - "\",\"Ebs\":{\"DeleteOnTermination\":true,\"VolumeSize\":30}}],\"InstanceMarketOptions\":{\"MarketType\":\"spot\",\"SpotOptions\":{\"SpotInstanceType\":\"one-time\"}},\"TagSpecifications\":[{\"ResourceType\":\"instance\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Spot Linux\"}]},{\"ResourceType\":\"volume\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Spot Linux\"}]}]}},\"ec2, linux, arm64 data\":{\"Type\":\"Pass\",\"ResultPath\":\"$.ec2\",\"Parameters\":{\"userdataTemplate\":\"#!/bin/bash -x\\nTASK_TOKEN=\\\"{}\\\"\\nlogGroupName=\\\"{}\\\"\\nrunnerNamePath=\\\"{}\\\"\\ngithubDomainPath=\\\"{}\\\"\\nownerPath=\\\"{}\\\"\\nrepoPath=\\\"{}\\\"\\nrunnerTokenPath=\\\"{}\\\"\\nlabels=\\\"{}\\\"\\nregistrationURL=\\\"{}\\\"\\n\\nheartbeat () \\\\{\\n while true; do\\n aws stepfunctions send-task-heartbeat --task-token \\\"$TASK_TOKEN\\\"\\n sleep 60\\n done\\n\\\\}\\nsetup_logs () \\\\{\\n cat < /tmp/log.conf || exit 1\\n \\\\{\\n \\\"logs\\\": \\\\{\\n \\\"log_stream_name\\\": \\\"unknown\\\",\\n \\\"logs_collected\\\": \\\\{\\n \\\"files\\\": \\\\{\\n \\\"collect_list\\\": [\\n \\\\{\\n \\\"file_path\\\": \\\"/var/log/runner.log\\\",\\n \\\"log_group_name\\\": \\\"$logGroupName\\\",\\n \\\"log_stream_name\\\": \\\"$runnerNamePath\\\",\\n \\\"timezone\\\": \\\"UTC\\\"\\n \\\\}\\n ]\\n \\\\}\\n \\\\}\\n \\\\}\\n \\\\}\\nEOF\\n /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/tmp/log.conf || exit 2\\n\\\\}\\naction () \\\\{\\n # Determine the value of RUNNER_FLAGS\\n if [ \\\"$(< RUNNER_VERSION)\\\" = \\\"latest\\\" ]; then\\n RUNNER_FLAGS=\\\"\\\"\\n else\\n RUNNER_FLAGS=\\\"--disableupdate\\\"\\n fi\\n\\n labelsTemplate=\\\"$labels,cdkghr:started:$(date +%s)\\\"\\n\\n # Execute the configuration command for runner registration\\n sudo -Hu runner /home/runner/config.sh --unattended --url \\\"$registrationURL\\\" --token \\\"$runnerTokenPath\\\" --ephemeral --work _work --labels \\\"$labelsTemplate\\\" $RUNNER_FLAGS --name \\\"$runnerNamePath\\\" || exit 1\\n\\n # Execute the run command\\n sudo --preserve-env=AWS_REGION -Hu runner /home/runner/run.sh || exit 2\\n\\n # Retrieve the status\\n STATUS=$(grep -Phors \\\"finish job request for job [0-9a-f\\\\-]+ with result: K.*\\\" /home/runner/_diag/ | tail -n1)\\n\\n # Check and print the job status\\n [ -n \\\"$STATUS\\\" ] && echo CDKGHA JOB DONE \\\"$labels\\\" \\\"$STATUS\\\"\\n\\\\}\\nheartbeat &\\nif setup_logs && action | tee /var/log/runner.log 2>&1; then\\n aws stepfunctions send-task-success --task-token \\\"$TASK_TOKEN\\\" --task-output '\\\\{\\\"ok\\\": true\\\\}'\\nelse\\n aws stepfunctions send-task-failure --task-token \\\"$TASK_TOKEN\\\"\\nfi\\nsleep 10 # give cloudwatch agent its default 5 seconds buffer duration to upload logs\\npoweroff\\n\"},\"Next\":\"ec2, linux, arm64 subnet1\"},\"ec2, linux, arm64 subnet1\":{\"End\":true,\"Catch\":[{\"ErrorEquals\":[\"Ec2.Ec2Exception\",\"States.Timeout\"],\"ResultPath\":\"$.lastSubnetError\",\"Next\":\"ec2, linux, arm64 subnet2\"}],\"Type\":\"Task\",\"Comment\":\"", + "\",\"Ebs\":{\"DeleteOnTermination\":true,\"VolumeSize\":40}}],\"InstanceMarketOptions\":{\"MarketType\":\"spot\",\"SpotOptions\":{\"SpotInstanceType\":\"one-time\"}},\"TagSpecifications\":[{\"ResourceType\":\"instance\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Spot Linux\"}]},{\"ResourceType\":\"volume\",\"Tags\":[{\"Key\":\"GitHubRunners:Provider\",\"Value\":\"github-runners-test/EC2 Spot Linux\"}]}]}},\"ec2, linux, arm64 data\":{\"Type\":\"Pass\",\"ResultPath\":\"$.ec2\",\"Parameters\":{\"userdataTemplate\":\"#!/bin/bash -x\\nTASK_TOKEN=\\\"{}\\\"\\nlogGroupName=\\\"{}\\\"\\nrunnerNamePath=\\\"{}\\\"\\ngithubDomainPath=\\\"{}\\\"\\nownerPath=\\\"{}\\\"\\nrepoPath=\\\"{}\\\"\\nrunnerTokenPath=\\\"{}\\\"\\nlabels=\\\"{}\\\"\\nregistrationURL=\\\"{}\\\"\\n\\nheartbeat () \\\\{\\n while true; do\\n aws stepfunctions send-task-heartbeat --task-token \\\"$TASK_TOKEN\\\"\\n sleep 60\\n done\\n\\\\}\\nsetup_logs () \\\\{\\n cat < /tmp/log.conf || exit 1\\n \\\\{\\n \\\"logs\\\": \\\\{\\n \\\"log_stream_name\\\": \\\"unknown\\\",\\n \\\"logs_collected\\\": \\\\{\\n \\\"files\\\": \\\\{\\n \\\"collect_list\\\": [\\n \\\\{\\n \\\"file_path\\\": \\\"/var/log/runner.log\\\",\\n \\\"log_group_name\\\": \\\"$logGroupName\\\",\\n \\\"log_stream_name\\\": \\\"$runnerNamePath\\\",\\n \\\"timezone\\\": \\\"UTC\\\"\\n \\\\}\\n ]\\n \\\\}\\n \\\\}\\n \\\\}\\n \\\\}\\nEOF\\n /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/tmp/log.conf || exit 2\\n\\\\}\\naction () \\\\{\\n # Determine the value of RUNNER_FLAGS\\n if [ \\\"$(< RUNNER_VERSION)\\\" = \\\"latest\\\" ]; then\\n RUNNER_FLAGS=\\\"\\\"\\n else\\n RUNNER_FLAGS=\\\"--disableupdate\\\"\\n fi\\n\\n labelsTemplate=\\\"$labels,cdkghr:started:$(date +%s)\\\"\\n\\n # Execute the configuration command for runner registration\\n sudo -Hu runner /home/runner/config.sh --unattended --url \\\"$registrationURL\\\" --token \\\"$runnerTokenPath\\\" --ephemeral --work _work --labels \\\"$labelsTemplate\\\" $RUNNER_FLAGS --name \\\"$runnerNamePath\\\" || exit 1\\n\\n # Execute the run command\\n sudo --preserve-env=AWS_REGION -Hu runner /home/runner/run.sh || exit 2\\n\\n # Retrieve the status\\n STATUS=$(grep -Phors \\\"finish job request for job [0-9a-f\\\\-]+ with result: K.*\\\" /home/runner/_diag/ | tail -n1)\\n\\n # Check and print the job status\\n [ -n \\\"$STATUS\\\" ] && echo CDKGHA JOB DONE \\\"$labels\\\" \\\"$STATUS\\\"\\n\\\\}\\nheartbeat &\\nif setup_logs && action | tee /var/log/runner.log 2>&1; then\\n aws stepfunctions send-task-success --task-token \\\"$TASK_TOKEN\\\" --task-output '\\\\{\\\"ok\\\": true\\\\}'\\nelse\\n aws stepfunctions send-task-failure --task-token \\\"$TASK_TOKEN\\\"\\nfi\\nsleep 10 # give cloudwatch agent its default 5 seconds buffer duration to upload logs\\npoweroff\\n\"},\"Next\":\"ec2, linux, arm64 subnet1\"},\"ec2, linux, arm64 subnet1\":{\"End\":true,\"Catch\":[{\"ErrorEquals\":[\"Ec2.Ec2Exception\",\"States.Timeout\"],\"ResultPath\":\"$.lastSubnetError\",\"Next\":\"ec2, linux, arm64 subnet2\"}],\"Type\":\"Task\",\"Comment\":\"", { "Ref": "VpcPublicSubnet1Subnet5C2D37C4" }, diff --git a/test/default.integ.ts b/test/default.integ.ts index 8590e835..a4d6ff40 100644 --- a/test/default.integ.ts +++ b/test/default.integ.ts @@ -6,6 +6,7 @@ import * as cdk from 'aws-cdk-lib'; import { aws_codebuild as codebuild, aws_ec2 as ec2, aws_ecs as ecs } from 'aws-cdk-lib'; +import { EbsDeviceVolumeType } from 'aws-cdk-lib/aws-ec2'; import { Architecture, CodeBuildRunnerProvider, @@ -160,6 +161,12 @@ const runners = new GitHubRunners(stack, 'runners', { vpc, maxInstances: 1, spot: true, + storageSize: cdk.Size.gibibytes(40), + storageOptions: { + volumeType: EbsDeviceVolumeType.GP3, + iops: 1500, + throughput: 150, + }, }), new EcsRunnerProvider(stack, 'ECS ARM64', { labels: ['ecs', 'linux', 'arm64'], @@ -232,12 +239,19 @@ const runners = new GitHubRunners(stack, 'runners', { labels: ['ec2', 'linux', 'x64'], imageBuilder: amiX64Builder, vpc, + storageSize: cdk.Size.gibibytes(40), + storageOptions: { + volumeType: EbsDeviceVolumeType.GP3, + iops: 3000, + throughput: 200, + }, }), new Ec2RunnerProvider(stack, 'EC2 Spot Linux', { labels: ['ec2-spot', 'linux', 'x64'], imageBuilder: amiX64Builder, spot: true, vpc, + storageSize: cdk.Size.gibibytes(40), }), new Ec2RunnerProvider(stack, 'EC2 Linux arm64', { labels: ['ec2', 'linux', 'arm64'],