diff --git a/.changeset/nice-roses-grab.md b/.changeset/nice-roses-grab.md new file mode 100644 index 000000000..9b1389195 --- /dev/null +++ b/.changeset/nice-roses-grab.md @@ -0,0 +1,5 @@ +--- +"@guardian/cdk": minor +--- + +feat(asg): Allow setting the defaultInstanceWarmup option on auto scaling groups provisioned by our EC2 pattern diff --git a/src/constructs/autoscaling/asg.ts b/src/constructs/autoscaling/asg.ts index 657960977..03f9d538f 100644 --- a/src/constructs/autoscaling/asg.ts +++ b/src/constructs/autoscaling/asg.ts @@ -1,3 +1,4 @@ +import type { Duration } from "aws-cdk-lib"; import { Tags, Token } from "aws-cdk-lib"; import { AutoScalingGroup, GroupMetric, GroupMetrics } from "aws-cdk-lib/aws-autoscaling"; import type { AutoScalingGroupProps, CfnAutoScalingGroup } from "aws-cdk-lib/aws-autoscaling"; @@ -51,6 +52,7 @@ export interface GuAutoScalingGroupProps withoutImdsv2?: boolean; httpPutResponseHopLimit?: number; enabledDetailedInstanceMonitoring?: boolean; + defaultInstanceWarmup?: Duration; } /** @@ -97,6 +99,7 @@ export class GuAutoScalingGroup extends GuAppAwareConstruct(AutoScalingGroup) { httpPutResponseHopLimit, updatePolicy, enabledDetailedInstanceMonitoring, + defaultInstanceWarmup, } = props; // Ensure min and max are defined in the same way. Throwing an `Error` when necessary. For example when min is defined via a Mapping, but max is not. @@ -137,7 +140,7 @@ export class GuAutoScalingGroup extends GuAppAwareConstruct(AutoScalingGroup) { launchTemplate.connections.addSecurityGroup(sg), ); - const asgProps = { + const asgProps: AutoScalingGroupProps = { ...props, launchTemplate, maxCapacity: maximumInstances ?? minimumInstances * 2, @@ -150,6 +153,7 @@ export class GuAutoScalingGroup extends GuAppAwareConstruct(AutoScalingGroup) { instanceType: undefined, role: undefined, userData: undefined, + defaultInstanceWarmup, }; super(scope, id, asgProps); diff --git a/src/patterns/ec2-app/base.test.ts b/src/patterns/ec2-app/base.test.ts index 6bf343f46..5f970d8d5 100644 --- a/src/patterns/ec2-app/base.test.ts +++ b/src/patterns/ec2-app/base.test.ts @@ -1,3 +1,4 @@ +import { Duration } from "aws-cdk-lib"; import { Match, Template } from "aws-cdk-lib/assertions"; import { BlockDeviceVolume, EbsDeviceVolumeType, UpdatePolicy } from "aws-cdk-lib/aws-autoscaling"; import { InstanceClass, InstanceSize, InstanceType, Peer, Port, UserData, Vpc } from "aws-cdk-lib/aws-ec2"; @@ -1151,4 +1152,29 @@ UserData from accessed construct`); }, }); }); + + it("set defaultInstanceWarmup on the ASG when set", function () { + const stack = simpleGuStackForTesting(); + new GuEc2App(stack, { + applicationPort: 3000, + app: "test-gu-ec2-app", + access: { scope: AccessScope.PUBLIC }, + instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.MEDIUM), + monitoringConfiguration: { noMonitoring: true }, + userData: UserData.forLinux(), + certificateProps: { + domainName: "domain-name-for-your-application.example", + }, + scaling: { + minimumInstances: 1, + }, + enabledDetailedInstanceMonitoring: true, + defaultInstanceWarmup: Duration.minutes(2), + }); + Template.fromStack(stack).hasResource("AWS::AutoScaling::AutoScalingGroup", { + Properties: { + DefaultInstanceWarmup: 120, + }, + }); + }); }); diff --git a/src/patterns/ec2-app/base.ts b/src/patterns/ec2-app/base.ts index a8952ba3c..f86002206 100644 --- a/src/patterns/ec2-app/base.ts +++ b/src/patterns/ec2-app/base.ts @@ -315,6 +315,15 @@ export interface GuEc2AppProps extends AppIdentity { * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-monitoring.html */ enabledDetailedInstanceMonitoring?: boolean; + /** + * You can specify how long after an instance reaches the InService state it waits before contributing + * usage data to the aggregated metrics. This specified time is called the default instance warmup. + * This keeps dynamic scaling from being affected by metrics for individual instances that aren't yet + * handling application traffic and that might be experiencing temporarily high usage of compute resources. + * + * @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-default-instance-warmup.html + */ + defaultInstanceWarmup?: Duration; } function restrictedCidrRanges(ranges: IPeer[]) { @@ -371,6 +380,7 @@ export class GuEc2App extends Construct { instanceMetadataHopLimit, updatePolicy, enabledDetailedInstanceMonitoring, + defaultInstanceWarmup, } = props; super(scope, app); // The assumption is `app` is unique @@ -423,6 +433,7 @@ export class GuEc2App extends Construct { httpPutResponseHopLimit: instanceMetadataHopLimit, updatePolicy, enabledDetailedInstanceMonitoring, + defaultInstanceWarmup, }); // This allows automatic shipping of instance Cloud Init logs when using the