Skip to content

Commit

Permalink
feat(autoscaling): enable group metrics collections (#7432)
Browse files Browse the repository at this point in the history
* feat(autoscaling): add metrics collections

Co-authored-by: Neta Nir <[email protected]>
Co-authored-by: Eli Polonsky <[email protected]>
  • Loading branch information
3 people authored Jul 20, 2020
1 parent c03efe1 commit 9867555
Show file tree
Hide file tree
Showing 4 changed files with 901 additions and 22 deletions.
24 changes: 23 additions & 1 deletion packages/@aws-cdk/aws-autoscaling/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,36 @@ about allowing connections between resources backed by instances.

To enable the max instance lifetime support, specify `maxInstanceLifetime` property
for the `AutoscalingGroup` resource. The value must be between 7 and 365 days(inclusive).
To clear a previously set value, just leave this property undefinied.
To clear a previously set value, leave this property undefined.

### Instance Monitoring

To disable detailed instance monitoring, specify `instanceMonitoring` property
for the `AutoscalingGroup` resource as `Monitoring.BASIC`. Otherwise detailed monitoring
will be enabled.

### Monitoring Group Metrics

Group metrics are used to monitor group level properties; they describe the group rather than any of its instances (e.g GroupMaxSize, the group maximum size). To enable group metrics monitoring, use the `groupMetrics` property.
All group metrics are reported in a granularity of 1 minute at no additional charge.

See [EC2 docs](https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-instance-monitoring.html#as-group-metrics) for a list of all available group metrics.

To enable group metrics monitoring using the `groupMetrics` property:

```ts
// Enable monitoring of all group metrics
new autoscaling.AutoScalingGroup(stack, 'ASG', {
groupMetrics: [GroupMetrics.all()],
// ...
});

// Enable monitoring for a subset of group metrics
new autoscaling.AutoScalingGroup(stack, 'ASG', {
groupMetrics: [new autoscaling.GroupMetrics(GroupMetric.MIN_SIZE, GroupMetric.MAX_SIZE)],
// ...
});
```

### Future work

Expand Down
117 changes: 112 additions & 5 deletions packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export interface CommonAutoScalingGroupProps {
* 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,
* simply leave this property undefinied.
* leave this property undefined.
*
* @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-max-instance-lifetime.html
*
Expand All @@ -236,8 +236,16 @@ export interface CommonAutoScalingGroupProps {
readonly instanceMonitoring?: Monitoring;

/**
* The name of the Auto Scaling group. This name must be unique per Region per account.
* 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[];

/**
* The name of the Auto Scaling group. This name must be unique per Region per account.
* @default - Auto generated by CloudFormation
*/
readonly autoScalingGroupName?: string;
Expand Down Expand Up @@ -296,6 +304,88 @@ export interface AutoScalingGroupProps extends CommonAutoScalingGroupProps {
readonly role?: iam.IRole;
}

/**
* 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;
}
}

abstract class AutoScalingGroupBase extends Resource implements IAutoScalingGroup {

public abstract autoScalingGroupName: string;
Expand Down Expand Up @@ -470,7 +560,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
public readonly userData: ec2.UserData;

/**
* The maximum spot price configured for thie autoscaling group. `undefined`
* The maximum spot price configured for the autoscaling group. `undefined`
* indicates that this group uses on-demand capacity.
*/
public readonly spotPrice?: string;
Expand All @@ -485,6 +575,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
private readonly securityGroups: ec2.ISecurityGroup[] = [];
private readonly loadBalancerNames: string[] = [];
private readonly targetGroupArns: string[] = [];
private readonly groupMetrics: GroupMetrics[] = [];
private readonly notifications: NotificationConfiguration[] = [];

constructor(scope: Construct, id: string, props: AutoScalingGroupProps) {
Expand All @@ -507,6 +598,10 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements

this.grantPrincipal = this.role;

if (props.groupMetrics) {
this.groupMetrics.push(...props.groupMetrics);
}

const iamProfile = new iam.CfnInstanceProfile(this, 'InstanceProfile', {
roles: [ this.role.roleName ],
});
Expand Down Expand Up @@ -595,6 +690,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
loadBalancerNames: Lazy.listValue({ produce: () => this.loadBalancerNames }, { omitEmpty: true }),
targetGroupArns: Lazy.listValue({ produce: () => this.targetGroupArns }, { omitEmpty: true }),
notificationConfigurations: this.renderNotificationConfiguration(),
metricsCollection: Lazy.anyValue({ produce: () => this.renderMetricsCollection() }),
vpcZoneIdentifier: subnetIds,
healthCheckType: props.healthCheck && props.healthCheck.type,
healthCheckGracePeriod: props.healthCheck && props.healthCheck.gracePeriod && props.healthCheck.gracePeriod.toSeconds(),
Expand Down Expand Up @@ -740,6 +836,17 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
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,
}));
}
}

/**
Expand Down Expand Up @@ -786,7 +893,7 @@ export interface NotificationConfiguration {
*/
export enum ScalingEvent {
/**
* Notify when an instance was launced
* Notify when an instance was launched
*/
INSTANCE_LAUNCH = 'autoscaling:EC2_INSTANCE_LAUNCH',

Expand Down Expand Up @@ -888,7 +995,7 @@ export interface RollingUpdateConfiguration {

/**
* A list of ScalingEvents, you can use one of the predefined lists, such as ScalingEvents.ERRORS
* or create a custome group by instantiating a `NotificationTypes` object, e.g: `new NotificationTypes(`NotificationType.INSTANCE_LAUNCH`)`.
* or create a custom group by instantiating a `NotificationTypes` object, e.g: `new NotificationTypes(`NotificationType.INSTANCE_LAUNCH`)`.
*/
export class ScalingEvents {
/**
Expand Down
121 changes: 105 additions & 16 deletions packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ nodeunitShim({
test.done();
},

'userdata can be overriden by image'(test: Test) {
'userdata can be overridden by image'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = mockVpc(stack);
Expand All @@ -209,7 +209,7 @@ nodeunitShim({
test.done();
},

'userdata can be overriden at ASG directly'(test: Test) {
'userdata can be overridden at ASG directly'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = mockVpc(stack);
Expand Down Expand Up @@ -1088,24 +1088,92 @@ nodeunitShim({
test.done();
},

'throw if notification and notificationsTopics are both configured'(test: Test) {
'test GroupMetrics.all(), adds a single MetricsCollection with no Metrics specified'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = mockVpc(stack);
const topic = new sns.Topic(stack, 'MyTopic');
// When
new autoscaling.AutoScalingGroup(stack, 'ASG', {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO),
machineImage: new ec2.AmazonLinuxImage(),
vpc,
groupMetrics: [autoscaling.GroupMetrics.all()],
});

// THEN
test.throws(() => {
new autoscaling.AutoScalingGroup(stack, 'MyASG', {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO),
machineImage: new ec2.AmazonLinuxImage(),
vpc,
notificationsTopic: topic,
notifications: [{
topic,
}],
});
}, 'Cannot set \'notificationsTopic\' and \'notifications\', \'notificationsTopic\' is deprecated use \'notifications\' instead');
// Then
expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', {
MetricsCollection: [
{
Granularity: '1Minute',
Metrics: ABSENT,
},
],
}));
test.done();
},

'test can specify a subset of group metrics'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = mockVpc(stack);

// WHEN
new autoscaling.AutoScalingGroup(stack, 'ASG', {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO),
machineImage: new ec2.AmazonLinuxImage(),
groupMetrics: [
new autoscaling.GroupMetrics(autoscaling.GroupMetric.MIN_SIZE,
autoscaling.GroupMetric.MAX_SIZE,
autoscaling.GroupMetric.DESIRED_CAPACITY,
autoscaling.GroupMetric.IN_SERVICE_INSTANCES),
new autoscaling.GroupMetrics(autoscaling.GroupMetric.PENDING_INSTANCES,
autoscaling.GroupMetric.STANDBY_INSTANCES,
autoscaling.GroupMetric.TOTAL_INSTANCES,
autoscaling.GroupMetric.TERMINATING_INSTANCES,
),
],
vpc,
});

// Then
expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', {
MetricsCollection: [
{
Granularity: '1Minute',
Metrics: [ 'GroupMinSize', 'GroupMaxSize', 'GroupDesiredCapacity', 'GroupInServiceInstances' ],
}, {
Granularity: '1Minute',
Metrics: [ 'GroupPendingInstances', 'GroupStandbyInstances', 'GroupTotalInstances', 'GroupTerminatingInstances' ],
},
],
}));
test.done();
},

'test deduplication of group metrics '(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = mockVpc(stack);
new autoscaling.AutoScalingGroup(stack, 'ASG', {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO),
machineImage: new ec2.AmazonLinuxImage(),
vpc,
groupMetrics: [new autoscaling.GroupMetrics(autoscaling.GroupMetric.MIN_SIZE,
autoscaling.GroupMetric.MAX_SIZE,
autoscaling.GroupMetric.MAX_SIZE,
autoscaling.GroupMetric.MIN_SIZE,
)],
});

// Then
expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', {
MetricsCollection: [
{
Granularity: '1Minute',
Metrics: [ 'GroupMinSize', 'GroupMaxSize' ],
},
],
}));
test.done();
},

Expand Down Expand Up @@ -1154,6 +1222,27 @@ nodeunitShim({
test.done();
},

'throw if notification and notificationsTopics are both configured'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = mockVpc(stack);
const topic = new sns.Topic(stack, 'MyTopic');

// THEN
test.throws(() => {
new autoscaling.AutoScalingGroup(stack, 'MyASG', {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO),
machineImage: new ec2.AmazonLinuxImage(),
vpc,
notificationsTopic: topic,
notifications: [{
topic,
}],
});
}, 'Cannot set \'notificationsTopic\' and \'notifications\', \'notificationsTopic\' is deprecated use \'notifications\' instead');
test.done();
},

'notificationTypes default includes all non test NotificationType'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
Expand Down
Loading

0 comments on commit 9867555

Please sign in to comment.