Skip to content

Commit d13b64a

Browse files
authored
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: #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*
1 parent 47943d2 commit d13b64a

File tree

15 files changed

+654
-30
lines changed

15 files changed

+654
-30
lines changed

Diff for: packages/@aws-cdk/aws-autoscaling/README.md

+30
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,36 @@ autoScalingGroup.scaleOnSchedule('AllowDownscalingAtNight', {
278278
});
279279
```
280280

281+
### Block Devices
282+
283+
This type specifies how block devices are exposed to the instance. You can specify virtual devices and EBS volumes.
284+
285+
#### GP3 Volumes
286+
287+
You can only specify the `throughput` on GP3 volumes.
288+
289+
```ts
290+
declare const vpc: ec2.Vpc;
291+
declare const instanceType: ec2.InstanceType;
292+
declare const machineImage: ec2.IMachineImage;
293+
294+
const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', {
295+
vpc,
296+
instanceType,
297+
machineImage,
298+
blockDevices: [{
299+
{
300+
deviceName: 'gp3-volume',
301+
volume: autoscaling.BlockDeviceVolume.ebs(15, {
302+
volumeType: autoscaling.EbsDeviceVolumeType.GP3,
303+
throughput: 125,
304+
}),
305+
},
306+
}],
307+
// ...
308+
});
309+
```
310+
281311
## Configuring Instances using CloudFormation Init
282312

283313
It is possible to use the CloudFormation Init mechanism to configure the

Diff for: packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -2167,7 +2167,31 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevices: Block
21672167
}
21682168

21692169
if (ebs) {
2170-
const { iops, volumeType } = ebs;
2170+
const { iops, volumeType, throughput } = ebs;
2171+
2172+
if (throughput) {
2173+
const throughputRange = { Min: 125, Max: 1000 };
2174+
const { Min, Max } = throughputRange;
2175+
2176+
if (volumeType != EbsDeviceVolumeType.GP3) {
2177+
throw new Error('throughput property requires volumeType: EbsDeviceVolumeType.GP3');
2178+
}
2179+
2180+
if (throughput < Min || throughput > Max) {
2181+
throw new Error(
2182+
`throughput property takes a minimum of ${Min} and a maximum of ${Max}`,
2183+
);
2184+
}
2185+
2186+
const maximumThroughputRatio = 0.25;
2187+
if (iops) {
2188+
const iopsRatio = (throughput / iops);
2189+
if (iopsRatio > maximumThroughputRatio) {
2190+
throw new Error(`Throughput (MiBps) to iops ratio of ${iopsRatio} is too high; maximum is ${maximumThroughputRatio} MiBps per iops`);
2191+
}
2192+
}
2193+
}
2194+
21712195

21722196
if (!iops) {
21732197
if (volumeType === EbsDeviceVolumeType.IO1) {

Diff for: packages/@aws-cdk/aws-autoscaling/lib/volume.ts

+8
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ export interface EbsDeviceOptionsBase {
6666
* @default {@link EbsDeviceVolumeType.GP2}
6767
*/
6868
readonly volumeType?: EbsDeviceVolumeType;
69+
70+
/**
71+
* The throughput that the volume supports, in MiB/s
72+
* Takes a minimum of 125 and maximum of 1000.
73+
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html
74+
* @default - 125 MiB/s. Only valid on gp3 volumes.
75+
*/
76+
readonly throughput?: number;
6977
}
7078

7179
/**

Diff for: packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts

+79
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,12 @@ describe('auto scaling group', () => {
699699
}, {
700700
deviceName: 'none',
701701
volume: autoscaling.BlockDeviceVolume.noDevice(),
702+
}, {
703+
deviceName: 'gp3-with-throughput',
704+
volume: autoscaling.BlockDeviceVolume.ebs(15, {
705+
volumeType: autoscaling.EbsDeviceVolumeType.GP3,
706+
throughput: 350,
707+
}),
702708
}],
703709
});
704710

@@ -739,6 +745,14 @@ describe('auto scaling group', () => {
739745
DeviceName: 'none',
740746
NoDevice: true,
741747
},
748+
{
749+
DeviceName: 'gp3-with-throughput',
750+
Ebs: {
751+
VolumeSize: 15,
752+
VolumeType: 'gp3',
753+
Throughput: 350,
754+
},
755+
},
742756
],
743757
});
744758
});
@@ -809,6 +823,71 @@ describe('auto scaling group', () => {
809823
}).toThrow(/maxInstanceLifetime must be between 1 and 365 days \(inclusive\)/);
810824
});
811825

826+
test.each([124, 1001])('throws if throughput is set less than 125 or more than 1000', (throughput) => {
827+
const stack = new cdk.Stack();
828+
const vpc = mockVpc(stack);
829+
830+
expect(() => {
831+
new autoscaling.AutoScalingGroup(stack, 'MyStack', {
832+
machineImage: new ec2.AmazonLinuxImage(),
833+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO),
834+
vpc,
835+
maxInstanceLifetime: cdk.Duration.days(0),
836+
blockDevices: [{
837+
deviceName: 'ebs',
838+
volume: autoscaling.BlockDeviceVolume.ebs(15, {
839+
volumeType: autoscaling.EbsDeviceVolumeType.GP3,
840+
throughput,
841+
}),
842+
}],
843+
});
844+
}).toThrow(/throughput property takes a minimum of 125 and a maximum of 1000/);
845+
});
846+
847+
test.each([
848+
...Object.values(autoscaling.EbsDeviceVolumeType).filter((v) => v !== 'gp3'),
849+
])('throws if throughput is set on any volume type other than GP3', (volumeType) => {
850+
const stack = new cdk.Stack();
851+
const vpc = mockVpc(stack);
852+
853+
expect(() => {
854+
new autoscaling.AutoScalingGroup(stack, 'MyStack', {
855+
machineImage: new ec2.AmazonLinuxImage(),
856+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO),
857+
vpc,
858+
maxInstanceLifetime: cdk.Duration.days(0),
859+
blockDevices: [{
860+
deviceName: 'ebs',
861+
volume: autoscaling.BlockDeviceVolume.ebs(15, {
862+
volumeType: volumeType,
863+
throughput: 150,
864+
}),
865+
}],
866+
});
867+
}).toThrow(/throughput property requires volumeType: EbsDeviceVolumeType.GP3/);
868+
});
869+
870+
test('throws if throughput / iops ratio is greater than 0.25', () => {
871+
const stack = new cdk.Stack();
872+
const vpc = mockVpc(stack);
873+
expect(() => {
874+
new autoscaling.AutoScalingGroup(stack, 'MyStack', {
875+
machineImage: new ec2.AmazonLinuxImage(),
876+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO),
877+
vpc,
878+
maxInstanceLifetime: cdk.Duration.days(0),
879+
blockDevices: [{
880+
deviceName: 'ebs',
881+
volume: autoscaling.BlockDeviceVolume.ebs(15, {
882+
volumeType: autoscaling.EbsDeviceVolumeType.GP3,
883+
throughput: 751,
884+
iops: 3000,
885+
}),
886+
}],
887+
});
888+
}).toThrow('Throughput (MiBps) to iops ratio of 0.25033333333333335 is too high; maximum is 0.25 MiBps per iops');
889+
});
890+
812891
test('can configure instance monitoring', () => {
813892
// GIVEN
814893
const stack = new cdk.Stack();

Diff for: packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/aws-cdk-asg-integ.assets.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"version": "20.0.0",
2+
"version": "21.0.0",
33
"files": {
4-
"cb1b7cc4cd8286ab836dcbb42f408e2c39d60c34a017d1dac4b998c1891d843b": {
4+
"2ca8f144c3e288148d58c9b9e86c9034f6a72b09cecffac3a5d406f8f53d5b18": {
55
"source": {
66
"path": "aws-cdk-asg-integ.template.json",
77
"packaging": "file"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "cb1b7cc4cd8286ab836dcbb42f408e2c39d60c34a017d1dac4b998c1891d843b.json",
12+
"objectKey": "2ca8f144c3e288148d58c9b9e86c9034f6a72b09cecffac3a5d406f8f53d5b18.json",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}

Diff for: packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/aws-cdk-asg-integ.template.json

+128
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,130 @@
625625
"IgnoreUnmodifiedGroupSizeProperties": true
626626
}
627627
}
628+
},
629+
"AsgWithGp3BlockdeviceInstanceSecurityGroup54D76206": {
630+
"Type": "AWS::EC2::SecurityGroup",
631+
"Properties": {
632+
"GroupDescription": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceSecurityGroup",
633+
"SecurityGroupEgress": [
634+
{
635+
"CidrIp": "0.0.0.0/0",
636+
"Description": "Allow all outbound traffic by default",
637+
"IpProtocol": "-1"
638+
}
639+
],
640+
"Tags": [
641+
{
642+
"Key": "Name",
643+
"Value": "aws-cdk-asg-integ/AsgWithGp3Blockdevice"
644+
}
645+
],
646+
"VpcId": {
647+
"Ref": "VPCB9E5F0B4"
648+
}
649+
}
650+
},
651+
"AsgWithGp3BlockdeviceInstanceRoleF52FB39B": {
652+
"Type": "AWS::IAM::Role",
653+
"Properties": {
654+
"AssumeRolePolicyDocument": {
655+
"Statement": [
656+
{
657+
"Action": "sts:AssumeRole",
658+
"Effect": "Allow",
659+
"Principal": {
660+
"Service": "ec2.amazonaws.com"
661+
}
662+
}
663+
],
664+
"Version": "2012-10-17"
665+
},
666+
"Tags": [
667+
{
668+
"Key": "Name",
669+
"Value": "aws-cdk-asg-integ/AsgWithGp3Blockdevice"
670+
}
671+
]
672+
}
673+
},
674+
"AsgWithGp3BlockdeviceInstanceProfile2FC414A5": {
675+
"Type": "AWS::IAM::InstanceProfile",
676+
"Properties": {
677+
"Roles": [
678+
{
679+
"Ref": "AsgWithGp3BlockdeviceInstanceRoleF52FB39B"
680+
}
681+
]
682+
}
683+
},
684+
"AsgWithGp3BlockdeviceLaunchConfig24411F5E": {
685+
"Type": "AWS::AutoScaling::LaunchConfiguration",
686+
"Properties": {
687+
"ImageId": {
688+
"Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter"
689+
},
690+
"InstanceType": "t3.micro",
691+
"BlockDeviceMappings": [
692+
{
693+
"DeviceName": "ebs",
694+
"Ebs": {
695+
"DeleteOnTermination": true,
696+
"Encrypted": true,
697+
"Throughput": 125,
698+
"VolumeSize": 15,
699+
"VolumeType": "gp3"
700+
}
701+
}
702+
],
703+
"IamInstanceProfile": {
704+
"Ref": "AsgWithGp3BlockdeviceInstanceProfile2FC414A5"
705+
},
706+
"SecurityGroups": [
707+
{
708+
"Fn::GetAtt": [
709+
"AsgWithGp3BlockdeviceInstanceSecurityGroup54D76206",
710+
"GroupId"
711+
]
712+
}
713+
],
714+
"UserData": {
715+
"Fn::Base64": "#!/bin/bash"
716+
}
717+
},
718+
"DependsOn": [
719+
"AsgWithGp3BlockdeviceInstanceRoleF52FB39B"
720+
]
721+
},
722+
"AsgWithGp3BlockdeviceASGE82AA487": {
723+
"Type": "AWS::AutoScaling::AutoScalingGroup",
724+
"Properties": {
725+
"MaxSize": "10",
726+
"MinSize": "0",
727+
"DesiredCapacity": "5",
728+
"LaunchConfigurationName": {
729+
"Ref": "AsgWithGp3BlockdeviceLaunchConfig24411F5E"
730+
},
731+
"Tags": [
732+
{
733+
"Key": "Name",
734+
"PropagateAtLaunch": true,
735+
"Value": "aws-cdk-asg-integ/AsgWithGp3Blockdevice"
736+
}
737+
],
738+
"VPCZoneIdentifier": [
739+
{
740+
"Ref": "VPCPrivateSubnet1Subnet8BCA10E0"
741+
},
742+
{
743+
"Ref": "VPCPrivateSubnet2SubnetCFCDAA7A"
744+
}
745+
]
746+
},
747+
"UpdatePolicy": {
748+
"AutoScalingScheduledAction": {
749+
"IgnoreUnmodifiedGroupSizeProperties": true
750+
}
751+
}
628752
}
629753
},
630754
"Parameters": {
@@ -636,6 +760,10 @@
636760
"Type": "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>",
637761
"Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2"
638762
},
763+
"SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": {
764+
"Type": "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>",
765+
"Default": "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2"
766+
},
639767
"BootstrapVersion": {
640768
"Type": "AWS::SSM::Parameter::Value<String>",
641769
"Default": "/cdk-bootstrap/hnb659fds/version",
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"version":"20.0.0"}
1+
{"version":"21.0.0"}

Diff for: packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.js.snapshot/integ.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "20.0.0",
2+
"version": "21.0.0",
33
"testCases": {
44
"integ.asg-lt": {
55
"stacks": [

0 commit comments

Comments
 (0)