From d13b64af178579ae57ddc6da8d1fb53f26ac9777 Mon Sep 17 00:00:00 2001 From: Cameron Sumpter Date: Fri, 25 Nov 2022 11:57:51 -0500 Subject: [PATCH] feat(autoscaling): support for throughput on GP3 volumes (#22441) Adds support for the `throughput` property on GP3 volumes in both autoscaling and EC2 packages since their volume implementations are separate per the comment https://github.com/aws/aws-cdk/blob/master/packages/@aws-cdk/aws-autoscaling/lib/volume.ts#L1. Change includes unit test coverage, integration test coverage on the autoscaling changes, and validation of the inputq to throughput. I was on the fence about whether to include the validation for synth time checking since as far as I can tell validation is not performed consistently across the CDK at synth time. Happy to modify that behavior pending reviewer feedback. It was not obvious to me at first pass where similar integration test coverage that was added to the autoscaling package could be added to the EC2 package, so if the reviewer would like to see integration tests for GP3 volumes on in the EC2 package, would you mind providing a hint as to where that test might fit in the existing test paradigm? Happy to break this change into two PRs -- one for EC2 and one for autoscaling. closes: https://github.com/aws/aws-cdk/issues/16213 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-autoscaling/README.md | 30 +++ .../aws-autoscaling/lib/auto-scaling-group.ts | 26 +- .../@aws-cdk/aws-autoscaling/lib/volume.ts | 8 + .../test/auto-scaling-group.test.ts | 79 ++++++ .../aws-cdk-asg-integ.assets.json | 6 +- .../aws-cdk-asg-integ.template.json | 128 +++++++++ .../test/integ.asg-lt.js.snapshot/cdk.out | 2 +- .../test/integ.asg-lt.js.snapshot/integ.json | 2 +- .../integ.asg-lt.js.snapshot/manifest.json | 46 +++- .../test/integ.asg-lt.js.snapshot/tree.json | 244 ++++++++++++++++-- .../aws-autoscaling/test/integ.asg-lt.ts | 19 ++ packages/@aws-cdk/aws-ec2/README.md | 13 + packages/@aws-cdk/aws-ec2/lib/volume.ts | 34 +++ packages/@aws-cdk/aws-ec2/test/volume.test.ts | 45 ++++ .../cdk.out | 2 +- 15 files changed, 654 insertions(+), 30 deletions(-) diff --git a/packages/@aws-cdk/aws-autoscaling/README.md b/packages/@aws-cdk/aws-autoscaling/README.md index 4df0c2ab8ff09..8b61d74c27e61 100644 --- a/packages/@aws-cdk/aws-autoscaling/README.md +++ b/packages/@aws-cdk/aws-autoscaling/README.md @@ -278,6 +278,36 @@ autoScalingGroup.scaleOnSchedule('AllowDownscalingAtNight', { }); ``` +### Block Devices + +This type specifies how block devices are exposed to the instance. You can specify virtual devices and EBS volumes. + +#### GP3 Volumes + +You can only specify the `throughput` on GP3 volumes. + +```ts +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + +const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', { + vpc, + instanceType, + machineImage, + blockDevices: [{ + { + deviceName: 'gp3-volume', + volume: autoscaling.BlockDeviceVolume.ebs(15, { + volumeType: autoscaling.EbsDeviceVolumeType.GP3, + throughput: 125, + }), + }, + }], + // ... +}); +``` + ## Configuring Instances using CloudFormation Init It is possible to use the CloudFormation Init mechanism to configure the diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 0c5006c29f514..d3ca3fee08be5 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -2167,7 +2167,31 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevices: Block } if (ebs) { - const { iops, volumeType } = ebs; + const { iops, volumeType, throughput } = ebs; + + if (throughput) { + const throughputRange = { Min: 125, Max: 1000 }; + const { Min, Max } = throughputRange; + + if (volumeType != EbsDeviceVolumeType.GP3) { + throw new Error('throughput property requires volumeType: EbsDeviceVolumeType.GP3'); + } + + if (throughput < Min || throughput > Max) { + throw new Error( + `throughput property takes a minimum of ${Min} and a maximum of ${Max}`, + ); + } + + 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) { diff --git a/packages/@aws-cdk/aws-autoscaling/lib/volume.ts b/packages/@aws-cdk/aws-autoscaling/lib/volume.ts index 1c5f5da6dc659..c252479afc864 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/volume.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/volume.ts @@ -66,6 +66,14 @@ export interface EbsDeviceOptionsBase { * @default {@link EbsDeviceVolumeType.GP2} */ readonly volumeType?: EbsDeviceVolumeType; + + /** + * The throughput that the volume supports, in MiB/s + * Takes a minimum of 125 and maximum of 1000. + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html + * @default - 125 MiB/s. Only valid on gp3 volumes. + */ + readonly throughput?: number; } /** diff --git a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts index c0a979408a945..13e735b1e0a0a 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts @@ -699,6 +699,12 @@ describe('auto scaling group', () => { }, { deviceName: 'none', volume: autoscaling.BlockDeviceVolume.noDevice(), + }, { + deviceName: 'gp3-with-throughput', + volume: autoscaling.BlockDeviceVolume.ebs(15, { + volumeType: autoscaling.EbsDeviceVolumeType.GP3, + throughput: 350, + }), }], }); @@ -739,6 +745,14 @@ describe('auto scaling group', () => { DeviceName: 'none', NoDevice: true, }, + { + DeviceName: 'gp3-with-throughput', + Ebs: { + VolumeSize: 15, + VolumeType: 'gp3', + Throughput: 350, + }, + }, ], }); }); @@ -809,6 +823,71 @@ describe('auto scaling group', () => { }).toThrow(/maxInstanceLifetime must be between 1 and 365 days \(inclusive\)/); }); + test.each([124, 1001])('throws if throughput is set less than 125 or more than 1000', (throughput) => { + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + + expect(() => { + new autoscaling.AutoScalingGroup(stack, 'MyStack', { + machineImage: new ec2.AmazonLinuxImage(), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + vpc, + maxInstanceLifetime: cdk.Duration.days(0), + blockDevices: [{ + deviceName: 'ebs', + volume: autoscaling.BlockDeviceVolume.ebs(15, { + volumeType: autoscaling.EbsDeviceVolumeType.GP3, + throughput, + }), + }], + }); + }).toThrow(/throughput property takes a minimum of 125 and a maximum of 1000/); + }); + + test.each([ + ...Object.values(autoscaling.EbsDeviceVolumeType).filter((v) => v !== 'gp3'), + ])('throws if throughput is set on any volume type other than GP3', (volumeType) => { + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + + expect(() => { + new autoscaling.AutoScalingGroup(stack, 'MyStack', { + machineImage: new ec2.AmazonLinuxImage(), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + vpc, + maxInstanceLifetime: cdk.Duration.days(0), + blockDevices: [{ + deviceName: 'ebs', + volume: autoscaling.BlockDeviceVolume.ebs(15, { + volumeType: volumeType, + throughput: 150, + }), + }], + }); + }).toThrow(/throughput property requires volumeType: EbsDeviceVolumeType.GP3/); + }); + + test('throws if throughput / iops ratio is greater than 0.25', () => { + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + expect(() => { + new autoscaling.AutoScalingGroup(stack, 'MyStack', { + machineImage: new ec2.AmazonLinuxImage(), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + vpc, + maxInstanceLifetime: cdk.Duration.days(0), + blockDevices: [{ + deviceName: 'ebs', + volume: autoscaling.BlockDeviceVolume.ebs(15, { + volumeType: autoscaling.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('can configure instance monitoring', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/aws-cdk-asg-integ.assets.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/aws-cdk-asg-integ.assets.json index 93be1296147da..82db7bdafa693 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/aws-cdk-asg-integ.assets.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/aws-cdk-asg-integ.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "cb1b7cc4cd8286ab836dcbb42f408e2c39d60c34a017d1dac4b998c1891d843b": { + "2ca8f144c3e288148d58c9b9e86c9034f6a72b09cecffac3a5d406f8f53d5b18": { "source": { "path": "aws-cdk-asg-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "cb1b7cc4cd8286ab836dcbb42f408e2c39d60c34a017d1dac4b998c1891d843b.json", + "objectKey": "2ca8f144c3e288148d58c9b9e86c9034f6a72b09cecffac3a5d406f8f53d5b18.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/aws-cdk-asg-integ.template.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/aws-cdk-asg-integ.template.json index b1a90800b11ac..d063936bdcbbb 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/aws-cdk-asg-integ.template.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/aws-cdk-asg-integ.template.json @@ -625,6 +625,130 @@ "IgnoreUnmodifiedGroupSizeProperties": true } } + }, + "AsgWithGp3BlockdeviceInstanceSecurityGroup54D76206": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-asg-integ/AsgWithGp3Blockdevice" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "AsgWithGp3BlockdeviceInstanceRoleF52FB39B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-asg-integ/AsgWithGp3Blockdevice" + } + ] + } + }, + "AsgWithGp3BlockdeviceInstanceProfile2FC414A5": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "AsgWithGp3BlockdeviceInstanceRoleF52FB39B" + } + ] + } + }, + "AsgWithGp3BlockdeviceLaunchConfig24411F5E": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t3.micro", + "BlockDeviceMappings": [ + { + "DeviceName": "ebs", + "Ebs": { + "DeleteOnTermination": true, + "Encrypted": true, + "Throughput": 125, + "VolumeSize": 15, + "VolumeType": "gp3" + } + } + ], + "IamInstanceProfile": { + "Ref": "AsgWithGp3BlockdeviceInstanceProfile2FC414A5" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "AsgWithGp3BlockdeviceInstanceSecurityGroup54D76206", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": "#!/bin/bash" + } + }, + "DependsOn": [ + "AsgWithGp3BlockdeviceInstanceRoleF52FB39B" + ] + }, + "AsgWithGp3BlockdeviceASGE82AA487": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "10", + "MinSize": "0", + "DesiredCapacity": "5", + "LaunchConfigurationName": { + "Ref": "AsgWithGp3BlockdeviceLaunchConfig24411F5E" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-cdk-asg-integ/AsgWithGp3Blockdevice" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + }, + "UpdatePolicy": { + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } } }, "Parameters": { @@ -636,6 +760,10 @@ "Type": "AWS::SSM::Parameter::Value", "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2" }, + "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2" + }, "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", "Default": "/cdk-bootstrap/hnb659fds/version", diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/cdk.out b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/integ.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/integ.json index cf65d0be11f3b..b2a0249ca14c8 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/integ.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "integ.asg-lt": { "stacks": [ diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/manifest.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/manifest.json index 65c55da1f466b..626c4977f059b 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,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}/cb1b7cc4cd8286ab836dcbb42f408e2c39d60c34a017d1dac4b998c1891d843b.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/2ca8f144c3e288148d58c9b9e86c9034f6a72b09cecffac3a5d406f8f53d5b18.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -237,6 +237,48 @@ "data": "AsgFromMipWithoutDistributionASG4BF292F9" } ], + "/aws-cdk-asg-integ/AsgWithGp3Blockdevice": [ + { + "type": "aws:cdk:warning", + "data": "desiredCapacity has been configured. Be aware this will reset the size of your AutoScalingGroup on every deployment. See https://github.com/aws/aws-cdk/issues/5215" + } + ], + "/aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceSecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AsgWithGp3BlockdeviceInstanceSecurityGroup54D76206" + } + ], + "/aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AsgWithGp3BlockdeviceInstanceRoleF52FB39B" + } + ], + "/aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceProfile": [ + { + "type": "aws:cdk:logicalId", + "data": "AsgWithGp3BlockdeviceInstanceProfile2FC414A5" + } + ], + "/aws-cdk-asg-integ/AsgWithGp3Blockdevice/LaunchConfig": [ + { + "type": "aws:cdk:logicalId", + "data": "AsgWithGp3BlockdeviceLaunchConfig24411F5E" + } + ], + "/aws-cdk-asg-integ/AsgWithGp3Blockdevice/ASG": [ + { + "type": "aws:cdk:logicalId", + "data": "AsgWithGp3BlockdeviceASGE82AA487" + } + ], + "/aws-cdk-asg-integ/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter": [ + { + "type": "aws:cdk:logicalId", + "data": "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + } + ], "/aws-cdk-asg-integ/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/tree.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/tree.json index 0004eee300a03..10f3be8834c97 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } }, "aws-cdk-asg-integ": { @@ -80,16 +80,16 @@ "id": "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", "path": "aws-cdk-asg-integ/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.1.85" + "fqn": "@aws-cdk/core.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-asg-integ/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "T4gLT": { @@ -156,16 +156,16 @@ "id": "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-arm64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", "path": "aws-cdk-asg-integ/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-arm64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" } }, "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-arm64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118": { "id": "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-arm64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118", "path": "aws-cdk-asg-integ/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-hvm-arm64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "VPC": { @@ -243,8 +243,8 @@ "id": "Acl", "path": "aws-cdk-asg-integ/VPC/PublicSubnet1/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -410,8 +410,8 @@ "id": "Acl", "path": "aws-cdk-asg-integ/VPC/PublicSubnet2/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -577,8 +577,8 @@ "id": "Acl", "path": "aws-cdk-asg-integ/VPC/PrivateSubnet1/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -696,8 +696,8 @@ "id": "Acl", "path": "aws-cdk-asg-integ/VPC/PrivateSubnet2/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -1000,17 +1000,219 @@ "fqn": "@aws-cdk/aws-autoscaling.AutoScalingGroup", "version": "0.0.0" } + }, + "AsgWithGp3Blockdevice": { + "id": "AsgWithGp3Blockdevice", + "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice", + "children": { + "InstanceSecurityGroup": { + "id": "InstanceSecurityGroup", + "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceSecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceSecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceSecurityGroup", + "securityGroupEgress": [ + { + "cidrIp": "0.0.0.0/0", + "description": "Allow all outbound traffic by default", + "ipProtocol": "-1" + } + ], + "tags": [ + { + "key": "Name", + "value": "aws-cdk-asg-integ/AsgWithGp3Blockdevice" + } + ], + "vpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSecurityGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.SecurityGroup", + "version": "0.0.0" + } + }, + "InstanceRole": { + "id": "InstanceRole", + "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceRole", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-asg-integ/AsgWithGp3Blockdevice" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "InstanceProfile": { + "id": "InstanceProfile", + "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceProfile", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::InstanceProfile", + "aws:cdk:cloudformation:props": { + "roles": [ + { + "Ref": "AsgWithGp3BlockdeviceInstanceRoleF52FB39B" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnInstanceProfile", + "version": "0.0.0" + } + }, + "LaunchConfig": { + "id": "LaunchConfig", + "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/LaunchConfig", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AutoScaling::LaunchConfiguration", + "aws:cdk:cloudformation:props": { + "imageId": { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "instanceType": "t3.micro", + "blockDeviceMappings": [ + { + "deviceName": "ebs", + "ebs": { + "deleteOnTermination": true, + "encrypted": true, + "volumeType": "gp3", + "throughput": 125, + "volumeSize": 15 + } + } + ], + "iamInstanceProfile": { + "Ref": "AsgWithGp3BlockdeviceInstanceProfile2FC414A5" + }, + "securityGroups": [ + { + "Fn::GetAtt": [ + "AsgWithGp3BlockdeviceInstanceSecurityGroup54D76206", + "GroupId" + ] + } + ], + "userData": { + "Fn::Base64": "#!/bin/bash" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.CfnLaunchConfiguration", + "version": "0.0.0" + } + }, + "ASG": { + "id": "ASG", + "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/ASG", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AutoScaling::AutoScalingGroup", + "aws:cdk:cloudformation:props": { + "maxSize": "10", + "minSize": "0", + "desiredCapacity": "5", + "launchConfigurationName": { + "Ref": "AsgWithGp3BlockdeviceLaunchConfig24411F5E" + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-asg-integ/AsgWithGp3Blockdevice", + "propagateAtLaunch": true + } + ], + "vpcZoneIdentifier": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.CfnAutoScalingGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-autoscaling.AutoScalingGroup", + "version": "0.0.0" + } + }, + "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter": { + "id": "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", + "path": "aws-cdk-asg-integ/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118": { + "id": "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118", + "path": "aws-cdk-asg-integ/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.ts index e8fef26b36f9b..d215e03d344bf 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.ts @@ -67,4 +67,23 @@ new autoscaling.AutoScalingGroup(stack, 'AsgFromMipWithoutDistribution', { desiredCapacity: 5, }); +new autoscaling.AutoScalingGroup(stack, 'AsgWithGp3Blockdevice', { + minCapacity: 0, + maxCapacity: 10, + desiredCapacity: 5, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + blockDevices: [{ + deviceName: 'ebs', + mappingEnabled: true, + volume: autoscaling.BlockDeviceVolume.ebs(15, { + deleteOnTermination: true, + encrypted: true, + volumeType: autoscaling.EbsDeviceVolumeType.GP3, + throughput: 125, + }), + }], + vpc, +}); + app.synth(); diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 783ff94728e7e..91f61d1b961c2 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -1326,6 +1326,19 @@ You can configure [tag propagation on volume creation](https://docs.aws.amazon.c }); ``` +#### Throughput on GP3 Volumes + +You can specify the `throughput` of a GP3 volume from 125 (default) to 1000. + +```ts +new ec2.Volume(this, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(125), + volumeType: EbsDeviceVolumeType.GP3, + throughput: 125, +}); +``` + ### Configuring Instance Metadata Service (IMDS) #### Toggling IMDSv1 diff --git a/packages/@aws-cdk/aws-ec2/lib/volume.ts b/packages/@aws-cdk/aws-ec2/lib/volume.ts index 3e01d3823207c..8c7864e75b161 100644 --- a/packages/@aws-cdk/aws-ec2/lib/volume.ts +++ b/packages/@aws-cdk/aws-ec2/lib/volume.ts @@ -442,6 +442,14 @@ export interface VolumeProps { * @default RemovalPolicy.RETAIN */ readonly removalPolicy?: RemovalPolicy; + + /** + * 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-properties-ec2-ebs-volume.html#cfn-ec2-ebs-volume-throughput + * @default - 125 MiB/s. Only valid on gp3 volumes. + */ + readonly throughput?: number; } /** @@ -702,6 +710,17 @@ export class Volume extends VolumeBase { if (props.size && (props.iops > maximumRatio * props.size.toGibibytes({ rounding: SizeRoundingBehavior.FAIL }))) { throw new Error(`\`${volumeType}\` volumes iops has a maximum ratio of ${maximumRatio} IOPS/GiB.`); } + + const maximumThroughputRatios: { [key: string]: number } = {}; + maximumThroughputRatios[EbsDeviceVolumeType.GP3] = 0.25; + const maximumThroughputRatio = maximumThroughputRatios[volumeType]; + if (props.throughput && props.iops) { + const iopsRatio = (props.throughput / props.iops); + if (iopsRatio > maximumThroughputRatio) { + throw new Error(`Throughput (MiBps) to iops ratio of ${iopsRatio} is too high; maximum is ${maximumThroughputRatio} MiBps per iops`); + } + + } } if (props.enableMultiAttach) { @@ -734,5 +753,20 @@ export class Volume extends VolumeBase { throw new Error(`\`${volumeType}\` volumes must be between ${Min} GiB and ${Max} GiB in size.`); } } + + if (props.throughput) { + const throughputRange = { Min: 125, Max: 1000 }; + const { Min, Max } = throughputRange; + if (props.volumeType != EbsDeviceVolumeType.GP3) { + throw new Error( + 'throughput property requires volumeType: EbsDeviceVolumeType.GP3', + ); + } + if (props.throughput < Min || props.throughput > Max) { + throw new Error( + `throughput property takes a minimum of ${Min} and a maximum of ${Max}`, + ); + } + } } } diff --git a/packages/@aws-cdk/aws-ec2/test/volume.test.ts b/packages/@aws-cdk/aws-ec2/test/volume.test.ts index a52afe2ad5dc2..56f7db2f314e2 100644 --- a/packages/@aws-cdk/aws-ec2/test/volume.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/volume.test.ts @@ -1436,4 +1436,49 @@ describe('volume', () => { }).toThrow(/volumes must be between/); } }); + + test.each([124, 1001])('throws if throughput is set less than 125 or more than 1000', (throughput) => { + const stack = new cdk.Stack(); + expect(() => { + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(1), + volumeType: EbsDeviceVolumeType.GP3, + throughput, + }); + }).toThrow(/throughput property takes a minimum of 125 and a maximum of 1000/); + }); + + test.each([ + ...Object.values(EbsDeviceVolumeType).filter((v) => v !== 'gp3'), + ])('throws if throughput is set on any volume type other than GP3', (volumeType) => { + const stack = new cdk.Stack(); + const iops = [ + EbsDeviceVolumeType.PROVISIONED_IOPS_SSD, + EbsDeviceVolumeType.PROVISIONED_IOPS_SSD_IO2, + ].includes(volumeType) ? 100 : null; + expect(() => { + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(125), + volumeType, + ...iops ? { iops }: {}, + throughput: 125, + }); + }).toThrow(/throughput property requires volumeType: EbsDeviceVolumeType.GP3/); + }); + + test('Invalid iops to throughput ratio', () => { + const stack = new cdk.Stack(); + expect(() => { + new Volume(stack, 'Volume', { + availabilityZone: 'us-east-1a', + size: cdk.Size.gibibytes(125), + volumeType: EbsDeviceVolumeType.GP3, + iops: 3000, + throughput: 751, + }); + }).toThrow('Throughput (MiBps) to iops ratio of 0.25033333333333335 is too high; maximum is 0.25 MiBps per iops'); + }); + }); diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.js.snapshot/cdk.out b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.js.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.js.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file