The Serverless Framework is the most popular framework for building serverless applications, but does how it compare to the new AWS CDK? In July 2019, AWS announced its own framework Cloud Development Kit. AWS CDK is a framework to deploy serverless applications and any AWS resource. AWS CDK helps you achieve infrastructure as code similar to AWS CloudFormation and Terraform.
This post will compare Serverless Framework and AWS CDK in different areas: framework ease of use, extensibility, and security. By the end you should be able to determine whether you should stay with Serverless or adopt CDK.
Overview of the frameworks
Both AWS CDK (which we will refer to as “CDK”) and the Serverless Framework (which we will refer to as “Serverless”) are both JavaScript frameworks that you may install as a Command Line Interface (CLI) script via npm or yarn. Both support AWS, but their use varies.
What is the Serverless Framework?
Serverless allows you to deploy serverless applications to multiple cloud providers. Serverless supports the following providers:
- AWS
- Azure
- Tencent Cloud
- Google Cloud
- Knative
- Alibaba Cloud
- Cloudflare
- fn
- Kubeless
- OpenWhisk
- spotinst
The Serverless configuration file (named “serverless.yml”) uses a similar format for most providers, which allows you to switch mental context between providers reasonably easily. The “serverless.yml” file will enable you to specify your configuration using YAML syntax and put your function source code like JavaScript, Python, Go, and any other language the cloud provider supports. Serverless will deserialize the “serverless.yml” and convert it to the cloud provider’s underlying format (e.g., AWS CloudFormation template).
What is Amazon Web Services (AWS) Cloud Development Kit (CDK)?
CDK allows you to deploy resources in AWS using TypeScript, JavaScript, Python, Java, and .NET. Your source code defines both the resources and the files those resources need (e.g., AWS Lambda function source code). CDK will synthesize the source code to create the appropriate AWS CloudFormation template.
Let’s Compare the Two
1. Setting up a basic project
We will review how to set up a project on AWS for both frameworks.
I will assume you already have an AWS account and know how to create the appropriate IAM credentials or temporary credentials to deploy AWS resources.
I will assume you have Node.js, npm, and your desired programming language installed on your machine.
Serverless Framework project setup
To install Serverless, use npm:
npm install -g serverless
This allows you to use “serverless” or “sls” as a CLI command.
To create a new Serverless project, use the “serverless” or “sls” command and follow the on-screen prompts:
sls
You will find the following starter files in your new project.
$ ls sls-example/
handler.js serverless.yml
I will prefer the “sls” command throughout and will choose Node.js JavaScript (or TypeScript) as the Lambda function language.
AWS CDK project setup
We need to install the AWS CLI and CDK.
To install the AWS CLI, follow the AWS CLI installation instructions since they vary per operating system
To install CDK, use npm:
npm install -g aws-cdk
To create a new CDK project, use the “cdk” command.
mkdir cdk-example
cd cdk-example
cdk init app --language=javascript
You will find the following starter files in your new project.
$ ls
README.md lib package.json
bin node_modules test
cdk.json package-lock.json
I will prefer the Node.js JavaScript as the Lambda function language. I recommend you consider TypeScript over JavaScript since CDK was built with TypeScript. I am choosing Node for the comparison to Serverless.
Comparison
Both are pretty easy to set up the basic project structure.
2. Ease of use
I would argue each framework is relatively easy to use. You might prefer one framework over another depending on whether you like writing code and depending on your understanding and knowledge of AWS.
2.1. Deploying a Lambda function
We will start with a simple use case.
Serverless Framework
Let’s suppose you only want to deploy a Lambda function. The previous project set up already has one Lambda function ready to deploy.
# serverless.yml
# automatically created file
# no code changes made
service: sls-example
provider:
name: aws
runtime: nodejs12.x
functions:
hello:
handler: handler.hello
Serverless will assume you want a dev
stage and the us-east-1
region.
/* handler.js */
/* automatically created file */
/* no code changes made */
'use strict';
module.exports.hello = async event => {
return {
statusCode: 200,
body: JSON.stringify(
{
message: 'Go Serverless v1.0! Your function executed successfully!',
input: event,
},
null,
2
),
};
};
Now we just need to deploy the configuration.
sls deploy --aws-profile my_aws_profile
Serverless will create a CloudFormation template that deploys the following items:
- S3 bucket that contains the Serverless configuration files and the appropriate S3 bucket policy
- One Lambda function
- The latest Lambda function version
- One CloudWatch Logs log group to capture the function logs
- The IAM role that Lambda function will use to access any AWS services and resources
A small number of lines in the “serverless.yml” file deployed the “handler.js” function code and set up all the necessary supporting resources. Furthemore, the resource naming convention follows a naming pattern. Not bad!
AWS CDK
The project (i.e., app) structure we previously created has no resources. We will need to add the desired resources.
We will need to install an additional package to define the Lambda in our app.
To install the additional package, run the following npm command:
npm i --save @aws-cdk/aws-lambda
We can now define a Lambda function in our app by modifying the existing code.
We do not need to make changes to the bin/cdk-example.js
file.
#!/usr/bin/env node
/* bin/cdk-example.js */
/* automatically created file */
/* no code changes made */
const cdk = require('@aws-cdk/core');
const { CdkExampleStack } = require('../lib/cdk-example-stack');
const app = new cdk.App();
new CdkExampleStack(app, 'CdkExampleStack');
We need to add the Lambda function to the lib/cdk-example-stack.js
file.
/* lib/cdk-example-stack.js */
/* automatically created file */
/* code changes made */
'use strict';
const cdk = require('@aws-cdk/core');
/* addition */
const lambda = require('@aws-cdk/aws-lambda');
const fs = require('fs');
/* end addition */
class CdkExampleStack extends cdk.Stack {
/**
*
* @param {cdk.Construct} scope
* @param {string} id
* @param {cdk.StackProps=} props
*/
constructor(scope, id, props) {
super(scope, id, props);
/* addition */
const helloSrc = fs.readFileSync('handler.js').toString();
const helloLambda = new lambda.Function(this, 'HelloLambda', {
runtime: lambda.Runtime.NODEJS_12_X,
handler: 'handler.hello',
code: new lambda.InlineCode(helloSrc, {
encoding: 'utf-8',
}),
});
/* end addition */
}
}
module.exports = { CdkExampleStack };
We will use the same handler.js
file from the Serverless example and add it to the cdk-example
folder.
We are ready to deploy. Let’s run the CDK CLI commands.
# check for errors
cdk synth
# deploy
cdk deploy --profile my_aws_profile
CDK will use the default region in your AWS CLI configuration or us-east-1
if there is no default region.
CDK will create a CloudFormation template that deploys the following items:
- CDK metadata
- One Lambda function
- The IAM role that Lambda function will use to access any AWS services and resources
What happened to the S3 bucket, S3 bucket policy, latest Lambda function version, and CloudWatch Logs log group that Serverless deploys?
CDK uses CDK metadata rather than uploading the CloudFormation template and the other resources Serverless needs to an S3 bucket and creating the appropriate bucket policy. We get a security bonus because we cannot access the CDK configuration from the AWS console. Nice!
Serverless assumes you want a version of the Lambda function whenever you update it. This might be a useful feature, but, in most cases, you will probably only need the latest version. CDK does not make that assumption for you. If you want versioned functions, you need to specify it.
AWS will automatically create a CloudWatch Logs log group when the Lambda triggers the first time. We, therefore, do not need to specify it in our app. But, CDK or CloudFormation will not automatically delete the log group when deleting the stack since it’s not part of the configuration. If we want that to happen, we need to add the log group in the lib/cdk-example-stack.js file.
CDK assumes you want to use the best practices and trusted patterns. It, therefore, created the IAM role for the function.
Comparison
Serverless made it super simple to create a Lambda function and the supporting resources. But, it does create a new S3 bucket to hold the Serverless configuration.
CDK required a little extra effort because it did not come with a sample Lambda function. I had to read the API reference to determine how to add a Lambda. Furthermore, it did not deploy all the resources I would have wanted (e.g., the CloudWatch Logs log group). But, it limited the number of resources and gave me more control.
2.2. Adding a DynamoDB table
A Lambda function more than likely will need to get data from a database. We will use DynamoDB, which is a serverless database table service.
Serverless Framework
Serverless does not support certain resources (e.g., DynamoDB tables and S3 buckets) out of the box. We therefore need to resort to using CloudFormation to create them.
We will append the CloudFormation template code to the existing serverless.yml
configuration file.
# serverless.yml
# additions
resources:
Resources:
TableUsers:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:service}-${self:provider.stage}-users
AttributeDefinitions:
- AttributeName: PartitionKey
AttributeType: S
- AttributeName: SortKey
AttributeType: S
KeySchema:
- AttributeName: PartitionKey
KeyType: HASH
- AttributeName: SortKey
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
# end additions
When we deploy again, we will see a new resource for the DynamoDB table.
We will want our Lambda function to be able to read data from the table. We will need to add an IAM policy to the serverless.yml
file.
# serverless.yml
# addition
iamRoleStatements:
- Effect: "Allow"
Action:
- "dynamodb:GetItem"
Resource:
Fn::Join:
- ""
- - "arn:aws:dynamodb:"
- "Ref" : "AWS::Region"
- ":"
- "Ref" : "AWS::AccountId"
- ":table/"
- "Ref" : "DdbTableHello"
# end addition
When we deploy it, will not see any new resources. We can go to the IamRoleLambdaExecution
IAM role resource and see it have the IAM policy we just added.
{
"Action": [
"dynamodb:GetItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/sls-example-dev-hello",
"Effect": "Allow"
}
It was quite a bit of effort to add the DynamoDB table and give the Lambda function permission to get an item from it.
AWS CDK
We need to add an additional package to add DynamoDB to our app.
npm i --save @aws-cdk/aws-dynamodb
We need to update the lib/cdk-example-stack.js
file to add the DynamoDB resource. I will limit the code shown to focus on the changes.
/* lib/cdk-example-stack.js */
/* automatically created file */
/* code changes made */
'use strict';
const cdk = require('@aws-cdk/core');
/* addition */
/* end addition */
/* addition 2 */
const dynamodb = require('@aws-cdk/aws-dynamodb');
/* end addition 2 */
class CdkExampleStack extends cdk.Stack {
constructor(scope, id, props) {
super(scope, id, props);
/* addition */
/* end addition */
/* addition 2 */
const helloTable = new dynamodb.Table(this, 'HelloTable', {
partitionKey: {
name: 'PartitionKey',
type: dynamodb.AttributeType.STRING,
billingMode: dynamodb.BillingMode.PROVISIONED,
},
sortKey: { name: 'SortKey', type: dynamodb.AttributeType.STRING },
readCapacity: 1,
writeCapacity: 1,
});
helloTable.grantReadData(helloLambda);
/* end addition 2 */
}
}
module.exports = { CdkExampleStack };
When we synthesize and deploy, we will find two new resources:
- One DynamoDB table
- One IAM policy
The IAM policy will show the following:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:Query",
"dynamodb:GetItem",
"dynamodb:Scan"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:123456789012:table/CdkExampleStack-HelloTable2C0887DE-1D2AVL6SE753L"
],
"Effect": "Allow"
}
]
}
You may notice this IAM policy has more allowed actions than the one we created with Serverless. The IAM policy we created with Serverless only contained the policy we defined in the serverless.yml file. Whereas, we used the convenient grantReadData function to automatically create all the read permissions the function will ever need. We could have manually created the IAM policy and add it to the role. Although the IAM policy we created with CDK is technically not least privileged, the intent is least privileged. Doing a read by getting one item vs. getting multiple items is still a read nonetheless. If we wanted the actual least privilege, we would need to add more code to the file.
Comparison
Serverless did not simplify our creation of the DynamoDB table the IAM policy. We would attach the IAM role policy assigned to the Lambda function. We had to resort to using native CloudFormation template code and IAM policy statements.
CDK simplified the DynamoDB and IAM policy creation with a few code lines and without knowing the CloudFormation template code and IAM policy statements. We received the benefit of getting all the privileges the Lambda function would need to perform a read. Still, it may not be least privileged if we truly wanted the function only to get one read. We would then need to add more code to add the appropriate IAM policy and lose its simplicity.
3. Extensibility
In this section, we will review the extensibility of CDK and Serverless.
CDK
CDK uses constructs and defines them in three levels. Level 1 allows you to work directly with CloudFormation. Level 2 provides an abstraction and human-readable code; see the examples above. Level 3 will enable you to combine Level 1 and Level 2 constructs with building reusable patterns (e.g., deploying an S3 bucket and CloudFront distribution to host a static web site).
AWS has built several Level 3 constructs to solve many common patterns. They distribute under their AWS Solutions Constructs. For example, you can install their construct to deploy an S3 bucket and CloudFront distribution.
npm i --save @aws-solutions-constructs/aws-cloudfront-s3
We will create a lib/cloudfront-s3.js
file to define our new stack.
/* lib/cloudfront-s3.js */
/* new file */
'use strict';
const cdk = require('@aws-cdk/core');
const {
CloudFrontToS3,
} = require('@aws-solutions-constructs/aws-cloudfront-s3');
class CloudFrontS3Stack extends cdk.Stack {
/**
*
* @param {cdk.Construct} scope
* @param {string} id
* @param {cdk.StackProps=} props
*/
constructor(scope, id, props) {
super(scope, id, props);
new CloudFrontToS3(stack, 'cloudfront-s3', {});
}
}
module.exports = { CloudFrontS3Stack };
Now we add that stack to our app.
#!/usr/bin/env node
/* bin/cdk-example.js */
/* automatically created file */
/* code changes made */
'use strict';
const cdk = require('@aws-cdk/core');
const { CdkExampleStack } = require('../lib/cdk-example-stack');
const { CloudFrontS3Stack } = require('../lib/cloudfront-s3-stack');
const app = new cdk.App();
new CdkExampleStack(app, 'CdkExampleStack');
new CloudFrontS3Stack(app, 'CloudFrontS3Stack');
When we deploy, CDK automatically creates the S3 bucket, CloudFront distribution, and CloudFront origin access identity that allows CloudFront to distribute the S3 objects. We will need to upload our files to the S3 bucket and create a CloudFront invalidation to clear the CloudFront cache. We will still need to create the Route 53 hosted zone, Certificate Manager certificate, and Route 53 A alias record to the CloudFront distribution to have a fully working web site.
CDK also supports building and using plugins. Plugins modify the CDK deploy process, whereas constructs allow us to build reusable patterns. For example, the previous Level 3 construct allowed us to deploy multiple CloudFront-S3 patterns. In contrast, the cdk-multiple-profile-plugin will enable us to deploy to using multiple AWS profiles (which could be multiple AWS accounts).
Serverless
Serverless has many open-source plugins that extend the deployment process and even allow us to extend how it processes the serverless.yml file.
For example, we can deploy a static web site. The fullstack-serverless plugin allows us to deploy the S3 bucket, CloudFront distribution, and CloudFront origin access identity, run the build command for the web site, and upload the files to the S3 bucket.
We install the plugin.
npm install --save-dev fullstack-serverless
We add the configuration to the serverless.yml
file.
# serverless.yml
# only some of the options are displayed
plugins:
- fullstack-serverless
custom:
fullstack:
domain: my-custom-domain.com
certificate: arn:aws:acm:us-east-1:...
bucketName: my-website
distributionFolder: websiteDir/dist
indexDocument: index.html
errorDocument: error.html
singlePageApp: true
compressWebContent: true
clientCommand: npm run build
clientSrcPath: websiteDir
When we deploy, we will have a lot of the steps completed. We will still need to create the Route 53 hosted zone, Certificate Manager certificate, and Route 53 A alias record to the CloudFront distribution to have a fully working web site.
Comparison
Both CDK and Serverless support plugins. CDK provides Level 3 constructs to build patterns. In contrast, Serverless does not have this concept aside from making a plugin that allows you to modify the serverless.yml file that specifies the build patterns. Serverless is a stable and well-established framework with multiple plugins. CDK is slightly over one year old, and some of its Level 2 constructs and Level 3 constructs are still experimental. The experimental constructs might introduce breaking changes in future releases. Of course, there are no guarantees that Serverless plugins will not introduce breaking changes as they are maintained outside of the control of Serverless, Inc.
4. Stability
I would argue both Serverless and CDK are stable. Serverless, Inc. manages Serverless, and they follow established software development processes. AWS manages CDK and also follow established software development processes.
The Serverless Framework is stable, and they provide backward compatibility with their current V1 and its minor releases. CDK itself is stable, and so are all the Level 1 constructs I have reviewed. This means you can reliably build a CDK app using CloudFormation settings without worrying about breaking changes.
As mentioned earlier, some of the CDK Level 2 and Level 3 constructs are experimental, and Serverless plugins may or may not be stable. Essentially, the potential for breaking changes comes from leveraging conveniences built on top of the base functionality. CDK’s advantage over Serverless is that AWS is building and maintaining Level 2 and Level 3 constructs, which gives AWS more stability over the entire ecosystem. In contrast, Serverless has no direct control over the plugins.
5. Security considerations
5.1. Required AWS resources to deploy
Serverless requires an S3 bucket that contains the Serverless configuration files. If you deploy a Serverless configuration with no resources specified in the serverless.yml file, it will still create that S3 bucket.
CDK does create an S3 bucket to host its configuration data. If you deploy an empty CDK app, it will generate a CDK metadata resource that is not accessible via the AWS console or CLI.
5.2. Resource configurations
Serverless assumes some resources it believes you may want (e.g., Lambda versioning, CloudWatch Logs log groups and one IAM role for all your functions). The bucket is not Internet-accessible, but it is not configured as a private bucket.
CDK tries not to assume the resources you want and gives you most of the control (e.g., it will not create the Lambda versioning and CloudWatch Logs log groups, and it will not create one IAM role for all your functions). Suppose you specify a Lambda function needs a package that contains all its dependencies. In that case, it will automatically create the S3 bucket it needs to deploy the package to the Lambda function. Still, it configures it as a private bucket. As you saw in the example above, CDK did not create the S3 bucket because the Lambda function code was inline code rather than a package.
5.3. Principle of least privilege
The CDK Level 2 constructs provide the convenience of granting privileges using “grant” functions. These “grant” functions create the appropriate least-privileged IAM roles (e.g., it adds all the IAM policy statements that allow read access between the two specific resources and does not add any create/delete/update permissions).
Serverless assumes you want one IAM role for all your functions. This works fine when you have one function per stack, but might start to violate the principle of least privilege when you add more functions. You will need a plugin to help you define IAM roles for each function, or avoid using the built-in IAM roles and manually define them with a CloudFormation template in the “resources” section of the “serverless.yml” file.
We are only using AWS. Which should we use?
Both Serverless and CDK ultimately build a CloudFormation template, but how it creates the template differs. In some respects, Serverless makes deploying serverless applications easier, while in other regards, it requires more work. CDK requires a better understanding of AWS and gives you more control. When it comes to deploying resources other than Lambda functions, CDK needs less code than Serverless (unless you are using Serverless plugins).
If you are new to AWS and serverless computing and want to start deploying a quickly as possible, I recommend starting with Serverless.
Suppose you are new to AWS and want to learn how AWS works. In that case, I suggest watching YouTube videos about the AWS Certified Cloud Practitioner certification exam to understand AWS. When you have a basic understanding of how AWS works, Serverless is still probably the best starting point. Still, you will have sufficient knowledge to start using CDK.
Suppose you have moderate to advanced knowledge of AWS. In that case, CDK will provide you with more control to achieve any simple or complex deployment you wish to achieve. Serverless will also help you achieve the same outcome, but you may need to build your own AWS CloudFormation templates. If you like the simplicity of the YAML configuration, you might want to use Serverless.
Suppose you are more of a DevOps engineer rather than a software developer. In that case, you might lean toward Serverless, but writing the source code for CDK is probably not out of your reach.
View the source code at https://github.com/miguel-a-calles-mba/secjuice/tree/master/cdk-vs-sls.
Before you go
About the author
Originally published on Secjuice.com
Photo by Luca Bravo on Unsplash