This project is configured to be deployed to AWS Lambda as a custom runtime. It uses the NuGet package Amazon.Lambda.RuntimeSupport as the .NET Lambda runtime.
In my observations if you turn on the trimming and ready to run features as describe below the cold start performance for a .NET 7 custom runtime is on par with a .NET Core 3.1 managed runtime. Would love to hear other people's findings.
The easiest way to deploy a .NET 7 Custom Runtime Lambda function is to use the Amazon.Lambda.Tools .NET CLI tool. To install the tool use the following command:
dotnet tool install -g Amazon.Lambda.Tools
Once installed to run the following command to start the deployment process:
dotnet lambda deploy-function
The section below describe important things to know about what is going on in the code and project file for this Lambda project.
In order to deploy a .NET Lambda function as a custom runtime you need to include the .NET runtime in the publish bundle. This project has the
aws-lambda-tools-defaults.json file set the msbuild-parameters
property with the value --self-contained true
which tells the underlying
dotnet publish command to included the .NET Runtime.
Since this is a self contained publish including the .NET runtime the deployment bundle is larger then a deployment bundle for a managed runtime like .NET Core 3.1. By default the hello world Lambda function is about 33 Megs. .NET 7 has new tricks up its sleeve for reducing the size with its Trimming features.
In the csproj file trimming has been enabled by setting the PublishTrimmed
property to true. The default
trimming mode in .NET 7 is link
mode which actually trims out unused code from assemblies. This can reduce the
hello world Lambda function from 33 megs to 11 megs. In your particular Lambda function you might find this
setting is too aggressive and in that case you should set TrimMode
to copyused
which trims at an
assembly level or turn off trimming.
R2R is the process of converting assemblies from the agnostic IL to machine specific instruction code. This conversion process would normally happen at startup of the application which can be costly for cold starts but R2R can greatly improve the startup. R2R has been around for quite sometime but it has always been awkward to use especially from a development environment.
As mentioned in the .NET 7 RC1 blog post
there is a new tool to generate R2R images called crossgen2. This makes it really easy to create R2R images of your
assemblies. All that you have to do is set the PublishReadyToRun
property to true
in the project file. Unlike
past versions of .NET you no longer have to build on Linux to turn this feature. You can develop on any platform and
publish to Lambda assemblies that have already been converted to machine specific instruction code.
Turning on R2R is also critical if you enable trimming because when the link trimming is executed it removes the R2R images from the system packages. So you need to enabled R2R to get all those assemblies optimized again. It will increase the size of the deployment bundle a bit but it is worth it.
The actual Lambda function in this case is a simple function that shows how to use the AWS SDK for .NET by returning back the list of S3 bucket. Since this is a custom runtime the programming model is a little different then .NET Lambda functions for managed runtime like .NET Core 3.1.
In a custom runtime the project is a executable console application that contains a main
function to bootstrap
the Lambda runtime.
Since this is .NET 7 we can take advantage of all of the latest C# features like top level statements to reduce the boiler plate code to a tiny amount.
using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;
using Amazon.S3;
using var s3Client = new AmazonS3Client();
// Lambda function that returns the list of S3 buckets.
var listS3BucketsLambdaFunction = async (ILambdaContext context) =>
{
return (await s3Client.ListBucketsAsync()).Buckets;
};
// Startup Lambda .NET runtime
using var handlerWrapper = HandlerWrapper.GetHandlerWrapper(listS3BucketsLambdaFunction, new DefaultLambdaJsonSerializer());
using var bootstrap = new LambdaBootstrap(handlerWrapper);
await bootstrap.RunAsync();