Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lambda): autoscaling for lambda aliases #8883

Merged
merged 47 commits into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
06ad798
preliminary API
Jul 2, 2020
2aeebb4
fixed awslint issues
Jul 2, 2020
2ba8685
Merge branch 'master' into autoscale-lambda
kaizencc Jul 2, 2020
721f9b3
added dependency
Jul 2, 2020
67ce00a
Merge branch 'autoscale-lambda' of https://github.com/kaizen3031593/a…
Jul 2, 2020
9d50cd3
removed autoscaling from lambda-version
Jul 6, 2020
595b408
modified things based off of code review
Jul 6, 2020
7a2c60f
added unit tests
Jul 6, 2020
b3c5c11
added integ test
Jul 7, 2020
e78c0f4
fixed linting issues
Jul 7, 2020
883c377
Merge branch 'master' into autoscale-lambda
kaizencc Jul 7, 2020
02e1c86
added readme comments
Jul 7, 2020
614f109
updated alias with implicit dependency
Jul 7, 2020
41935b2
Merge branch 'master' into autoscale-lambda
kaizencc Jul 7, 2020
4c64b6c
typo
Jul 7, 2020
710a623
updated readme
Jul 7, 2020
66b4ee9
made minor edits
Jul 7, 2020
0b53cf8
typo
Jul 7, 2020
77884af
made requested changes
Jul 16, 2020
627390c
Merge branch 'master' into autoscale-lambda
kaizencc Jul 16, 2020
73bdf11
typo
Jul 16, 2020
35df97a
last minute doc changes
Jul 16, 2020
6a5e976
updated docs and readme
Jul 20, 2020
34ae9d5
changed targetUtilizationPercent to targetUtilizationValue
Jul 21, 2020
e999fa2
Merge branch 'master' into autoscale-lambda
kaizencc Jul 21, 2020
3e162e4
readme modification
Jul 21, 2020
133c161
missed a few changes
Jul 21, 2020
7244d4a
yet another
Jul 21, 2020
b513569
update integ test
Jul 21, 2020
ba7d067
incorporated elads comments
Jul 22, 2020
54d10dc
minor edit
Jul 22, 2020
83b5f10
Merge branch 'master' into autoscale-lambda
kaizencc Jul 22, 2020
ed87489
fixed test case
Jul 22, 2020
eb907d8
readme change
Jul 22, 2020
e73ae72
satisfied aws-lint
Aug 3, 2020
21a3e46
Merge branch 'master' into autoscale-lambda
kaizencc Aug 3, 2020
35044db
Merge branch 'master' into autoscale-lambda
NetaNir Aug 3, 2020
7f3e403
cr changes
NetaNir Aug 6, 2020
281a871
spaces everywhere
NetaNir Aug 6, 2020
6c02e22
temp
NetaNir Aug 10, 2020
57d51a6
split files to allow not exporting private types
NetaNir Aug 11, 2020
86be93f
Merge branch 'master' into autoscale-lambda
NetaNir Aug 11, 2020
741b5a5
fix docs
NetaNir Aug 11, 2020
74ea805
fix;
NetaNir Aug 11, 2020
9ab7fee
final changes
Aug 11, 2020
3519d02
Merge branch 'master' into autoscale-lambda
NetaNir Aug 11, 2020
90138af
Merge branch 'master' into autoscale-lambda
NetaNir Aug 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions packages/@aws-cdk/aws-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,35 @@ const fn = new lambda.Function(this, 'MyFunction', {
See [the AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html)
managing concurrency.

### AutoScaling

kaizencc marked this conversation as resolved.
Show resolved Hide resolved
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:

```ts
const alias = new lambda.Alias(stack, 'Alias', {
aliasName: 'prod',
version,
});

// Create AutoScaling target
const as = alias.addAutoScaling({ maxCapacity: 50 })

// Configure Target Tracking
as.scaleOnUtilization({
utilizationTarget: 0.5,
});

// Configure Scheduled Scaling
as.scaleOnSchedule('ScaleUpInTheMorning', {
schedule: appscaling.Schedule.cron({ hour: '8', minute: '0'}),
minCapacity: 20,
});
```

[Example of Lambda AutoScaling usage](test/integ.autoscaling.lit.ts)

See [the AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html) on autoscaling lambda functions.

### Log Group

Lambda functions automatically create a log group with the name `/aws/lambda/<function-name>` upon first execution with
Expand Down
36 changes: 36 additions & 0 deletions packages/@aws-cdk/aws-lambda/lib/alias.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import * as appscaling from '@aws-cdk/aws-applicationautoscaling';
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as iam from '@aws-cdk/aws-iam';
import { Construct } from '@aws-cdk/core';
import { EventInvokeConfigOptions } from './event-invoke-config';
import { IFunction, QualifiedFunctionBase } from './function-base';
import { extractQualifierFromArn, IVersion } from './lambda-version';
import { CfnAlias } from './lambda.generated';
import { ScalableFunctionAttribute } from './private/scalable-function-attribute';
import { AutoScalingOptions, IScalableFunctionAttribute } from './scalable-attribute-api';

export interface IAlias extends IFunction {
/**
Expand Down Expand Up @@ -129,6 +133,9 @@ export class Alias extends QualifiedFunctionBase implements IAlias {

protected readonly canCreatePermissions: boolean = true;

private scalableAlias?: ScalableFunctionAttribute;
private readonly scalingRole: iam.IRole;

constructor(scope: Construct, id: string, props: AliasProps) {
super(scope, id, {
physicalName: props.aliasName,
Expand All @@ -147,6 +154,15 @@ export class Alias extends QualifiedFunctionBase implements IAlias {
provisionedConcurrencyConfig: this.determineProvisionedConcurrency(props),
});

// Use a Service Linked Role
// https://docs.aws.amazon.com/autoscaling/application/userguide/application-auto-scaling-service-linked-roles.html
this.scalingRole = iam.Role.fromRoleArn(this, 'ScalingRole', this.stack.formatArn({
service: 'iam',
region: '',
resource: 'role/aws-service-role/lambda.application-autoscaling.amazonaws.com',
resourceName: 'AWSServiceRoleForApplicationAutoScaling_LambdaConcurrency',
}));

this.functionArn = this.getResourceArnAttribute(alias.ref, {
service: 'lambda',
resource: 'function',
Expand Down Expand Up @@ -193,6 +209,26 @@ export class Alias extends QualifiedFunctionBase implements IAlias {
});
}

/**
* Configure provisioned concurrency autoscaling on a function alias. Returns a scalable attribute that can call
* `scaleOnUtilization()` and `scaleOnSchedule()`.
*
* @param options Autoscaling options
*/
public addAutoScaling(options: AutoScalingOptions): IScalableFunctionAttribute {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be a better user experience if we change this to a getter autoscaling that (instantiates, if not already, and) returns scalableAlias?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might want to keep the same pattern as we have in all other L2 which implements autoscaling (ecs, ddb, ec2)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with returning IScalableFunctionAttribute in the addAutoScaling() method.

My question is if this should be -

export class ScalableFunctionAttribute extends appscaling.BaseScalableAttribute implements IScalableFunctionAttribute {

Keeps the type expectations in the correct place and any mismatches will be reported at the correct locations by the compiler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, you are right, I have change it!

if (this.scalableAlias) {
throw new Error('AutoScaling already enabled for this alias');
}
return this.scalableAlias = new ScalableFunctionAttribute(this, 'AliasScaling', {
minCapacity: options.minCapacity ?? 1,
maxCapacity: options.maxCapacity,
resourceId: `function:${this.functionName}`,
dimension: 'lambda:function:ProvisionedConcurrency',
serviceNamespace: appscaling.ServiceNamespace.LAMBDA,
role: this.scalingRole,
});
}
kaizencc marked this conversation as resolved.
Show resolved Hide resolved

/**
* Calculate the routingConfig parameter from the input props
*/
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-lambda/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './event-source';
export * from './event-source-mapping';
export * from './destination';
export * from './event-invoke-config';
export * from './scalable-attribute-api';

export * from './log-retention';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as appscaling from '@aws-cdk/aws-applicationautoscaling';
import { Construct, Token } from '@aws-cdk/core';
import { IScalableFunctionAttribute, UtilizationScalingOptions } from '../scalable-attribute-api';

/**
* A scalable lambda alias attribute
*/
export class ScalableFunctionAttribute extends appscaling.BaseScalableAttribute implements IScalableFunctionAttribute{
constructor(scope: Construct, id: string, props: ScalableFunctionAttributeProps){
super(scope, id, props);
}

/**
* Scale out or in to keep utilization at a given level. The utilization is tracked by the
* LambdaProvisionedConcurrencyUtilization metric, emitted by lambda. See:
* https://docs.aws.amazon.com/lambda/latest/dg/monitoring-metrics.html#monitoring-metrics-concurrency
*/
public scaleOnUtilization(options: UtilizationScalingOptions) {
if ( !Token.isUnresolved(options.utilizationTarget) && (options.utilizationTarget < 0.1 || options.utilizationTarget > 0.9)) {
throw new Error('Utilization Target should be between 0.1 and 0.9.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new Error('Utilization Target should be between 0.1 and 0.9.');
throw new Error(`Utilization Target should be between 0.1 and 0.9. Found ${options.utilizationTarget}`);

}
super.doScaleToTrackMetric('Tracking', {
targetValue: options.utilizationTarget,
predefinedMetric: appscaling.PredefinedMetric.LAMBDA_PROVISIONED_CONCURRENCY_UTILIZATION,
...options,
});
}

/**
* Scale out or in based on schedule.
*/
public scaleOnSchedule(id: string, action: appscaling.ScalingSchedule) {
super.doScaleOnSchedule(id, action);
}
}

/**
* Properties of a scalable function attribute
*/
export interface ScalableFunctionAttributeProps extends appscaling.BaseScalableAttributeProps {
}
46 changes: 46 additions & 0 deletions packages/@aws-cdk/aws-lambda/lib/scalable-attribute-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as appscaling from '@aws-cdk/aws-applicationautoscaling';
import { IConstruct } from '@aws-cdk/core';


/**
* Interface for scalable attributes
*/
export interface IScalableFunctionAttribute extends IConstruct {
/**
* Scale out or in to keep utilization at a given level. The utilization is tracked by the
* LambdaProvisionedConcurrencyUtilization metric, emitted by lambda. See:
* https://docs.aws.amazon.com/lambda/latest/dg/monitoring-metrics.html#monitoring-metrics-concurrency
*/
scaleOnUtilization(options: UtilizationScalingOptions): void;
/**
* Scale out or in based on schedule.
*/
scaleOnSchedule(id: string, actions: appscaling.ScalingSchedule): void;
}

/**
* Options for enabling Lambda utilization tracking
*/
export interface UtilizationScalingOptions extends appscaling.BaseTargetTrackingProps {
/**
* Utilization target for the attribute. For example, .5 indicates that 50 percent of allocated provisioned concurrency is in use.
*/
readonly utilizationTarget: number;
}

/**
* Properties for enabling Lambda autoscaling
*/
export interface AutoScalingOptions {
/**
* Minimum capacity to scale to
*
* @default 1
*/
readonly minCapacity?: number;

/**
* Maximum capacity to scale to
*/
readonly maxCapacity: number;
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-lambda/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"sinon": "^9.0.2"
},
"dependencies": {
"@aws-cdk/aws-applicationautoscaling": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-codeguruprofiler": "0.0.0",
"@aws-cdk/aws-ec2": "0.0.0",
Expand All @@ -99,6 +100,7 @@
},
"homepage": "https://github.com/aws/aws-cdk",
"peerDependencies": {
"@aws-cdk/aws-applicationautoscaling": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-codeguruprofiler": "0.0.0",
"@aws-cdk/aws-ec2": "0.0.0",
Expand Down
164 changes: 164 additions & 0 deletions packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
{
"Resources": {
"MyLambdaServiceRole4539ECB6": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
]
]
}
]
}
},
"MyLambdaCCE802FB": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"ZipFile": "exports.handler = async () => {\nconsole.log('hello world');\n};"
},
"Handler": "index.handler",
"Role": {
"Fn::GetAtt": [
"MyLambdaServiceRole4539ECB6",
"Arn"
]
},
"Runtime": "nodejs10.x"
},
"DependsOn": [
"MyLambdaServiceRole4539ECB6"
]
},
"MyLambdaVersion16CDE3C40": {
"Type": "AWS::Lambda::Version",
"Properties": {
"FunctionName": {
"Ref": "MyLambdaCCE802FB"
},
"Description": "integ-test"
}
},
"Alias325C5727": {
"Type": "AWS::Lambda::Alias",
"Properties": {
"FunctionName": {
"Ref": "MyLambdaCCE802FB"
},
"FunctionVersion": {
"Fn::GetAtt": [
"MyLambdaVersion16CDE3C40",
"Version"
]
},
"Name": "prod"
}
},
"AliasAliasScalingTarget7449FF0E": {
"Type": "AWS::ApplicationAutoScaling::ScalableTarget",
"Properties": {
"MaxCapacity": 50,
"MinCapacity": 3,
"ResourceId": {
"Fn::Join": [
"",
[
"function:",
{
"Fn::Select": [
6,
{
"Fn::Split": [
":",
{
"Ref": "Alias325C5727"
}
]
}
]
},
":prod"
]
]
},
"RoleARN": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::",
{
"Ref": "AWS::AccountId"
},
":role/aws-service-role/lambda.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_LambdaConcurrency"
]
]
},
"ScalableDimension": "lambda:function:ProvisionedConcurrency",
"ServiceNamespace": "lambda",
"ScheduledActions": [
{
"ScalableTargetAction": {
"MinCapacity": 20
},
"Schedule": "cron(0 8 * * ? *)",
"ScheduledActionName": "ScaleUpInTheMorning"
},
{
"ScalableTargetAction": {
"MaxCapacity": 20
},
"Schedule": "cron(0 20 * * ? *)",
"ScheduledActionName": "ScaleDownAtNight"
}
]
}
},
"AliasAliasScalingTargetTrackingA7718D48": {
"Type": "AWS::ApplicationAutoScaling::ScalingPolicy",
"Properties": {
"PolicyName": "awslambdaautoscalingAliasAliasScalingTargetTrackingD339330D",
"PolicyType": "TargetTrackingScaling",
"ScalingTargetId": {
"Ref": "AliasAliasScalingTarget7449FF0E"
},
"TargetTrackingScalingPolicyConfiguration": {
"PredefinedMetricSpecification": {
"PredefinedMetricType": "LambdaProvisionedConcurrencyUtilization"
},
"TargetValue": 0.5
}
}
}
},
"Outputs": {
"Output": {
"Value": {
"Ref": "MyLambdaCCE802FB"
}
}
}
}
Loading