« Lambda » : différence entre les versions
(171 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 52 : | Ligne 52 : | ||
Use {{boxx|AWS_LAMBDA_EXEC_WRAPPER}} to point to your wrapper script. | Use {{boxx|AWS_LAMBDA_EXEC_WRAPPER}} to point to your wrapper script. | ||
= . | == Custom runtime == | ||
Provide your custom runtime. | |||
* unsupported programming language | |||
= [https://docs.aws.amazon.com/lambda/latest/dg/csharp-handler.html Handler (entry point)] = | |||
Method responsible for processing input events. | |||
{| class="wikitable wtp" | |||
|- | |||
| synchronous execution || result returned to the calling app | |||
|- | |||
| asynchronous execution || result sent to the configured destination otherwise lost | |||
|} | |||
= Configuration = | |||
{| class="wikitable wtp" | |||
! Setting | |||
! Description | |||
|- | |||
| Memory || from 128 MB (default) to 10 GB | |||
|- | |||
| vCPU || 1'769 MB = 1 vCPU, 10 GB = 6 vCPU, for single-threaded code allocated more than 1'769 MB is useless. Compare ARM and x86. | |||
|- | |||
| timeout || from 1s to 15mn, default to 3s | |||
|- | |||
| VPC || | |||
* by default no VPC access configured → Amazon VPC → internet access | |||
* if VPC access is configured → access to VPC resources → no internet connection | |||
** use an Hyperplane ENI to add internet connection in addition of a VPC access | |||
|- | |||
| Ephemeral storage || {{boxx|/tmp}} from 512 MB (default) to 10 GB | |||
|- | |||
| Execution role || grants permissions to access AWS services | |||
|- | |||
| Resource-based policy || define what others are allowed to do to the function | |||
|- | |||
| Environment variables || | |||
* Key-value pair of strings | |||
* Stored in the configuration for a Lambda function version | |||
* Accessible within the Lambda code function | |||
|- | |||
| Logging || format (text or JSON), level, group | |||
|} | |||
= Storage = | |||
== Directories == | |||
{| class="wikitable wtp wtmono1" | |||
! Path | |||
! Description | |||
|- | |||
| /var/task || Lambda package (function files) | |||
|- | |||
| /tmp || Temporary storage with write access | |||
|- | |||
| /opt || Lambda layer (layer files) | |||
|} | |||
== Elastic File System (EFS) == | |||
* Persistent | |||
* Serverless, scalable | |||
* Support concurrent access | |||
* Require VPC access | |||
== S3 == | |||
* Persistent | |||
* No VPC required | |||
* Accessed via API | |||
= IAM entities = | |||
{| class="wikitable wtp" | {| class="wikitable wtp" | ||
! | ! Type | ||
! | ! Description | ||
|- | |||
| User || individual with unique credentials | |||
|- | |||
| Role || entity for temporary access (access to a db) | |||
|- | |||
| Policy || permission for access to resources (apply to a user or a role to read from a specific db table) | |||
|- | |||
| User group || logical collection of users for easier management | |||
|} | |||
= Synchronous execution = | |||
== Invocation result == | |||
{| class="wikitable wtp" | |||
! Result content | |||
! Description | |||
|- | |||
| Status code || 200 success, 4xx or 5xx error | |||
|- | |||
| Executed version || | |||
|- | |- | ||
| | | Log result || last 4 KB of the execution log in base64 encoding | ||
|- | |- | ||
| | | Function error || only if an error occured. Code error, timeout, invalid JSON output | ||
|- | |- | ||
| | | Response body || payload | ||
|} | |} | ||
= [https:// | = [https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html Asynchronous invocation] = | ||
On VS, if the extension {{boxx|AWS Toolkit}} is installed you have the {{boxx|AWS .NET Mock Lambda Test Tool}} available. | When a Lambda function is invoked asynchronously: | ||
* an event is placed in its internal queue | |||
<filebox fn='Properties | * it returns a success response (202 Accepted) without additional information to the invoker | ||
* a separate process reads events from the queue and sends them to the lambda function | |||
* the result (Invocation Record) and the error are sent to the destinations | |||
== [https://aws.amazon.com/blogs/compute/introducing-aws-lambda-destinations Destination] == | |||
Allow to route asynchronous function results as an execution record to a destination resource.<br> | |||
Available destinations: | |||
* [[Simple_Notification_Service|Simple Notification Service (SNS)]] topic | |||
* [[Simple_queue_service|Simple Queue Service (SQS)]] queue | |||
* Lambda function | |||
* [[EventBridge]] event bus | |||
== Event Source (trigger) == | |||
* [[Simple_queue_service|Simple Queue Service (SQS)]] queue | |||
* DynamoDB stream | |||
* DocumentDB stream | |||
* Kinesis data stream | |||
* Message Queue (MQ) | |||
* Kafka | |||
Lambda has builtin functionalities to poll the event source with batching and filtering functionality.<br> | |||
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.<br> | |||
After X fails that exceeds the configured retry attempts (0, 1 or 2), the failed invocation goes to: | |||
* a dead-letter queue | |||
* a failure destination | |||
== CLI == | |||
<kode lang='ps'> | |||
# async call (invoke via queue) | |||
aws lambda invoke \ | |||
--profile [Profile] \ | |||
--function-name [Function] \ | |||
--invocation-type Event \ | |||
--payload $([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes('{ "key": "value" }'))) \ | |||
response.json | |||
</kode> | |||
= Optimization = | |||
== Make the most of warm start == | |||
The initialization of the function is run only during the cold start.<br> | |||
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 == | |||
{{warn | Only for Java runtime}} | |||
Lambda initializes the function at publish rather than during cold-start invocation.<br> | |||
If not invoked, snapshots are deleted after 14 days.<br> | |||
Limitations:<br> | |||
* cannot be used with provisioned concurrency | |||
* supports limited runtimes, architecture and ephemeral storage | |||
Ensure to (with post-snapshot hook):<br> | |||
* 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 | |||
= Concurrency = | |||
* [https://docs.aws.amazon.com/lambda/latest/dg/lambda-concurrency.html Lambda function scaling] | |||
{| class="wikitable wtp" | |||
! Concurrency type | |||
! Description | |||
|- | |||
| Account concurrency || max number of concurrent instances across all functions (AWS → Lambda → Dashboard) | |||
|- | |||
| Reserved concurrency || upper limit of a function's concurrency | |||
|- | |||
| Burst concurrency || increase quota, default to 1'000 instances every 10 s | |||
|- | |||
| Provisioned concurrency || initialize execution env ahead of time. Application Auto Scaling can be used to automatically adjust provisioned concurrency | |||
|} | |||
= [https://docs.aws.amazon.com/lambda/latest/dg/csharp-dev-env.html Setting up your .NET development environment] = | |||
<kode lang='ps'> | |||
# install the VS project template | |||
dotnet new install Amazon.Lambda.Templates | |||
# install the command line tools | |||
dotnet tool install -g Amazon.Lambda.Tools | |||
</kode> | |||
Install the {{boxx|AWS Toolkit for Visual Studio}} extension. | |||
= AWS Lambda application types = | |||
{| class="wikitable wtp" | |||
! 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: {{boxx|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. | |||
ex: {{boxx|MyProject::MyNamespace.MyClass::MyFunctionHandler}} | |||
<filebox fn='Function.cs'> | |||
// 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))] | |||
namespace MyProject; | |||
public class Function | |||
{ | |||
public string FunctionHandler(string input, ILambdaContext context) | |||
{ | |||
return input.ToUpper(); | |||
} | |||
} | |||
</filebox> | |||
<filebox fn='aws-lambda-tools-defaults.json'> | |||
{ | |||
"Information": [ ], | |||
"profile": "MyAwsProfile", | |||
"region": "eu-central-1", | |||
"configuration": "Release", | |||
"function-architecture": "x86_64", | |||
"function-runtime": "dotnet8", | |||
"function-memory-size": 512, | |||
"function-timeout": 30, | |||
"function-handler": "MyProject::MyProject.Function::FunctionHandler" | |||
} | |||
</filebox> | |||
On VS, if the extension {{boxx|AWS Toolkit}} is installed, you have the {{boxx| [https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool AWS .NET Mock Lambda Test Tool]}} available which allows you to debug locally. | |||
<filebox fn='Properties/launchSettings.json'> | |||
{ | { | ||
"profiles": { | "profiles": { | ||
Ligne 73 : | Ligne 297 : | ||
"commandName": "Executable", | "commandName": "Executable", | ||
"commandLineArgs": "--port 5050", | "commandLineArgs": "--port 5050", | ||
" | "workingDirectory": ".\\bin\\$(Configuration)\\net8.0", | ||
" | "executablePath": "%USERPROFILE%\\.dotnet\\tools\\dotnet-lambda-test-tool-8.0.exe" | ||
} | } | ||
} | } | ||
} | } | ||
</filebox> | </filebox> | ||
= [https://docs.aws.amazon.com/lambda/latest/dg/csharp-handler.html#csharp-handler-executable C# executable assembly] = | |||
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. | |||
<kode lang='cs'> | |||
var handler = async (string argument1, ILambdaContext context) => { }; | |||
// bootstrap the Lambda runtime and pass it the handler method | |||
await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()).Build().RunAsync(); | |||
</kode> | |||
To [https://scientificprogrammer.net/2023/07/19/the-easiest-way-to-run-and-debug-net-aws-lambda-locally/ debug locally] you have to create the debug configuration file for you lambda. | |||
<filebox fn='Properties\launchSettings.json' collapsed> | |||
{ | |||
"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" | |||
} | |||
} | |||
} | |||
} | |||
</filebox> | |||
= Context = | |||
<filebox fn='Function.cs'> | |||
public string FunctionHandler(string input, ILambdaContext context) | |||
{ | |||
Logger.LogInfo("Function Name: {0}:{1}", context.FunctionName, context.FunctionVersion); | |||
Logger.LogInfo("Log Group: {0}", context.LogGroupName); | |||
Logger.LogInfo("Stream Name: {0}", context.LogStreamName); | |||
Logger.LogInfo("Request Id: {0}", context.AwsRequestId); | |||
Logger.LogInfo("Memory Limit: {0} MB", context.MemoryLimitInMB); | |||
Logger.LogInfo("Remaining timeLimit: {0} ms", context.RemainingTime); | |||
var contextInfo = new Dictionary<string, object> | |||
{ | |||
{ "Context", context } | |||
}; | |||
Logger.AppendKeys(contextInfo); | |||
Logger.LogInfo("Context info"); | |||
Logger.RemoveKeys(contextInfo.Keys.ToArray()); | |||
</filebox> | |||
= LambdaJsonSerializer = | |||
* [https://nodogmablog.bryanhogan.net/2023/03/how-to-handle-enums-in-the-input-to-a-net-aws-lambda-function/ Handle Enums in the input arguments of .NET AWS Lambda Functions] | |||
* {{boxx|CamelCaseLambdaJsonSerializer}} already exists | |||
<filebox fn='Function.cs'> | |||
// create a custom serializer | |||
public class EnumCamelCaseLambdaJsonSerializer : DefaultLambdaJsonSerializer | |||
{ | |||
public EnumCamelCaseLambdaJsonSerializer() | |||
: base(options => | |||
{ | |||
options.Converters.Add(new JsonStringEnumConverter()); | |||
options.PropertyNamingPolicy = new AwsNamingPolicy(JsonNamingPolicy.CamelCase); | |||
}) { } | |||
} | |||
// then use it | |||
[assembly: LambdaSerializer(typeof(EnumCamelCaseLambdaJsonSerializer))] | |||
</filebox> | |||
= Stream as input = | |||
<filebox fn='Function.cs'> | |||
// use a stream as input argument and deserialize it | |||
public string FunctionHandler(Stream input, ILambdaContext context) | |||
{ | |||
var options = new JsonSerializerOptions | |||
{ | |||
PropertyNameCaseInsensitive = true, | |||
Converters = { new JsonStringEnumConverter() } | |||
}; | |||
JsonSerializer.Deserialize<MyClass>(input, options); | |||
} | |||
</filebox> | |||
= Logging = | |||
Lambda pushes logs to the CloudWatch Logs service under {{boxx|/aws/lambda/[FunctionName]}}.<br> | |||
Permissions (function's execution role, ex: {{boxx|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) {{boxx|YYYY/MM/DD[Function version][Execution env GUID]}} | |||
<kode lang='cs'> | |||
LambdaLogger.Log("Message"); | |||
</kode> | |||
= [https://docs.powertools.aws.dev/lambda/dotnet Powertools] = | |||
Allows Logging, [https://docs.powertools.aws.dev/lambda/dotnet/core/tracing Tracing], [https://docs.powertools.aws.dev/lambda/dotnet/core/metrics Metrics], Parameters. | |||
== [https://docs.powertools.aws.dev/lambda/dotnet/core/logging Powertools Logging] == | |||
Use structured logging. | |||
<kode lang='cs'> | |||
// nuget package: AWS.Lambda.Powertools.Logging | |||
public class Function | |||
{ | |||
public Function() | |||
{ | |||
Logger.UseFormatter(new CustomLogFormatter()); // 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)] | |||
public string FunctionHandler(string input, ILambdaContext context) | |||
{ | |||
Logger.LogInformation("Input: '{0}'", input); | |||
// add structured JSON object to all the next log entries | |||
var contextInfo = new Dictionary<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 entries | |||
return input; | |||
} | |||
} | |||
</kode> | |||
<filebox fn='CustomLogFormatter.cs' collapsed> | |||
public class CustomLogFormatter : ILogFormatter | |||
{ | |||
public object FormatLogEntry(LogEntry logEntry) | |||
{ | |||
return new | |||
{ | |||
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 | |||
}, | |||
}; | |||
} | |||
} | |||
</filebox> | |||
Define the desired log level of the lambda: | |||
# Configuration tab → Monitoring and operation tools → Logging configuration → Edit button | |||
# Log format = JSON | |||
# Application log level = | |||
# System log level = | |||
Priority of log level settings in Powertools for AWS Lambda: | |||
# {{boxx|AWS_LAMBDA_LOG_LEVEL}} environment variable | |||
# Setting the log level in code using {{boxx|1=[Logging(LogLevel = )]}} | |||
# {{boxx|POWERTOOLS_LOG_LEVEL}} environment variable | |||
== [https://docs.powertools.aws.dev/lambda/dotnet/utilities/parameters Parameters] == | |||
Fetch values from Secrets Manager, Parameter Store, DynamoDB and App Config | |||
<kode lang='cs'> | |||
public class Function | |||
{ | |||
public string FunctionHandler(string input, ILambdaContext context) | |||
{ | |||
// Get Secrets Provider instance (Secrets Manager) | |||
ISecretsProvider secretsProvider = ParametersManager.SecretsProvider; | |||
// Retrieve a single secret | |||
string? value = await secretsProvider.GetAsync("/my/secret"); | |||
} | |||
} | |||
</kode> | |||
= Handle exceptions = | |||
<filebox fn='Function.cs'> | |||
try | |||
{ | |||
await Task.Delay(2000); | |||
throw new Exception("XXX", new Exception("YYY")); | |||
result = input.ToUpper(); | |||
} | |||
catch (Exception e) | |||
{ | |||
// log the exception object | |||
var exceptionInfo = new Dictionary<string, object> | |||
{ | |||
{ e.GetType().Name, e } | |||
}; | |||
Logger.AppendKeys(exceptionInfo); | |||
Logger.LogCritical($"Exception: {e.Message}"); | |||
Logger.RemoveKeys(exceptionInfo.Keys.ToArray()); | |||
// log the exception and possibly the first inner exception | |||
Logger.LogCritical($"Exception: {e.Message}"); | |||
Logger.LogCritical($"Stack trace: {e.StackTrace}"); | |||
if (e.InnerException is not null) | |||
{ | |||
Logger.LogCritical($"Exception: {e.InnerException.Message}"); | |||
Logger.LogCritical($"Stack trace: {e.InnerException.StackTrace}"); | |||
} | |||
} | |||
</filebox> | |||
== Timeout / ResponseSizeTooLarge== | |||
<filebox fn='Function.cs'> | |||
var response = await amazonLambdaClient.InvokeAsync(request); | |||
var payload = Encoding.ASCII.GetString(response.Payload.ToArray()); // to debug only | |||
if (response.FunctionError == "Unhandled") | |||
{ | |||
var jsonSerializerOptions = new JsonSerializerOptions | |||
{ | |||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase | |||
}; | |||
var lambdaError = await JsonSerializer.DeserializeAsync<LambdaError>(response.Payload, jsonSerializerOptions); | |||
} | |||
record LambdaError(string ErrorType, string ErrorMessage); | |||
</filebox> | |||
<kode lang='json' collapsed> | |||
{ | |||
"errorType":"Sandbox.Timedout", | |||
"errorMessage":"RequestId: 19c4a134-16ff-47ae-aa4e-1d1f7fe31d51 Error: Task timed out after 15.00 seconds" | |||
} | |||
{ | |||
"errorType": "Function.ResponseSizeTooLarge", | |||
"errorMessage": "Response payload size exceeded maximum allowed payload size (6291556 bytes)." | |||
} | |||
</kode> | |||
= [https://docs.aws.amazon.com/lambda/latest/dg/lambda-deploy-functions.html Deployment] = | = [https://docs.aws.amazon.com/lambda/latest/dg/lambda-deploy-functions.html Deployment] = | ||
= | <kode lang='ps'> | ||
dotnet lambda deploy-function [AssemblyName] --profile [Profile] | |||
</kode> | |||
== From VS with AWS Toolkit == | |||
Right-click on the project → Publish to AWS Lambda | |||
<filebox fn='aws-lambda-tools-defaults.json'> | |||
{ | |||
"Information" : [ ], | |||
"profile" : "Profile-Name", | |||
"region" : "eu-central-1", | |||
"configuration" : "Release", | |||
"function-architecture" : "x86_64", | |||
"function-runtime" : "dotnet8", | |||
"function-memory-size" : 512, | |||
"function-timeout" : 30, | |||
"function-handler" : "ProjectName::NamespaceName.ClassName::FunctionHandlerName", | |||
"environment-variables" : "\"POWERTOOLS_SERVICE_NAME\"=\"PowertoolsFunction\";\"POWERTOOLS_LOG_LEVEL\"=\"Info\";\"POWERTOOLS_LOGGER_CASE\"=\"PascalCase\";\"POWERTOOLS_TRACER_CAPTURE_RESPONSE\"=\"true\";\"POWERTOOLS_TRACER_CAPTURE_ERROR\"=\"true\";\"POWERTOOLS_METRICS_NAMESPACE\"=\"powertools_function\"", | |||
"framework" : "net8.0", | |||
"function-name" : "ProjectName", | |||
"package-type" : "Zip", | |||
"function-role" : "arn:aws:iam::637423632242:role/Deploy_Lambda", | |||
"function-subnets" : "", | |||
"function-security-groups" : "", | |||
"tracing-mode" : "PassThrough", | |||
"image-tag" : "" | |||
} | |||
</filebox> | |||
= Permissions = | |||
== WatchLogs == | |||
== EventBridge == | |||
= Call a lambda from code = | = Call a lambda from code = | ||
<kode lang='cs'> | <kode lang='cs'> | ||
// nuget: AWSSDK.Lambda, AWSSDK.SSO, AWSSDK.SSOOIDC | |||
var jsonSerializerOptions = new JsonSerializerOptions | var jsonSerializerOptions = new JsonSerializerOptions | ||
{ | { | ||
Ligne 92 : | Ligne 602 : | ||
}; | }; | ||
var amazonLambdaClient = new AmazonLambdaClient(); | var amazonLambdaConfig = new AmazonLambdaConfig | ||
{ | |||
Profile = new Profile("MyProfile"), | |||
RegionEndpoint = RegionEndpoint.USEast1 | |||
}; | |||
var amazonLambdaClient = new AmazonLambdaClient(amazonLambdaConfig); | |||
var request = new InvokeRequest | var request = new InvokeRequest | ||
{ | { | ||
FunctionName = | FunctionName = "MyFunction", | ||
Payload = JsonSerializer.Serialize(myObject, jsonSerializerOptions), | Payload = JsonSerializer.Serialize(myObject, jsonSerializerOptions), | ||
LogType = LogType.Tail | LogType = LogType.Tail, | ||
InvocationType = InvocationType.Event // async call (invoke with queue) | |||
InvocationType = InvocationType.RequestResponse // sync call (invoke directly and wait for the response) | |||
}; | }; | ||
var response = await | var response = await amazonLambdaClient.InvokeAsync(request); | ||
var payload = Encoding.ASCII.GetString(response.Payload.ToArray()); // to debug only | |||
if(response. | if (response.FunctionError == "Unhandled") | ||
{ | { | ||
var | var lambdaError = await JsonSerializer.DeserializeAsync<LambdaError>(response.Payload, jsonSerializerOptions); | ||
} | } | ||
else | |||
{ | |||
var result = JsonSerializer.Deserialize<MyClass>(response.Payload, jsonSerializerOptions); | |||
} | |||
record LambdaError(string ErrorType, string ErrorMessage); | |||
</kode> | </kode> | ||
= Templates = | |||
<kode lang='ps'> | |||
# list your installed Amazon Lambda Templates | |||
dotnet new list lambda | |||
# install Amazon Lambda Templates | |||
dotnet new install Amazon.Lambda.Templates | |||
</kode> | |||
{| class="wikitable wtp" | |||
! Template | |||
! Description | |||
|- | |||
| Lambda ASP.NET Core Web API || Web API | |||
|- | |||
| Lambda ASP.NET Core Web Application with Razor Pages || Razor MVC Web Application | |||
|- | |||
| Lambda Serverless with Powertools for AWS Lambda (.NET) || Powertools is a developer toolkit to implement Serverless best practices and increase developer velocity | |||
|} | |||
== Lambda ASP.NET Core Web API == | |||
Use a CloudFormation stack and an S3 to upload the application (zip) and the CloudFormation template file. | |||
* CloudFormation stack | |||
* IAM Role (Lambda Role) | |||
* IAM Role (Lambda Role) | |||
* Lambda Function (Web API) | |||
* VPC | |||
* API Gateway (REST) | |||
= Errors = | |||
== [https://stackoverflow.com/questions/74144256/c-sharp-aws-lambda-function-could-not-find-the-specified-handler-assembly-with Could not find the specified handler assembly with the file name LambdaTest] == | |||
The Lambda on AWS has wrongly set the handler to LambdaTest.<br> | |||
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 | |||
# Service = CloudWatch Logs | |||
# All CloudWatch Logs actions (logs:*) | |||
# All Resources |
Dernière version du 16 août 2024 à 09:54
Links
Description
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.
Custom runtime
Provide your custom runtime.
- unsupported programming language
Handler (entry point)
Method responsible for processing input events.
synchronous execution | result returned to the calling app |
asynchronous execution | result sent to the configured destination otherwise lost |
Configuration
Setting | Description |
---|---|
Memory | from 128 MB (default) to 10 GB |
vCPU | 1'769 MB = 1 vCPU, 10 GB = 6 vCPU, for single-threaded code allocated more than 1'769 MB is useless. Compare ARM and x86. |
timeout | from 1s to 15mn, default to 3s |
VPC |
|
Ephemeral storage | /tmp from 512 MB (default) to 10 GB |
Execution role | grants permissions to access AWS services |
Resource-based policy | define what others are allowed to do to the function |
Environment variables |
|
Logging | format (text or JSON), level, group |
Storage
Directories
Path | Description |
---|---|
/var/task | Lambda package (function files) |
/tmp | Temporary storage with write access |
/opt | Lambda layer (layer files) |
Elastic File System (EFS)
- Persistent
- Serverless, scalable
- Support concurrent access
- Require VPC access
S3
- Persistent
- No VPC required
- Accessed via API
IAM entities
Type | Description |
---|---|
User | individual with unique credentials |
Role | entity for temporary access (access to a db) |
Policy | permission for access to resources (apply to a user or a role to read from a specific db table) |
User group | logical collection of users for easier management |
Synchronous execution
Invocation result
Result content | Description |
---|---|
Status code | 200 success, 4xx or 5xx error |
Executed version | |
Log result | last 4 KB of the execution log in base64 encoding |
Function error | only if an error occured. Code error, timeout, invalid JSON output |
Response body | payload |
Asynchronous invocation
When a Lambda function is invoked asynchronously:
- an event is placed in its internal queue
- it returns a success response (202 Accepted) without additional information to the invoker
- a separate process reads events from the queue and sends them to the lambda function
- the result (Invocation Record) and the error are sent to the destinations
Destination
Allow to route asynchronous function results as an execution record to a destination resource.
Available destinations:
- Simple Notification Service (SNS) topic
- Simple Queue Service (SQS) queue
- Lambda function
- EventBridge event bus
Event Source (trigger)
- Simple Queue Service (SQS) queue
- DynamoDB stream
- DocumentDB stream
- Kinesis data stream
- Message Queue (MQ)
- Kafka
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:
- a dead-letter queue
- a failure destination
CLI
# async call (invoke via queue) aws lambda invoke \ --profile [Profile] \ --function-name [Function] \ --invocation-type Event \ --payload $([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes('{ "key": "value" }'))) \ response.json |
Optimization
Make the most of warm start
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
Concurrency
Concurrency type | Description |
---|---|
Account concurrency | max number of concurrent instances across all functions (AWS → Lambda → Dashboard) |
Reserved concurrency | upper limit of a function's concurrency |
Burst concurrency | increase quota, default to 1'000 instances every 10 s |
Provisioned concurrency | initialize execution env ahead of time. Application Auto Scaling can be used to automatically adjust provisioned concurrency |
Setting up your .NET development environment
# 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.
ex: MyProject::MyNamespace.MyClass::MyFunctionHandler
Function.cs |
// 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))] namespace MyProject; public class Function { public string FunctionHandler(string input, ILambdaContext context) { return input.ToUpper(); } } |
aws-lambda-tools-defaults.json |
{ "Information": [ ], "profile": "MyAwsProfile", "region": "eu-central-1", "configuration": "Release", "function-architecture": "x86_64", "function-runtime": "dotnet8", "function-memory-size": 512, "function-timeout": 30, "function-handler": "MyProject::MyProject.Function::FunctionHandler" } |
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" } } } |
C# executable assembly
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.
var handler = async (string argument1, ILambdaContext context) => { }; // bootstrap the Lambda runtime and pass it the handler method await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()).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" } } } } |
Context
Function.cs |
public string FunctionHandler(string input, ILambdaContext context) { Logger.LogInfo("Function Name: {0}:{1}", context.FunctionName, context.FunctionVersion); Logger.LogInfo("Log Group: {0}", context.LogGroupName); Logger.LogInfo("Stream Name: {0}", context.LogStreamName); Logger.LogInfo("Request Id: {0}", context.AwsRequestId); Logger.LogInfo("Memory Limit: {0} MB", context.MemoryLimitInMB); Logger.LogInfo("Remaining timeLimit: {0} ms", context.RemainingTime); var contextInfo = new Dictionary<string, object> { { "Context", context } }; Logger.AppendKeys(contextInfo); Logger.LogInfo("Context info"); Logger.RemoveKeys(contextInfo.Keys.ToArray()); |
LambdaJsonSerializer
- Handle Enums in the input arguments of .NET AWS Lambda Functions
- CamelCaseLambdaJsonSerializer already exists
Function.cs |
// create a custom serializer public class EnumCamelCaseLambdaJsonSerializer : DefaultLambdaJsonSerializer { public EnumCamelCaseLambdaJsonSerializer() : base(options => { options.Converters.Add(new JsonStringEnumConverter()); options.PropertyNamingPolicy = new AwsNamingPolicy(JsonNamingPolicy.CamelCase); }) { } } // then use it [assembly: LambdaSerializer(typeof(EnumCamelCaseLambdaJsonSerializer))] |
Stream as input
Function.cs |
// use a stream as input argument and deserialize it public string FunctionHandler(Stream input, ILambdaContext context) { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, Converters = { new JsonStringEnumConverter() } }; 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]
LambdaLogger.Log("Message"); |
Powertools
Allows Logging, Tracing, Metrics, Parameters.
Powertools Logging
Use structured logging.
// nuget package: AWS.Lambda.Powertools.Logging public class Function { public Function() { Logger.UseFormatter(new CustomLogFormatter()); // 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)] public string FunctionHandler(string input, ILambdaContext context) { Logger.LogInformation("Input: '{0}'", input); // add structured JSON object to all the next log entries var contextInfo = new Dictionary<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 entries return input; } } |
CustomLogFormatter.cs |
public class CustomLogFormatter : ILogFormatter { public object FormatLogEntry(LogEntry logEntry) { return new { 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 }, }; } } |
Define the desired log level of the lambda:
- Configuration tab → Monitoring and operation tools → Logging configuration → Edit button
- Log format = JSON
- Application log level =
- System log level =
Priority of log level settings in Powertools for AWS Lambda:
- AWS_LAMBDA_LOG_LEVEL environment variable
- Setting the log level in code using [Logging(LogLevel = )]
- POWERTOOLS_LOG_LEVEL environment variable
Parameters
Fetch values from Secrets Manager, Parameter Store, DynamoDB and App Config
public class Function { public string FunctionHandler(string input, ILambdaContext context) { // Get Secrets Provider instance (Secrets Manager) ISecretsProvider secretsProvider = ParametersManager.SecretsProvider; // Retrieve a single secret string? value = await secretsProvider.GetAsync("/my/secret"); } } |
Handle exceptions
Function.cs |
try { await Task.Delay(2000); throw new Exception("XXX", new Exception("YYY")); result = input.ToUpper(); } catch (Exception e) { // log the exception object var exceptionInfo = new Dictionary<string, object> { { e.GetType().Name, e } }; Logger.AppendKeys(exceptionInfo); Logger.LogCritical($"Exception: {e.Message}"); Logger.RemoveKeys(exceptionInfo.Keys.ToArray()); // log the exception and possibly the first inner exception Logger.LogCritical($"Exception: {e.Message}"); Logger.LogCritical($"Stack trace: {e.StackTrace}"); if (e.InnerException is not null) { Logger.LogCritical($"Exception: {e.InnerException.Message}"); Logger.LogCritical($"Stack trace: {e.InnerException.StackTrace}"); } } |
Timeout / ResponseSizeTooLarge
Function.cs |
var response = await amazonLambdaClient.InvokeAsync(request); var payload = Encoding.ASCII.GetString(response.Payload.ToArray()); // to debug only if (response.FunctionError == "Unhandled") { var jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var lambdaError = await JsonSerializer.DeserializeAsync<LambdaError>(response.Payload, jsonSerializerOptions); } record LambdaError(string ErrorType, string ErrorMessage); |
{ "errorType":"Sandbox.Timedout", "errorMessage":"RequestId: 19c4a134-16ff-47ae-aa4e-1d1f7fe31d51 Error: Task timed out after 15.00 seconds" } { "errorType": "Function.ResponseSizeTooLarge", "errorMessage": "Response payload size exceeded maximum allowed payload size (6291556 bytes)." } |
Deployment
dotnet lambda deploy-function [AssemblyName] --profile [Profile] |
From VS with AWS Toolkit
Right-click on the project → Publish to AWS Lambda
aws-lambda-tools-defaults.json |
{ "Information" : [ ], "profile" : "Profile-Name", "region" : "eu-central-1", "configuration" : "Release", "function-architecture" : "x86_64", "function-runtime" : "dotnet8", "function-memory-size" : 512, "function-timeout" : 30, "function-handler" : "ProjectName::NamespaceName.ClassName::FunctionHandlerName", "environment-variables" : "\"POWERTOOLS_SERVICE_NAME\"=\"PowertoolsFunction\";\"POWERTOOLS_LOG_LEVEL\"=\"Info\";\"POWERTOOLS_LOGGER_CASE\"=\"PascalCase\";\"POWERTOOLS_TRACER_CAPTURE_RESPONSE\"=\"true\";\"POWERTOOLS_TRACER_CAPTURE_ERROR\"=\"true\";\"POWERTOOLS_METRICS_NAMESPACE\"=\"powertools_function\"", "framework" : "net8.0", "function-name" : "ProjectName", "package-type" : "Zip", "function-role" : "arn:aws:iam::637423632242:role/Deploy_Lambda", "function-subnets" : "", "function-security-groups" : "", "tracing-mode" : "PassThrough", "image-tag" : "" } |
Permissions
WatchLogs
EventBridge
Call a lambda from code
// nuget: AWSSDK.Lambda, AWSSDK.SSO, AWSSDK.SSOOIDC var jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Converters = { new JsonStringEnumConverter() } }; var amazonLambdaConfig = new AmazonLambdaConfig { Profile = new Profile("MyProfile"), RegionEndpoint = RegionEndpoint.USEast1 }; var amazonLambdaClient = new AmazonLambdaClient(amazonLambdaConfig); var request = new InvokeRequest { FunctionName = "MyFunction", Payload = JsonSerializer.Serialize(myObject, jsonSerializerOptions), LogType = LogType.Tail, InvocationType = InvocationType.Event // async call (invoke with queue) InvocationType = InvocationType.RequestResponse // sync call (invoke directly and wait for the response) }; var response = await amazonLambdaClient.InvokeAsync(request); var payload = Encoding.ASCII.GetString(response.Payload.ToArray()); // to debug only if (response.FunctionError == "Unhandled") { var lambdaError = await JsonSerializer.DeserializeAsync<LambdaError>(response.Payload, jsonSerializerOptions); } else { var result = JsonSerializer.Deserialize<MyClass>(response.Payload, jsonSerializerOptions); } record LambdaError(string ErrorType, string ErrorMessage); |
Templates
# list your installed Amazon Lambda Templates dotnet new list lambda # install Amazon Lambda Templates dotnet new install Amazon.Lambda.Templates |
Template | Description |
---|---|
Lambda ASP.NET Core Web API | Web API |
Lambda ASP.NET Core Web Application with Razor Pages | Razor MVC Web Application |
Lambda Serverless with Powertools for AWS Lambda (.NET) | Powertools is a developer toolkit to implement Serverless best practices and increase developer velocity |
Lambda ASP.NET Core Web API
Use a CloudFormation stack and an S3 to upload the application (zip) and the CloudFormation template file.
- CloudFormation stack
- IAM Role (Lambda Role)
- IAM Role (Lambda Role)
- Lambda Function (Web API)
- VPC
- API Gateway (REST)
Errors
Could not find the specified handler assembly with the file name LambdaTest
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
- Service = CloudWatch Logs
- All CloudWatch Logs actions (logs:*)
- All Resources