Skip to content

Commit 7861c6f

Browse files
authored
feat: add construct library for Application AutoScaling (#933)
Adds a construct library for Application AutoScaling. The DynamoDB construct library has been updated to use the new AutoScaling mechanism, which allows more configuration and uses a Service Linked Role instead of a role per table. BREAKING CHANGE: instead of `addReadAutoScaling()`, call `autoScaleReadCapacity()`, and similar for write scaling. Fixes #856, #861, #640, #644.
1 parent bca99c6 commit 7861c6f

Some content is hidden

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

41 files changed

+2541
-1147
lines changed
Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,149 @@
1-
## The CDK Construct Library for AWS Application Auto-Scaling
2-
This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project.
1+
## AWS Application AutoScaling Construct Library
2+
3+
**Application AutoScaling** is used to configure autoscaling for all
4+
services other than scaling EC2 instances. For example, you will use this to
5+
scale ECS tasks, DynamoDB capacity, Spot Fleet sizes and more.
6+
7+
As a CDK user, you will probably not have to interact with this library
8+
directly; instead, it will be used by other construct libraries to
9+
offer AutoScaling features for their own constructs.
10+
11+
This document will describe the general autoscaling features and concepts;
12+
your particular service may offer only a subset of these.
13+
14+
### AutoScaling basics
15+
16+
Resources can offer one or more **attributes** to autoscale, typically
17+
representing some capacity dimension of the underlying service. For example,
18+
a DynamoDB Table offers autoscaling of the read and write capacity of the
19+
table proper and its Global Secondary Indexes, an ECS Service offers
20+
autoscaling of its task count, an RDS Aurora cluster offers scaling of its
21+
replica count, and so on.
22+
23+
When you enable autoscaling for an attribute, you specify a minimum and a
24+
maximum value for the capacity. AutoScaling policies that respond to metrics
25+
will never go higher or lower than the indicated capacity (but scheduled
26+
scaling actions might, see below).
27+
28+
There are three ways to scale your capacity:
29+
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%.
35+
* **On a schedule**; you might want to organize your scaling around traffic
36+
flows you expect, by scaling out in the morning and scaling in in the
37+
evening.
38+
39+
The general pattern of autoscaling will look like this:
40+
41+
```ts
42+
const capacity = resource.autoScaleCapacity({
43+
minCapacity: 5,
44+
maxCapacity: 100
45+
});
46+
47+
// Enable a type of metric scaling and/or schedule scaling
48+
capacity.scaleOnMetric(...);
49+
capacity.scaleToTrackMetric(...);
50+
capacity.scaleOnSchedule(...);
51+
```
52+
53+
### AutoScaling in response to a metric
54+
55+
This type of scaling scales in and out in deterministics steps that you
56+
configure, in response to metric values. For example, your scaling strategy
57+
to scale in response to CPU usage might look like this:
58+
59+
```
60+
Scaling -1 (no change) +1 +3
61+
│ │ │ │ │
62+
├────────┼───────────────────────┼────────┼────────┤
63+
│ │ │ │ │
64+
CPU usage 0% 10% 50% 70% 100%
65+
```
66+
67+
(Note that this is not necessarily a recommended scaling strategy, but it's
68+
a possible one. You will have to determine what thresholds are right for you).
69+
70+
You would configure it like this:
71+
72+
```ts
73+
capacity.scaleOnMetric('ScaleToCPU', {
74+
metric: service.metricCpuUtilization(),
75+
scalingSteps: [
76+
{ upper: 10, change: -1 },
77+
{ lower: 50, change: +1 },
78+
{ lower: 70, change: +3 },
79+
],
80+
81+
// Change this to AdjustmentType.PercentChangeInCapacity to interpret the
82+
// 'change' numbers before as percentages instead of capacity counts.
83+
adjustmentType: autoscaling.AdjustmentType.ChangeInCapacity,
84+
});
85+
```
86+
87+
The AutoScaling construct library will create the required CloudWatch alarms and
88+
AutoScaling policies for you.
89+
90+
### AutoScaling by tracking a metric value
91+
92+
This type of scaling scales in and out in order to keep a metric (typically
93+
representing utilization) around a value you prefer. This type of scaling is
94+
typically heavily service-dependent in what metric you can use, and so
95+
different services will have different methods here to set up target tracking
96+
scaling.
97+
98+
The following example configures the read capacity of a DynamoDB table
99+
to be around 60% utilization:
100+
101+
```ts
102+
const readCapacity = table.autosScaleReadCapacity({
103+
minCapacity: 10,
104+
maxCapacity: 1000
105+
});
106+
readCapacity.scaleOnUtilization({
107+
targetUtilizationPercent: 60
108+
});
109+
```
110+
111+
### AutoScaling on a schedule
112+
113+
This type of scaling is used to change capacities based on time. It works
114+
by changing the `minCapacity` and `maxCapacity` of the attribute, and so
115+
can be used for two purposes:
116+
117+
* Scale in and out on a schedule by setting the `minCapacity` high or
118+
the `maxCapacity` low.
119+
* Still allow the regular scaling actions to do their job, but restrict
120+
the range they can scale over (by setting both `minCapacity` and
121+
`maxCapacity` but changing their range over time).
122+
123+
The following schedule expressions can be used:
124+
125+
* `at(yyyy-mm-ddThh:mm:ss)` -- scale at a particular moment in time
126+
* `rate(value unit)` -- scale every minute/hour/day
127+
* `cron(mm hh dd mm dow)` -- scale on arbitrary schedules
128+
129+
Of these, the cron expression is the most useful but also the most
130+
complicated. There is a `Cron` helper class to help build cron expressions.
131+
132+
The following example scales the fleet out in the morning, and lets natural
133+
scaling take over at night:
134+
135+
```ts
136+
const capacity = resource.autoScaleCapacity({
137+
minCapacity: 1,
138+
maxCapacity: 50,
139+
});
140+
141+
capacity.scaleOnSchedule('PrescaleInTheMorning', {
142+
schedule: autoscaling.Cron.dailyUtc(8),
143+
minCapacity: 20,
144+
});
145+
146+
capacity.scaleOnSchedule('AllowDownscalingAtNight', {
147+
schedule: autoscaling.Cron.dailyUtc(20),
148+
minCapacity: 1
149+
});
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import iam = require('@aws-cdk/aws-iam');
2+
import cdk = require('@aws-cdk/cdk');
3+
import { ScalableTarget, ScalingSchedule, ServiceNamespace } from './scalable-target';
4+
import { BasicStepScalingPolicyProps } from './step-scaling-policy';
5+
import { BasicTargetTrackingScalingPolicyProps } from './target-tracking-scaling-policy';
6+
7+
/**
8+
* Properties for a ScalableTableAttribute
9+
*/
10+
export interface BaseScalableAttributeProps extends EnableScalingProps {
11+
/**
12+
* Service namespace of the scalable attribute
13+
*/
14+
serviceNamespace: ServiceNamespace;
15+
16+
/**
17+
* Resource ID of the attribute
18+
*/
19+
resourceId: string;
20+
21+
/**
22+
* Scalable dimension of the attribute
23+
*/
24+
dimension: string;
25+
26+
/**
27+
* Role to use for scaling
28+
*/
29+
role: iam.IRole;
30+
}
31+
32+
/**
33+
* Represent an attribute for which autoscaling can be configured
34+
*
35+
* This class is basically a light wrapper around ScalableTarget, but with
36+
* all methods protected instead of public so they can be selectively
37+
* exposed and/or more specific versions of them can be exposed by derived
38+
* classes for individual services support autoscaling.
39+
*
40+
* Typical use cases:
41+
*
42+
* - Hide away the PredefinedMetric enum for target tracking policies.
43+
* - Don't expose all scaling methods (for example Dynamo tables don't support
44+
* Step Scaling, so the Dynamo subclass won't expose this method).
45+
*/
46+
export abstract class BaseScalableAttribute extends cdk.Construct {
47+
private target: ScalableTarget;
48+
49+
public constructor(parent: cdk.Construct, id: string, protected readonly props: BaseScalableAttributeProps) {
50+
super(parent, id);
51+
52+
this.target = new ScalableTarget(this, 'Target', {
53+
serviceNamespace: this.props.serviceNamespace,
54+
scalableDimension: this.props.dimension,
55+
resourceId: this.props.resourceId,
56+
role: this.props.role,
57+
minCapacity: props.minCapacity !== undefined ? props.minCapacity : 1,
58+
maxCapacity: props.maxCapacity
59+
});
60+
}
61+
62+
/**
63+
* Scale out or in based on time
64+
*/
65+
protected scaleOnSchedule(id: string, props: ScalingSchedule) {
66+
this.target.scaleOnSchedule(id, props);
67+
}
68+
69+
/**
70+
* Scale out or in based on a metric value
71+
*/
72+
protected scaleOnMetric(id: string, props: BasicStepScalingPolicyProps) {
73+
this.target.scaleOnMetric(id, props);
74+
}
75+
76+
/**
77+
* Scale out or in in order to keep a metric around a target value
78+
*/
79+
protected scaleToTrackMetric(id: string, props: BasicTargetTrackingScalingPolicyProps) {
80+
this.target.scaleToTrackMetric(id, props);
81+
}
82+
}
83+
84+
/**
85+
* Properties for enabling DynamoDB capacity scaling
86+
*/
87+
export interface EnableScalingProps {
88+
/**
89+
* Minimum capacity to scale to
90+
*
91+
* @default 1
92+
*/
93+
minCapacity?: number;
94+
95+
/**
96+
* Maximum capacity to scale to
97+
*/
98+
maxCapacity: number;
99+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Helper class to generate Cron expressions
3+
*/
4+
export class Cron {
5+
6+
/**
7+
* Return a cron expression to run every day at a particular time
8+
*
9+
* The time is specified in UTC.
10+
*
11+
* @param hour The hour in UTC to schedule this action
12+
* @param minute The minute in the our to schedule this action (defaults to 0)
13+
*/
14+
public static dailyUtc(hour: number, minute?: number) {
15+
minute = minute || 0;
16+
// 3rd and 5th expression are mutually exclusive, one of them should be ?
17+
return `cron(${minute} ${hour} * * ?)`;
18+
}
19+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
// AWS::ApplicationAutoScaling CloudFormation Resources:
22
export * from './applicationautoscaling.generated';
3+
4+
export * from './base-scalable-attribute';
5+
export * from './cron';
6+
export * from './scalable-target';
7+
export * from './step-scaling-policy';
8+
export * from './step-scaling-action';
9+
export * from './target-tracking-scaling-policy';

0 commit comments

Comments
 (0)