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