Compilar o código .NET da função do Lambda em um formato de runtime nativo - AWS Lambda

Compilar o código .NET da função do Lambda em um formato de runtime nativo

O .NET 8 oferece suporte à compilação Ahead Of Time (AOT) nativa. Com a AOT nativa, é possível compilar o código da função do Lambda para um formato de runtime nativo, o que elimina a necessidade de compilar o código .NET em runtime. A compilação AOT nativa pode reduzir o tempo de inicialização a frio para funções do Lambda gravadas em .NET. Para obter mais informações, consulte Introdução ao runtime do .NET 8 para AWS Lambda no Blog AWS Compute.

Runtime do Lambda

Para implantar uma função do Lambda criada com compilação AOT nativa, use o runtime do Lambda do .NET 8 gerenciado. Esse runtime oferece suporte a arquiteturas x86_64 e arm64.

Quando uma função do Lambda .NET é implantada, sua aplicação é compilada em código de linguagem intermediária (IL). Em tempo de execução o compilador just-in-time (JIT) no runtime do Lambda assume o código IL e o compila em código de máquina conforme necessário. Com uma função do Lambda que é compilada antecipadamente com AOT nativa, você compila seu código em código de máquina ao implantar sua função. Assim, você se torna independente do runtime do .NET ou do SDK no runtime do Lambda para compilar seu código antes que ele seja executado.

Uma limitação da AOT é que o código da aplicação deve ser compilado em um ambiente com o mesmo sistema operacional Amazon Linux 2023 (AL2023) usado pelo runtime do .NET 8. A CLI do Lambda .NET fornece funcionalidade para compilar a aplicação em um contêiner do Docker usando uma imagem do AL2023.

Para evitar possíveis problemas de compatibilidade entre arquiteturas, é altamente recomendável compilar o código em um ambiente com a mesma arquitetura de processador configurada para sua função. Para saber mais sobre as limitações da compilação entre arquiteturas, consulte Compilação cruzada na documentação do Microsoft.NET.

Pré-requisitos

Docker

Para usar a AOT nativa, o código da função deve ser compilado em um ambiente com o mesmo sistema operacional AL2023 que o runtime do .NET 8. Os comandos da CLI do .NET nas seções a seguir usam o Docker para desenvolver e compilar funções do Lambda em um ambiente do AL2023.

SDK do .NET 8

A compilação AOT nativa é um recurso do .NET 8. É necessário instalar o SDK do .NET 8 no computador de compilação, e não somente o runtime.

Amazon.Lambda.Tools

Para criar as funções do Lambda, você usa a extensão .NET Global Tools do Amazon.Lambda.Tools. Para instalar Amazon.Lambda.Tools, execute o comando a seguir:

dotnet tool install -g Amazon.Lambda.Tools

Para obter mais informações sobre a extensão Amazon.Lambda.Tools da CLI do .NET, consulte o repositório AWS Extensions for .NET CLI no GitHub.

Amazon.Lambda.Templates

Para gerar o código da função do Lambda, use o Pacote do NuGet Amazon.Lambda.Templates. Para instalar esse pacote de modelo, execute o comando a seguir:

dotnet new install Amazon.Lambda.Templates

Conceitos básicos

Tanto a CLI Global do .NET quanto o AWS Serverless Application Model (AWS SAM) fornecem modelos de introdução para criar aplicações usando a AOT nativa. Para criar sua primeira função do Lambda da AOT nativa, execute as etapas nas seguintes instruções.

Para inicializar e implantar uma função do Lambda compilada na AOT nativa
  1. Inicialize um novo projeto usando o modelo da AOT nativa e, em seguida, navegue até o diretório que contém os arquivos .cs e .csproj criados. Neste exemplo, nossa função tem o nome NativeAotSample.

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

    O arquivo Function.cs criado pelo modelo de AOT nativa contém o seguinte código de função.

    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

    A AOT nativa compila a aplicaçãp em um único binário nativo. O ponto de entrada desse binário é o método static Main. No static Main, o runtime do Lambda é inicializado e o método FunctionHandler é configurado. Como parte da inicialização do runtime, um serializador gerado pela fonte é configurado usando new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>()

  2. Para implantar a aplicação no Lambda, certifique-se de que o Docker esteja sendo executado em seu ambiente local e execute o comando a seguir.

    dotnet lambda deploy-function

    Nos bastidores, a CLI global do .NET baixa uma imagem do Docker do AL2023 e compila o código da aplicação em um contêiner em execução. O binário compilado é enviado de volta para o sistema de arquivos local antes de ser implantado no Lambda.

  3. Teste a função executando o comando a seguir. Substitua <FUNCTION_NAME> pelo nome que você escolheu para a função no assistente de implantação.

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

    A resposta da CLI inclui detalhes de desempenho da inicialização a frio (duração da inicialização) e do tempo total de execução da invocação da função.

  4. Para excluir os recursos do AWS que você criou seguindo as etapas anteriores, execute o comando a seguir. Substitua <FUNCTION_NAME> pelo nome que você escolheu para a função no assistente de implantação. Excluindo os recursos da AWS que não está mais usando, você evita que encargos desnecessários sejam cobrados em sua Conta da AWS.

    dotnet lambda delete-function <FUNCTION_NAME>

Serialização

Para implantar funções no Lambda usando a AOT nativa, seu código de função deve usar serialização gerada pela fonte. Em vez de usar a reflexão de runtime para reunir os metadados necessários para acessar as propriedades do objeto para serialização, os geradores de fonte geram arquivos fonte em C# que são compilados quando você cria a aplicação. Para configurar corretamente o serializador gerado pela fonte, certifique-se de incluir todos os objetos de entrada e saída que a função usa, bem como todo os tipos personalizados. Por exemplo, uma função do Lambda que recebe eventos de uma API REST do API Gateway e retorna um tipo de Product personalizado incluiria um serializador definido da seguinte forma.

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

Remoção

A AOT nativa remove o código da aplicação como parte da compilação para garantir que o binário seja o menor possível. O.NET 8 para Lambda fornece suporte aprimorado à remoção em comparação com as versões anteriores do .NET. Suporte foi adicionado às bibliotecas de runtime do Lambda, ao AWS SDK para .NET, ao .NET Lambda Annotations e ao próprio .NET 8.

Essas melhorias oferecem o potencial de eliminar os avisos de remoção em tempo de compilação, mas nunca será totalmente seguro remover o .NET. Isso significa que partes das bibliotecas das quais a função depende podem ser removidas como parte da etapa de compilação. É possível gerenciar isso definindo TrimmerRootAssemblies como parte do seu arquivo .csproj, conforme mostrado no exemplo a seguir.

<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>

Observe que, quando você recebe um aviso de remoção, adicionar a classe que gera o aviso a TrimmerRootAssembly pode não resolver o problema. Um aviso de remoção indica que a classe está tentando acessar alguma outra classe que não pode ser determinada até o tempo de execução. Para evitar erros de tempo de execução, adicione essa segunda classe a TrimmerRootAssembly.

Para saber mais sobre como gerenciar avisos de remoção, consulte Introdução aos avisos de remoção na documentação do Microsoft .NET.

Solução de problemas

Erro: não há suporte para a compilação nativa entre sistemas operacionais.

Sua versão da ferramenta global do NET Core para Amazon.Lambda.Tools está desatualizada. Atualize para a versão mais recente e tente novamente.

Docker: o sistema operacional de imagem “linux” não pode ser usado nesta plataforma.

O Docker está configurado para usar contêineres do Windows em seu sistema. Alterne para contêineres do Linux para executar o ambiente de compilação AOT nativo.

Para obter mais informações sobre erros comuns, consulte o repositório AWS NativeAOT para .NET no GitHub.