Serverless service / Function as a service allowing to run code without having to worry about underlying hardware and OS.
Event driven: the lambda is triggered by an event.
Pay only for what you use: per request and based on the duration of the code execution.
Use cases
Data transformation (Kinesis Data Stream as input)
File processing (when uploaded to S3 bucket)
Website backend microservice
Scheduled tasks
Bad use cases
Long running processes (timeout after 15mn)
Constant workload (no scalability and high cost)
Large code base (needed to be loaded at startup)
State management (lambda are stateless)
Anti-patterns
Monolithic function
increase package size
hard to enforce least privilege permissions
hard to upgrade, maintain and test
Recursion
endless loop
Orchestration
avoid complex workflow logic
ties lambda with other systems
instead consider AWS Step Functions or EventBridge
Chaining (synchronously invoke another lambda)
instead use EventBridge or QueueService
Waiting (synchronously call services or databases)
instead use asynchronous calls
Runtime
OS
Libraries
Programming language (.NET, Node.js, Python, Go, Ruby, Java)
Environnement variables
DOTNET_STARTUP_HOOKS
ex: path to an assembly to inject logging
Wrapper scripts
Execute the wrapper on top of the runtime and the lambda function.
run shell commands and binaries
Use AWS_LAMBDA_EXEC_WRAPPER to point to your wrapper script.
Lambda has builtin functionalities to poll the event source with batching and filtering functionality.
Event source mapping polls and gathers records before it will initialize a Lambda function and send a batch of events to it for processing.
Error handling
If the invocation errors, then it will try the same invocation again after a minute.
After X fails that exceeds the configured retry attempts (0, 1 or 2), the failed invocation goes to:
The initialization of the function is run only during the cold start.
During the warm start the environment is thawed and only the function handler is executed.
Use function initialization to: (outside of the handler function)
load external libraries
open db connection
load configuration and secrets
use the ephemeral storage, such as a local cache for files
SnapStart
Only for Java runtime
Lambda initializes the function at publish rather than during cold-start invocation.
If not invoked, snapshots are deleted after 14 days.
Limitations:
cannot be used with provisioned concurrency
supports limited runtimes, architecture and ephemeral storage
Ensure to (with post-snapshot hook):
reestablish network connections
have preloaded data up to date
take care of uniqueness (generation of random numbers or GUID), each function starts with a clone of the snapshot
# install the VS project template
dotnet new install Amazon.Lambda.Templates
# install the command line tools
dotnet tool install -g Amazon.Lambda.Tools
Install the AWS Toolkit for Visual Studio extension.
AWS Lambda application types
AWS Lambda application type
Description
Class library
Executable assembly
AWS Serverless Application
ASP.NET application hosted in the AWS environment. It has a handful of additional dependencies that make it interoperate with AWS runtime.
C# class library
You provide Lambda with information about your function's handler in the form of a handler string: ASSEMBLY::TYPE::METHOD
ASSEMBLY is the name of the .NET assembly file for your application. If you use the Amazon.Lambda.Tools CLI to build your application and you don't set the assembly name using the AssemblyName property in the .csproj file, then ASSEMBLY is simply the name of your .csproj file.
TYPE is the full name of the handler type, which consists of the Namespace and the ClassName.
METHOD is the name of the function handler method in your code.
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespaceMyProject;
publicclassFunction
{
publicstringFunctionHandler(stringinput, ILambdaContextcontext)
{
return input.ToUpper();
}
}
On VS, if the extension AWS Toolkit is installed, you have the AWS .NET Mock Lambda Test Tool available which allows you to debug locally.
Properties/launchSettings.json
{"profiles":{"Mock Lambda Test Tool":{"commandName":"Executable","commandLineArgs":"--port 5050","workingDirectory":".\\bin\\$(Configuration)\\net8.0","executablePath":"%USERPROFILE%\\.dotnet\\tools\\dotnet-lambda-test-tool-8.0.exe"}}}
Using the C# 9's top-level statements feature, you generate an executable assembly which will be run by the Lambda. You provide Lambda only with the name of the executable assembly to run.
varhandler = async (string argument1, ILambdaContext context) => { };
// bootstrap the Lambda runtime and pass it the handler methodawait LambdaBootstrapBuilder.Create(handler, newDefaultLambdaJsonSerializer()).Build().RunAsync();
To debug locally you have to create the debug configuration file for you lambda.
Properties\launchSettings.json
{"profiles":{"Mock Lambda Test Tool":{"commandName":"Executable","commandLineArgs":"--port 5050","executablePath":"%USERPROFILE%\\.dotnet\\tools\\dotnet-lambda-test-tool-8.0.exe","workingDirectory":".\\bin\\$(Configuration)\\net8.0","environmentVariables":{"AWS_LAMBDA_RUNTIME_API":"localhost:5050","AWS_PROFILE":"MyProfile","AWS_REGION":"us-east-1"}}}}
// create a custom serializerpublicclassEnumCamelCaseLambdaJsonSerializer : DefaultLambdaJsonSerializer
{
publicEnumCamelCaseLambdaJsonSerializer()
: base(options =>
{
options.Converters.Add(newJsonStringEnumConverter());
options.PropertyNamingPolicy = newAwsNamingPolicy(JsonNamingPolicy.CamelCase);
}) { }
}
// then use it
[assembly: LambdaSerializer(typeof(EnumCamelCaseLambdaJsonSerializer))]
Stream as input
Function.cs
// use a stream as input argument and deserialize itpublicstringFunctionHandler(Streaminput, ILambdaContextcontext)
{
varoptions = newJsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
Converters = { newJsonStringEnumConverter() }
};
JsonSerializer.Deserialize<MyClass>(input, options);
}
Logging
Lambda pushes logs to the CloudWatch Logs service under /aws/lambda/[FunctionName].
Permissions (function's execution role, ex: AWSLambdaBasicExecutionRole policy):
create CloudWatch groups
create CloudWatch streams
write log events
Logs are organized into groups (1 group per function)
each group contains a collection of streams (1 stream per function instance) YYYY/MM/DD[Function version][Execution env GUID]
// nuget package: AWS.Lambda.Powertools.LoggingpublicclassFunction
{
publicFunction()
{
Logger.UseFormatter(newCustomLogFormatter()); // optionally change the format of the logs
}
// When debugging in non-production environments, you can instruct Logger to log the incoming event
[Logging(LogEvent = true)]
// define the case of property names (default: snake)
[Logging(LoggerOutputCase = LoggerOutputCase.CamelCase)]
// Delete all custom keys. Due to Lambda Execution Context reuse, this means that custom keys can be persisted across invocations.
[Logging(ClearState = true)]
publicstringFunctionHandler(stringinput, ILambdaContextcontext)
{
Logger.LogInformation("Input: '{0}'", input);
// add structured JSON object to all the next log entriesvarcontextInfo = newDictionary<string, object>
{
{ "contextInfo", context }
};
Logger.AppendKeys(contextInfo);
Logger.LogInformation("Context info"); // log anything
Logger.RemoveKeys(contextInfo.Keys.ToArray()); // remove the structured JSON object to all the next log entriesreturn input;
}
}
CustomLogFormatter.cs
publicclassCustomLogFormatter : ILogFormatter
{
publicobjectFormatLogEntry(LogEntrylogEntry)
{
returnnew
{
Message = logEntry.Message,
Service = logEntry.Service,
CorrelationIds = new
{
AwsRequestId = logEntry.LambdaContext?.AwsRequestId,
XRayTraceId = logEntry.XRayTraceId,
CorrelationId = logEntry.CorrelationId
},
LambdaFunction = new
{
Name = logEntry.LambdaContext?.FunctionName,
Arn = logEntry.LambdaContext?.InvokedFunctionArn,
MemoryLimitInMB = logEntry.LambdaContext?.MemoryLimitInMB,
Version = logEntry.LambdaContext?.FunctionVersion,
ColdStart = logEntry.ColdStart,
},
Level = logEntry.Level.ToString(),
Timestamp = logEntry.Timestamp.ToString("o"),
Logger = new
{
Name = logEntry.Name,
SampleRate = logEntry.SamplingRate
},
};
}
}
The Lambda on AWS has wrongly set the handler to LambdaTest.
AWS - Lambda - Functions - select your function - Code tab - Runtime settings - Edit - change the Handler
Your function doesn't have permission to write to Amazon CloudWatch Logs
AWS → IAM → Roles → [the roles used by your lambda] → Persissions policies → Add permission → Create inline policy