AWS Lambda Layers were introduced on top of the Lambda deployment stack for cloud applications. They are added to provide support for reusable components and in an attempt to reduce the coldstart issues of the Lambda services. In this article, let's look in-detail about what the fuss is about these Lambda layers and how we can add Layers to our ASP.NET Core application deployed in an AWS Lambda.
What is a Layer?
What does a Layer mean in an application? It can be assumed as a part of the application stack which encapsulates a set of components which share similar responsibilities. For example, a Data layer encapsulates the data-logic of the application stack and the other components talk to the back-end through the components of this layer. A layer acts independently in the form of a library and is portable and reusabe. Similarly, a Lambda Layer refers to an application layer which can be reused across different lambda functions by means of a reference.
Take the example of an ASP.NET Core application published to a server or a folder. Along side the application binary in which the Main() resides and functions, there are several other binaries which represent a variety of packages that are used in the application to achieve a variety of functionalities or to solve a set of problems. And the other layers of the application are also included in the form of their binaries. This generally increases the overall deployment package which is in normal scenarios unavoidable.
When when it comes to deployments in AWS Lambda, there is a cap on the deployment package size and this size also impacts the deployment time and the time taken to boot up the application. When moving to a microservice architecture where each individual service has its own reference to these common layers for their functionalities, this kind of scenarios might give rise to the need for a common place where all these microservices which require a certain library to access can look into and access. This creates an opportunity of code reuse and also reduces the deployment package sizes of individual microservices. In an ideal scenario, we will pack these common libraries which are to be used by the services into a "common place" and then while deploying the services into lambda, it skips all the binaries which are already available in that "common place" and packs only what is not available with the lambda. This is how a Layer works.
How Lambda Layer works?
How can a Lambda Layer help?
Is Everything Good with Layers?
Creating Lambda Layers in ASP.NET Core:
To create and deploy a dotnet core library as a lambda layer, we make use of runtime package stores. It is a feature of dotnet core which was introduced in dotnetcore2.0 and is available since then. A runtime package store helps create a grouping of the packages which are used in a dotnetcore application which can be externalized and used. It generates an xml file which keeps a list of all the packages that are now grouped. Lambda Layer uses this capability in dotnetcore and goes through this xml to make a note of all the available packages and ignores them when the lambda function is being deployed.
To demonstrate this, let's create a simple ASP.NET Core API that uses the Serverless Application Model (SAM) for deploying into an AWS Lambda. Before that, make sure that you have the AWS Toolkit for Visual Studio and dotnetcore installed. To install AWS Tools for dotnetcore, run:
> dotnet tool install -g Amazon.Lambda.Tools #if already installed - update# > dotnet tool update -g Amazon.Lambda.Tools
Then install the AWS Lambda Templates for dotnetcore via CLI.
> dotnet new -i Amazon.Lambda.Templates
Create a new API serverless template project from the list of templates we just installed.
> dotnet new serverless.AspNetCoreWebAPI --name AwsLayers.App
This creates a new ASP.NET Core API with deployment settings preconfigured. This project can be deployed by means of a file serverless.template which is a JSON file that specifies what to build and how to deploy. For now, lets forget about it and observe the libraries that come with the application.
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> <AWSProjectType>Lambda</AWSProjectType> </PropertyGroup> <ItemGroup> <PackageReference Include="AWSSDK.S3" Version="126.96.36.199" /> <PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="188.8.131.52" /> <PackageReference Include="Amazon.Lambda.AspNetCoreServer" Version="5.1.1" /> </ItemGroup> </Project>
The libraries this API uses are for setting up the Lambda layer and adding an S3 Client; that this API comes with as a usecase. Let's try packaging this API and see what happens:
> dotnet lambda package
This publishes the API along with all the referenced packages and creates a deployment zip. This zip can be uploaded into Lambda console to deploy as a function. The size of this zip is around 610kb, even without a single line of code written. As the API grows and we add more libraries, this size increases which can affect the API deployment and later its performance. With the concept of Layers, we can separate these referenced packages into a runtime store and deploy them seperately into a layer. Then we can configure our API to exclude these packages in the lambda deployment package and instead access these packages from the layer during runtime.
To do this, we need to specify a package store manifest that contains all these packages to be added to the store. This manifest takes the form of a csproj file, and and so let's add a new csproj file called Dependencies.csproj and move all the package references used in the API project into this csproj file. The file looks like below:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <PreserveCompilationContext>true</PreserveCompilationContext> <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> <AWSProjectType>Lambda</AWSProjectType> <OutputType>Library</OutputType> <StartupObject /> </PropertyGroup> <ItemGroup> <PackageReference Include="AWSSDK.S3" Version="184.108.40.206" /> <PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="220.127.116.11" /> <PackageReference Include="Amazon.Lambda.AspNetCoreServer" Version="5.1.1" /> </ItemGroup> </Project>
The API project now only has a ProjectReference to this Dependencies.csproj:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> <AWSProjectType>Lambda</AWSProjectType> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\dependencies\dependencies.csproj" /> </ItemGroup> </Project>
Now we'll use 'dotnet store' command which creates a runtime package store.
> dotnet store --manifest dependencies.csproj --runtime linux-x64 --framework netcoreapp3.1 --framework-version 3.1.0 --output bin/dotnetcore/store --skip-optimization
This command builds using the csproj file and publishes all the packages specified into the path /dotnetcore/store along with an artifact.xml created under the same folder.
The artifact.xml file contains this:
<StoreArtifacts> <Package Id="AWSSDK.Core" Version="18.104.22.168" /> <Package Id="AWSSDK.S3" Version="22.214.171.124" /> <Package Id="AWSSDK.Core" Version="3.3.100" /> <Package Id="AWSSDK.Extensions.NETCore.Setup" Version="126.96.36.199" /> <Package Id="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.0" /> <Package Id="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" /> <Package Id="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" /> <Package Id="Microsoft.Extensions.Primitives" Version="2.0.0" /> <Package Id="System.Runtime.CompilerServices.Unsafe" Version="4.4.0" /> <Package Id="Amazon.Lambda.APIGatewayEvents" Version="2.1.0" /> <Package Id="Amazon.Lambda.ApplicationLoadBalancerEvents" Version="2.0.0" /> <Package Id="Amazon.Lambda.AspNetCoreServer" Version="5.1.1" /> <Package Id="Amazon.Lambda.Core" Version="1.1.0" /> <Package Id="Amazon.Lambda.Logging.AspNetCore" Version="3.0.0" /> <Package Id="Amazon.Lambda.Serialization.SystemTextJson" Version="2.0.1" /> <Package Id="Microsoft.Extensions.Configuration" Version="2.1.0" /> <Package Id="Microsoft.Extensions.Configuration.Abstractions" Version="2.1.0" /> <Package Id="Microsoft.Extensions.Configuration.Binder" Version="2.1.0" /> <Package Id="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.0" /> <Package Id="Microsoft.Extensions.Logging" Version="2.1.0" /> <Package Id="Microsoft.Extensions.Logging.Abstractions" Version="2.1.0" /> <Package Id="Microsoft.Extensions.Options" Version="2.1.0" /> <Package Id="Microsoft.Extensions.Primitives" Version="2.1.0" /> <Package Id="System.Runtime.CompilerServices.Unsafe" Version="4.5.0" /> </StoreArtifacts>
It lists down all the package references inside the dependencies.csproj, along with libraries these packages depend internally - so that one need not go look for any other packages other than these during runtime.
Zip the dotnetcore folder into package.zip file and also copy the artifact.xml in the same path as the package.zip file. Upload these two files into an S3 bucket, into a folder say /dependenciesLayer which we shall use as we move forward.
In Lambda section under AWS Console, click on Layers under Additional Resources section and click on Create Layer.
In the fields, give these values:
select the Upload from S3 option and give the path of the package.zip we've uploaded before and finally click Create. This completes the Layer creation. Copy the Layer Arn value generated, which we shall use in our API project.
Back in the API project, let's run the lambda deploy command again and this time pass another parameter with the layerArn we just copied before.
> dotnet lambda package --function-layers arn:aws:lambda:us-west-2:123456789:layer:dependenciesLayer:1
Observe the log, which now excludes all the packages which were present in the Dependencies.csproj and instead just adds two binaries in the deployment zip - the API binary and the Dependencies binary. Also, notice the size of the zip file - its now just 61 KB!
From 610kb without Layer to just 61 KB with a Layer attached!! Now this Layer can be reused across all the lambda functions we deploy in the future and they can make use of the Core Lambda packages for Dotnetcore without having to include them in the project. Pretty awesome right?
In this way, we can make use of Layers to help reduce our deployment sizes and reuse all such packages across other lambda functions.
In the next article, we shall see how we can adapt this in packing our own custom dotnetcore library and create a layer and use it in this project.
Enjoying my posts?
You can now show me your support! 😊
* Use() method: Used to create a simple middleware which can be "chained" to other functions over the pipeline. Takes two arguments: RequestDelegate ...
* Response.Redirect() redirects browser to another page, history is updated, trip back to client where browser loads the new page. * Server.Transfer( ...
We can make use of the built-in UseExceptionHandler() middleware for catching Global Errors in ASP.NET Core. ``` app.UseExceptionHandler(err => ...
To create a strongly-typed class for binding to a configuration section: * The property names and their types match the key names and their value t ...
A Configuration section can be bound to a strictly-typed class object in two ways: * use Configuration.Bind() by passing the configuration section to ...
aws lambda layers .net core aws lambda layers aws lambda .net core aws lambda .net core 3.1 aws lambda dotnet core aws lambda c#
Subscribe to my newsletter and get the first copy of every new post delivered straight into your inbox.