Skip to content

Commit d9d9b90

Browse files
authored
feat(lambda): autoscaling for lambda aliases (#8883)
1 parent ea39fbd commit d9d9b90

9 files changed

+584
-2
lines changed

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

+29
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,35 @@ const fn = new lambda.Function(this, 'MyFunction', {
272272
See [the AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html)
273273
managing concurrency.
274274

275+
### AutoScaling
276+
277+
You can use Application AutoScaling to automatically configure the provisioned concurrency for your functions. AutoScaling can be set to track utilization or be based on a schedule. To configure AutoScaling on a function alias:
278+
279+
```ts
280+
const alias = new lambda.Alias(stack, 'Alias', {
281+
aliasName: 'prod',
282+
version,
283+
});
284+
285+
// Create AutoScaling target
286+
const as = alias.addAutoScaling({ maxCapacity: 50 })
287+
288+
// Configure Target Tracking
289+
as.scaleOnUtilization({
290+
utilizationTarget: 0.5,
291+
});
292+
293+
// Configure Scheduled Scaling
294+
as.scaleOnSchedule('ScaleUpInTheMorning', {
295+
schedule: appscaling.Schedule.cron({ hour: '8', minute: '0'}),
296+
minCapacity: 20,
297+
});
298+
```
299+
300+
[Example of Lambda AutoScaling usage](test/integ.autoscaling.lit.ts)
301+
302+
See [the AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html) on autoscaling lambda functions.
303+
275304
### Log Group
276305

277306
Lambda functions automatically create a log group with the name `/aws/lambda/<function-name>` upon first execution with

packages/@aws-cdk/aws-lambda/lib/alias.ts

+36
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import * as appscaling from '@aws-cdk/aws-applicationautoscaling';
12
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
3+
import * as iam from '@aws-cdk/aws-iam';
24
import { Construct } from '@aws-cdk/core';
35
import { EventInvokeConfigOptions } from './event-invoke-config';
46
import { IFunction, QualifiedFunctionBase } from './function-base';
57
import { extractQualifierFromArn, IVersion } from './lambda-version';
68
import { CfnAlias } from './lambda.generated';
9+
import { ScalableFunctionAttribute } from './private/scalable-function-attribute';
10+
import { AutoScalingOptions, IScalableFunctionAttribute } from './scalable-attribute-api';
711

812
export interface IAlias extends IFunction {
913
/**
@@ -129,6 +133,9 @@ export class Alias extends QualifiedFunctionBase implements IAlias {
129133

130134
protected readonly canCreatePermissions: boolean = true;
131135

136+
private scalableAlias?: ScalableFunctionAttribute;
137+
private readonly scalingRole: iam.IRole;
138+
132139
constructor(scope: Construct, id: string, props: AliasProps) {
133140
super(scope, id, {
134141
physicalName: props.aliasName,
@@ -147,6 +154,15 @@ export class Alias extends QualifiedFunctionBase implements IAlias {
147154
provisionedConcurrencyConfig: this.determineProvisionedConcurrency(props),
148155
});
149156

157+
// Use a Service Linked Role
158+
// https://docs.aws.amazon.com/autoscaling/application/userguide/application-auto-scaling-service-linked-roles.html
159+
this.scalingRole = iam.Role.fromRoleArn(this, 'ScalingRole', this.stack.formatArn({
160+
service: 'iam',
161+
region: '',
162+
resource: 'role/aws-service-role/lambda.application-autoscaling.amazonaws.com',
163+
resourceName: 'AWSServiceRoleForApplicationAutoScaling_LambdaConcurrency',
164+
}));
165+
150166
this.functionArn = this.getResourceArnAttribute(alias.ref, {
151167
service: 'lambda',
152168
resource: 'function',
@@ -193,6 +209,26 @@ export class Alias extends QualifiedFunctionBase implements IAlias {
193209
});
194210
}
195211

212+
/**
213+
* Configure provisioned concurrency autoscaling on a function alias. Returns a scalable attribute that can call
214+
* `scaleOnUtilization()` and `scaleOnSchedule()`.
215+
*
216+
* @param options Autoscaling options
217+
*/
218+
public addAutoScaling(options: AutoScalingOptions): IScalableFunctionAttribute {
219+
if (this.scalableAlias) {
220+
throw new Error('AutoScaling already enabled for this alias');
221+
}
222+
return this.scalableAlias = new ScalableFunctionAttribute(this, 'AliasScaling', {
223+
minCapacity: options.minCapacity ?? 1,
224+
maxCapacity: options.maxCapacity,
225+
resourceId: `function:${this.functionName}`,
226+
dimension: 'lambda:function:ProvisionedConcurrency',
227+
serviceNamespace: appscaling.ServiceNamespace.LAMBDA,
228+
role: this.scalingRole,
229+
});
230+
}
231+
196232
/**
197233
* Calculate the routingConfig parameter from the input props
198234
*/

packages/@aws-cdk/aws-lambda/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export * from './event-source';
1313
export * from './event-source-mapping';
1414
export * from './destination';
1515
export * from './event-invoke-config';
16+
export * from './scalable-attribute-api';
1617

1718
export * from './log-retention';
1819

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as appscaling from '@aws-cdk/aws-applicationautoscaling';
2+
import { Construct, Token } from '@aws-cdk/core';
3+
import { IScalableFunctionAttribute, UtilizationScalingOptions } from '../scalable-attribute-api';
4+
5+
/**
6+
* A scalable lambda alias attribute
7+
*/
8+
export class ScalableFunctionAttribute extends appscaling.BaseScalableAttribute implements IScalableFunctionAttribute{
9+
constructor(scope: Construct, id: string, props: ScalableFunctionAttributeProps){
10+
super(scope, id, props);
11+
}
12+
13+
/**
14+
* Scale out or in to keep utilization at a given level. The utilization is tracked by the
15+
* LambdaProvisionedConcurrencyUtilization metric, emitted by lambda. See:
16+
* https://docs.aws.amazon.com/lambda/latest/dg/monitoring-metrics.html#monitoring-metrics-concurrency
17+
*/
18+
public scaleOnUtilization(options: UtilizationScalingOptions) {
19+
if ( !Token.isUnresolved(options.utilizationTarget) && (options.utilizationTarget < 0.1 || options.utilizationTarget > 0.9)) {
20+
throw new Error(`Utilization Target should be between 0.1 and 0.9. Found ${options.utilizationTarget}.`);
21+
}
22+
super.doScaleToTrackMetric('Tracking', {
23+
targetValue: options.utilizationTarget,
24+
predefinedMetric: appscaling.PredefinedMetric.LAMBDA_PROVISIONED_CONCURRENCY_UTILIZATION,
25+
...options,
26+
});
27+
}
28+
29+
/**
30+
* Scale out or in based on schedule.
31+
*/
32+
public scaleOnSchedule(id: string, action: appscaling.ScalingSchedule) {
33+
super.doScaleOnSchedule(id, action);
34+
}
35+
}
36+
37+
/**
38+
* Properties of a scalable function attribute
39+
*/
40+
export interface ScalableFunctionAttributeProps extends appscaling.BaseScalableAttributeProps {
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as appscaling from '@aws-cdk/aws-applicationautoscaling';
2+
import { IConstruct } from '@aws-cdk/core';
3+
4+
5+
/**
6+
* Interface for scalable attributes
7+
*/
8+
export interface IScalableFunctionAttribute extends IConstruct {
9+
/**
10+
* Scale out or in to keep utilization at a given level. The utilization is tracked by the
11+
* LambdaProvisionedConcurrencyUtilization metric, emitted by lambda. See:
12+
* https://docs.aws.amazon.com/lambda/latest/dg/monitoring-metrics.html#monitoring-metrics-concurrency
13+
*/
14+
scaleOnUtilization(options: UtilizationScalingOptions): void;
15+
/**
16+
* Scale out or in based on schedule.
17+
*/
18+
scaleOnSchedule(id: string, actions: appscaling.ScalingSchedule): void;
19+
}
20+
21+
/**
22+
* Options for enabling Lambda utilization tracking
23+
*/
24+
export interface UtilizationScalingOptions extends appscaling.BaseTargetTrackingProps {
25+
/**
26+
* Utilization target for the attribute. For example, .5 indicates that 50 percent of allocated provisioned concurrency is in use.
27+
*/
28+
readonly utilizationTarget: number;
29+
}
30+
31+
/**
32+
* Properties for enabling Lambda autoscaling
33+
*/
34+
export interface AutoScalingOptions {
35+
/**
36+
* Minimum capacity to scale to
37+
*
38+
* @default 1
39+
*/
40+
readonly minCapacity?: number;
41+
42+
/**
43+
* Maximum capacity to scale to
44+
*/
45+
readonly maxCapacity: number;
46+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"sinon": "^9.0.2"
8484
},
8585
"dependencies": {
86+
"@aws-cdk/aws-applicationautoscaling": "0.0.0",
8687
"@aws-cdk/aws-cloudwatch": "0.0.0",
8788
"@aws-cdk/aws-codeguruprofiler": "0.0.0",
8889
"@aws-cdk/aws-ec2": "0.0.0",
@@ -99,6 +100,7 @@
99100
},
100101
"homepage": "https://github.com/aws/aws-cdk",
101102
"peerDependencies": {
103+
"@aws-cdk/aws-applicationautoscaling": "0.0.0",
102104
"@aws-cdk/aws-cloudwatch": "0.0.0",
103105
"@aws-cdk/aws-codeguruprofiler": "0.0.0",
104106
"@aws-cdk/aws-ec2": "0.0.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
{
2+
"Resources": {
3+
"MyLambdaServiceRole4539ECB6": {
4+
"Type": "AWS::IAM::Role",
5+
"Properties": {
6+
"AssumeRolePolicyDocument": {
7+
"Statement": [
8+
{
9+
"Action": "sts:AssumeRole",
10+
"Effect": "Allow",
11+
"Principal": {
12+
"Service": "lambda.amazonaws.com"
13+
}
14+
}
15+
],
16+
"Version": "2012-10-17"
17+
},
18+
"ManagedPolicyArns": [
19+
{
20+
"Fn::Join": [
21+
"",
22+
[
23+
"arn:",
24+
{
25+
"Ref": "AWS::Partition"
26+
},
27+
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
28+
]
29+
]
30+
}
31+
]
32+
}
33+
},
34+
"MyLambdaCCE802FB": {
35+
"Type": "AWS::Lambda::Function",
36+
"Properties": {
37+
"Code": {
38+
"ZipFile": "exports.handler = async () => {\nconsole.log('hello world');\n};"
39+
},
40+
"Handler": "index.handler",
41+
"Role": {
42+
"Fn::GetAtt": [
43+
"MyLambdaServiceRole4539ECB6",
44+
"Arn"
45+
]
46+
},
47+
"Runtime": "nodejs10.x"
48+
},
49+
"DependsOn": [
50+
"MyLambdaServiceRole4539ECB6"
51+
]
52+
},
53+
"MyLambdaVersion16CDE3C40": {
54+
"Type": "AWS::Lambda::Version",
55+
"Properties": {
56+
"FunctionName": {
57+
"Ref": "MyLambdaCCE802FB"
58+
},
59+
"Description": "integ-test"
60+
}
61+
},
62+
"Alias325C5727": {
63+
"Type": "AWS::Lambda::Alias",
64+
"Properties": {
65+
"FunctionName": {
66+
"Ref": "MyLambdaCCE802FB"
67+
},
68+
"FunctionVersion": {
69+
"Fn::GetAtt": [
70+
"MyLambdaVersion16CDE3C40",
71+
"Version"
72+
]
73+
},
74+
"Name": "prod"
75+
}
76+
},
77+
"AliasAliasScalingTarget7449FF0E": {
78+
"Type": "AWS::ApplicationAutoScaling::ScalableTarget",
79+
"Properties": {
80+
"MaxCapacity": 50,
81+
"MinCapacity": 3,
82+
"ResourceId": {
83+
"Fn::Join": [
84+
"",
85+
[
86+
"function:",
87+
{
88+
"Fn::Select": [
89+
6,
90+
{
91+
"Fn::Split": [
92+
":",
93+
{
94+
"Ref": "Alias325C5727"
95+
}
96+
]
97+
}
98+
]
99+
},
100+
":prod"
101+
]
102+
]
103+
},
104+
"RoleARN": {
105+
"Fn::Join": [
106+
"",
107+
[
108+
"arn:",
109+
{
110+
"Ref": "AWS::Partition"
111+
},
112+
":iam::",
113+
{
114+
"Ref": "AWS::AccountId"
115+
},
116+
":role/aws-service-role/lambda.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_LambdaConcurrency"
117+
]
118+
]
119+
},
120+
"ScalableDimension": "lambda:function:ProvisionedConcurrency",
121+
"ServiceNamespace": "lambda",
122+
"ScheduledActions": [
123+
{
124+
"ScalableTargetAction": {
125+
"MinCapacity": 20
126+
},
127+
"Schedule": "cron(0 8 * * ? *)",
128+
"ScheduledActionName": "ScaleUpInTheMorning"
129+
},
130+
{
131+
"ScalableTargetAction": {
132+
"MaxCapacity": 20
133+
},
134+
"Schedule": "cron(0 20 * * ? *)",
135+
"ScheduledActionName": "ScaleDownAtNight"
136+
}
137+
]
138+
}
139+
},
140+
"AliasAliasScalingTargetTrackingA7718D48": {
141+
"Type": "AWS::ApplicationAutoScaling::ScalingPolicy",
142+
"Properties": {
143+
"PolicyName": "awslambdaautoscalingAliasAliasScalingTargetTrackingD339330D",
144+
"PolicyType": "TargetTrackingScaling",
145+
"ScalingTargetId": {
146+
"Ref": "AliasAliasScalingTarget7449FF0E"
147+
},
148+
"TargetTrackingScalingPolicyConfiguration": {
149+
"PredefinedMetricSpecification": {
150+
"PredefinedMetricType": "LambdaProvisionedConcurrencyUtilization"
151+
},
152+
"TargetValue": 0.5
153+
}
154+
}
155+
}
156+
},
157+
"Outputs": {
158+
"FunctionName": {
159+
"Value": {
160+
"Ref": "MyLambdaCCE802FB"
161+
}
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)