将 .NET Lambda 函数代码编译为本地运行时格式 - AWS Lambda

将 .NET Lambda 函数代码编译为本地运行时格式

.NET 8 支持本机的提前(AOT)编译。通过本机 AOT,您可以将 Lambda 函数代码编译为本机运行时格式,从而无需在运行时编译 .NET 代码。本机 AOT 编译可以减少您在.NET 中编写的 Lambda 函数的冷启动时间。有关更多信息,请参阅 AWS 计算博客上的适用于 AWS Lambda 的 .NET 8 运行时系统简介

Lambda 运行时

要部署使用本机 AOT 编译构建的 Lambda 函数,请使用托管的 .NET 8 Lambda 运行时系统。该运行时系统支持使用 x86_64 和 arm64 结构。

当您在不使用 AOT 的情况下部署 .NET Lambda 函数时,您的应用程序首先将编译为中间语言(IL)代码。在运行时,Lambda 运行时系统中的即时(JIT)编译器获取 IL 代码并根据需要将其编译为机器代码。借助使用原生 AOT 提前编译的 Lambda 函数,您可以在部署函数时将代码编译成机器代码,这样您就不必依赖 .NET 运行时系统或 Lambda 运行时系统中的 SDK 来在代码运行之前对其进行编译。

AOT 的一个限制是,您的应用程序代码必须在 Amazon Linux 2023(AL2023)操作系统与 .NET 8 运行时系统所用操作系统相同的环境中编译。.NET Lambda CLI 提供了使用 AL2023 映像在 Docker 容器中编译应用程序的功能。

为避免跨架构兼容性方面的潜在问题,我们强烈建议您在与您为功能配置的处理器架构相同的环境中编译代码。要详细了解跨架构编译的局限性,请参阅 Microsoft .NET 文档中的交叉编译

先决条件

Docker

要使用本机 AOT,必须在与 .NET 8 运行时系统具有相同 AL2023 操作系统的环境中进行编译。以下各节中的 .NET CLI 命令使用 Docker 在 AL2023 环境中开发和构建 Lambda 函数。

.NET 8 SDK

本机 AOT 编译是 .NET 8 的一项功能。您必须在生成计算机上安装 .NET 8 SDK,而不仅仅是在运行时系统上。

Amazon.Lambda.Tools

要创建您的 Lambda 函数,请使用 Amazon.Lambda.Tools .NET 全球工具扩展。要安装 Amazon.Lambda.Tools,请运行以下命令:

dotnet tool install -g Amazon.Lambda.Tools

有关 Amazon.Lambda.Tools .NET CLI 扩展的更多信息,请参阅 GitHub 上的适用于 .NET CLI 的 AWS 扩展存储库。

Amazon.Lambda.Templates

要生成您的 Lambda 函数代码,请使用 Amazon.Lambda.Templates NuGet 包。要安装此模板包,请运行以下命令:

dotnet new install Amazon.Lambda.Templates

开始使用

.NET 全局 CLI 和 AWS Serverless Application Model(AWS SAM)都提供了使用本机 AOT 构建应用程序的入门模板。要构建您的第一个本机 AOT Lambda 函数,请执行以下说明中的步骤。

要初始化和部署本机 AOT 编译的 Lambda 函数
  1. 使用本机 AOT 模板初始化新项目,然后导航到包含已创建的 .cs.csproj 文件的目录。在此示例中,我们将函数命名为 NativeAotSample

    dotnet new lambda.NativeAOT -n NativeAotSample cd ./NativeAotSample/src/NativeAotSample

    由本机 AOT 模板创建的 Function.cs 文件包含以下函数代码。

    using Amazon.Lambda.Core; using Amazon.Lambda.RuntimeSupport; using Amazon.Lambda.Serialization.SystemTextJson; using System.Text.Json.Serialization; namespace NativeAotSample; public class Function { /// <summary> /// The main entry point for the Lambda function. The main function is called once during the Lambda init phase. It /// initializes the .NET Lambda runtime client passing in the function handler to invoke for each Lambda event and /// the JSON serializer to use for converting Lambda JSON format to the .NET types. /// </summary> private static async Task Main() { Func<string, ILambdaContext, string> handler = FunctionHandler; await LambdaBootstrapBuilder.Create(handler, new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>()) .Build() .RunAsync(); } /// <summary> /// A simple function that takes a string and does a ToUpper. /// /// To use this handler to respond to an AWS event, reference the appropriate package from /// https://github.com/aws/aws-lambda-dotnet#events /// and change the string input parameter to the desired event type. When the event type /// is changed, the handler type registered in the main method needs to be updated and the LambdaFunctionJsonSerializerContext /// defined below will need the JsonSerializable updated. If the return type and event type are different then the /// LambdaFunctionJsonSerializerContext must have two JsonSerializable attributes, one for each type. /// // When using Native AOT extra testing with the deployed Lambda functions is required to ensure // the libraries used in the Lambda function work correctly with Native AOT. If a runtime // error occurs about missing types or methods the most likely solution will be to remove references to trim-unsafe // code or configure trimming options. This sample defaults to partial TrimMode because currently the AWS // SDK for .NET does not support trimming. This will result in a larger executable size, and still does not // guarantee runtime trimming errors won't be hit. /// </summary> /// <param name="input"></param> /// <param name="context"></param> /// <returns></returns> public static string FunctionHandler(string input, ILambdaContext context) { return input.ToUpper(); } } /// <summary> /// This class is used to register the input event and return type for the FunctionHandler method with the System.Text.Json source generator. /// There must be a JsonSerializable attribute for each type used as the input and return type or a runtime error will occur /// from the JSON serializer unable to find the serialization information for unknown types. /// </summary> [JsonSerializable(typeof(string))] public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext { // By using this partial class derived from JsonSerializerContext, we can generate reflection free JSON Serializer code at compile time // which can deserialize our class and properties. However, we must attribute this class to tell it what types to generate serialization code for. // See https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-source-generation

    本机 AOT 将您的应用程序编译成单个本机二进制文件。该二进制文件的入口点是 static Main 方法。在 static Main 内,引导 Lambda 运行时系统并设置 FunctionHandler 方法。作为运行时系统引导程序的一部分,使用 new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>() 配置源代码生成的序列化器

  2. 要将您的应用程序部署到 Lambda,请确保 Docker 在您的本地环境中运行,并运行以下命令。

    dotnet lambda deploy-function

    在后台,.NET 全局 CLI 会下载 AL2023 Docker 映像,并在正在运行的容器中编译您的应用程序代码。在部署到 Lambda 之前,编译后的二进制文件会输出回您的本地文件系统。

  3. 通过运行以下命令来测试您的函数。将 <FUNCTION_NAME> 替换为您在部署向导中为函数选择的名称。

    dotnet lambda invoke-function <FUNCTION_NAME> --payload "hello world"

    来自 CLI 的响应包括冷启动的性能详细信息(初始化持续时间)以及函数调用的总运行时间。

  4. 要删除按照上述步骤创建的 AWS 资源,请运行以下命令。将 <FUNCTION_NAME> 替换为您在部署向导中为函数选择的名称。通过删除您不再使用的 AWS 资源,可防止您的 AWS 账户 产生不必要的费用。

    dotnet lambda delete-function <FUNCTION_NAME>

序列化

要使用本机 AOT 将函数部署到 Lambda,您的函数代码必须使用源代码生成的序列化。源代码生成器不是使用运行时系统反射来收集访问对象属性以进行序列化所需的元数据,而是生成在构建应用程序时编译的 C# 源文件。要正确配置源生成的序列化器,请确保包含了函数使用的任何输入和输出对象,以及任何自定义类型。例如,从 API Gateway REST API 接收事件并返回自定义 Product 类型的 Lambda 函数将包括一个定义如下的序列化器。

[JsonSerializable(typeof(APIGatewayProxyRequest))] [JsonSerializable(typeof(APIGatewayProxyResponse))] [JsonSerializable(typeof(Product))] public partial class CustomSerializer : JsonSerializerContext { }

修剪

本机 AOT 会在编译过程中对应用程序代码进行修剪,以确保二进制文件尽可能小。与以前的 .NET 版本相比,适用于 Lambda 的 .NET 8 提供了改进的修剪支持。已增加对 Lambda 运行时系统库AWS .NET SDK.NET Lambda 注释和 .NET 8 本身的支持。

这些改进有可能消除生成时修剪警告,但是 .NET 永远不会完全安全。这意味着在编译步骤中,您的函数所依赖的部分库可能会被剪掉。您可以通过将 TrimmerRootAssemblies 定义为 .csproj 文件的一部分来进行管理,如以下示例所示。

<ItemGroup> <TrimmerRootAssembly Include="AWSSDK.Core" /> <TrimmerRootAssembly Include="AWSXRayRecorder.Core" /> <TrimmerRootAssembly Include="AWSXRayRecorder.Handlers.AwsSdk" /> <TrimmerRootAssembly Include="Amazon.Lambda.APIGatewayEvents" /> <TrimmerRootAssembly Include="bootstrap" /> <TrimmerRootAssembly Include="Shared" /> </ItemGroup>

请注意,当您收到修剪警告时,添加生成警告的类到 TrimmerRootAssembly 中可能无法解决问题。修剪警告表示,该类正在尝试访问一些直到运行时才能确定的其他类。为避免运行时系统错误,请将第二个类添加到 TrimmerRootAssembly

要了解有关管理修剪警告的更多信息,请参阅 Microsoft .NET 文档中的修剪警告简介

故障排除

错误:不支持跨操作系统本机编译。

您的 Amazon.Lambda.Tools .NET Core Global Tool 版本已过期。更新到最新版本,并重新尝试。

Docker:映像操作系统“linux”不能在此平台上使用。

系统上的 Docker 配置为使用 Windows 容器。切换到 Linux 容器以运行本机 AOT 构建环境。

有关常见错误的更多信息,请参阅 GitHub 上的 AWS NativeAOT for .NET 存储库。