Skip to content

Commit d397dd7

Browse files
authored
feat(aws-autoscaling): add instance AutoScaling (#1134)
It's now possible to add autoscaling policies (step scaling, target tracking, and scheduled) to AutoScalingGroup. It's also possible to add lifecycle hooks to an AutoScalingGroup, which will publish messages to SQS queues or SNS topics. ALSO IN THIS COMMIT - Fix an issue where an invalid PauseTime is generated on AutoScalingGroup. Fixes #1042, fixes #1113.
1 parent 2db536e commit d397dd7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+82956
-209
lines changed

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-applicationautoscaling/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ scaling actions might, see below).
2727

2828
There are three ways to scale your capacity:
2929

30-
* **In response to a metric**; for example, you might want to scale out
31-
if the CPU usage across your cluster starts to rise, and scale in
32-
when it drops again.
33-
* **By trying to keep a certain metric around a given value**; you might
34-
want to automatically scale out an in to keep your CPU usage around 50%.
30+
* **In response to a metric** (also known as step scaling); for example, you
31+
might want to scale out if the CPU usage across your cluster starts to rise,
32+
and scale in when it drops again.
33+
* **By trying to keep a certain metric around a given value** (also known as
34+
target tracking scaling); you might want to automatically scale out an in to
35+
keep your CPU usage around 50%.
3536
* **On a schedule**; you might want to organize your scaling around traffic
3637
flows you expect, by scaling out in the morning and scaling in in the
3738
evening.
@@ -50,7 +51,7 @@ capacity.scaleToTrackMetric(...);
5051
capacity.scaleOnSchedule(...);
5152
```
5253

53-
### AutoScaling in response to a metric
54+
### Step Scaling
5455

5556
This type of scaling scales in and out in deterministics steps that you
5657
configure, in response to metric values. For example, your scaling strategy
@@ -87,7 +88,7 @@ capacity.scaleOnMetric('ScaleToCPU', {
8788
The AutoScaling construct library will create the required CloudWatch alarms and
8889
AutoScaling policies for you.
8990

90-
### AutoScaling by tracking a metric value
91+
### Target Tracking Scaling
9192

9293
This type of scaling scales in and out in order to keep a metric (typically
9394
representing utilization) around a value you prefer. This type of scaling is
@@ -108,7 +109,7 @@ readCapacity.scaleOnUtilization({
108109
});
109110
```
110111

111-
### AutoScaling on a schedule
112+
### Scheduled Scaling
112113

113114
This type of scaling is used to change capacities based on time. It works
114115
by changing the `minCapacity` and `maxCapacity` of the attribute, and so

packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { findAlarmThresholds, normalizeIntervals } from '@aws-cdk/aws-autoscaling-common';
12
import cloudwatch = require('@aws-cdk/aws-cloudwatch');
23
import cdk = require('@aws-cdk/cdk');
3-
import { findAlarmThresholds, normalizeIntervals } from './interval-utils';
44
import { ScalableTarget } from './scalable-target';
55
import { AdjustmentType, MetricAggregationType, StepScalingAction } from './step-scaling-action';
66

@@ -81,7 +81,7 @@ export class StepScalingPolicy extends cdk.Construct {
8181
const intervals = normalizeIntervals(props.scalingSteps, changesAreAbsolute);
8282
const alarms = findAlarmThresholds(intervals);
8383

84-
if (alarms.lowerAlarmIntervalIndex) {
84+
if (alarms.lowerAlarmIntervalIndex !== undefined) {
8585
const threshold = intervals[alarms.lowerAlarmIntervalIndex].upper;
8686

8787
this.lowerAction = new StepScalingAction(this, 'LowerPolicy', {
@@ -104,14 +104,14 @@ export class StepScalingPolicy extends cdk.Construct {
104104
// Recommended by AutoScaling
105105
metric: props.metric.with({ periodSec: 60 }),
106106
alarmDescription: 'Lower threshold scaling alarm',
107-
comparisonOperator: cloudwatch.ComparisonOperator.LessThanThreshold,
107+
comparisonOperator: cloudwatch.ComparisonOperator.LessThanOrEqualToThreshold,
108108
evaluationPeriods: 1,
109109
threshold,
110110
});
111111
this.lowerAlarm.onAlarm(this.lowerAction);
112112
}
113113

114-
if (alarms.upperAlarmIntervalIndex) {
114+
if (alarms.upperAlarmIntervalIndex !== undefined) {
115115
const threshold = intervals[alarms.upperAlarmIntervalIndex].lower;
116116

117117
this.upperAction = new StepScalingAction(this, 'UpperPolicy', {
@@ -134,7 +134,7 @@ export class StepScalingPolicy extends cdk.Construct {
134134
// Recommended by AutoScaling
135135
metric: props.metric.with({ periodSec: 60 }),
136136
alarmDescription: 'Upper threshold scaling alarm',
137-
comparisonOperator: cloudwatch.ComparisonOperator.GreaterThanThreshold,
137+
comparisonOperator: cloudwatch.ComparisonOperator.GreaterThanOrEqualToThreshold,
138138
evaluationPeriods: 1,
139139
threshold,
140140
});
@@ -143,9 +143,40 @@ export class StepScalingPolicy extends cdk.Construct {
143143
}
144144
}
145145

146+
/**
147+
* A range of metric values in which to apply a certain scaling operation
148+
*/
146149
export interface ScalingInterval {
150+
/**
151+
* The lower bound of the interval.
152+
*
153+
* The scaling adjustment will be applied if the metric is higher than this value.
154+
*
155+
* @default Threshold automatically derived from neighbouring intervals
156+
*/
147157
lower?: number;
158+
159+
/**
160+
* The upper bound of the interval.
161+
*
162+
* The scaling adjustment will be applied if the metric is lower than this value.
163+
*
164+
* @default Threshold automatically derived from neighbouring intervals
165+
*/
148166
upper?: number;
167+
168+
/**
169+
* The capacity adjustment to apply in this interval
170+
*
171+
* The number is interpreted differently based on AdjustmentType:
172+
*
173+
* - ChangeInCapacity: add the adjustment to the current capacity.
174+
* The number can be positive or negative.
175+
* - PercentChangeInCapacity: add or remove the given percentage of the current
176+
* capacity to itself. The number can be in the range [-100..100].
177+
* - ExactCapacity: set the capacity to this number. The number must
178+
* be positive.
179+
*/
149180
change: number;
150181
}
151182

packages/@aws-cdk/aws-applicationautoscaling/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"pkglint": "^0.17.0"
6060
},
6161
"dependencies": {
62+
"@aws-cdk/aws-autoscaling-common": "^0.17.0",
6263
"@aws-cdk/aws-cloudwatch": "^0.17.0",
6364
"@aws-cdk/aws-iam": "^0.17.0",
6465
"@aws-cdk/cdk": "^0.17.0"
@@ -69,4 +70,4 @@
6970
"@aws-cdk/aws-iam": "^0.17.0",
7071
"@aws-cdk/cdk": "^0.17.0"
7172
}
72-
}
73+
}

packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import cdk = require('@aws-cdk/cdk');
33
import fc = require('fast-check');
44
import { Test } from 'nodeunit';
55
import appscaling = require('../lib');
6-
import { arbitrary_input_intervals, createScalableTarget } from './util';
6+
import { arbitrary_input_intervals } from './util';
7+
import { createScalableTarget } from './util';
78

89
export = {
910
'alarm thresholds are valid numbers'(test: Test) {
Lines changed: 12 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,8 @@
1+
import scalingcommon = require('@aws-cdk/aws-autoscaling-common');
12
import cdk = require('@aws-cdk/cdk');
23
import fc = require('fast-check');
34
import appscaling = require('../lib');
45
import { ServiceNamespace } from '../lib';
5-
import { normalizeIntervals } from '../lib/interval-utils';
6-
7-
/**
8-
* Arbitrary (valid) array of intervals
9-
*
10-
* There are many invalid combinations of interval arrays, so we have
11-
* to be very specific about generating arrays that are valid. We do this
12-
* by taking a full, valid interval schedule and progressively stripping parts
13-
* away from it.
14-
*
15-
* Some of the changes may change its meaning, but we take care to never leave
16-
* a schedule with insufficient information so that the parser will error out.
17-
*/
18-
export class ArbitraryIntervals extends fc.Arbitrary<appscaling.ScalingInterval[]> {
19-
public generate(mrng: fc.Random): fc.Shrinkable<appscaling.ScalingInterval[]> {
20-
const ret = new Array<appscaling.ScalingInterval>();
21-
22-
const absolute = mrng.nextBoolean();
23-
24-
// Ascending or descending scaling
25-
const factor = (mrng.nextBoolean() ? 1 : -1) * (absolute ? 10 : 1);
26-
const bias = absolute ? 50 : 0;
27-
28-
// Begin with a full schedule
29-
ret.push({ lower: 0, upper: 10, change: -2 * factor + bias });
30-
ret.push({ lower: 10, upper: 20, change: -1 * factor + bias });
31-
ret.push({ lower: 20, upper: 60, change: 0 + bias });
32-
ret.push({ lower: 60, upper: 80, change: 0 + bias });
33-
ret.push({ lower: 80, upper: 90, change: 1 * factor + bias });
34-
ret.push({ lower: 90, upper: Infinity, change: 2 * factor + bias});
35-
36-
// Take away parts from this. First we see if we do something to the 0-change alarms.
37-
// The actions can be: remove it OR turn it into a regular change value.
38-
const noChanges = ret.filter(x => x.change === bias);
39-
40-
if (!absolute) {
41-
if (mrng.nextBoolean()) {
42-
if (mrng.nextBoolean()) {
43-
ret.splice(ret.indexOf(noChanges[0]), 1);
44-
} else {
45-
noChanges[0].change = -1 * factor + bias;
46-
}
47-
}
48-
if (mrng.nextBoolean()) {
49-
if (mrng.nextBoolean()) {
50-
ret.splice(ret.indexOf(noChanges[1]), 1);
51-
} else {
52-
noChanges[1].change = 1 * factor + bias;
53-
}
54-
}
55-
} else {
56-
// In absolute mode both have to get the same treatment at the same time
57-
// otherwise we'll end up with a timeline with two gaps
58-
if (mrng.nextBoolean()) {
59-
ret.splice(ret.indexOf(noChanges[0]), 1);
60-
ret.splice(ret.indexOf(noChanges[1]), 1);
61-
} else {
62-
noChanges[0].change = -1 * factor + bias;
63-
noChanges[1].change = 1 * factor + bias;
64-
}
65-
}
66-
67-
// We might also take away either the bottom or the upper half
68-
if (mrng.nextInt(0, 2) === 0) {
69-
const signToStrip = mrng.nextBoolean() ? -1 : 1;
70-
let ix = ret.findIndex(x => Math.sign(x.change - bias) === signToStrip);
71-
while (ix >= 0) {
72-
ret.splice(ix, 1);
73-
ix = ret.findIndex(x => Math.sign(x.change - bias) === signToStrip);
74-
}
75-
}
76-
77-
// Then we're going to arbitrarily get rid of bounds in the most naive way possible
78-
const iterations = mrng.nextInt(0, 10);
79-
for (let iter = 0; iter < iterations; iter++) {
80-
const i = mrng.nextInt(0, ret.length - 1);
81-
if (mrng.nextBoolean()) {
82-
// scrap lower bound
83-
// okay if current interval has an upper bound AND the preceding interval has an upper bound
84-
if (ret[i].upper !== undefined && (i === 0 || ret[i - 1].upper !== undefined)) {
85-
ret[i].lower = undefined;
86-
}
87-
} else {
88-
// scrap upper bound
89-
// okay if current interval has a lower bound AND the succeeding interval has a lower bound
90-
if (ret[i].lower !== undefined && (i === ret.length - 1 || ret[i + 1].lower !== undefined)) {
91-
ret[i].upper = undefined;
92-
}
93-
}
94-
}
95-
96-
// Hide a property on the array
97-
(ret as any).absolute = absolute;
98-
99-
// Shrinkable that doesn't actually shrink
100-
return new fc.Shrinkable(ret);
101-
}
102-
}
103-
104-
export function arbitrary_input_intervals() {
105-
return new ArbitraryIntervals();
106-
}
107-
108-
/**
109-
* Normalized interval array
110-
*/
111-
export function arbitrary_complete_intervals() {
112-
return new ArbitraryIntervals().map(x => {
113-
return normalizeIntervals(x, (x as any).absolute);
114-
});
115-
}
1166

1177
export function createScalableTarget(parent: cdk.Construct) {
1188
return new appscaling.ScalableTarget(parent, 'Target', {
@@ -123,3 +13,14 @@ export function createScalableTarget(parent: cdk.Construct) {
12313
maxCapacity: 20,
12414
});
12515
}
16+
17+
export class ArbitraryInputIntervals extends fc.Arbitrary<appscaling.ScalingInterval[]> {
18+
public generate(mrng: fc.Random): fc.Shrinkable<appscaling.ScalingInterval[]> {
19+
const ret = scalingcommon.generateArbitraryIntervals(mrng);
20+
return new fc.Shrinkable(ret.intervals);
21+
}
22+
}
23+
24+
export function arbitrary_input_intervals() {
25+
return new ArbitraryInputIntervals();
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
lambda/bundle.zip
2+
3+
*.js
4+
tsconfig.json
5+
tslint.json
6+
*.js.map
7+
*.d.ts
8+
*.generated.ts
9+
dist
10+
lib/generated/resources.ts
11+
.jsii
12+
lib/*.zip
13+
14+
.LAST_BUILD
15+
.nyc_output
16+
coverage
17+
.nycrc
18+
.LAST_PACKAGE
19+
20+
*.snk
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Don't include original .ts files when doing `npm pack`
2+
*.ts
3+
!*.d.ts
4+
coverage
5+
.nyc_output
6+
*.tgz
7+
dist
8+
.LAST_PACKAGE
9+
.LAST_BUILD
10+
!*.js
11+
12+
# Include .jsii
13+
!.jsii
14+
15+
lambda/src
16+
lambda/test
17+
lambda/*.sh
18+
*.snk

0 commit comments

Comments
 (0)