in src/constructs/autoscaling/asg.ts [84:185]
constructor(scope: GuStack, id: string, props: GuAutoScalingGroupProps) {
const {
app,
additionalSecurityGroups = [],
blockDevices,
imageId = new GuAmiParameter(scope, { app }),
imageRecipe,
instanceType,
groupMetrics = [GroupMetrics.all()],
minimumInstances,
maximumInstances,
role = new GuInstanceRole(scope, { app }),
targetGroup,
userData,
vpc,
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.
if (Token.isUnresolved(minimumInstances) && !Token.isUnresolved(maximumInstances)) {
throw new Error(
"minimumInstances is defined via a Mapping, but maximumInstances is not. Create maximumInstances via a Mapping too.",
);
}
// Generate an ID unique to this app
const launchTemplateId = `${scope.stack}-${scope.stage}-${app}`;
const launchTemplate = new LaunchTemplate(scope, launchTemplateId, {
blockDevices,
detailedMonitoring: enabledDetailedInstanceMonitoring,
instanceType,
machineImage: {
getImage: (): MachineImageConfig => {
return {
osType: OperatingSystemType.LINUX,
userData,
imageId: imageId.valueAsString,
};
},
},
// Do not use the default AWS security group which allows egress on any port.
// Favour HTTPS only egress rules by default.
securityGroup: GuHttpsEgressSecurityGroup.forVpc(scope, { app, vpc }),
/*
Ensure we satisfy FSBP EC2.8 control. If needed, an escape hatch can override this.
See https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-8.
*/
requireImdsv2: true,
instanceMetadataTags: true,
userData,
role,
httpPutResponseHopLimit,
});
// Add additional consumer specified Security Groups
// Note: Launch templates via CDK allow specifying only one SG, so use connections
// https://github.com/aws/aws-cdk/issues/18712
additionalSecurityGroups.forEach((sg) => launchTemplate.connections.addSecurityGroup(sg));
const asgProps: AutoScalingGroupProps = {
...props,
launchTemplate,
maxCapacity: maximumInstances ?? minimumInstances * 2,
minCapacity: minimumInstances,
groupMetrics: groupMetrics,
// Omit userData, instanceType, blockDevices & role from asgProps
// As this are specified by the LaunchTemplate and must not be duplicated
blockDevices: undefined,
instanceType: undefined,
role: undefined,
userData: undefined,
defaultInstanceWarmup,
};
super(scope, id, asgProps);
this.app = app;
this.amiParameter = imageId;
this.imageRecipe = imageRecipe;
this.instanceLaunchTemplate = launchTemplate;
if (targetGroup) {
this.attachToApplicationTargetGroup(targetGroup);
}
const cfnAsg = this.node.defaultChild as CfnAutoScalingGroup;
// A CDK AutoScalingGroup comes with this update policy, whereas the CFN autscaling group
// leaves it to the default value, which is actually false.
// { UpdatePolicy: { autoScalingScheduledAction: { IgnoreUnmodifiedGroupSizeProperties: true }}
if (!updatePolicy) {
cfnAsg.addDeletionOverride("UpdatePolicy");
}
Tags.of(launchTemplate).add("App", app);
}