diff --git a/packages/@aws-cdk/aws-autoscaling/README.md b/packages/@aws-cdk/aws-autoscaling/README.md index 8b61d74c27e61..c8c7667ac5a5a 100644 --- a/packages/@aws-cdk/aws-autoscaling/README.md +++ b/packages/@aws-cdk/aws-autoscaling/README.md @@ -295,7 +295,7 @@ const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', { vpc, instanceType, machineImage, - blockDevices: [{ + blockDevices: [ { deviceName: 'gp3-volume', volume: autoscaling.BlockDeviceVolume.ebs(15, { @@ -303,7 +303,7 @@ const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', { throughput: 125, }), }, - }], + ], // ... }); ``` @@ -574,6 +574,35 @@ autoScalingGroup.addWarmPool({ }); ``` +### Default Instance Warming + +You can use the default instance warmup feature to improve the Amazon CloudWatch metrics used for dynamic scaling. +When default instance warmup is not enabled, each instance starts contributing usage data to the aggregated metrics +as soon as the instance reaches the InService state. However, if you enable default instance warmup, this lets +your instances finish warming up before they contribute the usage data. + +To optimize the performance of scaling policies that scale continuously, such as target tracking and step scaling +policies, we strongly recommend that you enable the default instance warmup, even if its value is set to 0 seconds. + +To set up Default Instance Warming for an autoscaling group, simply pass it in as a prop + +```ts +declare const vpc: ec2.Vpc; +declare const instanceType: ec2.InstanceType; +declare const machineImage: ec2.IMachineImage; + + +new autoscaling.AutoScalingGroup(this, 'ASG', { + vpc, + instanceType, + machineImage, + + // ... + + defaultInstanceWarmup: Duration.seconds(5), +}); +``` + ## Future work * [ ] CloudWatch Events (impossible to add currently as the AutoScalingGroup ARN is 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 df442537b7232..c70c32271c331 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -336,6 +336,23 @@ export interface CommonAutoScalingGroupProps { * @default - `TerminationPolicy.DEFAULT` */ readonly terminationPolicies?: TerminationPolicy[]; + + /** + * The amount of time, in seconds, until a newly launched instance can contribute to the Amazon CloudWatch metrics. + * This delay lets an instance finish initializing before Amazon EC2 Auto Scaling aggregates instance metrics, + * resulting in more reliable usage data. Set this value equal to the amount of time that it takes for resource + * consumption to become stable after an instance reaches the InService state. + * + * To optimize the performance of scaling policies that scale continuously, such as target tracking and + * step scaling policies, we strongly recommend that you enable the default instance warmup, even if its value is set to 0 seconds + * + * Default instance warmup will not be added if no value is specified + * + * @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-default-instance-warmup.html + * + * @default None + */ + readonly defaultInstanceWarmup?: Duration; } /** @@ -1344,6 +1361,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements maxInstanceLifetime: this.maxInstanceLifetime ? this.maxInstanceLifetime.toSeconds() : undefined, newInstancesProtectedFromScaleIn: Lazy.any({ produce: () => this.newInstancesProtectedFromScaleIn }), terminationPolicies: props.terminationPolicies, + defaultInstanceWarmup: props.defaultInstanceWarmup?.toSeconds(), ...this.getLaunchSettings(launchConfig, props.launchTemplate, props.mixedInstancesPolicy), }; 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 48062024b9fb4..553ed50b81dc4 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 @@ -286,6 +286,25 @@ describe('auto scaling group', () => { }); }); + test('can specify only defaultInstanceWarmup', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + + // WHEN + new autoscaling.AutoScalingGroup(stack, 'MyFleet', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + vpc, + defaultInstanceWarmup: cdk.Duration.seconds(5), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { + DefaultInstanceWarmup: 5, + }); + }); + test('addToRolePolicy can be used to add statements to the role policy', () => { const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' } }); const vpc = mockVpc(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 82db7bdafa693..6f9a7efb4533c 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": "21.0.0", + "version": "22.0.0", "files": { - "2ca8f144c3e288148d58c9b9e86c9034f6a72b09cecffac3a5d406f8f53d5b18": { + "768b8ff8b1178a04dbfca488da9459f4f402bfad643db0b4791787ef23ec4db5": { "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": "2ca8f144c3e288148d58c9b9e86c9034f6a72b09cecffac3a5d406f8f53d5b18.json", + "objectKey": "768b8ff8b1178a04dbfca488da9459f4f402bfad643db0b4791787ef23ec4db5.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 d063936bdcbbb..0698ba8c6c24d 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 @@ -507,6 +507,38 @@ } } }, + "AsgWithDefaultInstanceWarmupASG7A481C66": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "DefaultInstanceWarmup": 5, + "LaunchTemplate": { + "LaunchTemplateId": { + "Ref": "MainLT4FC09097" + }, + "Version": { + "Fn::GetAtt": [ + "MainLT4FC09097", + "LatestVersionNumber" + ] + } + }, + "VPCZoneIdentifier": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + }, + "UpdatePolicy": { + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, "AsgFromMipASG9EFCE2FC": { "Type": "AWS::AutoScaling::AutoScalingGroup", "Properties": { 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 8ecc185e9dbee..145739f539580 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":"21.0.0"} \ No newline at end of file +{"version":"22.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 b2a0249ca14c8..d0ed5ee3ba231 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": "21.0.0", + "version": "22.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 626c4977f059b..2aaf97a525a2a 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,12 +1,6 @@ { - "version": "21.0.0", + "version": "22.0.0", "artifacts": { - "Tree": { - "type": "cdk:tree", - "properties": { - "file": "tree.json" - } - }, "aws-cdk-asg-integ.assets": { "type": "cdk:asset-manifest", "properties": { @@ -23,7 +17,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}/2ca8f144c3e288148d58c9b9e86c9034f6a72b09cecffac3a5d406f8f53d5b18.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/768b8ff8b1178a04dbfca488da9459f4f402bfad643db0b4791787ef23ec4db5.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -213,6 +207,12 @@ "data": "AsgFromLTASG089B7C3A" } ], + "/aws-cdk-asg-integ/AsgWithDefaultInstanceWarmup/ASG": [ + { + "type": "aws:cdk:logicalId", + "data": "AsgWithDefaultInstanceWarmupASG7A481C66" + } + ], "/aws-cdk-asg-integ/AsgFromMip": [ { "type": "aws:cdk:warning", @@ -293,6 +293,12 @@ ] }, "displayName": "aws-cdk-asg-integ" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } } } } \ No newline at end of file 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 10f3be8834c97..a5f63ed131aae 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 @@ -4,14 +4,6 @@ "id": "App", "path": "", "children": { - "Tree": { - "id": "Tree", - "path": "Tree", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.140" - } - }, "aws-cdk-asg-integ": { "id": "aws-cdk-asg-integ", "path": "aws-cdk-asg-integ", @@ -856,6 +848,51 @@ "version": "0.0.0" } }, + "AsgWithDefaultInstanceWarmup": { + "id": "AsgWithDefaultInstanceWarmup", + "path": "aws-cdk-asg-integ/AsgWithDefaultInstanceWarmup", + "children": { + "ASG": { + "id": "ASG", + "path": "aws-cdk-asg-integ/AsgWithDefaultInstanceWarmup/ASG", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AutoScaling::AutoScalingGroup", + "aws:cdk:cloudformation:props": { + "maxSize": "1", + "minSize": "1", + "defaultInstanceWarmup": 5, + "launchTemplate": { + "launchTemplateId": { + "Ref": "MainLT4FC09097" + }, + "version": { + "Fn::GetAtt": [ + "MainLT4FC09097", + "LatestVersionNumber" + ] + } + }, + "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" + } + }, "AsgFromMip": { "id": "AsgFromMip", "path": "aws-cdk-asg-integ/AsgFromMip", @@ -1049,6 +1086,14 @@ "id": "InstanceRole", "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceRole", "children": { + "ImportInstanceRole": { + "id": "ImportInstanceRole", + "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceRole/ImportInstanceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "aws-cdk-asg-integ/AsgWithGp3Blockdevice/InstanceRole/Resource", @@ -1202,12 +1247,36 @@ "fqn": "@aws-cdk/core.Resource", "version": "0.0.0" } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-cdk-asg-integ/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-cdk-asg-integ/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } } }, "constructInfo": { "fqn": "@aws-cdk/core.Stack", "version": "0.0.0" } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.168" + } } }, "constructInfo": { 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 d215e03d344bf..3e07a7b1e093b 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-lt.ts @@ -34,6 +34,12 @@ new autoscaling.AutoScalingGroup(stack, 'AsgFromLT', { desiredCapacity: 5, }); +new autoscaling.AutoScalingGroup(stack, 'AsgWithDefaultInstanceWarmup', { + vpc, + launchTemplate: lt, + defaultInstanceWarmup: cdk.Duration.seconds(5), +}); + new autoscaling.AutoScalingGroup(stack, 'AsgFromMip', { vpc, mixedInstancesPolicy: {