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): instance maintenance policy for AutoScalingGroup #28092

Merged
merged 20 commits into from
Dec 22, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
Tokenization, withResolved,
} from '../../core';
import { AUTOSCALING_GENERATE_LAUNCH_TEMPLATE } from '../../cx-api';
import { max } from 'lodash';

/**
* Name tag constant
Expand Down Expand Up @@ -575,6 +576,45 @@ export interface LaunchTemplateOverrides {
readonly weightedCapacity?: number
}

/**
* InstanceMaintenancePolicy allows you to configure an instance maintenance policy for your Auto Scaling group to
* meet specific capacity requirements during events that cause instances to be replaced, such as an instance
* refresh or the health check process.
*
* https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-instance-maintenance-policy.html
*/
export interface InstanceMaintenancePolicy {
/**
* Specifies the upper threshold as a percentage of the desired capacity of the Auto Scaling group.
* It represents the maximum percentage of the group that can be in service and healthy, or pending,
* to support your workload when replacing instances.
*
* Value range is 100 to 200. After it's set, a value of -1 will clear the previously set value.
*
* Both MinHealthyPercentage and MaxHealthyPercentage must be specified, and the difference between
* them cannot be greater than 100. A large range increases the number of instances that can be
* replaced at the same time.
*
* @default - no value.
*/
readonly maxHealthyPercentage?: number;

/**
* Specifies the lower threshold as a percentage of the desired capacity of the Auto Scaling group.
* It represents the minimum percentage of the group to keep in service, healthy, and ready to use
* to support your workload when replacing instances.
*
* Value range is 0 to 100. After it's set, a value of -1 will clear the previously set value.
*
* Both MinHealthyPercentage and MaxHealthyPercentage must be specified, and the difference between
* them cannot be greater than 100. A large range increases the number of instances that can be
* replaced at the same time.
*
* @default - no value.
*/
readonly minHealthyPercentage?: number;
}

/**
* Properties of a Fleet
*/
Expand Down Expand Up @@ -686,6 +726,13 @@ export interface AutoScalingGroupProps extends CommonAutoScalingGroupProps {
* @default false
*/
readonly requireImdsv2?: boolean;

/**
* An instance maintenance policy.
*
* @default - no policy.
*/
readonly instanceMaintenancePolicy?: InstanceMaintenancePolicy;
}

/**
Expand Down Expand Up @@ -1408,6 +1455,8 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
}));
}

this.validateInstanceMaintenancePolicy(props.instanceMaintenancePolicy);

const { subnetIds, hasPublic } = props.vpc.selectSubnets(props.vpcSubnets);
const asgProps: CfnAutoScalingGroupProps = {
autoScalingGroupName: this.physicalName,
Expand All @@ -1427,6 +1476,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
terminationPolicies: props.terminationPolicies,
defaultInstanceWarmup: props.defaultInstanceWarmup?.toSeconds(),
capacityRebalance: props.capacityRebalance,
instanceMaintenancePolicy: props.instanceMaintenancePolicy,
...this.getLaunchSettings(launchConfig, props.launchTemplate ?? launchTemplateFromConfig, props.mixedInstancesPolicy),
};

Expand Down Expand Up @@ -1840,6 +1890,43 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements

return errors;
}

private validateInstanceMaintenancePolicy(policy: InstanceMaintenancePolicy | undefined) {
if (!policy) {
return;
}

const maxHealthyPercentage = policy.maxHealthyPercentage
const minHealthyPercentage = policy.minHealthyPercentage

if (
maxHealthyPercentage !== undefined
&& maxHealthyPercentage !== -1
&& (maxHealthyPercentage < 100 || maxHealthyPercentage > 200)
) {
throw new Error(`maxHealthyPercentage must be between 100 and 200, or -1 to clear the previously set value, got ${maxHealthyPercentage}`);
}
if (
minHealthyPercentage !== undefined
&& minHealthyPercentage !== -1
&& (minHealthyPercentage < 0 || minHealthyPercentage > 100)
) {
throw new Error(`minHealthyPercentage must be between 0 and 100, or -1 to clear the previously set value, got ${minHealthyPercentage}`);
}
if (
(maxHealthyPercentage !== undefined && minHealthyPercentage === undefined)
|| (maxHealthyPercentage === undefined && minHealthyPercentage !== undefined)
) {
throw new Error(`Both minHealthyPercentage and maxHealthyPercentage must be specified, got minHealthyPercentage: ${minHealthyPercentage} and maxHealthyPercentage: ${maxHealthyPercentage}`);
}
if (
maxHealthyPercentage !== undefined
&& minHealthyPercentage !== undefined
&& maxHealthyPercentage - minHealthyPercentage > 100
) {
throw new Error(`The difference between minHealthyPercentage and maxHealthyPercentage cannot be greater than 100, got ${maxHealthyPercentage - minHealthyPercentage}`);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can store all errors we get in an array and throw them all at the same time, so the user will know all the issues present.

Copy link
Contributor Author

@go-to-k go-to-k Dec 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, is that necessary?

I think the information given to the user would be more complex, and type inference would not work and branching would become more complicated.

(Because of the eslint error, in numerical comparisons, minHealthyPercentage !== undefined and maxHealthyPercentage !== undefined must be added to the numerical comparison.)

    const errors: string[] = [];

    if (minHealthyPercentage === undefined && maxHealthyPercentage === undefined) return;
    if (minHealthyPercentage === undefined || maxHealthyPercentage === undefined) {
      errors.push(`Both or neither of minHealthyPercentage and maxHealthyPercentage must be specified, got minHealthyPercentage: ${minHealthyPercentage} and maxHealthyPercentage: ${maxHealthyPercentage}`);
    }
    if ((minHealthyPercentage === -1 || maxHealthyPercentage === -1) && minHealthyPercentage !== maxHealthyPercentage) {
      errors.push(`Both minHealthyPercentage and maxHealthyPercentage must be -1 to clear the previously set value, got minHealthyPercentage: ${minHealthyPercentage} and maxHealthyPercentage: ${maxHealthyPercentage}`);
    }
    if (minHealthyPercentage !== -1 && minHealthyPercentage !== undefined && (minHealthyPercentage < 0 || minHealthyPercentage > 100)) {
      errors.push(`minHealthyPercentage must be between 0 and 100, or -1 to clear the previously set value, got ${minHealthyPercentage}`);
    }
    if (maxHealthyPercentage !== -1 && maxHealthyPercentage !== undefined && (maxHealthyPercentage < 0 || maxHealthyPercentage > 100)) {
      errors.push(`maxHealthyPercentage must be between 100 and 200, or -1 to clear the previously set value, got ${maxHealthyPercentage}`);
    }
    if (maxHealthyPercentage !== undefined && minHealthyPercentage !== undefined && maxHealthyPercentage - minHealthyPercentage > 100) {
      errors.push(`The difference between minHealthyPercentage and maxHealthyPercentage cannot be greater than 100, got ${maxHealthyPercentage - minHealthyPercentage}`);
    }

    if (errors.length > 0) {
      throw new Error(errors.join('\n'));
    }

    return {
      minHealthyPercentage,
      maxHealthyPercentage,
    };

}
}

/**
Expand Down