Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(autoscaling): support for throughput on GP3 volumes #22441

Merged
merged 8 commits into from Nov 25, 2022
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-autoscaling/README.md
Expand Up @@ -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
Expand Down
26 changes: 25 additions & 1 deletion packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts
Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions packages/@aws-cdk/aws-autoscaling/lib/volume.ts
Expand Up @@ -66,6 +66,14 @@ export interface EbsDeviceOptionsBase {
* @default {@link EbsDeviceVolumeType.GP2}
*/
readonly volumeType?: EbsDeviceVolumeType;

/**
* The throughput that the volume supports, in MiB/s
csumpter marked this conversation as resolved.
Show resolved Hide resolved
* 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;
}

/**
Expand Down
79 changes: 79 additions & 0 deletions packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts
Expand Up @@ -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,
}),
}],
});

Expand Down Expand Up @@ -739,6 +745,14 @@ describe('auto scaling group', () => {
DeviceName: 'none',
NoDevice: true,
},
{
DeviceName: 'gp3-with-throughput',
Ebs: {
VolumeSize: 15,
VolumeType: 'gp3',
Throughput: 350,
},
},
],
});
});
Expand Down Expand Up @@ -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();
Expand Down
@@ -1,15 +1,15 @@
{
"version": "20.0.0",
"version": "21.0.0",
"files": {
"cb1b7cc4cd8286ab836dcbb42f408e2c39d60c34a017d1dac4b998c1891d843b": {
"2ca8f144c3e288148d58c9b9e86c9034f6a72b09cecffac3a5d406f8f53d5b18": {
"source": {
"path": "aws-cdk-asg-integ.template.json",
"packaging": "file"
},
"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}"
}
}
Expand Down
Expand Up @@ -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": {
Expand All @@ -636,6 +760,10 @@
"Type": "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>",
"Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2"
},
"SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": {
"Type": "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>",
"Default": "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2"
},
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
Expand Down
@@ -1 +1 @@
{"version":"20.0.0"}
{"version":"21.0.0"}
@@ -1,5 +1,5 @@
{
"version": "20.0.0",
"version": "21.0.0",
"testCases": {
"integ.asg-lt": {
"stacks": [
Expand Down