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

Adding Lambda Provisioned Concurrency Example with AutoScaling #974

Merged
merged 5 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
68 changes: 68 additions & 0 deletions typescript/lambda-provisioned-concurrency-autoscaling/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@

# Provisioned Concurrency AutoScaling for Lambda Function

This example creates Provisioned Concurrency on lambda function and enable autoscaling(target and schedule) configuration with Provisioned Concurrency.


---

## Setup

Clone this repository:
```bash
git clone https://github.com/aws-samples/aws-cdk-examples.git
```

Change directory:
```bash
cd aws-cdk-examples/typescript/lambda-provisioned-concurrency-autoscaling
```

To build this app, you need to be in this example's root folder. Then run the following:
```bash
npm install -g aws-cdk
npm install
npm run build
```

This will install the necessary CDK, then this example's dependencies, and then build your TypeScript files and your CloudFormation template.


## Deployment

This stack uses assets, so the toolkit stack must be deployed to the environment. This can be done by running the following command:
```bash
cdk bootstrap aws://account-id/aws-region
```

At this point you can now synthesize the CloudFormation template for this code:
```bash
cdk synth
```
then check the output file in the "cdk.out" directory.

Run `cdk deploy`. This will deploy / redeploy your Stack to your AWS Account.



## Test

```bash
npm run test
```

## Clean Up
To clean up, issue this command:
```
cdk destroy
```

## Useful commands

* `cdk ls` list all stacks in the app
* `cdk synth` emits the synthesized CloudFormation template
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk docs` open CDK documentation

Enjoy!
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { LambdaPCScalingTargetStack } from '../lib/lambda-pc-autoscaling-target';
import { LambdaPCScalingScheduleStack } from '../lib/lambda-pc-autoscaling-schedule';

export const lambdaFunctionName = "LambdaPCAutoScalingExample"

const app = new cdk.App();
new LambdaPCScalingTargetStack(app, 'LambdaProvisionedConcurrencyScalingTarget', {
functionName: `${lambdaFunctionName}Target`,
});

new LambdaPCScalingScheduleStack(app, 'LambdaProvisionedConcurrencyScalingSchedule', {
functionName: `${lambdaFunctionName}Schedule`,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node --prefer-ts-exts bin/lambda-pc-autoscaling.ts"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const config = {
moduleFileExtensions: ["js"]
};

module.exports = config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
exports.handler = async () => {
return {
statusCode: 200,
body: JSON.stringify({ message: 'Hello, World!' }),
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Duration, Stack, StackProps} from "aws-cdk-lib";
import { Function, Runtime } from "aws-cdk-lib/aws-lambda";
import { Construct } from 'constructs';

import { ScalableTarget, Schedule, ServiceNamespace } from 'aws-cdk-lib/aws-applicationautoscaling';
import { Metric }from 'aws-cdk-lib/aws-cloudwatch';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Alias } from 'aws-cdk-lib/aws-lambda';



interface LambdaPCScalingScheduleStackProps extends StackProps {
functionName: string
}

export class LambdaPCScalingScheduleStack extends Stack {
private lambdaFunction: Function
private lambdaFunctionName: string

constructor(scope: Construct, id: string, props: LambdaPCScalingScheduleStackProps) {
super(scope, id, props);
this.lambdaFunctionName = props.functionName

// Create Sample Lambda Function which will create metrics
this.lambdaFunction = new NodejsFunction(this, 'LambdaFunction', {
functionName: this.lambdaFunctionName,
entry: `./lambda/lambda-handler.ts`,
runtime: Runtime.NODEJS_18_X,
memorySize: 512,
timeout: Duration.seconds(6),
});
// Enable Provisioned Concurrency
new Alias(this, `LambdaFunctionAlias`, {
aliasName: 'provisioned',
version: this.lambdaFunction.currentVersion,
provisionedConcurrentExecutions: 1,
});
// Enable AutoScaling Scalable Schedule
const asg = new ScalableTarget(this, `${this.lambdaFunctionName}ScalableSchedule`, {
serviceNamespace: ServiceNamespace.LAMBDA,
maxCapacity: 1,
minCapacity: 0,
resourceId: `function:${this.lambdaFunctionName}:provisioned`,
scalableDimension: 'lambda:function:ProvisionedConcurrency',
})
// Scaling out every weekday (Monday through Friday) at 11:00 AM(UTC+0),
asg.scaleOnSchedule(`${this.lambdaFunctionName}ScheduleScaleOut`, {
schedule: Schedule.expression('cron(0 11 ? * MON-FRI *))'),
minCapacity: 1,
maxCapacity: 1
})
// Scaling in every weekday (Monday through Friday) at 12:00 AM(UTC+0),
asg.scaleOnSchedule(`${this.lambdaFunctionName}ScheduleScaleIn`, {
schedule: Schedule.expression('cron(0 12 ? * MON-FRI *))'),
minCapacity: 0,
maxCapacity: 0
})
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Duration, Stack, StackProps } from "aws-cdk-lib";
import { Function, Runtime } from "aws-cdk-lib/aws-lambda";
import { Construct } from 'constructs';

import { ScalableTarget, ServiceNamespace } from 'aws-cdk-lib/aws-applicationautoscaling';
import { Metric }from 'aws-cdk-lib/aws-cloudwatch';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Alias } from 'aws-cdk-lib/aws-lambda';




interface LambdaPCScalingTargetStackProps extends StackProps {
functionName: string
}

export class LambdaPCScalingTargetStack extends Stack {
private lambdaFunction: Function
private lambdaFunctionName: string

constructor(scope: Construct, id: string, props: LambdaPCScalingTargetStackProps) {
super(scope, id, props);
this.lambdaFunctionName = props.functionName

// Create Sample Lambda Function which will create metrics
this.lambdaFunction = new NodejsFunction(this, 'LambdaFunction', {
functionName: this.lambdaFunctionName,
entry: `./lambda/lambda-handler.ts`,
runtime: Runtime.NODEJS_18_X,
memorySize: 512,
timeout: Duration.seconds(6),
});
// Enable Provisioned Concurrency
new Alias(this, `LambdaFunctionAlias`, {
aliasName: 'provisioned',
version: this.lambdaFunction.currentVersion,
provisionedConcurrentExecutions: 1,
});
// Create Metric of Lambda Provisioned Concurrency
const metrics = new Metric({
metricName: 'ProvisionedConcurrencyUtilization',
namespace: 'AWS/Lambda',
dimensionsMap: {
FunctionName: this.lambdaFunction.functionName,
Resource: `${this.lambdaFunction.functionName}:'provisioned`,
},
statistic: 'Maximum',
period: Duration.minutes(5),
});
// Enable AutoScaling Scalable Target when over target threshold(0.8)
new ScalableTarget(this, `${this.lambdaFunctionName}ScalableTarget`, {
serviceNamespace: ServiceNamespace.LAMBDA,
maxCapacity: 2,
minCapacity: 1,
resourceId: `function:${this.lambdaFunctionName}:provisioned`,
scalableDimension: 'lambda:function:ProvisionedConcurrency',
}).scaleToTrackMetric(`${this.lambdaFunctionName}PCScaleTracking`, {
targetValue: 0.8,
customMetric: metrics,
});
};
}
32 changes: 32 additions & 0 deletions typescript/lambda-provisioned-concurrency-autoscaling/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "lambda-provision-concurrenced-autoscaling",
"version": "0.1.0",
"bin": {
"lambda-pc-autoscaling": "bin/lambda-pc-autoscaling.js"
},
"author": {
"name": "Amazon Web Services",
"url": "https://aws.amazon.com",
"organization": true
},
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"cdk": "cdk",
"test": "jest --config=jest.config.js"
},
"devDependencies": {
"@aws-cdk/assert": "*",
"@types/node": "10.17.27",
"aws-cdk": "*",
"ts-node": "^10.9.1",
"@types/jest": "^26.0.24",
"jest": "^26.6.3",
"typescript": "~5.1.6"
},
"dependencies": {
"aws-cdk-lib": "^2.0.0",
"constructs": "^10.0.0"

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { App } from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import { LambdaPCScalingTargetStack } from '../lib/lambda-pc-autoscaling-target';
import { LambdaPCScalingScheduleStack } from '../lib/lambda-pc-autoscaling-schedule';

describe('CDK Stack Testing', () => {
test('Lambda Function PC Scaling Target Testing', () => {
const app = new App();
// WHEN
const LambdaPCScalingTargetstack = new LambdaPCScalingTargetStack(app, 'LambdaPCScalingTargetStackTest', {
functionName: `LambdaPCScalingTargetStackTesting`,
}
);
const targetAssert = Template.fromStack(LambdaPCScalingTargetstack);
// THEN
targetAssert.resourceCountIs('AWS::Lambda::Function', 1);
});

test('Lambda Function PC Scaling Schedule Testing', () => {
const app = new App();
// WHEN
const LambdaPCScalingSchedulestack = new LambdaPCScalingScheduleStack(app, 'LambdaPCScalingScheduleStackTest', {
functionName: `LambdaPCScalingScheduleStackTesting`,
}
);
// THEN
const scheduleAssert = Template.fromStack(LambdaPCScalingSchedulestack);
scheduleAssert.resourceCountIs('AWS::Lambda::Function', 1);
});
});

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"lib": ["es2018"],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"typeRoots": ["./node_modules/@types"]
},
"exclude": ["cdk.out"]
}
Loading