diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/aws-cdk-ec2-lt-metadata-1.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/aws-cdk-ec2-lt-metadata-1.assets.json index 1147a2215bf03..e5feb9f3572be 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/aws-cdk-ec2-lt-metadata-1.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/aws-cdk-ec2-lt-metadata-1.assets.json @@ -14,7 +14,7 @@ } } }, - "8cd8c63d04ed01a1a76f21f94fa840809a354a12b00cfac1a16d1bfb8973a85a": { + "bd2bb58782d64dfe3df50c686357988b4f69b5ee889621436ada0224fa5ffadb": { "source": { "path": "aws-cdk-ec2-lt-metadata-1.template.json", "packaging": "file" @@ -22,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "8cd8c63d04ed01a1a76f21f94fa840809a354a12b00cfac1a16d1bfb8973a85a.json", + "objectKey": "bd2bb58782d64dfe3df50c686357988b4f69b5ee889621436ada0224fa5ffadb.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/aws-cdk-ec2-lt-metadata-1.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/aws-cdk-ec2-lt-metadata-1.template.json index 061a117dbcb13..fe8553151c4f5 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/aws-cdk-ec2-lt-metadata-1.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/aws-cdk-ec2-lt-metadata-1.template.json @@ -159,6 +159,16 @@ "Type": "AWS::EC2::LaunchTemplate", "Properties": { "LaunchTemplateData": { + "BlockDeviceMappings": [ + { + "DeviceName": "/dev/xvda", + "Ebs": { + "Throughput": 250, + "VolumeSize": 15, + "VolumeType": "gp3" + } + } + ], "MetadataOptions": { "HttpEndpoint": "enabled", "HttpProtocolIpv6": "enabled", @@ -211,8 +221,7 @@ } ] } - ], - "VersionDescription": "test template v1" + ] } }, "sg2860DD91F": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/manifest.json index 92489c9202eb6..b24f45c6bfaed 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8cd8c63d04ed01a1a76f21f94fa840809a354a12b00cfac1a16d1bfb8973a85a.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/bd2bb58782d64dfe3df50c686357988b4f69b5ee889621436ada0224fa5ffadb.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/tree.json index ed2fedb904e4d..c47b3b78078ea 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.js.snapshot/tree.json @@ -31,8 +31,8 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_ec2.CfnVPC", + "version": "0.0.0" } }, "RestrictDefaultSecurityGroupCustomResource": { @@ -43,28 +43,28 @@ "id": "Default", "path": "aws-cdk-ec2-lt-metadata-1/MyVpc/RestrictDefaultSecurityGroupCustomResource/Default", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CustomResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_ec2.Vpc", + "version": "0.0.0" } }, "LatestNodeRuntimeMap": { "id": "LatestNodeRuntimeMap", "path": "aws-cdk-ec2-lt-metadata-1/LatestNodeRuntimeMap", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnMapping", + "version": "0.0.0" } }, "Custom::VpcRestrictDefaultSGCustomResourceProvider": { @@ -75,30 +75,30 @@ "id": "Staging", "path": "aws-cdk-ec2-lt-metadata-1/Custom::VpcRestrictDefaultSGCustomResourceProvider/Staging", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" } }, "Role": { "id": "Role", "path": "aws-cdk-ec2-lt-metadata-1/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" } }, "Handler": { "id": "Handler", "path": "aws-cdk-ec2-lt-metadata-1/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnResource", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CustomResourceProviderBase", + "version": "0.0.0" } }, "sg1": { @@ -125,14 +125,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_ec2.CfnSecurityGroup", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_ec2.SecurityGroup", + "version": "0.0.0" } }, "LT": { @@ -146,6 +146,16 @@ "aws:cdk:cloudformation:type": "AWS::EC2::LaunchTemplate", "aws:cdk:cloudformation:props": { "launchTemplateData": { + "blockDeviceMappings": [ + { + "deviceName": "/dev/xvda", + "ebs": { + "volumeSize": 15, + "throughput": 250, + "volumeType": "gp3" + } + } + ], "securityGroupIds": [ { "Fn::GetAtt": [ @@ -198,19 +208,18 @@ } ] } - ], - "versionDescription": "test template v1" + ] } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_ec2.CfnLaunchTemplate", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_ec2.LaunchTemplate", + "version": "0.0.0" } }, "sg2": { @@ -237,14 +246,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_ec2.CfnSecurityGroup", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_ec2.SecurityGroup", + "version": "0.0.0" } }, "LTWithMachineImage": { @@ -299,52 +308,52 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_ec2.CfnLaunchTemplate", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.aws_ec2.LaunchTemplate", + "version": "0.0.0" } }, "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter": { "id": "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", "path": "aws-cdk-ec2-lt-metadata-1/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" } }, "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118": { "id": "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118", "path": "aws-cdk-ec2-lt-metadata-1/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" } }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "aws-cdk-ec2-lt-metadata-1/BootstrapVersion", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" } }, "CheckBootstrapVersion": { "id": "CheckBootstrapVersion", "path": "aws-cdk-ec2-lt-metadata-1/CheckBootstrapVersion", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" } }, "LambdaTest": { @@ -371,22 +380,22 @@ "id": "BootstrapVersion", "path": "LambdaTest/DefaultTest/DeployAssert/BootstrapVersion", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" } }, "CheckBootstrapVersion": { "id": "CheckBootstrapVersion", "path": "LambdaTest/DefaultTest/DeployAssert/CheckBootstrapVersion", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" } } }, @@ -411,8 +420,8 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.3.0" + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.ts index 0aa48e0277a81..99d3922ec790b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.launch-template.ts @@ -23,6 +23,13 @@ const lt = new ec2.LaunchTemplate(stack, 'LT', { httpTokens: ec2.LaunchTemplateHttpTokens.REQUIRED, instanceMetadataTags: true, securityGroup: sg1, + blockDevices: [{ + deviceName: '/dev/xvda', + volume: ec2.BlockDeviceVolume.ebs(15, { + volumeType: ec2.EbsDeviceVolumeType.GP3, + throughput: 250, + }), + }], }); const sg2 = new ec2.SecurityGroup(stack, 'sg2', { diff --git a/packages/aws-cdk-lib/aws-ec2/README.md b/packages/aws-cdk-lib/aws-ec2/README.md index 686eb78ed2a3f..c15b6b785ae03 100644 --- a/packages/aws-cdk-lib/aws-ec2/README.md +++ b/packages/aws-cdk-lib/aws-ec2/README.md @@ -1603,6 +1603,33 @@ new ec2.Instance(this, 'Instance', { ``` +To specify the throughput value for `gp3` volumes, use the `throughput` property: + +```ts +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + +const kmsKey = new Key(this, 'KmsKey') + +new ec2.Instance(this, 'Instance', { + vpc, + instanceType, + machineImage, + + // ... + + blockDevices: [ + { + deviceName: '/dev/sda1', + volume: ec2.BlockDeviceVolume.ebs(100, { + volumeType: ec2.EbsDeviceVolumeType.GP3, + throughput: 250, + }), + }, + ], +}); + #### EBS Optimized Instances An Amazon EBS–optimized instance uses an optimized configuration stack and provides additional, dedicated capacity for Amazon EBS I/O. This optimization provides the best performance for your EBS volumes by minimizing contention between Amazon EBS I/O and other traffic from your instance. diff --git a/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts b/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts index 51a020fa1036f..b5ffff9228909 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts @@ -25,7 +25,25 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevic if (ebs) { - const { iops, volumeType, kmsKey, ...rest } = ebs; + const { iops, throughput, volumeType, kmsKey, ...rest } = ebs; + + if (throughput) { + if (volumeType != EbsDeviceVolumeType.GP3) { + throw new Error(`throughput requires volumeType: ${EbsDeviceVolumeType.GP3}, got: ${volumeType}.`); + } + + if (throughput < 125 || throughput > 1000) { + throw new Error(`throughput must be between 125 and 1000, got ${throughput}.`); + } + + const maximumThroughputRatio = 0.25; + if (iops) { + const iopsRatio = (throughput / iops); + if (iopsRatio > maximumThroughputRatio) { + throw new Error(`Throughput (MiBps) to iops ratio of ${iopsRatio} is too high; maximum is ${maximumThroughputRatio} MiBps per iops`); + } + } + } if (!iops) { if (volumeType === EbsDeviceVolumeType.IO1 || volumeType === EbsDeviceVolumeType.IO2) { @@ -43,6 +61,7 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevic finalEbs = { ...rest, iops, + throughput, volumeType, kmsKeyId: kmsKey?.keyArn, }; diff --git a/packages/aws-cdk-lib/aws-ec2/lib/volume.ts b/packages/aws-cdk-lib/aws-ec2/lib/volume.ts index 70d19b119f717..730a045ff4bbe 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/volume.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/volume.ts @@ -70,6 +70,17 @@ export interface EbsDeviceOptionsBase { * `@aws-cdk/aws-ec2:ebsDefaultGp3Volume` is enabled. */ readonly volumeType?: EbsDeviceVolumeType; + + /** + * The throughput to provision for a `gp3` volume. + * + * Valid Range: Minimum value of 125. Maximum value of 1000. + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html + * + * @default - 125 MiB/s. + */ + readonly throughput?: number; } /** diff --git a/packages/aws-cdk-lib/aws-ec2/test/launch-template.test.ts b/packages/aws-cdk-lib/aws-ec2/test/launch-template.test.ts index a818c23b8f1cb..88bc9c7e4115e 100644 --- a/packages/aws-cdk-lib/aws-ec2/test/launch-template.test.ts +++ b/packages/aws-cdk-lib/aws-ec2/test/launch-template.test.ts @@ -336,6 +336,12 @@ describe('LaunchTemplate', () => { }, { deviceName: 'ephemeral', volume: BlockDeviceVolume.ephemeral(0), + }, { + deviceName: 'gp3-with-throughput', + volume: BlockDeviceVolume.ebs(15, { + volumeType: EbsDeviceVolumeType.GP3, + throughput: 350, + }), }, ]; @@ -388,10 +394,60 @@ describe('LaunchTemplate', () => { DeviceName: 'ephemeral', VirtualName: 'ephemeral0', }, + { + DeviceName: 'gp3-with-throughput', + Ebs: { + VolumeSize: 15, + VolumeType: 'gp3', + Throughput: 350, + }, + }, ], }, }); }); + test.each([124, 1001])('throws if throughput is set less than 125 or more than 1000', (throughput) => { + expect(() => { + new LaunchTemplate(stack, 'LaunchTemplate', { + blockDevices: [{ + deviceName: 'ebs', + volume: BlockDeviceVolume.ebs(15, { + volumeType: EbsDeviceVolumeType.GP3, + throughput, + }), + }], + }); + }).toThrow(/throughput must be between 125 and 1000, got/); + }); + test.each([ + ...Object.values(EbsDeviceVolumeType).filter((v) => v !== 'gp3'), + ])('throws if throughput is set on any volume type other than GP3', (volumeType) => { + expect(() => { + new LaunchTemplate(stack, 'LaunchTemplate', { + blockDevices: [{ + deviceName: 'ebs', + volume: BlockDeviceVolume.ebs(15, { + volumeType: volumeType, + throughput: 150, + }), + }], + }); + }).toThrow(/throughput requires volumeType: gp3, got/); + }); + test('throws if throughput / iops ratio is greater than 0.25', () => { + expect(() => { + new LaunchTemplate(stack, 'LaunchTemplate', { + blockDevices: [{ + deviceName: 'ebs', + volume: BlockDeviceVolume.ebs(15, { + volumeType: EbsDeviceVolumeType.GP3, + throughput: 751, + iops: 3000, + }), + }], + }); + }).toThrow('Throughput (MiBps) to iops ratio of 0.25033333333333335 is too high; maximum is 0.25 MiBps per iops'); + }); test('Given instance profile', () => { // GIVEN