packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts (1,159 lines of code) (raw):
import { Construct } from 'constructs';
import { AutoScalingGroupRequireImdsv2Aspect } from './aspects';
import { CfnAutoScalingGroup, CfnAutoScalingGroupProps, CfnLaunchConfiguration } from './autoscaling.generated';
import { BasicLifecycleHookProps, LifecycleHook } from './lifecycle-hook';
import { BasicScheduledActionProps, ScheduledAction } from './scheduled-action';
import { BasicStepScalingPolicyProps, StepScalingPolicy } from './step-scaling-policy';
import { BaseTargetTrackingProps, PredefinedMetric, TargetTrackingScalingPolicy } from './target-tracking-scaling-policy';
import { TerminationPolicy } from './termination-policy';
import { BlockDevice, BlockDeviceVolume, EbsDeviceVolumeType } from './volume';
import { WarmPool, WarmPoolOptions } from './warm-pool';
import * as cloudwatch from '../../aws-cloudwatch';
import * as ec2 from '../../aws-ec2';
import * as elb from '../../aws-elasticloadbalancing';
import * as elbv2 from '../../aws-elasticloadbalancingv2';
import * as iam from '../../aws-iam';
import * as sns from '../../aws-sns';
import {
Annotations,
Aspects,
Aws,
CfnAutoScalingRollingUpdate, CfnCreationPolicy, CfnUpdatePolicy,
Duration, FeatureFlags, Fn, IResource, Lazy, PhysicalName, Resource, Stack, Tags,
Token,
Tokenization, UnscopedValidationError, ValidationError, withResolved,
} from '../../core';
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';
import { mutatingAspectPrio32333 } from '../../core/lib/private/aspect-prio';
import { AUTOSCALING_GENERATE_LAUNCH_TEMPLATE } from '../../cx-api';
/**
* Name tag constant
*/
const NAME_TAG: string = 'Name';
/**
* The monitoring mode for instances launched in an autoscaling group
*/
export enum Monitoring {
/**
* Generates metrics every 5 minutes
*/
BASIC,
/**
* Generates metrics every minute
*/
DETAILED,
}
/**
* Basic properties of an AutoScalingGroup, except the exact machines to run and where they should run
*
* Constructs that want to create AutoScalingGroups can inherit
* this interface and specialize the essential parts in various ways.
*/
export interface CommonAutoScalingGroupProps {
/**
* Minimum number of instances in the fleet
*
* @default 1
*/
readonly minCapacity?: number;
/**
* Maximum number of instances in the fleet
*
* @default desiredCapacity
*/
readonly maxCapacity?: number;
/**
* Initial amount of instances in the fleet
*
* If this is set to a number, every deployment will reset the amount of
* instances to this number. It is recommended to leave this value blank.
*
* @default minCapacity, and leave unchanged during deployment
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-as-group-desiredcapacity
*/
readonly desiredCapacity?: number;
/**
* Name of SSH keypair to grant access to instances
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified
*
* You can either specify `keyPair` or `keyName`, not both.
*
* @default - No SSH access will be possible.
* @deprecated - Use `keyPair` instead - https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2-readme.html#using-an-existing-ec2-key-pair
*/
readonly keyName?: string;
/**
* The SSH keypair to grant access to the instance.
*
* Feature flag `AUTOSCALING_GENERATE_LAUNCH_TEMPLATE` must be enabled to use this property.
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified.
*
* You can either specify `keyPair` or `keyName`, not both.
*
* @default - No SSH access will be possible.
*/
readonly keyPair?: ec2.IKeyPair;
/**
* Where to place instances within the VPC
*
* @default - All Private subnets.
*/
readonly vpcSubnets?: ec2.SubnetSelection;
/**
* SNS topic to send notifications about fleet changes
*
* @default - No fleet change notifications will be sent.
* @deprecated use `notifications`
*/
readonly notificationsTopic?: sns.ITopic;
/**
* Configure autoscaling group to send notifications about fleet changes to an SNS topic(s)
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-as-group-notificationconfigurations
* @default - No fleet change notifications will be sent.
*/
readonly notifications?: NotificationConfiguration[];
/**
* Whether the instances can initiate connections to anywhere by default
*
* @default true
*/
readonly allowAllOutbound?: boolean;
/**
* What to do when an AutoScalingGroup's instance configuration is changed
*
* This is applied when any of the settings on the ASG are changed that
* affect how the instances should be created (VPC, instance type, startup
* scripts, etc.). It indicates how the existing instances should be
* replaced with new instances matching the new config. By default,
* `updatePolicy` takes precedence over `updateType`.
*
* @default UpdateType.REPLACING_UPDATE, unless updatePolicy has been set
* @deprecated Use `updatePolicy` instead
*/
readonly updateType?: UpdateType;
/**
* Configuration for rolling updates
*
* Only used if updateType == UpdateType.RollingUpdate.
*
* @default - RollingUpdateConfiguration with defaults.
* @deprecated Use `updatePolicy` instead
*/
readonly rollingUpdateConfiguration?: RollingUpdateConfiguration;
/**
* Configuration for replacing updates.
*
* Only used if updateType == UpdateType.ReplacingUpdate. Specifies how
* many instances must signal success for the update to succeed.
*
* @default minSuccessfulInstancesPercent
* @deprecated Use `signals` instead
*/
readonly replacingUpdateMinSuccessfulInstancesPercent?: number;
/**
* If the ASG has scheduled actions, don't reset unchanged group sizes
*
* Only used if the ASG has scheduled actions (which may scale your ASG up
* or down regardless of cdk deployments). If true, the size of the group
* will only be reset if it has been changed in the CDK app. If false, the
* sizes will always be changed back to what they were in the CDK app
* on deployment.
*
* @default true
*/
readonly ignoreUnmodifiedSizeProperties?: boolean;
/**
* How many ResourceSignal calls CloudFormation expects before the resource is considered created
*
* @default 1 if resourceSignalTimeout is set, 0 otherwise
* @deprecated Use `signals` instead.
*/
readonly resourceSignalCount?: number;
/**
* The length of time to wait for the resourceSignalCount
*
* The maximum value is 43200 (12 hours).
*
* @default Duration.minutes(5) if resourceSignalCount is set, N/A otherwise
* @deprecated Use `signals` instead.
*/
readonly resourceSignalTimeout?: Duration;
/**
* Default scaling cooldown for this AutoScalingGroup
*
* @default Duration.minutes(5)
*/
readonly cooldown?: Duration;
/**
* Whether instances in the Auto Scaling Group should have public
* IP addresses associated with them.
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified
*
* @default - Use subnet setting.
*/
readonly associatePublicIpAddress?: boolean;
/**
* The maximum hourly price (in USD) to be paid for any Spot Instance launched to fulfill the request. Spot Instances are
* launched when the price you specify exceeds the current Spot market price.
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified
*
* @default none
*/
readonly spotPrice?: string;
/**
* Configuration for health checks
*
* @default - HealthCheck.ec2 with no grace period
* @deprecated Use `healthChecks` instead
*/
readonly healthCheck?: HealthCheck;
/**
* Configuration for EC2 or additional health checks
*
* Even when using `HealthChecks.withAdditionalChecks()`, the EC2 type is implicitly included.
*
* @default - EC2 type with no grace period
* @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-health-checks.html
*/
readonly healthChecks?: HealthChecks;
/**
* Specifies how block devices are exposed to the instance. You can specify virtual devices and EBS volumes.
*
* Each instance that is launched has an associated root device volume,
* either an Amazon EBS volume or an instance store volume.
* You can use block device mappings to specify additional EBS volumes or
* instance store volumes to attach to an instance when it is launched.
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified
*
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
*
* @default - Uses the block device mapping of the AMI
*/
readonly blockDevices?: BlockDevice[];
/**
* The maximum amount of time that an instance can be in service. The maximum duration applies
* to all current and future instances in the group. As an instance approaches its maximum duration,
* it is terminated and replaced, and cannot be used again.
*
* You must specify a value of at least 604,800 seconds (7 days). To clear a previously set value,
* leave this property undefined.
*
* @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-max-instance-lifetime.html
*
* @default none
*/
readonly maxInstanceLifetime?: Duration;
/**
* Controls whether instances in this group are launched with detailed or basic monitoring.
*
* When detailed monitoring is enabled, Amazon CloudWatch generates metrics every minute and your account
* is charged a fee. When you disable detailed monitoring, CloudWatch generates metrics every 5 minutes.
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified
*
* @see https://docs.aws.amazon.com/autoscaling/latest/userguide/as-instance-monitoring.html#enable-as-instance-metrics
*
* @default - Monitoring.DETAILED
*/
readonly instanceMonitoring?: Monitoring;
/**
* Enable monitoring for group metrics, these metrics describe the group rather than any of its instances.
* To report all group metrics use `GroupMetrics.all()`
* Group metrics are reported in a granularity of 1 minute at no additional charge.
* @default - no group metrics will be reported
*
*/
readonly groupMetrics?: GroupMetrics[];
/**
* Configure waiting for signals during deployment
*
* Use this to pause the CloudFormation deployment to wait for the instances
* in the AutoScalingGroup to report successful startup during
* creation and updates. The UserData script needs to invoke `cfn-signal`
* with a success or failure code after it is done setting up the instance.
*
* Without waiting for signals, the CloudFormation deployment will proceed as
* soon as the AutoScalingGroup has been created or updated but before the
* instances in the group have been started.
*
* For example, to have instances wait for an Elastic Load Balancing health check before
* they signal success, add a health-check verification by using the
* cfn-init helper script. For an example, see the verify_instance_health
* command in the Auto Scaling rolling updates sample template:
*
* https://github.com/awslabs/aws-cloudformation-templates/blob/master/aws/services/AutoScaling/AutoScalingRollingUpdates.yaml
*
* @default - Do not wait for signals
*/
readonly signals?: Signals;
/**
* What to do when an AutoScalingGroup's instance configuration is changed
*
* This is applied when any of the settings on the ASG are changed that
* affect how the instances should be created (VPC, instance type, startup
* scripts, etc.). It indicates how the existing instances should be
* replaced with new instances matching the new config. By default, nothing
* is done and only new instances are launched with the new config.
*
* @default - `UpdatePolicy.rollingUpdate()` if using `init`, `UpdatePolicy.none()` otherwise
*/
readonly updatePolicy?: UpdatePolicy;
/**
* Whether newly-launched instances are protected from termination by Amazon
* EC2 Auto Scaling when scaling in.
*
* By default, Auto Scaling can terminate an instance at any time after launch
* when scaling in an Auto Scaling Group, subject to the group's termination
* policy. However, you may wish to protect newly-launched instances from
* being scaled in if they are going to run critical applications that should
* not be prematurely terminated.
*
* This flag must be enabled if the Auto Scaling Group will be associated with
* an ECS Capacity Provider with managed termination protection.
*
* @default false
*/
readonly newInstancesProtectedFromScaleIn?: boolean;
/**
* The name of the Auto Scaling group. This name must be unique per Region per account.
* @default - Auto generated by CloudFormation
*/
readonly autoScalingGroupName?: string;
/**
* A policy or a list of policies that are used to select the instances to
* terminate. The policies are executed in the order that you list them.
*
* @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-instance-termination.html
*
* @default - `TerminationPolicy.DEFAULT`
*/
readonly terminationPolicies?: TerminationPolicy[];
/**
* A lambda function Arn that can be used as a custom termination policy to select the instances
* to terminate. This property must be specified if the TerminationPolicy.CUSTOM_LAMBDA_FUNCTION
* is used.
*
* @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/lambda-custom-termination-policy.html
*
* @default - No lambda function Arn will be supplied
*/
readonly terminationPolicyCustomLambdaFunctionArn?: string;
/**
* 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;
/**
* Indicates whether Capacity Rebalancing is enabled. When you turn on Capacity Rebalancing, Amazon EC2 Auto Scaling
* attempts to launch a Spot Instance whenever Amazon EC2 notifies that a Spot Instance is at an elevated risk of
* interruption. After launching a new instance, it then terminates an old instance.
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-as-group-capacityrebalance
*
* @default false
*
*/
readonly capacityRebalance?: boolean;
/**
* Add SSM session permissions to the instance role
*
* Setting this to `true` adds the necessary permissions to connect
* to the instance using SSM Session Manager. You can do this
* from the AWS Console.
*
* NOTE: Setting this flag to `true` may not be enough by itself.
* You must also use an AMI that comes with the SSM Agent, or install
* the SSM Agent yourself. See
* [Working with SSM Agent](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html)
* in the SSM Developer Guide.
*
* @default false
*/
readonly ssmSessionPermissions?: boolean;
/**
* The strategy for distributing instances across Availability Zones.
* @default None
*/
readonly azCapacityDistributionStrategy?: CapacityDistributionStrategy;
}
/**
* MixedInstancesPolicy allows you to configure a group that diversifies across On-Demand Instances
* and Spot Instances of multiple instance types. For more information, see Auto Scaling groups with
* multiple instance types and purchase options in the Amazon EC2 Auto Scaling User Guide:
*
* https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-purchase-options.html
*/
export interface MixedInstancesPolicy {
/**
* InstancesDistribution to use.
*
* @default - The value for each property in it uses a default value.
*/
readonly instancesDistribution?: InstancesDistribution;
/**
* Launch template to use.
*/
readonly launchTemplate: ec2.ILaunchTemplate;
/**
* Launch template overrides.
*
* The maximum number of instance types that can be associated with an Auto Scaling group is 40.
*
* The maximum number of distinct launch templates you can define for an Auto Scaling group is 20.
*
* @default - Do not provide any overrides
*/
readonly launchTemplateOverrides?: LaunchTemplateOverrides[];
}
/**
* Indicates how to allocate instance types to fulfill On-Demand capacity.
*/
export enum OnDemandAllocationStrategy {
/**
* This strategy uses the order of instance types in the LaunchTemplateOverrides to define the launch
* priority of each instance type. The first instance type in the array is prioritized higher than the
* last. If all your On-Demand capacity cannot be fulfilled using your highest priority instance, then
* the Auto Scaling group launches the remaining capacity using the second priority instance type, and
* so on.
*/
PRIORITIZED = 'prioritized',
/**
* This strategy uses the lowest-price instance types in each Availability Zone based on the current
* On-Demand instance price.
*
* To meet your desired capacity, you might receive On-Demand Instances of more than one instance type
* in each Availability Zone. This depends on how much capacity you request.
*/
LOWEST_PRICE = 'lowest-price',
}
/**
* Indicates how to allocate instance types to fulfill Spot capacity.
*/
export enum SpotAllocationStrategy {
/**
* The Auto Scaling group launches instances using the Spot pools with the lowest price, and evenly
* allocates your instances across the number of Spot pools that you specify.
*/
LOWEST_PRICE = 'lowest-price',
/**
* The Auto Scaling group launches instances using Spot pools that are optimally chosen based on the
* available Spot capacity.
*
* Recommended.
*/
CAPACITY_OPTIMIZED = 'capacity-optimized',
/**
* When you use this strategy, you need to set the order of instance types in the list of launch template
* overrides from highest to lowest priority (from first to last in the list). Amazon EC2 Auto Scaling
* honors the instance type priorities on a best-effort basis but optimizes for capacity first.
*/
CAPACITY_OPTIMIZED_PRIORITIZED = 'capacity-optimized-prioritized',
/**
* The price and capacity optimized allocation strategy looks at both price and
* capacity to select the Spot Instance pools that are the least likely to be
* interrupted and have the lowest possible price.
*/
PRICE_CAPACITY_OPTIMIZED = 'price-capacity-optimized',
}
/**
* InstancesDistribution is a subproperty of MixedInstancesPolicy that describes an instances distribution
* for an Auto Scaling group. The instances distribution specifies the distribution of On-Demand Instances
* and Spot Instances, the maximum price to pay for Spot Instances, and how the Auto Scaling group allocates
* instance types to fulfill On-Demand and Spot capacities.
*
* For more information and example configurations, see Auto Scaling groups with multiple instance types
* and purchase options in the Amazon EC2 Auto Scaling User Guide:
*
* https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-purchase-options.html
*/
export interface InstancesDistribution {
/**
* Indicates how to allocate instance types to fulfill On-Demand capacity. The only valid value is prioritized,
* which is also the default value.
*
* @default OnDemandAllocationStrategy.PRIORITIZED
*/
readonly onDemandAllocationStrategy?: OnDemandAllocationStrategy;
/**
* The minimum amount of the Auto Scaling group's capacity that must be fulfilled by On-Demand Instances. This
* base portion is provisioned first as your group scales. Defaults to 0 if not specified. If you specify weights
* for the instance types in the overrides, set the value of OnDemandBaseCapacity in terms of the number of
* capacity units, and not the number of instances.
*
* @default 0
*/
readonly onDemandBaseCapacity?: number;
/**
* Controls the percentages of On-Demand Instances and Spot Instances for your additional capacity beyond
* OnDemandBaseCapacity. Expressed as a number (for example, 20 specifies 20% On-Demand Instances, 80% Spot Instances).
* Defaults to 100 if not specified. If set to 100, only On-Demand Instances are provisioned.
*
* @default 100
*/
readonly onDemandPercentageAboveBaseCapacity?: number;
/**
* If the allocation strategy is lowest-price, the Auto Scaling group launches instances using the Spot pools with the
* lowest price, and evenly allocates your instances across the number of Spot pools that you specify. Defaults to
* lowest-price if not specified.
*
* If the allocation strategy is capacity-optimized (recommended), the Auto Scaling group launches instances using Spot
* pools that are optimally chosen based on the available Spot capacity. Alternatively, you can use capacity-optimized-prioritized
* and set the order of instance types in the list of launch template overrides from highest to lowest priority
* (from first to last in the list). Amazon EC2 Auto Scaling honors the instance type priorities on a best-effort basis but
* optimizes for capacity first.
*
* @default SpotAllocationStrategy.LOWEST_PRICE
*/
readonly spotAllocationStrategy?: SpotAllocationStrategy;
/**
* The number of Spot Instance pools to use to allocate your Spot capacity. The Spot pools are determined from the different instance
* types in the overrides. Valid only when the Spot allocation strategy is lowest-price. Value must be in the range of 1 to 20.
* Defaults to 2 if not specified.
*
* @default 2
*/
readonly spotInstancePools?: number;
/**
* The maximum price per unit hour that you are willing to pay for a Spot Instance. If you leave the value at its default (empty),
* Amazon EC2 Auto Scaling uses the On-Demand price as the maximum Spot price. To remove a value that you previously set, include
* the property but specify an empty string ("") for the value.
*
* @default "" - On-Demand price
*/
readonly spotMaxPrice?: string;
}
/**
* LaunchTemplateOverrides is a subproperty of LaunchTemplate that describes an override for a launch template.
*/
export interface LaunchTemplateOverrides {
/**
* The instance requirements. Amazon EC2 Auto Scaling uses your specified requirements to identify instance types.
* Then, it uses your On-Demand and Spot allocation strategies to launch instances from these instance types.
*
* You can specify up to four separate sets of instance requirements per Auto Scaling group.
* This is useful for provisioning instances from different Amazon Machine Images (AMIs) in the same Auto Scaling group.
* To do this, create the AMIs and create a new launch template for each AMI.
* Then, create a compatible set of instance requirements for each launch template.
*
* You must specify one of instanceRequirements or instanceType.
*
* @default - Do not override instance type
*/
readonly instanceRequirements?: CfnAutoScalingGroup.InstanceRequirementsProperty;
/**
* The instance type, such as m3.xlarge. You must use an instance type that is supported in your requested Region
* and Availability Zones.
*
* You must specify one of instanceRequirements or instanceType.
*
* @default - Do not override instance type
*/
readonly instanceType?: ec2.InstanceType;
/**
* Provides the launch template to be used when launching the instance type. For example, some instance types might
* require a launch template with a different AMI. If not provided, Amazon EC2 Auto Scaling uses the launch template
* that's defined for your mixed instances policy.
*
* @default - Do not override launch template
*/
readonly launchTemplate?: ec2.ILaunchTemplate;
/**
* The number of capacity units provided by the specified instance type in terms of virtual CPUs, memory, storage,
* throughput, or other relative performance characteristic. When a Spot or On-Demand Instance is provisioned, the
* capacity units count toward the desired capacity. Amazon EC2 Auto Scaling provisions instances until the desired
* capacity is totally fulfilled, even if this results in an overage. Value must be in the range of 1 to 999.
*
* For example, If there are 2 units remaining to fulfill capacity, and Amazon EC2 Auto Scaling can only provision
* an instance with a WeightedCapacity of 5 units, the instance is provisioned, and the desired capacity is exceeded
* by 3 units.
*
* @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-instance-weighting.html
*
* @default - Do not provide weight
*/
readonly weightedCapacity?: number;
}
/**
* Properties of a Fleet
*/
export interface AutoScalingGroupProps extends CommonAutoScalingGroupProps {
/**
* VPC to launch these instances in.
*/
readonly vpc: ec2.IVpc;
/**
* Launch template to use.
*
* Launch configuration related settings and MixedInstancesPolicy must not be specified when a
* launch template is specified.
*
* @default - Do not provide any launch template
*/
readonly launchTemplate?: ec2.ILaunchTemplate;
/**
* Mixed Instances Policy to use.
*
* Launch configuration related settings and Launch Template must not be specified when a
* MixedInstancesPolicy is specified.
*
* @default - Do not provide any MixedInstancesPolicy
*/
readonly mixedInstancesPolicy?: MixedInstancesPolicy;
/**
* Type of instance to launch
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified
*
* @default - Do not provide any instance type
*/
readonly instanceType?: ec2.InstanceType;
/**
* AMI to launch
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified
*
* @default - Do not provide any machine image
*/
readonly machineImage?: ec2.IMachineImage;
/**
* Security group to launch the instances in.
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified
*
* @default - A SecurityGroup will be created if none is specified.
*/
readonly securityGroup?: ec2.ISecurityGroup;
/**
* Specific UserData to use
*
* The UserData may still be mutated after creation.
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified
*
* @default - A UserData object appropriate for the MachineImage's
* Operating System is created.
*/
readonly userData?: ec2.UserData;
/**
* An IAM role to associate with the instance profile assigned to this Auto Scaling Group.
*
* The role must be assumable by the service principal `ec2.amazonaws.com`:
*
* `launchTemplate` and `mixedInstancesPolicy` must not be specified when this property is specified
*
* @example
*
* const role = new iam.Role(this, 'MyRole', {
* assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
* });
*
* @default A role will automatically be created, it can be accessed via the `role` property
*/
readonly role?: iam.IRole;
/**
* Apply the given CloudFormation Init configuration to the instances in the AutoScalingGroup at startup
*
* If you specify `init`, you must also specify `signals` to configure
* the number of instances to wait for and the timeout for waiting for the
* init process.
*
* @default - no CloudFormation init
*/
readonly init?: ec2.CloudFormationInit;
/**
* Use the given options for applying CloudFormation Init
*
* Describes the configsets to use and the timeout to wait
*
* @default - default options
*/
readonly initOptions?: ApplyCloudFormationInitOptions;
/**
* Whether IMDSv2 should be required on launched instances.
*
* @default false
*/
readonly requireImdsv2?: boolean;
/**
* 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 0 to 100. After it's set, both `minHealthyPercentage` and `maxHealthyPercentage` to
* -1 will clear the previously set value.
*
* Both or neither of `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.
*
* @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-instance-maintenance-policy.html
*
* @default - No instance maintenance policy.
*/
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, both `minHealthyPercentage` and `maxHealthyPercentage` to
* -1 will clear the previously set value.
*
* Both or neither of `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.
*
* @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-instance-maintenance-policy.html
*
* @default - No instance maintenance policy.
*/
readonly minHealthyPercentage?: number;
}
/**
* Configure whether the AutoScalingGroup waits for signals
*
* If you do configure waiting for signals, you should make sure the instances
* invoke `cfn-signal` somewhere in their UserData to signal that they have
* started up (either successfully or unsuccessfully).
*
* Signals are used both during intial creation and subsequent updates.
*/
export abstract class Signals {
/**
* Wait for the desiredCapacity of the AutoScalingGroup amount of signals to have been received
*
* If no desiredCapacity has been configured, wait for minCapacity signals intead.
*
* This number is used during initial creation and during replacing updates.
* During rolling updates, all updated instances must send a signal.
*/
public static waitForAll(options: SignalsOptions = {}): Signals {
validatePercentage(options.minSuccessPercentage);
return new class extends Signals {
public renderCreationPolicy(renderOptions: RenderSignalsOptions): CfnCreationPolicy {
return this.doRender(options, renderOptions.desiredCapacity ?? renderOptions.minCapacity);
}
}();
}
/**
* Wait for the minCapacity of the AutoScalingGroup amount of signals to have been received
*
* This number is used during initial creation and during replacing updates.
* During rolling updates, all updated instances must send a signal.
*/
public static waitForMinCapacity(options: SignalsOptions = {}): Signals {
validatePercentage(options.minSuccessPercentage);
return new class extends Signals {
public renderCreationPolicy(renderOptions: RenderSignalsOptions): CfnCreationPolicy {
return this.doRender(options, renderOptions.minCapacity);
}
}();
}
/**
* Wait for a specific amount of signals to have been received
*
* You should send one signal per instance, so this represents the number of
* instances to wait for.
*
* This number is used during initial creation and during replacing updates.
* During rolling updates, all updated instances must send a signal.
*/
public static waitForCount(count: number, options: SignalsOptions = {}): Signals {
validatePercentage(options.minSuccessPercentage);
return new class extends Signals {
public renderCreationPolicy(): CfnCreationPolicy {
return this.doRender(options, count);
}
}();
}
/**
* Render the ASG's CreationPolicy
*/
public abstract renderCreationPolicy(renderOptions: RenderSignalsOptions): CfnCreationPolicy;
/**
* Helper to render the actual creation policy, as the logic between them is quite similar
*/
protected doRender(options: SignalsOptions, count?: number): CfnCreationPolicy {
const minSuccessfulInstancesPercent = validatePercentage(options.minSuccessPercentage);
return {
...options.minSuccessPercentage !== undefined ? { autoScalingCreationPolicy: { minSuccessfulInstancesPercent } } : { },
resourceSignal: {
count,
timeout: options.timeout?.toIsoString(),
},
};
}
}
/**
* Input for Signals.renderCreationPolicy
*/
export interface RenderSignalsOptions {
/**
* The desiredCapacity of the ASG
*
* @default - desired capacity not configured
*/
readonly desiredCapacity?: number;
/**
* The minSize of the ASG
*
* @default - minCapacity not configured
*/
readonly minCapacity?: number;
}
/**
* Customization options for Signal handling
*/
export interface SignalsOptions {
/**
* The percentage of signals that need to be successful
*
* If this number is less than 100, a percentage of signals may be failure
* signals while still succeeding the creation or update in CloudFormation.
*
* @default 100
*/
readonly minSuccessPercentage?: number;
/**
* How long to wait for the signals to be sent
*
* This should reflect how long it takes your instances to start up
* (including instance start time and instance initialization time).
*
* @default Duration.minutes(5)
*/
readonly timeout?: Duration;
}
/**
* How existing instances should be updated
*/
export abstract class UpdatePolicy {
/**
* Create a new AutoScalingGroup and switch over to it
*/
public static replacingUpdate(): UpdatePolicy {
return new class extends UpdatePolicy {
public _renderUpdatePolicy(): CfnUpdatePolicy {
return {
autoScalingReplacingUpdate: { willReplace: true },
};
}
}();
}
/**
* Replace the instances in the AutoScalingGroup one by one, or in batches
*/
public static rollingUpdate(options: RollingUpdateOptions = {}): UpdatePolicy {
const minSuccessPercentage = validatePercentage(options.minSuccessPercentage);
return new class extends UpdatePolicy {
public _renderUpdatePolicy(renderOptions: RenderUpdateOptions): CfnUpdatePolicy {
return {
autoScalingRollingUpdate: {
maxBatchSize: options.maxBatchSize,
minInstancesInService: options.minInstancesInService,
suspendProcesses: options.suspendProcesses ?? DEFAULT_SUSPEND_PROCESSES,
minSuccessfulInstancesPercent:
minSuccessPercentage ?? renderOptions.creationPolicy?.autoScalingCreationPolicy?.minSuccessfulInstancesPercent,
waitOnResourceSignals: options.waitOnResourceSignals ?? renderOptions.creationPolicy?.resourceSignal !== undefined ? true : undefined,
pauseTime: options.pauseTime?.toIsoString() ?? renderOptions.creationPolicy?.resourceSignal?.timeout,
},
};
}
}();
}
/**
* Render the ASG's CreationPolicy
* @internal
*/
public abstract _renderUpdatePolicy(renderOptions: RenderUpdateOptions): CfnUpdatePolicy;
}
/**
* Options for rendering UpdatePolicy
*/
interface RenderUpdateOptions {
/**
* The Creation Policy already created
*
* @default - no CreationPolicy configured
*/
readonly creationPolicy?: CfnCreationPolicy;
}
/**
* Options for customizing the rolling update
*/
export interface RollingUpdateOptions {
/**
* The maximum number of instances that AWS CloudFormation updates at once.
*
* This number affects the speed of the replacement.
*
* @default 1
*/
readonly maxBatchSize?: number;
/**
* The minimum number of instances that must be in service before more instances are replaced.
*
* This number affects the speed of the replacement.
*
* @default 0
*/
readonly minInstancesInService?: number;
/**
* Specifies the Auto Scaling processes to suspend during a stack update.
*
* Suspending processes prevents Auto Scaling from interfering with a stack
* update.
*
* @default HealthCheck, ReplaceUnhealthy, AZRebalance, AlarmNotification, ScheduledActions.
*/
readonly suspendProcesses?: ScalingProcess[];
/**
* Specifies whether the Auto Scaling group waits on signals from new instances during an update.
*
* @default true if you configured `signals` on the AutoScalingGroup, false otherwise
*/
readonly waitOnResourceSignals?: boolean;
/**
* The pause time after making a change to a batch of instances.
*
* @default - The `timeout` configured for `signals` on the AutoScalingGroup
*/
readonly pauseTime?: Duration;
/**
* The percentage of instances that must signal success for the update to succeed.
*
* @default - The `minSuccessPercentage` configured for `signals` on the AutoScalingGroup
*/
readonly minSuccessPercentage?: number;
}
/**
* A set of group metrics
*/
export class GroupMetrics {
/**
* Report all group metrics.
*/
public static all(): GroupMetrics {
return new GroupMetrics();
}
/**
* @internal
*/
public _metrics = new Set<GroupMetric>();
constructor(...metrics: GroupMetric[]) {
metrics?.forEach(metric => this._metrics.add(metric));
}
}
/**
* Group metrics that an Auto Scaling group sends to Amazon CloudWatch.
*/
export class GroupMetric {
/**
* The minimum size of the Auto Scaling group
*/
public static readonly MIN_SIZE = new GroupMetric('GroupMinSize');
/**
* The maximum size of the Auto Scaling group
*/
public static readonly MAX_SIZE = new GroupMetric('GroupMaxSize');
/**
* The number of instances that the Auto Scaling group attempts to maintain
*/
public static readonly DESIRED_CAPACITY = new GroupMetric('GroupDesiredCapacity');
/**
* The number of instances that are running as part of the Auto Scaling group
* This metric does not include instances that are pending or terminating
*/
public static readonly IN_SERVICE_INSTANCES = new GroupMetric('GroupInServiceInstances');
/**
* The number of instances that are pending
* A pending instance is not yet in service, this metric does not include instances that are in service or terminating
*/
public static readonly PENDING_INSTANCES = new GroupMetric('GroupPendingInstances');
/**
* The number of instances that are in a Standby state
* Instances in this state are still running but are not actively in service
*/
public static readonly STANDBY_INSTANCES = new GroupMetric('GroupStandbyInstances');
/**
* The number of instances that are in the process of terminating
* This metric does not include instances that are in service or pending
*/
public static readonly TERMINATING_INSTANCES = new GroupMetric('GroupTerminatingInstances');
/**
* The total number of instances in the Auto Scaling group
* This metric identifies the number of instances that are in service, pending, and terminating
*/
public static readonly TOTAL_INSTANCES = new GroupMetric('GroupTotalInstances');
/**
* The name of the group metric
*/
public readonly name: string;
constructor(name: string) {
this.name = name;
}
}
/**
* The strategies for when launches fail in an Availability Zone.
*/
export enum CapacityDistributionStrategy {
/**
* If launches fail in an Availability Zone, Auto Scaling will continue to attempt to launch in the unhealthy zone to preserve a balanced distribution.
*/
BALANCED_ONLY = 'balanced-only',
/**
* If launches fail in an Availability Zone, Auto Scaling will attempt to launch in another healthy Availability Zone instead.
*/
BALANCED_BEST_EFFORT = 'balanced-best-effort',
}
abstract class AutoScalingGroupBase extends Resource implements IAutoScalingGroup {
public abstract autoScalingGroupName: string;
public abstract autoScalingGroupArn: string;
public abstract readonly osType: ec2.OperatingSystemType;
protected albTargetGroup?: elbv2.ApplicationTargetGroup;
public readonly grantPrincipal: iam.IPrincipal = new iam.UnknownPrincipal({ resource: this });
protected hasCalledScaleOnRequestCount: boolean = false;
/**
* Send a message to either an SQS queue or SNS topic when instances launch or terminate
*/
public addLifecycleHook(id: string, props: BasicLifecycleHookProps): LifecycleHook {
return new LifecycleHook(this, `LifecycleHook${id}`, {
autoScalingGroup: this,
...props,
});
}
/**
* Add a pool of pre-initialized EC2 instances that sits alongside an Auto Scaling group
*/
public addWarmPool(options?: WarmPoolOptions): WarmPool {
return new WarmPool(this, 'WarmPool', {
autoScalingGroup: this,
...options,
});
}
/**
* Scale out or in based on time
*/
public scaleOnSchedule(id: string, props: BasicScheduledActionProps): ScheduledAction {
return new ScheduledAction(this, `ScheduledAction${id}`, {
autoScalingGroup: this,
...props,
});
}
/**
* Scale out or in to achieve a target CPU utilization
*/
public scaleOnCpuUtilization(id: string, props: CpuUtilizationScalingProps): TargetTrackingScalingPolicy {
return new TargetTrackingScalingPolicy(this, `ScalingPolicy${id}`, {
autoScalingGroup: this,
predefinedMetric: PredefinedMetric.ASG_AVERAGE_CPU_UTILIZATION,
targetValue: props.targetUtilizationPercent,
...props,
});
}
/**
* Scale out or in to achieve a target network ingress rate
*/
public scaleOnIncomingBytes(id: string, props: NetworkUtilizationScalingProps): TargetTrackingScalingPolicy {
return new TargetTrackingScalingPolicy(this, `ScalingPolicy${id}`, {
autoScalingGroup: this,
predefinedMetric: PredefinedMetric.ASG_AVERAGE_NETWORK_IN,
targetValue: props.targetBytesPerSecond,
...props,
});
}
/**
* Scale out or in to achieve a target network egress rate
*/
public scaleOnOutgoingBytes(id: string, props: NetworkUtilizationScalingProps): TargetTrackingScalingPolicy {
return new TargetTrackingScalingPolicy(this, `ScalingPolicy${id}`, {
autoScalingGroup: this,
predefinedMetric: PredefinedMetric.ASG_AVERAGE_NETWORK_OUT,
targetValue: props.targetBytesPerSecond,
...props,
});
}
/**
* Scale out or in to achieve a target request handling rate
*
* The AutoScalingGroup must have been attached to an Application Load Balancer
* in order to be able to call this.
*/
public scaleOnRequestCount(id: string, props: RequestCountScalingProps): TargetTrackingScalingPolicy {
if (this.albTargetGroup === undefined) {
throw new ValidationError('Attach the AutoScalingGroup to a non-imported Application Load Balancer before calling scaleOnRequestCount()', this);
}
const resourceLabel = `${this.albTargetGroup.firstLoadBalancerFullName}/${this.albTargetGroup.targetGroupFullName}`;
if ((props.targetRequestsPerMinute === undefined) === (props.targetRequestsPerSecond === undefined)) {
throw new ValidationError('Specify exactly one of \'targetRequestsPerMinute\' or \'targetRequestsPerSecond\'', this);
}
let rpm: number;
if (props.targetRequestsPerSecond !== undefined) {
if (Token.isUnresolved(props.targetRequestsPerSecond)) {
throw new ValidationError('\'targetRequestsPerSecond\' cannot be an unresolved value; use \'targetRequestsPerMinute\' instead.', this);
}
rpm = props.targetRequestsPerSecond * 60;
} else {
rpm = props.targetRequestsPerMinute!;
}
const policy = new TargetTrackingScalingPolicy(this, `ScalingPolicy${id}`, {
autoScalingGroup: this,
predefinedMetric: PredefinedMetric.ALB_REQUEST_COUNT_PER_TARGET,
targetValue: rpm,
resourceLabel,
...props,
});
policy.node.addDependency(this.albTargetGroup.loadBalancerAttached);
this.hasCalledScaleOnRequestCount = true;
return policy;
}
/**
* Scale out or in in order to keep a metric around a target value
*/
public scaleToTrackMetric(id: string, props: MetricTargetTrackingProps): TargetTrackingScalingPolicy {
return new TargetTrackingScalingPolicy(this, `ScalingPolicy${id}`, {
autoScalingGroup: this,
customMetric: props.metric,
...props,
});
}
/**
* Scale out or in, in response to a metric
*/
public scaleOnMetric(id: string, props: BasicStepScalingPolicyProps): StepScalingPolicy {
return new StepScalingPolicy(this, id, { ...props, autoScalingGroup: this });
}
public addUserData(..._commands: string[]): void {
// do nothing
}
}
/**
* A Fleet represents a managed set of EC2 instances
*
* The Fleet models a number of AutoScalingGroups, a launch configuration, a
* security group and an instance role.
*
* It allows adding arbitrary commands to the startup scripts of the instances
* in the fleet.
*
* The ASG spans the availability zones specified by vpcSubnets, falling back to
* the Vpc default strategy if not specified.
*/
export class AutoScalingGroup extends AutoScalingGroupBase implements
elb.ILoadBalancerTarget,
ec2.IConnectable,
elbv2.IApplicationLoadBalancerTarget,
elbv2.INetworkLoadBalancerTarget {
public static fromAutoScalingGroupName(scope: Construct, id: string, autoScalingGroupName: string): IAutoScalingGroup {
class Import extends AutoScalingGroupBase {
public autoScalingGroupName = autoScalingGroupName;
public autoScalingGroupArn = Stack.of(this).formatArn({
service: 'autoscaling',
resource: 'autoScalingGroup:*:autoScalingGroupName',
resourceName: this.autoScalingGroupName,
});
public readonly osType = ec2.OperatingSystemType.UNKNOWN;
}
return new Import(scope, id);
}
/**
* The type of OS instances of this fleet are running.
*/
public readonly osType: ec2.OperatingSystemType;
/**
* The principal to grant permissions to
*/
public readonly grantPrincipal: iam.IPrincipal;
/**
* Name of the AutoScalingGroup
*/
public readonly autoScalingGroupName: string;
/**
* Arn of the AutoScalingGroup
*/
public readonly autoScalingGroupArn: string;
/**
* The maximum spot price configured for the autoscaling group. `undefined`
* indicates that this group uses on-demand capacity.
*/
public readonly spotPrice?: string;
/**
* The maximum amount of time that an instance can be in service.
*/
public readonly maxInstanceLifetime?: Duration;
private readonly autoScalingGroup: CfnAutoScalingGroup;
private readonly securityGroup?: ec2.ISecurityGroup;
private readonly securityGroups?: ec2.ISecurityGroup[];
private readonly loadBalancerNames: string[] = [];
private readonly targetGroupArns: string[] = [];
private readonly groupMetrics: GroupMetrics[] = [];
private readonly notifications: NotificationConfiguration[] = [];
private readonly launchTemplate?: ec2.LaunchTemplate;
private readonly _connections?: ec2.Connections;
private readonly _userData?: ec2.UserData;
private readonly _role?: iam.IRole;
protected newInstancesProtectedFromScaleIn?: boolean;
constructor(scope: Construct, id: string, props: AutoScalingGroupProps) {
super(scope, id, {
physicalName: props.autoScalingGroupName,
});
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
this.newInstancesProtectedFromScaleIn = props.newInstancesProtectedFromScaleIn;
if (props.initOptions && !props.init) {
throw new ValidationError('Setting \'initOptions\' requires that \'init\' is also set', this);
}
if (props.groupMetrics) {
this.groupMetrics.push(...props.groupMetrics);
}
let launchConfig: CfnLaunchConfiguration | undefined = undefined;
let launchTemplateFromConfig: ec2.LaunchTemplate | undefined = undefined;
if (props.launchTemplate || props.mixedInstancesPolicy) {
this.verifyNoLaunchConfigPropIsGiven(props);
const bareLaunchTemplate = props.launchTemplate;
const mixedInstancesPolicy = props.mixedInstancesPolicy;
if (bareLaunchTemplate && mixedInstancesPolicy) {
throw new ValidationError('Setting \'mixedInstancesPolicy\' must not be set when \'launchTemplate\' is set', this);
}
if (bareLaunchTemplate && bareLaunchTemplate instanceof ec2.LaunchTemplate) {
if (!bareLaunchTemplate.instanceType) {
throw new ValidationError('Setting \'launchTemplate\' requires its \'instanceType\' to be set', this);
}
if (!bareLaunchTemplate.imageId) {
throw new ValidationError('Setting \'launchTemplate\' requires its \'machineImage\' to be set', this);
}
this.launchTemplate = bareLaunchTemplate;
}
if (mixedInstancesPolicy && mixedInstancesPolicy.launchTemplate instanceof ec2.LaunchTemplate) {
if (!mixedInstancesPolicy.launchTemplate.imageId) {
throw new ValidationError('Setting \'mixedInstancesPolicy.launchTemplate\' requires its \'machineImage\' to be set', this);
}
this.launchTemplate = mixedInstancesPolicy.launchTemplate;
}
this._role = this.launchTemplate?.role;
this.grantPrincipal = this._role || new iam.UnknownPrincipal({ resource: this });
this.osType = this.launchTemplate?.osType ?? ec2.OperatingSystemType.UNKNOWN;
} else {
if (!props.machineImage) {
throw new ValidationError('Setting \'machineImage\' is required when \'launchTemplate\' and \'mixedInstancesPolicy\' is not set', this);
}
if (!props.instanceType) {
throw new ValidationError('Setting \'instanceType\' is required when \'launchTemplate\' and \'mixedInstancesPolicy\' is not set', this);
}
if (props.keyName && props.keyPair) {
throw new ValidationError('Cannot specify both of \'keyName\' and \'keyPair\'; prefer \'keyPair\'', this);
}
Tags.of(this).add(NAME_TAG, this.node.path);
this.securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'InstanceSecurityGroup', {
vpc: props.vpc,
allowAllOutbound: props.allowAllOutbound !== false,
});
this._role = props.role || new iam.Role(this, 'InstanceRole', {
roleName: PhysicalName.GENERATE_IF_NEEDED,
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
});
this.grantPrincipal = this._role;
const iamProfile = new iam.CfnInstanceProfile(this, 'InstanceProfile', {
roles: [this.role.roleName],
});
// generate launch template from launch config props when feature flag is set
if (FeatureFlags.of(this).isEnabled(AUTOSCALING_GENERATE_LAUNCH_TEMPLATE)) {
const instanceProfile = iam.InstanceProfile.fromInstanceProfileAttributes(this, 'ImportedInstanceProfile', {
instanceProfileArn: iamProfile.attrArn,
role: this.role,
});
launchTemplateFromConfig = new ec2.LaunchTemplate(this, 'LaunchTemplate', {
machineImage: props.machineImage,
instanceType: props.instanceType,
detailedMonitoring: props.instanceMonitoring !== undefined && props.instanceMonitoring === Monitoring.DETAILED,
securityGroup: this.securityGroup,
userData: props.userData,
associatePublicIpAddress: props.associatePublicIpAddress,
spotOptions: props.spotPrice !== undefined ? { maxPrice: parseFloat(props.spotPrice) } : undefined,
blockDevices: props.blockDevices,
instanceProfile,
keyPair: props.keyPair,
...(props.keyName ? { keyName: props.keyName } : {}),
});
this.osType = launchTemplateFromConfig.osType!;
this.launchTemplate = launchTemplateFromConfig;
} else {
this._connections = new ec2.Connections({ securityGroups: [this.securityGroup] });
this.securityGroups = [this.securityGroup];
if (props.keyPair) {
throw new ValidationError('Can only use \'keyPair\' when feature flag \'AUTOSCALING_GENERATE_LAUNCH_TEMPLATE\' is set', this);
}
// use delayed evaluation
const imageConfig = props.machineImage.getImage(this);
this._userData = props.userData ?? imageConfig.userData;
const userDataToken = Lazy.string({ produce: () => Fn.base64(this.userData!.render()) });
const securityGroupsToken = Lazy.list({ produce: () => this.securityGroups!.map(sg => sg.securityGroupId) });
launchConfig = new CfnLaunchConfiguration(this, 'LaunchConfig', {
imageId: imageConfig.imageId,
keyName: props.keyName,
instanceType: props.instanceType.toString(),
instanceMonitoring: (props.instanceMonitoring !== undefined ? (props.instanceMonitoring === Monitoring.DETAILED) : undefined),
securityGroups: securityGroupsToken,
iamInstanceProfile: iamProfile.ref,
userData: userDataToken,
associatePublicIpAddress: props.associatePublicIpAddress,
spotPrice: props.spotPrice,
blockDeviceMappings: (props.blockDevices !== undefined ?
synthesizeBlockDeviceMappings(this, props.blockDevices) : undefined),
});
launchConfig.node.addDependency(this.role);
this.osType = imageConfig.osType;
}
}
if (props.ssmSessionPermissions && this._role) {
this._role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'));
}
// desiredCapacity just reflects what the user has supplied.
const desiredCapacity = props.desiredCapacity;
const minCapacity = props.minCapacity ?? 1;
const maxCapacity = props.maxCapacity ??
desiredCapacity ??
(Token.isUnresolved(minCapacity) ? minCapacity : Math.max(minCapacity, 1));
withResolved(minCapacity, maxCapacity, (min, max) => {
if (min > max) {
throw new ValidationError(`minCapacity (${min}) should be <= maxCapacity (${max})`, this);
}
});
withResolved(desiredCapacity, minCapacity, (desired, min) => {
if (desired === undefined) { return; }
if (desired < min) {
throw new ValidationError(`Should have minCapacity (${min}) <= desiredCapacity (${desired})`, this);
}
});
withResolved(desiredCapacity, maxCapacity, (desired, max) => {
if (desired === undefined) { return; }
if (max < desired) {
throw new ValidationError(`Should have desiredCapacity (${desired}) <= maxCapacity (${max})`, this);
}
});
if (desiredCapacity !== undefined) {
Annotations.of(this).addWarningV2('@aws-cdk/aws-autoscaling:desiredCapacitySet', 'desiredCapacity has been configured. Be aware this will reset the size of your AutoScalingGroup on every deployment. See https://github.com/aws/aws-cdk/issues/5215');
}
this.maxInstanceLifetime = props.maxInstanceLifetime;
// See https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-max-instance-lifetime.html for details on max instance lifetime.
if (this.maxInstanceLifetime && !this.maxInstanceLifetime.isUnresolved() &&
(this.maxInstanceLifetime.toSeconds() !== 0) &&
(this.maxInstanceLifetime.toSeconds() < 86400 || this.maxInstanceLifetime.toSeconds() > 31536000)) {
throw new ValidationError('maxInstanceLifetime must be between 1 and 365 days (inclusive)', this);
}
if (props.notificationsTopic && props.notifications) {
throw new ValidationError('Cannot set \'notificationsTopic\' and \'notifications\', \'notificationsTopic\' is deprecated use \'notifications\' instead', this);
}
if (props.notificationsTopic) {
this.notifications = [{
topic: props.notificationsTopic,
}];
}
if (props.notifications) {
this.notifications = props.notifications.map(nc => ({
topic: nc.topic,
scalingEvents: nc.scalingEvents ?? ScalingEvents.ALL,
}));
}
const { subnetIds, hasPublic } = props.vpc.selectSubnets(props.vpcSubnets);
const terminationPolicies: string[] = [];
if (props.terminationPolicies) {
props.terminationPolicies.forEach((terminationPolicy, index) => {
if (terminationPolicy === TerminationPolicy.CUSTOM_LAMBDA_FUNCTION) {
if (index !== 0) {
throw new ValidationError('TerminationPolicy.CUSTOM_LAMBDA_FUNCTION must be specified first in the termination policies', this);
}
if (!props.terminationPolicyCustomLambdaFunctionArn) {
throw new ValidationError('terminationPolicyCustomLambdaFunctionArn property must be specified if the TerminationPolicy.CUSTOM_LAMBDA_FUNCTION is used', this);
}
terminationPolicies.push(props.terminationPolicyCustomLambdaFunctionArn);
} else {
terminationPolicies.push(terminationPolicy);
}
});
}
const { healthCheckType, healthCheckGracePeriod } = this.renderHealthChecks(props.healthChecks, props.healthCheck);
const asgProps: CfnAutoScalingGroupProps = {
autoScalingGroupName: this.physicalName,
availabilityZoneDistribution: props.azCapacityDistributionStrategy
? { capacityDistributionStrategy: props.azCapacityDistributionStrategy }
: undefined,
cooldown: props.cooldown?.toSeconds().toString(),
minSize: Tokenization.stringifyNumber(minCapacity),
maxSize: Tokenization.stringifyNumber(maxCapacity),
desiredCapacity: desiredCapacity !== undefined ? Tokenization.stringifyNumber(desiredCapacity) : undefined,
loadBalancerNames: Lazy.list({ produce: () => this.loadBalancerNames }, { omitEmpty: true }),
targetGroupArns: Lazy.list({ produce: () => this.targetGroupArns }, { omitEmpty: true }),
notificationConfigurations: this.renderNotificationConfiguration(),
metricsCollection: Lazy.any({ produce: () => this.renderMetricsCollection() }),
vpcZoneIdentifier: subnetIds,
healthCheckType,
healthCheckGracePeriod,
maxInstanceLifetime: this.maxInstanceLifetime ? this.maxInstanceLifetime.toSeconds() : undefined,
newInstancesProtectedFromScaleIn: Lazy.any({ produce: () => this.newInstancesProtectedFromScaleIn }),
terminationPolicies: terminationPolicies.length === 0 ? undefined : terminationPolicies,
defaultInstanceWarmup: props.defaultInstanceWarmup?.toSeconds(),
capacityRebalance: props.capacityRebalance,
instanceMaintenancePolicy: this.renderInstanceMaintenancePolicy(
props.minHealthyPercentage,
props.maxHealthyPercentage,
),
...this.getLaunchSettings(launchConfig, props.launchTemplate ?? launchTemplateFromConfig, props.mixedInstancesPolicy),
};
if (!hasPublic && props.associatePublicIpAddress) {
throw new ValidationError("To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetType: SubnetType.PUBLIC })", this);
}
this.autoScalingGroup = new CfnAutoScalingGroup(this, 'ASG', asgProps);
this.autoScalingGroupName = this.getResourceNameAttribute(this.autoScalingGroup.ref),
this.autoScalingGroupArn = Stack.of(this).formatArn({
service: 'autoscaling',
resource: 'autoScalingGroup:*:autoScalingGroupName',
resourceName: this.autoScalingGroupName,
});
this.node.defaultChild = this.autoScalingGroup;
this.applyUpdatePolicies(props, { desiredCapacity, minCapacity });
if (props.init) {
this.applyCloudFormationInit(props.init, props.initOptions);
}
this.spotPrice = props.spotPrice;
if (props.requireImdsv2) {
Aspects.of(this).add(new AutoScalingGroupRequireImdsv2Aspect(), {
priority: mutatingAspectPrio32333(this),
});
}
this.node.addValidation({ validate: () => this.validateTargetGroup() });
}
/**
* Add the security group to all instances via the launch template
* security groups array.
*
* @param securityGroup: The security group to add
*/
@MethodMetadata()
public addSecurityGroup(securityGroup: ec2.ISecurityGroup): void {
if (FeatureFlags.of(this).isEnabled(AUTOSCALING_GENERATE_LAUNCH_TEMPLATE)) {
this.launchTemplate?.addSecurityGroup(securityGroup);
} else {
if (!this.securityGroups) {
throw new ValidationError('You cannot add security groups when the Auto Scaling Group is created from a Launch Template.', this);
}
this.securityGroups.push(securityGroup);
}
}
/**
* Attach to a classic load balancer
*/
@MethodMetadata()
public attachToClassicLB(loadBalancer: elb.LoadBalancer): void {
this.loadBalancerNames.push(loadBalancer.loadBalancerName);
}
/**
* Attach to ELBv2 Application Target Group
*/
@MethodMetadata()
public attachToApplicationTargetGroup(targetGroup: elbv2.IApplicationTargetGroup): elbv2.LoadBalancerTargetProps {
this.targetGroupArns.push(targetGroup.targetGroupArn);
if (targetGroup instanceof elbv2.ApplicationTargetGroup) {
// Copy onto self if it's a concrete type. We need this for autoscaling
// based on request count, which we cannot do with an imported TargetGroup.
this.albTargetGroup = targetGroup;
}
targetGroup.registerConnectable(this);
return { targetType: elbv2.TargetType.INSTANCE };
}
/**
* Attach to ELBv2 Application Target Group
*/
@MethodMetadata()
public attachToNetworkTargetGroup(targetGroup: elbv2.INetworkTargetGroup): elbv2.LoadBalancerTargetProps {
this.targetGroupArns.push(targetGroup.targetGroupArn);
return { targetType: elbv2.TargetType.INSTANCE };
}
@MethodMetadata()
public addUserData(...commands: string[]): void {
this.userData.addCommands(...commands);
}
/**
* Adds a statement to the IAM role assumed by instances of this fleet.
*/
@MethodMetadata()
public addToRolePolicy(statement: iam.PolicyStatement) {
this.role.addToPrincipalPolicy(statement);
}
/**
* Use a CloudFormation Init configuration at instance startup
*
* This does the following:
*
* - Attaches the CloudFormation Init metadata to the AutoScalingGroup resource.
* - Add commands to the UserData to run `cfn-init` and `cfn-signal`.
* - Update the instance's CreationPolicy to wait for `cfn-init` to finish
* before reporting success.
*/
@MethodMetadata()
public applyCloudFormationInit(init: ec2.CloudFormationInit, options: ApplyCloudFormationInitOptions = {}) {
if (!this.autoScalingGroup.cfnOptions.creationPolicy?.resourceSignal) {
throw new ValidationError('When applying CloudFormationInit, you must also configure signals by supplying \'signals\' at instantiation time.', this);
}
init.attach(this.autoScalingGroup, {
platform: this.osType,
instanceRole: this.role,
userData: this.userData,
configSets: options.configSets,
embedFingerprint: options.embedFingerprint,
printLog: options.printLog,
ignoreFailures: options.ignoreFailures,
includeRole: options.includeRole,
includeUrl: options.includeUrl,
});
}
/**
* Ensures newly-launched instances are protected from scale-in.
*/
@MethodMetadata()
public protectNewInstancesFromScaleIn() {
this.newInstancesProtectedFromScaleIn = true;
}
/**
* Returns `true` if newly-launched instances are protected from scale-in.
*/
@MethodMetadata()
public areNewInstancesProtectedFromScaleIn(): boolean {
return this.newInstancesProtectedFromScaleIn === true;
}
/**
* The network connections associated with this resource.
*/
public get connections(): ec2.Connections {
if (this._connections) {
return this._connections;
}
if (this.launchTemplate) {
return this.launchTemplate.connections;
}
throw new ValidationError('AutoScalingGroup can only be used as IConnectable if it is not created from an imported Launch Template.', this);
}
/**
* The Base64-encoded user data to make available to the launched EC2 instances.
*
* @throws an error if a launch template is given and it does not provide a non-null `userData`
*/
public get userData(): ec2.UserData {
if (this._userData) {
return this._userData;
}
if (this.launchTemplate?.userData) {
return this.launchTemplate.userData;
}
throw new ValidationError('The provided launch template does not expose its user data.', this);
}
/**
* The IAM Role in the instance profile
*
* @throws an error if a launch template is given
*/
public get role(): iam.IRole {
if (this._role) {
return this._role;
}
throw new ValidationError('The provided launch template does not expose or does not define its role.', this);
}
private verifyNoLaunchConfigPropIsGiven(props: AutoScalingGroupProps) {
if (props.machineImage) {
throw new ValidationError('Setting \'machineImage\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.instanceType) {
throw new ValidationError('Setting \'instanceType\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.role) {
throw new ValidationError('Setting \'role\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.userData) {
throw new ValidationError('Setting \'userData\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.securityGroup) {
throw new ValidationError('Setting \'securityGroup\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.keyName) {
throw new ValidationError('Setting \'keyName\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.keyPair) {
throw new ValidationError('Setting \'keyPair\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.instanceMonitoring) {
throw new ValidationError('Setting \'instanceMonitoring\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.associatePublicIpAddress !== undefined) {
throw new ValidationError('Setting \'associatePublicIpAddress\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.spotPrice) {
throw new ValidationError('Setting \'spotPrice\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.blockDevices) {
throw new ValidationError('Setting \'blockDevices\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
if (props.requireImdsv2) {
throw new ValidationError('Setting \'requireImdsv2\' must not be set when \'launchTemplate\' or \'mixedInstancesPolicy\' is set', this);
}
}
/**
* Apply CloudFormation update policies for the AutoScalingGroup
*/
private applyUpdatePolicies(props: AutoScalingGroupProps, signalOptions: RenderSignalsOptions) {
// Make sure people are not using the old and new properties together
const oldProps: Array<keyof AutoScalingGroupProps> = [
'updateType',
'rollingUpdateConfiguration',
'resourceSignalCount',
'resourceSignalTimeout',
'replacingUpdateMinSuccessfulInstancesPercent',
];
for (const prop of oldProps) {
if ((props.signals || props.updatePolicy) && props[prop] !== undefined) {
throw new ValidationError(`Cannot set 'signals'/'updatePolicy' and '${prop}' together. Prefer 'signals'/'updatePolicy'`, this);
}
}
// Reify updatePolicy to `rollingUpdate` default in case it is combined with `init`
props = {
...props,
updatePolicy: props.updatePolicy ?? (props.init ? UpdatePolicy.rollingUpdate() : undefined),
};
if (props.signals || props.updatePolicy) {
this.applyNewSignalUpdatePolicies(props, signalOptions);
} else {
this.applyLegacySignalUpdatePolicies(props);
}
// The following is technically part of the "update policy" but it's also a completely
// separate aspect of rolling/replacing update, so it's just its own top-level property.
// Default is 'true' because that's what you're most likely to want
if (props.ignoreUnmodifiedSizeProperties !== false) {
this.autoScalingGroup.cfnOptions.updatePolicy = {
...this.autoScalingGroup.cfnOptions.updatePolicy,
autoScalingScheduledAction: { ignoreUnmodifiedGroupSizeProperties: true },
};
}
if (props.signals && !props.init) {
// To be able to send a signal using `cfn-init`, the execution role needs
// `cloudformation:SignalResource`. Normally the binding of CfnInit would
// grant that permissions and another one, but if the user wants to use
// `signals` without `init`, add the permissions here.
//
// If they call `applyCloudFormationInit()` after construction, nothing bad
// happens either, we'll just have a duplicate statement which doesn't hurt.
this.addToRolePolicy(new iam.PolicyStatement({
actions: ['cloudformation:SignalResource'],
resources: [Aws.STACK_ID],
}));
}
}
/**
* Use 'signals' and 'updatePolicy' to determine the creation and update policies
*/
private applyNewSignalUpdatePolicies(props: AutoScalingGroupProps, signalOptions: RenderSignalsOptions) {
this.autoScalingGroup.cfnOptions.creationPolicy = props.signals?.renderCreationPolicy(signalOptions);
this.autoScalingGroup.cfnOptions.updatePolicy = props.updatePolicy?._renderUpdatePolicy({
creationPolicy: this.autoScalingGroup.cfnOptions.creationPolicy,
});
}
private applyLegacySignalUpdatePolicies(props: AutoScalingGroupProps) {
if (props.updateType === UpdateType.REPLACING_UPDATE) {
this.autoScalingGroup.cfnOptions.updatePolicy = {
...this.autoScalingGroup.cfnOptions.updatePolicy,
autoScalingReplacingUpdate: {
willReplace: true,
},
};
if (props.replacingUpdateMinSuccessfulInstancesPercent !== undefined) {
// Yes, this goes on CreationPolicy, not as a process parameter to ReplacingUpdate.
// It's a little confusing, but the docs seem to explicitly state it will only be used
// during the update?
//
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-creationpolicy.html
this.autoScalingGroup.cfnOptions.creationPolicy = {
...this.autoScalingGroup.cfnOptions.creationPolicy,
autoScalingCreationPolicy: {
minSuccessfulInstancesPercent: validatePercentage(props.replacingUpdateMinSuccessfulInstancesPercent),
},
};
}
} else if (props.updateType === UpdateType.ROLLING_UPDATE) {
this.autoScalingGroup.cfnOptions.updatePolicy = {
...this.autoScalingGroup.cfnOptions.updatePolicy,
autoScalingRollingUpdate: renderRollingUpdateConfig(props.rollingUpdateConfiguration),
};
}
if (props.resourceSignalCount !== undefined || props.resourceSignalTimeout !== undefined) {
this.autoScalingGroup.cfnOptions.creationPolicy = {
...this.autoScalingGroup.cfnOptions.creationPolicy,
resourceSignal: {
count: props.resourceSignalCount,
timeout: props.resourceSignalTimeout && props.resourceSignalTimeout.toIsoString(),
},
};
}
}
private renderNotificationConfiguration(): CfnAutoScalingGroup.NotificationConfigurationProperty[] | undefined {
if (this.notifications.length === 0) {
return undefined;
}
return this.notifications.map(notification => ({
topicArn: notification.topic.topicArn,
notificationTypes: notification.scalingEvents ? notification.scalingEvents._types : ScalingEvents.ALL._types,
}));
}
private renderMetricsCollection(): CfnAutoScalingGroup.MetricsCollectionProperty[] | undefined {
if (this.groupMetrics.length === 0) {
return undefined;
}
return this.groupMetrics.map(group => ({
granularity: '1Minute',
metrics: group._metrics?.size !== 0 ? [...group._metrics].map(m => m.name) : undefined,
}));
}
private getLaunchSettings(launchConfig?: CfnLaunchConfiguration, launchTemplate?: ec2.ILaunchTemplate, mixedInstancesPolicy?: MixedInstancesPolicy)
: Pick<CfnAutoScalingGroupProps, 'launchConfigurationName'>
| Pick<CfnAutoScalingGroupProps, 'launchTemplate'>
| Pick<CfnAutoScalingGroupProps, 'mixedInstancesPolicy'> {
if (launchConfig) {
return {
launchConfigurationName: launchConfig.ref,
};
}
if (launchTemplate) {
return {
launchTemplate: this.convertILaunchTemplateToSpecification(launchTemplate),
};
}
if (mixedInstancesPolicy) {
let instancesDistribution: CfnAutoScalingGroup.InstancesDistributionProperty | undefined = undefined;
if (mixedInstancesPolicy.instancesDistribution) {
const dist = mixedInstancesPolicy.instancesDistribution;
instancesDistribution = {
onDemandAllocationStrategy: dist.onDemandAllocationStrategy?.toString(),
onDemandBaseCapacity: dist.onDemandBaseCapacity,
onDemandPercentageAboveBaseCapacity: dist.onDemandPercentageAboveBaseCapacity,
spotAllocationStrategy: dist.spotAllocationStrategy?.toString(),
spotInstancePools: dist.spotInstancePools,
spotMaxPrice: dist.spotMaxPrice,
};
}
return {
mixedInstancesPolicy: {
instancesDistribution,
launchTemplate: {
launchTemplateSpecification: this.convertILaunchTemplateToSpecification(mixedInstancesPolicy.launchTemplate),
...(mixedInstancesPolicy.launchTemplateOverrides ? {
overrides: mixedInstancesPolicy.launchTemplateOverrides.map(override => {
if (override.weightedCapacity && Math.floor(override.weightedCapacity) !== override.weightedCapacity) {
throw new ValidationError('Weight must be an integer', this);
}
if (!override.instanceType && !override.instanceRequirements) {
throw new ValidationError('You must specify either \'instanceRequirements\' or \'instanceType\'.', this);
}
if (override.instanceType && override.instanceRequirements) {
throw new ValidationError('You can specify either \'instanceRequirements\' or \'instanceType\', not both.', this);
}
return {
instanceType: override.instanceType?.toString(),
launchTemplateSpecification: override.launchTemplate
? this.convertILaunchTemplateToSpecification(override.launchTemplate)
: undefined,
instanceRequirements: override.instanceRequirements,
weightedCapacity: override.weightedCapacity?.toString(),
};
}),
} : {}),
},
},
};
}
throw new ValidationError('Either launchConfig, launchTemplate or mixedInstancesPolicy needs to be specified.', this);
}
private convertILaunchTemplateToSpecification(launchTemplate: ec2.ILaunchTemplate): CfnAutoScalingGroup.LaunchTemplateSpecificationProperty {
if (launchTemplate.launchTemplateId) {
return {
launchTemplateId: launchTemplate.launchTemplateId,
version: launchTemplate.versionNumber,
};
} else {
return {
launchTemplateName: launchTemplate.launchTemplateName,
version: launchTemplate.versionNumber,
};
}
}
private validateTargetGroup(): string[] {
const errors = new Array<string>();
if (this.hasCalledScaleOnRequestCount && this.targetGroupArns.length > 1) {
errors.push('Cannon use multiple target groups if `scaleOnRequestCount()` is being used.');
}
return errors;
}
private renderInstanceMaintenancePolicy(
minHealthyPercentage?: number,
maxHealthyPercentage?: number,
): CfnAutoScalingGroup.InstanceMaintenancePolicyProperty | undefined {
if (minHealthyPercentage === undefined && maxHealthyPercentage === undefined) return;
if (minHealthyPercentage === undefined || maxHealthyPercentage === undefined) {
throw new ValidationError(`Both or neither of minHealthyPercentage and maxHealthyPercentage must be specified, got minHealthyPercentage: ${minHealthyPercentage} and maxHealthyPercentage: ${maxHealthyPercentage}`, this);
}
if ((minHealthyPercentage === -1 || maxHealthyPercentage === -1) && minHealthyPercentage !== maxHealthyPercentage) {
throw new ValidationError(`Both minHealthyPercentage and maxHealthyPercentage must be -1 to clear the previously set value, got minHealthyPercentage: ${minHealthyPercentage} and maxHealthyPercentage: ${maxHealthyPercentage}`, this);
}
if (minHealthyPercentage !== -1 && (minHealthyPercentage < 0 || minHealthyPercentage > 100)) {
throw new ValidationError(`minHealthyPercentage must be between 0 and 100, or -1 to clear the previously set value, got ${minHealthyPercentage}`, this);
}
if (maxHealthyPercentage !== -1 && (maxHealthyPercentage < 100 || maxHealthyPercentage > 200)) {
throw new ValidationError(`maxHealthyPercentage must be between 100 and 200, or -1 to clear the previously set value, got ${maxHealthyPercentage}`, this);
}
if (maxHealthyPercentage - minHealthyPercentage > 100) {
throw new ValidationError(`The difference between minHealthyPercentage and maxHealthyPercentage cannot be greater than 100, got ${maxHealthyPercentage - minHealthyPercentage}`, this);
}
return {
minHealthyPercentage,
maxHealthyPercentage,
};
}
private renderHealthChecks(healthChecks?: HealthChecks, healthCheck?: HealthCheck): { healthCheckType?: string; healthCheckGracePeriod?: number } {
if (healthCheck && healthChecks) {
throw new ValidationError('Cannot specify both \'healthCheck\' and \'healthChecks\'. Please use \'healthChecks\' only.', this);
}
let healthCheckType: string | undefined;
let healthCheckGracePeriod: number | undefined;
if (healthChecks) {
healthCheckType = healthChecks.types.join(',');
healthCheckGracePeriod = healthChecks.gracePeriod?.toSeconds();
} else if (healthCheck) {
healthCheckType = healthCheck.type;
healthCheckGracePeriod = healthCheck.gracePeriod?.toSeconds();
}
return { healthCheckType, healthCheckGracePeriod };
}
}
/**
* The type of update to perform on instances in this AutoScalingGroup
*
* @deprecated Use UpdatePolicy instead
*/
export enum UpdateType {
/**
* Don't do anything
*/
NONE = 'None',
/**
* Replace the entire AutoScalingGroup
*
* Builds a new AutoScalingGroup first, then delete the old one.
*/
REPLACING_UPDATE = 'Replace',
/**
* Replace the instances in the AutoScalingGroup.
*/
ROLLING_UPDATE = 'RollingUpdate',
}
/**
* AutoScalingGroup fleet change notifications configurations.
* You can configure AutoScaling to send an SNS notification whenever your Auto Scaling group scales.
*/
export interface NotificationConfiguration {
/**
* SNS topic to send notifications about fleet scaling events
*/
readonly topic: sns.ITopic;
/**
* Which fleet scaling events triggers a notification
* @default ScalingEvents.ALL
*/
readonly scalingEvents?: ScalingEvents;
}
/**
* Fleet scaling events
*/
export enum ScalingEvent {
/**
* Notify when an instance was launched
*/
INSTANCE_LAUNCH = 'autoscaling:EC2_INSTANCE_LAUNCH',
/**
* Notify when an instance was terminated
*/
INSTANCE_TERMINATE = 'autoscaling:EC2_INSTANCE_TERMINATE',
/**
* Notify when an instance failed to terminate
*/
INSTANCE_TERMINATE_ERROR = 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR',
/**
* Notify when an instance failed to launch
*/
INSTANCE_LAUNCH_ERROR = 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR',
/**
* Send a test notification to the topic
*/
TEST_NOTIFICATION = 'autoscaling:TEST_NOTIFICATION',
}
/**
* Additional settings when a rolling update is selected
* @deprecated use `UpdatePolicy.rollingUpdate()`
*/
export interface RollingUpdateConfiguration {
/**
* The maximum number of instances that AWS CloudFormation updates at once.
*
* @default 1
*/
readonly maxBatchSize?: number;
/**
* The minimum number of instances that must be in service before more instances are replaced.
*
* This number affects the speed of the replacement.
*
* @default 0
*/
readonly minInstancesInService?: number;
/**
* The percentage of instances that must signal success for an update to succeed.
*
* If an instance doesn't send a signal within the time specified in the
* pauseTime property, AWS CloudFormation assumes that the instance wasn't
* updated.
*
* This number affects the success of the replacement.
*
* If you specify this property, you must also enable the
* waitOnResourceSignals and pauseTime properties.
*
* @default 100
*/
readonly minSuccessfulInstancesPercent?: number;
/**
* The pause time after making a change to a batch of instances.
*
* This is intended to give those instances time to start software applications.
*
* Specify PauseTime in the ISO8601 duration format (in the format
* PT#H#M#S, where each # is the number of hours, minutes, and seconds,
* respectively). The maximum PauseTime is one hour (PT1H).
*
* @default Duration.minutes(5) if the waitOnResourceSignals property is true, otherwise 0
*/
readonly pauseTime?: Duration;
/**
* Specifies whether the Auto Scaling group waits on signals from new instances during an update.
*
* AWS CloudFormation must receive a signal from each new instance within
* the specified PauseTime before continuing the update.
*
* To have instances wait for an Elastic Load Balancing health check before
* they signal success, add a health-check verification by using the
* cfn-init helper script. For an example, see the verify_instance_health
* command in the Auto Scaling rolling updates sample template.
*
* @default true if you specified the minSuccessfulInstancesPercent property, false otherwise
*/
readonly waitOnResourceSignals?: boolean;
/**
* Specifies the Auto Scaling processes to suspend during a stack update.
*
* Suspending processes prevents Auto Scaling from interfering with a stack
* update.
*
* @default HealthCheck, ReplaceUnhealthy, AZRebalance, AlarmNotification, ScheduledActions.
*/
readonly suspendProcesses?: ScalingProcess[];
}
/**
* A list of ScalingEvents, you can use one of the predefined lists, such as ScalingEvents.ERRORS
* or create a custom group by instantiating a `NotificationTypes` object, e.g: `new NotificationTypes(`NotificationType.INSTANCE_LAUNCH`)`.
*/
export class ScalingEvents {
/**
* Fleet scaling errors
*/
public static readonly ERRORS = new ScalingEvents(ScalingEvent.INSTANCE_LAUNCH_ERROR, ScalingEvent.INSTANCE_TERMINATE_ERROR);
/**
* All fleet scaling events
*/
public static readonly ALL = new ScalingEvents(ScalingEvent.INSTANCE_LAUNCH,
ScalingEvent.INSTANCE_LAUNCH_ERROR,
ScalingEvent.INSTANCE_TERMINATE,
ScalingEvent.INSTANCE_TERMINATE_ERROR);
/**
* Fleet scaling launch events
*/
public static readonly LAUNCH_EVENTS = new ScalingEvents(ScalingEvent.INSTANCE_LAUNCH, ScalingEvent.INSTANCE_LAUNCH_ERROR);
/**
* Fleet termination launch events
*/
public static readonly TERMINATION_EVENTS = new ScalingEvents(ScalingEvent.INSTANCE_TERMINATE, ScalingEvent.INSTANCE_TERMINATE_ERROR);
/**
* @internal
*/
public readonly _types: ScalingEvent[];
constructor(...types: ScalingEvent[]) {
this._types = types;
}
}
export enum ScalingProcess {
LAUNCH = 'Launch',
TERMINATE = 'Terminate',
HEALTH_CHECK = 'HealthCheck',
REPLACE_UNHEALTHY = 'ReplaceUnhealthy',
AZ_REBALANCE = 'AZRebalance',
ALARM_NOTIFICATION = 'AlarmNotification',
SCHEDULED_ACTIONS = 'ScheduledActions',
ADD_TO_LOAD_BALANCER = 'AddToLoadBalancer',
INSTANCE_REFRESH = 'InstanceRefresh',
}
// Recommended list of processes to suspend from here:
// https://aws.amazon.com/premiumsupport/knowledge-center/auto-scaling-group-rolling-updates/
const DEFAULT_SUSPEND_PROCESSES = [ScalingProcess.HEALTH_CHECK, ScalingProcess.REPLACE_UNHEALTHY, ScalingProcess.AZ_REBALANCE,
ScalingProcess.ALARM_NOTIFICATION, ScalingProcess.SCHEDULED_ACTIONS, ScalingProcess.INSTANCE_REFRESH];
/**
* EC2 Heath check options
*
* @deprecated Use Ec2HealthChecksOptions instead
*/
export interface Ec2HealthCheckOptions {
/**
* Specified the time Auto Scaling waits before checking the health status of an EC2 instance that has come into service
*
* @default Duration.seconds(0)
*/
readonly grace?: Duration;
}
/**
* ELB Heath check options
*
* @deprecated Use AdditionalHealthChecksOptions instead
*/
export interface ElbHealthCheckOptions {
/**
* Specified the time Auto Scaling waits before checking the health status of an EC2 instance that has come into service
*
* This option is required for ELB health checks.
*/
readonly grace: Duration;
}
/**
* Health check settings
*
* @deprecated Use HealthChecks instead
*/
export class HealthCheck {
/**
* Use EC2 for health checks
*
* @param options EC2 health check options
*/
public static ec2(options: Ec2HealthCheckOptions = {}): HealthCheck {
return new HealthCheck(HealthCheckType.EC2, options.grace);
}
/**
* Use ELB for health checks.
* It considers the instance unhealthy if it fails either the EC2 status checks or the load balancer health checks.
*
* @param options ELB health check options
*/
public static elb(options: ElbHealthCheckOptions): HealthCheck {
return new HealthCheck(HealthCheckType.ELB, options.grace);
}
private constructor(public readonly type: string, public readonly gracePeriod?: Duration) { }
}
/**
* Heath checks base options
*/
interface HealthChecksBaseOptions {
/**
* Specified the time Auto Scaling waits before checking the health status of an EC2 instance that has come into service
* and marking it unhealthy due to a failed health check.
*
* @default Duration.seconds(0)
* @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/health-check-grace-period.html
*/
readonly gracePeriod?: Duration;
}
/**
* EC2 Heath checks options
*/
export interface Ec2HealthChecksOptions extends HealthChecksBaseOptions {
}
/**
* Additional Heath checks options
*/
export interface AdditionalHealthChecksOptions extends HealthChecksBaseOptions {
/**
* One or more health check types other than EC2.
*/
readonly additionalTypes: AdditionalHealthCheckType[];
}
/**
* Health check settings for multiple types
*/
export class HealthChecks {
/**
* Use EC2 only for health checks.
*
* @param options EC2 health checks options
*/
public static ec2(options: Ec2HealthChecksOptions = {}): HealthChecks {
return new HealthChecks(['EC2'], options.gracePeriod);
}
/**
* Use additional health checks other than EC2.
*
* Specify types other than EC2, as EC2 is always enabled.
* It considers the instance unhealthy if it fails either the EC2 status checks or the additional health checks.
*
* @param options Additional health checks options
*/
public static withAdditionalChecks(options: AdditionalHealthChecksOptions): HealthChecks {
return new HealthChecks(options.additionalTypes, options.gracePeriod);
}
private constructor(public readonly types: string[], public readonly gracePeriod?: Duration) {
if (types.length === 0) {
throw new UnscopedValidationError('At least one health check type must be specified in \'additionalTypes\' for \'healthChecks\'');
}
}
}
/**
* @deprecated Use AdditionalHealthCheckType instead
*/
enum HealthCheckType {
EC2 = 'EC2',
ELB = 'ELB',
}
/**
* Additional Health Check Type
*/
export enum AdditionalHealthCheckType {
/**
* ELB Health Check
*/
ELB = 'ELB',
/**
* EBS Health Check
*/
EBS = 'EBS',
/**
* VPC LATTICE Health Check
*/
VPC_LATTICE = 'VPC_LATTICE',
}
/**
* Render the rolling update configuration into the appropriate object
*/
function renderRollingUpdateConfig(config: RollingUpdateConfiguration = {}): CfnAutoScalingRollingUpdate {
const waitOnResourceSignals = config.minSuccessfulInstancesPercent !== undefined;
const pauseTime = config.pauseTime || (waitOnResourceSignals ? Duration.minutes(5) : Duration.seconds(0));
return {
maxBatchSize: config.maxBatchSize,
minInstancesInService: config.minInstancesInService,
minSuccessfulInstancesPercent: validatePercentage(config.minSuccessfulInstancesPercent),
waitOnResourceSignals,
pauseTime: pauseTime && pauseTime.toIsoString(),
suspendProcesses: config.suspendProcesses ?? DEFAULT_SUSPEND_PROCESSES,
};
}
function validatePercentage(x?: number): number | undefined {
if (x === undefined || (0 <= x && x <= 100)) { return x; }
throw new UnscopedValidationError(`Expected: a percentage 0..100, got: ${x}`);
}
/**
* An AutoScalingGroup
*/
export interface IAutoScalingGroup extends IResource, iam.IGrantable {
/**
* The name of the AutoScalingGroup
* @attribute
*/
readonly autoScalingGroupName: string;
/**
* The arn of the AutoScalingGroup
* @attribute
*/
readonly autoScalingGroupArn: string;
/**
* The operating system family that the instances in this auto-scaling group belong to.
* Is 'UNKNOWN' for imported ASGs.
*/
readonly osType: ec2.OperatingSystemType;
/**
* Add command to the startup script of fleet instances.
* The command must be in the scripting language supported by the fleet's OS (i.e. Linux/Windows).
* Does nothing for imported ASGs.
*/
addUserData(...commands: string[]): void;
/**
* Send a message to either an SQS queue or SNS topic when instances launch or terminate
*/
addLifecycleHook(id: string, props: BasicLifecycleHookProps): LifecycleHook;
/**
* Add a pool of pre-initialized EC2 instances that sits alongside an Auto Scaling group
*/
addWarmPool(options?: WarmPoolOptions): WarmPool;
/**
* Scale out or in based on time
*/
scaleOnSchedule(id: string, props: BasicScheduledActionProps): ScheduledAction;
/**
* Scale out or in to achieve a target CPU utilization
*/
scaleOnCpuUtilization(id: string, props: CpuUtilizationScalingProps): TargetTrackingScalingPolicy;
/**
* Scale out or in to achieve a target network ingress rate
*/
scaleOnIncomingBytes(id: string, props: NetworkUtilizationScalingProps): TargetTrackingScalingPolicy;
/**
* Scale out or in to achieve a target network egress rate
*/
scaleOnOutgoingBytes(id: string, props: NetworkUtilizationScalingProps): TargetTrackingScalingPolicy;
/**
* Scale out or in in order to keep a metric around a target value
*/
scaleToTrackMetric(id: string, props: MetricTargetTrackingProps): TargetTrackingScalingPolicy;
/**
* Scale out or in, in response to a metric
*/
scaleOnMetric(id: string, props: BasicStepScalingPolicyProps): StepScalingPolicy;
}
/**
* Properties for enabling scaling based on CPU utilization
*/
export interface CpuUtilizationScalingProps extends BaseTargetTrackingProps {
/**
* Target average CPU utilization across the task
*/
readonly targetUtilizationPercent: number;
}
/**
* Properties for enabling scaling based on network utilization
*/
export interface NetworkUtilizationScalingProps extends BaseTargetTrackingProps {
/**
* Target average bytes/seconds on each instance
*/
readonly targetBytesPerSecond: number;
}
/**
* Properties for enabling scaling based on request/second
*/
export interface RequestCountScalingProps extends BaseTargetTrackingProps {
/**
* Target average requests/seconds on each instance
*
* @deprecated Use 'targetRequestsPerMinute' instead
* @default - Specify exactly one of 'targetRequestsPerMinute' and 'targetRequestsPerSecond'
*/
readonly targetRequestsPerSecond?: number;
/**
* Target average requests/minute on each instance
* @default - Specify exactly one of 'targetRequestsPerMinute' and 'targetRequestsPerSecond'
*/
readonly targetRequestsPerMinute?: number;
}
/**
* Properties for enabling tracking of an arbitrary metric
*/
export interface MetricTargetTrackingProps extends BaseTargetTrackingProps {
/**
* Metric to track
*
* The metric must represent a utilization, so that if it's higher than the
* target value, your ASG should scale out, and if it's lower it should
* scale in.
*/
readonly metric: cloudwatch.IMetric;
/**
* Value to keep the metric around
*/
readonly targetValue: number;
}
/**
* Synthesize an array of block device mappings from a list of block device
*
* @param construct the instance/asg construct, used to host any warning
* @param blockDevices list of block devices
*/
function synthesizeBlockDeviceMappings(construct: Construct, blockDevices: BlockDevice[]): CfnLaunchConfiguration.BlockDeviceMappingProperty[] {
return blockDevices.map<CfnLaunchConfiguration.BlockDeviceMappingProperty>(({ deviceName, volume, mappingEnabled }) => {
const { virtualName, ebsDevice: ebs } = volume;
if (volume === BlockDeviceVolume._NO_DEVICE || mappingEnabled === false) {
return {
deviceName,
noDevice: true,
};
}
if (ebs) {
const { iops, volumeType, throughput } = ebs;
if (throughput) {
const throughputRange = { Min: 125, Max: 1000 };
const { Min, Max } = throughputRange;
if (volumeType != EbsDeviceVolumeType.GP3) {
throw new ValidationError('throughput property requires volumeType: EbsDeviceVolumeType.GP3', construct);
}
if (throughput < Min || throughput > Max) {
throw new ValidationError(
`throughput property takes a minimum of ${Min} and a maximum of ${Max}`, construct,
);
}
const maximumThroughputRatio = 0.25;
if (iops) {
const iopsRatio = (throughput / iops);
if (iopsRatio > maximumThroughputRatio) {
throw new ValidationError(`Throughput (MiBps) to iops ratio of ${iopsRatio} is too high; maximum is ${maximumThroughputRatio} MiBps per iops`, construct);
}
}
}
if (!iops) {
if (volumeType === EbsDeviceVolumeType.IO1) {
throw new ValidationError('iops property is required with volumeType: EbsDeviceVolumeType.IO1', construct);
}
} else if (volumeType !== EbsDeviceVolumeType.IO1) {
Annotations.of(construct).addWarningV2('@aws-cdk/aws-autoscaling:iopsIgnored', 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1');
}
}
return {
deviceName, ebs, virtualName,
};
});
}
/**
* Options for applying CloudFormation init to an instance or instance group
*/
export interface ApplyCloudFormationInitOptions {
/**
* ConfigSet to activate
*
* @default ['default']
*/
readonly configSets?: string[];
/**
* Force instance replacement by embedding a config fingerprint
*
* If `true` (the default), a hash of the config will be embedded into the
* UserData, so that if the config changes, the UserData changes and
* instances will be replaced (given an UpdatePolicy has been configured on
* the AutoScalingGroup).
*
* If `false`, no such hash will be embedded, and if the CloudFormation Init
* config changes nothing will happen to the running instances. If a
* config update introduces errors, you will not notice until after the
* CloudFormation deployment successfully finishes and the next instance
* fails to launch.
*
* @default true
*/
readonly embedFingerprint?: boolean;
/**
* Print the results of running cfn-init to the Instance System Log
*
* By default, the output of running cfn-init is written to a log file
* on the instance. Set this to `true` to print it to the System Log
* (visible from the EC2 Console), `false` to not print it.
*
* (Be aware that the system log is refreshed at certain points in
* time of the instance life cycle, and successful execution may
* not always show up).
*
* @default true
*/
readonly printLog?: boolean;
/**
* Don't fail the instance creation when cfn-init fails
*
* You can use this to prevent CloudFormation from rolling back when
* instances fail to start up, to help in debugging.
*
* @default false
*/
readonly ignoreFailures?: boolean;
/**
* Include --url argument when running cfn-init and cfn-signal commands
*
* This will be the cloudformation endpoint in the deployed region
* e.g. https://cloudformation.us-east-1.amazonaws.com
*
* @default false
*/
readonly includeUrl?: boolean;
/**
* Include --role argument when running cfn-init and cfn-signal commands
*
* This will be the IAM instance profile attached to the EC2 instance
*
* @default false
*/
readonly includeRole?: boolean;
}