Skip to content

Commit

Permalink
feat(lambda): autoscaling for lambda aliases (#8883)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaizencc authored Aug 11, 2020
1 parent ea39fbd commit d9d9b90
Show file tree
Hide file tree
Showing 9 changed files with 584 additions and 2 deletions.
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

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 {
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,
});
}

/**
* 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. 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": {
"FunctionName": {
"Value": {
"Ref": "MyLambdaCCE802FB"
}
}
}
}
Loading

0 comments on commit d9d9b90

Please sign in to comment.