Módulo 3: .NET no AWS Lambda
MÓDULO DE APRENDIZAGEM
Observe que você pode seguir os exemplos apresentados aqui, mas isso não é necessário.
O AWS Lambda oferece suporte a várias versões do .NET, nas arquiteturas x86_64 e Arm64 (Graviton2). Escolha a arquitetura que você quiser. Seu código e processo de implantação não mudam.
Como o Lambda é um serviço sem servidor, você paga somente pelo que usar. Se a função do Lambda precisar ser executada poucas vezes por dia, você só pagará por isso. Mas como ela pode ser escalada de acordo com suas necessidades, também pode iniciar milhares de instâncias simultaneamente!
Tempo para a conclusão
60 minutos
Preços
Conforme mencionado acima, você paga apenas pelo que usa. O valor pago é calculado com base no período de execução da função do Lambda (arredondado para o milissegundo mais próximo) e na quantidade de memória que você alocou para a função.
Lembre-se de que é a quantidade de memória alocada, não a quantidade usada durante uma invocação, que é usada no cálculo do preço. Isso justifica testar as funções para avaliar a quantidade máxima de memória que elas usam durante uma invocação. Manter a memória alocada na menor quantidade necessária ajudará a reduzir o custo do uso das funções do Lambda. Consulte esta página de preços do AWS Lambda para saber mais.
Observe que, se a função do Lambda usar outros serviços, como S3 ou Kinesis, esses serviços poderão ser cobrados.
Versões do. NET com suporte
Há várias maneiras de executar binários.NET na plataforma Lambda. O mais comum, e o que deve ser considerado primeiro, é usar um runtime gerenciado fornecido pela AWS. Essa é a maneira mais simples de começar, a mais conveniente e oferece o melhor desempenho. Se você não quiser ou não puder usar o runtime gerenciado, você tem duas outras opções: um runtime personalizado ou uma imagem de contêiner. Ambas as opções têm suas próprias vantagens e desvantagens e serão discutidas com mais detalhes abaixo.
Runtimes gerenciados
O serviço AWS Lambda fornece uma variedade de runtimes muito usados, nos quais você pode executar o código. Em relação aos runtimes do .NET, a AWS os mantém atualizados e corrigidos conforme necessário, com as versões mais recentes disponíveis da Microsoft. Você, como desenvolvedor, não precisa fazer nada em relação ao gerenciamento do runtime que seu código usará, além de especificar a versão que deseja usar em seu conhecido arquivo.csproj.
No momento, a AWS oferece apenas runtimes gerenciados para versões de suporte de longo prazo (LTS) do runtime do .NET, no momento em que este artigo foi escrito, ou seja, .NET 3.1 e .NET 6. Os runtimes gerenciados do. NET estão disponíveis para as arquiteturas x86_64 e arm64 e são executados no Amazon Linux 2. Se você quiser usar uma versão do .NET diferente das oferecidas pela AWS, crie seu próprio runtime personalizado ou crie uma imagem de contêiner que atenda às suas necessidades.
Se você estiver interessado em sair do universo .NET, é bom saber que o serviço Lambda também oferece runtimes gerenciados para outras linguagens, como Node.js, Python, Ruby, Java e Go. Para obter detalhes completos sobre a lista de runtimes gerenciados e as versões das linguagens com suporte, consulte esta página sobre os runtimes disponíveis.
Runtimes personalizados
Os runtimes personalizados são os que você mesmo cria e agrupa. Há algumas razões pelas quais você faria isso. A mais comum é que você use uma versão .NET que não é oferecida como um runtime gerenciado pelo serviço Lambda. Outro motivo menos comum seria se você quisesse ter um controle preciso sobre as versões secundárias e de patch do runtime.
Criar o runtime personalizado exige muito pouco esforço da sua parte. Tudo o que você precisa fazer é passar:
--self-contained fiel ao comando build.
Isso pode ser feito diretamente com o dotnet build. Você também pode fazer isso por meio do arquivo aws-lambda-tools-defaults.json com o seguinte parâmetro:
"msbuild-parameters": "--self-contained true"
Isso é tudo, um simples sinalizador de compilador no empacotamento da função do Lambda no .NET. O pacote a ser implantado agora conterá seu código, além dos arquivos necessários do runtime do .NET que você escolheu.
Agora cabe a você corrigir e atualizar o runtime conforme achar melhor. A atualização do runtime exige que você reimplante a função, porque o código da função e o runtime são empacotados juntos.
O pacote implantado é significativamente maior em comparação com o runtime gerenciado porque contém todos os arquivos do runtime necessários. Isso afeta negativamente os horários de início a frio (falaremos mais sobre isso mais tarde). Para ajudar a reduzir esse tamanho, considere usar os atributos de compilação do .NET Trimming e ReadyToRun. Antes disso, leia a documentação sobre esses atributos.
Você pode criar um runtime personalizado com qualquer versão do .NET que seja executada no Linux. Um caso de uso comum é a implantação de funções com as versões “atuais” ou de pré-visualização do .NET.
Ao usar runtimes personalizados, você pode usar uma grande variedade de linguagens oferecidas pela comunidade. Ou até mesmo crie seu próprio runtime personalizado, como outros fizeram para executar linguagens como Erlang e COBOL.
Imagens de contêiner
Além do runtime gerenciado e do runtime personalizado, o serviço AWS Lambda também oferece a capacidade de empacotar seu código em uma imagem de contêiner e implantar essa imagem no serviço Lambda. A opção será adequada para as equipes que investiram tempo na criação e implantação de seu código em contêineres ou para aquelas que precisam de mais controle sobre o sistema operacional e o ambiente em que o código é executado. Suporte para imagens de até 10 GB de tamanho.
A AWS fornece uma variedade de imagens básicas para o .NET e .NET Core. https://gallery.ecr.aws/lambda/dotnet. Isso ajudará você a começar muito rapidamente.
Outra opção é criar uma imagem personalizada especificamente para sua função. Esse é um caso de uso mais avançado e exige que você edite o Dockerfile para atender às suas necessidades. Essa abordagem não está incluída neste curso, mas se você seguir esse caminho, dê uma olhada nos Dockerfiles neste repositório - https://github.com/aws/aws-lambda-dotnet/tree/master/LambdaRuntimeDockerfiles/Images.
Observe que a atualização da função do Lambda será mais lenta com contêineres, devido ao tamanho do upload. Os contêineres também têm as piores inicializações a frio das três opções. Falaremos mais sobre isso posteriormente no módulo.
Escolher o runtime para você
Se você deseja o melhor desempenho de inicialização, facilidade de implantação e para começar, e estiver satisfeito com as versões LTS do. NET, opte pelos runtimes gerenciados do .NET.
As imagens de contêiner são uma ótima opção que permite usar imagens que a AWS criou para uma variedade de versões do .NET. Você também pode escolher sua própria imagem de contêiner e ajustar o sistema operacional e o ambiente em que o código é executado. As imagens de contêiner também são adequadas para organizações que já usam contêineres extensivamente.
Se você tiver requisitos muito específicos em relação às versões do .NET e de suas bibliotecas de runtime e você mesmo quiser controlá-los, considere usar um runtime personalizado. No entanto, lembre-se de que cabe a você manter e corrigir o runtime. Se a Microsoft lançar uma atualização de segurança, você precisa estar ciente disso e atualizar seu runtime personalizado de forma adequada. Do ponto de vista do desempenho, o runtime personalizado é o mais lento dos três para iniciar.
Depois que a função do Lambda for iniciada, o desempenho do runtime gerenciado, da imagem do contêiner e do runtime personalizado será muito semelhante.
AWS SDK para .NET
Se você desenvolve aplicações .NET que usam serviços da AWS, provavelmente já usou o AWS SDK para .NET. O SDK facilita que um desenvolvedor.NET chame os serviços da AWS de forma consistente e familiar. O SDK é mantido atualizado à medida que os serviços são lançados ou atualizados. O SDK está disponível para download no NuGet.
Como acontece com muitas coisas relacionadas à AWS, o SDK é dividido em pacotes menores, cada um lidando com um único serviço.
Por exemplo, se você quiser acessar buckets do S3 na sua aplicação .NET, use o pacote AWSSDK.S3 NuGet. Ou, se você quiser acessar o DynamoDB na aplicação .NET, use o pacote AWSSDK.DynamoDBv2 NuGet.
Mas você só adiciona os pacotes NuGet de que precisa. Ao dividir o SDK em pacotes menores, você mantém seu próprio pacote de implantação menor.
Se o manipulador de funções do Lambda precisar receber eventos de outros serviços da AWS, procure pacotes NuGet relacionados a eventos específicos. Eles contêm os tipos relevantes para lidar com os eventos. Os pacotes seguem o padrão de nomenclatura de AWSSDK.Lambda.[SERVICE]Events.
Por exemplo, se sua função do Lambda for acionada por -
eventos do S3 recebidos, use o pacote AWSSDK.Lambda.S3Events
eventos do Kinesis recebidos, use o pacote AWSSDK.Lambda.KinesisEvents
notificações do SNS recebidas, use o pacote AWSSDK.Lambda.SNSEvents
mensagens do SQS recebidas, use o pacote AWSSDK.Lambda.SQSEvents
Usar o SDK para interagir com os serviços da AWS é muito fácil. Adicione uma referência ao pacote NuGet em seu projeto e chame o serviço como faria com qualquer outra biblioteca do .NET que você usar.
O fato de você usar o SDK em uma função do Lambda não afeta a forma como você o utiliza.
Lembre-se de que você não precisa necessariamente adicionar nenhum pacote NuGet do AWS SDK ao seu projeto. Por exemplo, se a função do Lambda chama um SQL Server do AWS RDS, você pode simplesmente usar o Entity Framework para acessar o banco de dados. Nenhuma biblioteca específica adicional da AWS é necessária. Porém, se você quiser recuperar um nome de usuário ou senha para o banco de dados do Secrets Manager, será necessário adicionar o pacote NuGet AWSSDK.SecretsManager.
Uma nota sobre permissões
Como regra geral, você deve usar o nível mais baixo de permissões necessário para realizar uma tarefa. Durante este curso, você será incentivado e mostraremos como fazer isso.
No entanto, para simplificar o ensino deste curso, sugerimos que você use um usuário da AWS com a política AdministratorAccess anexada. Essa política permite que você crie os perfis necessários quando implantar funções do Lambda. Quando não estiver trabalhando no curso, você deve remover essa política do seu usuário da AWS.
Uma função do Lambda em .NET do estilo Hello World
Como você viu em um módulo anterior, é muito fácil criar, implantar e invocar uma função do Lambda em .NET. Nesta seção, você fará o mesmo, porém mais lentamente, e eu explicarei o que está acontecendo em cada etapa. O código gerado e os arquivos de configuração serão discutidos.
Criar a função
Você precisará instalar as ferramentas necessárias para acompanhar o processo. Consulte o módulo 3 para obter mais detalhes sobre como fazer isso.
Se você não quiser fazer isso agora, aqui está um lembrete rápido.
Instale os modelos da função do Lambda em .NET:
dotnet new -i Amazon.Lambda.Templates
Instale as ferramentas .NET para implantar e gerenciar as funções do Lambda:
dotnet tool install -g Amazon.Lambda.Tools
Agora que você tem os modelos instalados, poderá criar uma nova função
Na linha de comando, execute:
dotnet new lambda.EmptyFunction -n HelloEmptyFunction
Isso cria um novo diretório chamado HelloEmptyFunction. Dentro dele estão mais dois diretórios, src e test. Como os nomes sugerem, o diretório src contém o código da função e o diretório de teste contém os testes unitários para a função. Ao navegar por esses diretórios, você descobrirá que cada um deles contém outro diretório. Dentro desses subdiretórios estão os arquivos de código da função e os arquivos de teste da unidade.
HelloEmptyFunction
├───src
│ └───HelloEmptyFunction
│ aws-lambda-tools-defaults.json // The default configuration file
│ Function.cs // The code for the function
│ HelloEmptyFunction.csproj // Standard C# project file
│ Readme.md // A readme file
│
└───test
└───HelloEmptyFunction.Tests
FunctionTest.cs // The unit tests for the function
HelloEmptyFunction.Tests.csproj // Standard C# project file
Vamos primeiro dar uma olhada no arquivo Function.cs.
using Amazon.Lambda.Core;
// 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 HelloEmptyFunction;
public class Function
{
/// <summary>
/// A simple function that takes a string and does a ToUpper
/// </summary>
/// <param name="input"></param>
/// <param name="context"></param>
/// <returns></returns>
public string FunctionHandler(string input, ILambdaContext context)
{
return input.ToUpper();
}
}
Algumas notas sobre as linhas que precisam de uma pequena explicação:
A linha 4 permite a conversão da entrada JSON em uma classe .NET.
Na linha 17, a entrada JSON para a função será convertida em uma string.
Na linha 17, um objeto ILambdaContext é passado como um parâmetro, que pode ser usado para registrar, determinar o nome da função, há quanto tempo a função está em execução e outras informações.
Como você pode ver, o código é muito simples e deve ser familiar para qualquer pessoa que tenha trabalhado com C#.
Embora o método FunctionHandler aqui seja síncrono, as funções do Lambda podem ser assíncronas, assim como qualquer outro método do .NET. Tudo o que você precisa fazer é alterar o FunctionHandler para
public async Task<string> FunctionHandler(..)
Vamos dar uma olhada no arquivo aws-lambda-tools-defaults.json:
{
"Information": [
"This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
"To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
"dotnet lambda help",
"All the command line options for the Lambda command can be specified in this file."
],
"profile": "",
"region": "",
"configuration": "Release",
"function-runtime": "dotnet6",
"function-memory-size": 256,
"function-timeout": 30,
"function-handler": "HelloEmptyFunction::HelloEmptyFunction.Function::FunctionHandler"
}
A linha 10 especifica que a função deve ser criada na configuração de lançamento.
A linha 11 especifica qual o runtime usar para o serviço Lambda.
A linha 12 especifica a quantidade de memória a ser alocada para a função, nesse caso 256 MB.
A linha 13 especifica o tempo limite da função, nesse caso 30 segundos. O tempo limite máximo permitido é de 15 minutos.
A linha 14 especifica o manipulador da função. Esse é o método que será invocado pelo serviço Lambda quando essa função for chamada.
O manipulador da função é composto por três partes:
"AssemblyName::Namespace.ClassName::MethodName"
Um pouco mais tarde, você criará e implantará essa função no serviço AWS Lambda usando a configuração neste arquivo.
Mas primeiro, vamos dar uma olhada no projeto de teste e em no arquivo HelloEmptyFunction.Tests.cs:
using Xunit;
using Amazon.Lambda.Core;
using Amazon.Lambda.TestUtilities;
namespace HelloEmptyFunction.Tests;
public class FunctionTest
{
[Fact]
public void TestToUpperFunction()
{
// Invoke the lambda function and confirm the string was upper cased.
var function = new Function();
var context = new TestLambdaContext();
var upperCase = function.FunctionHandler("hello world", context);
Assert.Equal("HELLO WORLD", upperCase);
}
}
O código aqui é relativamente simples e usa a estrutura de teste xUnit. Você pode testar as funções do Lambda da mesma forma que testaria qualquer outro método.
A linha 14 cria uma nova instância da classe Function.
A linha 15 cria uma nova instância da classe TestLambdaContext, que será passada para a função do Lambda na próxima linha.
A linha 16 invoca o método FunctionHandler na função, passando a string “hello world” e o contexto. Ela armazena a resposta na variável upperCase..
A linha 18 afirma que a variável upperCase é igual a “HELLO WORLD”.
Você pode executar esse teste na linha de comando ou dentro do seu IDE favorito. Existem outros tipos de testes que podem ser executados nas funções do Lambda. Se você estiver interessado em aprender mais sobre isso, consulte um módulo posterior, onde aprenderá sobre várias maneiras de testar e depurar as funções do Lambda.
Implantar a função
Agora que você tem uma função do Lambda e talvez tenha executado um teste de unidade, chegou a hora de implantar a função do Lambda no serviço AWS Lambda.
Na linha de comando, navegue até o diretório que contém o arquivo HelloEmptyFunction.csproj e execute o seguinte comando:
dotnet lambda deploy-function HelloEmptyFunction
Você verá uma saída que inclui o seguinte (reduzida para maior clareza) -
... dotnet publish --output "C:\dev\Lambda_Course_Samples\HelloEmptyFunction\src\HelloEmptyFunction\bin\Release\net6.0\publish" --configuration "Release" --framework "net6.0" /p:GenerateRuntimeConfigurationFiles=true --runtime linux-x64 --self-contained false
Zipping publish folder C:\dev\Lambda_Course_Samples\HelloEmptyFunction\src\HelloEmptyFunction\bin\Release\net6.0\publish to C:\dev\Lambda_Course_Samples\HelloEmptyFunction\src\HelloEmptyFunction\bin\Release\net6.0\HelloEmptyFunction.zip
... zipping: Amazon.Lambda.Core.dll
... zipping: Amazon.Lambda.Serialization.SystemTextJson.dll
... zipping: HelloEmptyFunction.deps.json
... zipping: HelloEmptyFunction.dll
... zipping: HelloEmptyFunction.pdb
... zipping: HelloEmptyFunction.runtimeconfig.json
Created publish archive (C:\dev\Lambda_Course_Samples\HelloEmptyFunction\src\HelloEmptyFunction\bin\Release\net6.0\HelloEmptyFunction.zip).
A linha 1 compila e publica o projeto. Observe que o runtime é linux-x64 e o sinalizador independente é falso (o que significa que a função usará o runtime gerenciado do .NET no serviço Lambda, em vez de um runtime personalizado).
A linha 2, compacta o projeto publicado em um arquivo zip.
As linhas de 3 a 8 mostram os arquivos que estão sendo compactados.
A linha 9 confirma que o arquivo zip foi criado.
Agora será necessário “Selecionar o perfil do IAM para fornecer credenciais da AWS ao seu código”. Será apresentada uma lista de perfis criados anteriormente, mas na parte inferior da lista estará a opção “*** Criar novo perfil do IAM ***”. Digite o número ao lado dessa opção.
Será necessário “Inserir o nome do novo perfil do IAM:”. Digite “HelloEmptyFunctionRole”.
Em seguida, você deverá “Selecionar a política do IAM para anexar ao novo perfil e conceder permissões” e uma lista de políticas será apresentada. O resultado será semelhante ao mostrado abaixo, mas o seu poderá ser mais longo -
1) AWSLambdaReplicator (Grants Lambda Replicator necessary permissions to replicate functions ...)
2) AWSLambdaDynamoDBExecutionRole (Provides list and read access to DynamoDB streams and writ ...)
3) AWSLambdaExecute (Provides Put, Get access to S3 and full access to CloudWatch Logs.)
4) AWSLambdaSQSQueueExecutionRole (Provides receive message, delete message, and read attribu ...)
5) AWSLambdaKinesisExecutionRole (Provides list and read access to Kinesis streams and write ...)
6) AWSLambdaBasicExecutionRole (Provides write permissions to CloudWatch Logs.)
7) AWSLambdaInvocation-DynamoDB (Provides read access to DynamoDB Streams.)
8) AWSLambdaVPCAccessExecutionRole (Provides minimum permissions for a Lambda function to exe ...)
9) AWSLambdaRole (Default policy for AWS Lambda service role.)
10) AWSLambdaENIManagementAccess (Provides minimum permissions for a Lambda function to manage ...)
11) AWSLambdaMSKExecutionRole (Provides permissions required to access MSK Cluster within a VP ...)
12) AWSLambda_ReadOnlyAccess (Grants read-only access to AWS Lambda service, AWS Lambda consol ...)
13) AWSLambda_FullAccess (Grants full access to AWS Lambda service, AWS Lambda console feature ...)
Selecione “AWSLambdaBasicExecutionRole”. Esse é o número 6 na minha lista.
Depois de um momento, você verá -
Waiting for new IAM Role to propagate to AWS regions
............... Done
New Lambda function created
Agora você pode invocar a função.
Invocar a função
Linha de comando
Você pode usar a ferramenta dotnet lambda para invocar a função no shell de sua preferência:
dotnet lambda invoke-function HelloEmptyFunction --payload "Invoking a Lambda function"
Para funções do Lambda simples, como as descritas acima, não há nenhuma opção de JSON, mas quando você quiser passar um JSON que precisa ser desserializado, o escape da carga variará de acordo com o shell que você estiver usando.
Você verá um resultado semelhante ao seguinte:
Amazon Lambda Tools for .NET Core applications (5.4.1)
Project Home: https://github.com/aws/aws-extensions-for-dotnet-cli, https://github.com/aws/aws-lambda-dotnet
Payload:
"INVOKING A LAMBDA FUNCTION"
Log Tail:
START RequestId: 3d43c8be-8eca-48a1-9e51-96d9c84947b2 Version: $LATEST
END RequestId: 3d43c8be-8eca-48a1-9e51-96d9c84947b2
REPORT RequestId: 3d43c8be-8eca-48a1-9e51-96d9c84947b2 Duration: 244.83 ms Billed Duration: 245 ms
Memory Size: 256 MB Max Memory Used: 68 MB Init Duration: 314.32 ms
O resultado “Payload:” é a resposta da função do Lambda.
Observe como o final do log contém informações úteis sobre a invocação da função do Lambda, como por quanto tempo ela foi executada e o volume de memória utilizado. Para uma função simples como essa, 244,83 ms pode parecer muito, mas essa foi a primeira vez que a função foi invocada, o que significa que mais trabalho precisava ser executado; as invocações subsequentes teriam sido mais rápidas. Consulte a seção sobre inicializações a frio para obter mais informações.
Vamos fazer uma pequena alteração no código para adicionar algumas de nossas próprias declarações de log.
Adicione o seguinte acima da declaração de returno no método FunctionHandler:
context.Logger.LogInformation("Input: " + input);
Implante novamente usando:
dotnet lambda deploy-function HelloEmptyFunction
Desta vez, não haverá perguntas sobre o perfil ou as permissões.
Depois que a função for implantada, você poderá invocá-la novamente.
dotnet lambda invoke-function HelloEmptyFunction --payload "Invoking a Lambda function"
Desta vez, o resultado conterá uma declaração de log extra.
Payload:
"INVOKING A LAMBDA FUNCTION"
Log Tail:
START RequestId: 7f77a371-c183-494f-bb44-883fe0c57471 Version: $LATEST
2022-06-03T15:36:20.238Z 7f77a371-c183-494f-bb44-883fe0c57471 info Input: Invoking a Lambda function
END RequestId: 7f77a371-c183-494f-bb44-883fe0c57471
REPORT RequestId: 7f77a371-c183-494f-bb44-883fe0c57471 Duration: 457.22 ms Billed Duration: 458 ms
Memory Size: 256 MB Max Memory Used: 62 MB Init Duration: 262.12 ms
Lá, na linha 6, estará a declaração de log. Os logs de funções do Lambda também são gravados nos logs do CloudWatch (desde que você tenha dado permissões à função do Lambda para fazer isso).
Console da AWS
Outra forma de invocar a função é no Console da AWS.
Faça login no Console da AWS e selecione a função do Lambda que você quiser invocar.
Clique na guia Teste.
Desça até a seção Event JSON e insira “Invocar uma função do Lambda”, incluindo as aspas.
Em seguida, clique no botão Testar.
Você verá uma saída semelhante ao seguinte:
Observe que o resultado do log também está visível.
Uma função do Lambda no .NET que usa uma carga JSON
O exemplo anterior era uma função simples que pegava uma string e retornava uma string. É um bom exemplo para começar.
Mas você provavelmente desejará enviar cargas JSON para funções do Lambda. Na verdade, se outro serviço da AWS invocar a função do Lambda, ele enviará uma carga JSON. Essas cargas JSON geralmente são bastante complexas, mas um modelo para a carga estará disponível no NuGet. Por exemplo, se você estiver manipulando eventos do Kinesis com a função do Lambda, o pacote Amazon.Lambda.KinesisEvents tem um modelo KinesisEvent. O mesmo vale para eventos do S3, eventos do SQS, etc.
Em vez de usar um desses modelos agora, você invocará uma nova função do Lambda com uma carga que representa uma pessoa.
{
"FirstName": "Alan",
"LastName": "Adams"
}
A classe C# apropriada para desserializar a carga JSON é:
public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
Crie a função
Como antes, crie uma nova função usando o seguinte comando:
dotnet new lambda.EmptyFunction -n HelloPersonFunction
Altere a função
Altere o código do método FunctionHandler para que fique assim:
public string FunctionHandler(Person input, ILambdaContext context)
{
return $"Hello, {input.FirstName} {input.LastName}";
}
public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
Esse é o mesmo comando que você usou há alguns minutos:
dotnet lambda deploy-function HelloPersonFunction
Invoque a função
Agora, a função do Lambda pode receber uma carga JSON, mas a forma como você a invoca depende do shell que você estiver usando, devido à forma como o JSON é evadido em cada shell.
Se você estiver usando PowerShell ou bash, use:
dotnet lambda invoke-function HelloPersonFunction --payload '{ \"FirstName\": \"Alan\", \"LastName\": \"Adams\" }'
dotnet lambda invoke-function HelloPersonFunction --payload "{ \"FirstName\": \"Alan\", \"LastName\": \"Adams\" }"
Faça login no Console da AWS e selecione a função do Lambda que você quiser invocar.
{
"FirstName": "Alan",
"LastName": "Adams"
}
Você verá uma saída semelhante ao seguinte:
Na próxima seção, você verá como implantar uma função do Lambda que responde às solicitações HTTP.
Criação e execução de uma aplicação de API da Web como uma função do Lambda
Mas você também pode invocar a função do Lambda por meio de uma solicitação HTTP, e esse é um caso de uso muito comum.
O conjunto de ferramentas da AWS para .NET oferece alguns modelos que você pode usar para criar uma função do Lambda simples para hospedar uma aplicação de API da Web.
O mais familiar provavelmente será o modelo Sserverless.AspNetCoreWebAPI, que cria uma aplicação de API da Web simples que pode ser chamada por meio de uma solicitação HTTP. O modelo do projeto inclui um modelo de configuração do CloudFormation que cria um API Gateway que encaminha solicitações HTTP para a função do Lambda.
Quando implantado no AWS Lambda, o API Gateway traduz a solicitação HTTP em um evento do API Gateway e envia esse JSON para a função do Lambda. Nenhum servidor Kestrel está sendo executado na função do Lambda quando ela é implantada no serviço Lambda.
Mas quando você o executa localmente, um servidor Web Kestrel é iniciado, o que torna muito fácil escrever o código e testá-lo da maneira familiar, o que você faria com qualquer aplicação de API da Web. Você pode até mesmo fazer a depuração normal linha por linha. O melhor dos dois universos!
Crie a função
dotnet new serverless.AspNetCoreWebAPI -n HelloAspNetCoreWebAPI
├───src
│ └───AspNetCoreWebAPI
│ │ appsettings.Development.json
│ │ appsettings.json
│ │ AspNetCoreWebAPI.csproj
│ │ aws-lambda-tools-defaults.json // basic Lambda function config, and points to serverless.template file for deployment
│ │ LambdaEntryPoint.cs // Contains the function handler method, this handles the incoming JSON payload
│ │ LocalEntryPoint.cs // Equivalent to Program.cs when running locally, starts Kestrel (only locally)
│ │ Readme.md
│ │ serverless.template // CloudFormation template for deployment
│ │ Startup.cs // Familiar Startup.cs, can use dependency injection, read config, etc.
│ │
│ └───Controllers
│ ValuesController.cs // Familiar API controller
│
└───test
└───AspNetCoreWebAPI.Tests
│ appsettings.json
│ AspNetCoreWebAPI.Tests.csproj
│ ValuesControllerTests.cs // Unit test for ValuesController
│
└───SampleRequests
ValuesController-Get.json // JSON representing an APIGatewayProxyRequest, used by the unit test
Implante a função
Antes de tentar implantar a função sem servidor, você precisa de um bucket do S3. Isso será usado pelas ferramentas de implantação para armazenar uma pilha do CloudFormation.
Você pode usar um bucket do S3 existente ou, se não tiver um, use as instruções abaixo.
aws s3api create-bucket --bucket your-unique-bucket-name1234
aws s3api create-bucket --bucket your-unique-bucket-name1234 --create-bucket-configuration LocationConstraint=REGION
aws s3api create-bucket --bucket lambda-course-2022
dotnet lambda deploy-serverless
Enter CloudFormation Stack Name: (CloudFormation stack name for an AWS Serverless application)
Em seguida, o nome do bucket do S3 será solicitado. Use o nome do bucket que você criou anteriormente ou de um bucket existente que você quiser usar para essa finalidade.
Depois de inserir isso, o processo de criação e implantação começará.
Isso levará mais tempo do que os exemplos que usam modelos de projeto lambda.* porque há mais infraestrutura para criar e conectar.
O resultado será dividido em duas seções distintas.
A seção superior será semelhante à que você viu na implantação de funções anteriores, uma publicação e um zip do projeto, mas desta vez o artefato será carregado no S3.
..snip
... zipping: AspNetCoreWebAPI.runtimeconfig.json
... zipping: aws-lambda-tools-defaults.json
Created publish archive (C:\Users\someuser\AppData\Local\Temp\AspNetCoreFunction-CodeUri-Or-ImageUri-637907144179228995.zip).
Lambda project successfully packaged: C:\Users\ someuser\AppData\Local\Temp\AspNetCoreFunction-CodeUri-Or-ImageUri-637907144179228995.zip
Uploading to S3. (Bucket: lambda-course-2022 Key: AspNetCoreWebAPI/AspNetCoreFunction-CodeUri-Or-ImageUri-637907144179228995-637907144208759417.zip)
... Progress: 100%
Uploading to S3. (Bucket: lambda-course-2022 Key: AspNetCoreWebAPI/AspNetCoreWebAPI-serverless-637907144211067892.template)
... Progress: 100%
Found existing stack: False
CloudFormation change set created
... Waiting for change set to be reviewed
Created CloudFormation stack AspNetCoreWebAPI
Timestamp Logical Resource Id Status
-------------------- ---------------------------------------- ----------------------------------------
6/10/2022 09:53 AM AspNetCoreWebAPI CREATE_IN_PROGRESS
6/10/2022 09:53 AM AspNetCoreFunctionRole CREATE_IN_PROGRESS
6/10/2022 09:53 AM AspNetCoreFunctionRole CREATE_IN_PROGRESS
6/10/2022 09:54 AM AspNetCoreFunctionRole CREATE_COMPLETE
6/10/2022 09:54 AM AspNetCoreFunction CREATE_IN_PROGRESS
6/10/2022 09:54 AM AspNetCoreFunction CREATE_IN_PROGRESS
6/10/2022 09:54 AM AspNetCoreFunction CREATE_COMPLETE
6/10/2022 09:54 AM ServerlessRestApi CREATE_IN_PROGRESS
6/10/2022 09:54 AM ServerlessRestApi CREATE_IN_PROGRESS
6/10/2022 09:54 AM ServerlessRestApi CREATE_COMPLETE
6/10/2022 09:54 AM ServerlessRestApiDeploymentcfb7a37fc3 CREATE_IN_PROGRESS
6/10/2022 09:54 AM AspNetCoreFunctionProxyResourcePermissionProd CREATE_IN_PROGRESS
6/10/2022 09:54 AM AspNetCoreFunctionRootResourcePermissionProd CREATE_IN_PROGRESS
6/10/2022 09:54 AM AspNetCoreFunctionProxyResourcePermissionProd CREATE_IN_PROGRESS
6/10/2022 09:54 AM AspNetCoreFunctionRootResourcePermissionProd CREATE_IN_PROGRESS
6/10/2022 09:54 AM ServerlessRestApiDeploymentcfb7a37fc3 CREATE_IN_PROGRESS
6/10/2022 09:54 AM ServerlessRestApiDeploymentcfb7a37fc3 CREATE_COMPLETE
6/10/2022 09:54 AM ServerlessRestApiProdStage CREATE_IN_PROGRESS
6/10/2022 09:54 AM ServerlessRestApiProdStage CREATE_IN_PROGRESS
6/10/2022 09:54 AM ServerlessRestApiProdStage CREATE_COMPLETE
6/10/2022 09:54 AM AspNetCoreFunctionProxyResourcePermissionProd CREATE_COMPLETE
6/10/2022 09:54 AM AspNetCoreFunctionRootResourcePermissionProd CREATE_COMPLETE
6/10/2022 09:54 AM AspNetCoreWebAPI CREATE_COMPLETE
Stack finished updating with status: CREATE_COMPLETE
Output Name Value
------------------------------ --------------------------------------------------
ApiURL https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/Prod/
Logo na parte inferior está o URL público que você pode usar para invocar a API.
Invocar a função
Em seguida, tente abrir https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/Prod/api/values. Você invocará o método GET do controlador de valores, da mesma forma que faria em uma aplicação normal de API da Web.
Observe que, ao usar um API Gateway, o gateway impõe seu próprio tempo limite de 29 segundos. Se a função do Lambda for executada por mais tempo do que isso, você não receberá uma resposta.
Se você estiver interessado, há algumas maneiras de revisar os recursos que foram criados.
Para analisar os recursos da AWS criados, você pode usar:
aws cloudformation describe-stack-resources --stack-name AspNetCoreWebAPI
Se você quiser um resultado mais sucinto, use:
aws cloudformation describe-stack-resources --stack-name AspNetCoreWebAPI --query 'StackResources[].[{ResourceType:ResourceType, LogicalResourceId:LogicalResourceId, PhysicalResourceId:PhysicalResourceId}]'
Com esses exemplos, você pode criar e implantar suas próprias funções do Lambda. E você pode ter aprendido um pouco sobre como as funções do Lambda do .NET são invocadas. Esse é o assunto da próxima seção.
URLs da função: uma alternativa aos API Gateways
Se tudo o que você precisa é de uma função do Lambda que responda a uma solicitação HTTP simples, considere usar URLs da função do Lambda.
Elas permitem que você atribua um endpoint HTTPS a uma função do Lambda. Em seguida, você invoca a função do Lambda fazendo uma solicitação ao endpoint HTTPS. Para obter mais informações, leia esta publicação do blog e estes documentos.
Limpar os recursos que você criou
dotnet lambda delete-function HelloEmptyFunction
dotnet lambda delete-function HelloPersonFunction
Observe que os comandos acima não excluem o perfil que você criou.
Para excluir a função do Lambda que hospedou a aplicação de API da Web e todos os recursos associados, execute:
dotnet lambda delete-serverless AspNetCoreWebAPI
Como uma função do Lambda no .NET é invocada
Como você pode ver nos exemplos acima, você pode invocar uma função do Lambda no .NET com uma string simples, um objeto JSON e uma solicitação HTTP. As funções do Lambda também podem ser invocadas por outros serviços, como S3 (quando ocorrer uma alteração no arquivo), Kinesis (quando um evento chegar), DynamoDB (quando ocorrer uma alteração em uma tabela), SMS (quando chegar uma mensagem), Step Functions etc.
Como uma função do Lambda lida com todas essas formas diferentes de invocar?
Nos bastidores, essas funções do Lambda são invocadas quando o serviço Lambda executa o manipulador de funções e passa a entrada JSON. Se você examinou o aws-lambda-tools-defaults.json, poderá ver o “manipulador de funções”: especificado. Para funções do Lambda no .NET, o manipulador inclui o "AssemblyName::Namespace.ClassName::MethodName".
As funções do Lambda também podem ser invocadas passando um fluxo para elas, mas esse é um cenário menos comum. Consulte a página sobre como lidar com fluxos para obter mais informações.
Cada função do Lambda tem um único manipulador de funções.
Junto com a entrada JSON, o manipulador de funções do Lambda também pode usar um objeto ILambdaContext opcional. Isso dá acesso às informações sobre a invocação atual, como o tempo restante para concluir, o nome e a versão da função. Você também pode gravar mensagens de log por meio do objeto ILambdaContext.
Todos os eventos são JSON
O que torna muito fácil para um serviço da AWS invocar uma função do Lambda no .NET é que esses serviços emitem JSON e, conforme discutido acima, a função do Lambda no .NET aceita a entrada JSON. Todos os eventos gerados por diferentes serviços produzem JSON com formatos diferentes, mas os pacotes NuGet de eventos do AWS Lambda incluem todos os tipos de objetos relevantes necessários para serializar o JSON de volta em um objeto com o qual você possa trabalhar.
Consulte https://www.nuget.org/packages?packagetype=&sortby=relevance&q=Amazon.Lambda&prerel=True para obter uma lista dos pacotes Lambda disponíveis. Você precisará pesquisar nesses resultados o tipo de evento no qual estiver interessado.
Por exemplo, se você quiser acionar uma função do Lambda em resposta a uma alteração de arquivo em um bucket do S3, você precisará criar uma função do Lambda que aceite um objeto do tipo S3Event. Em seguida, adicione o pacote Amazon.Lambda.S3Events ao seu projeto. Altere então o método do manipulador de funções para:
public async string FunctionHandler(S3Event s3Event, ILambdaContext context)
{
...
}
Isso é tudo o que você precisa para lidar com o evento do S3. Você pode examinar programaticamente o evento, ver qual ação foi executada no arquivo, em qual bucket ele estava, etc. O Amazon.Lambda.S3Events permite que você trabalhe com o evento, não com o próprio S3. Se você quiser interagir com o serviço do S3, também precisará adicionar o pacote AWSSDK.S3 NuGet ao seu projeto. Um módulo posterior abordará o tópico dos serviços da AWS que invocam as funções do Lambda.
O mesmo padrão segue para outros tipos de eventos. Adicione o pacote NuGet, altere o parâmetro para o manipulador de funções e você então poderá trabalhar com o objeto do evento.
Aqui estão alguns dos pacotes comuns que você pode usar para lidar com eventos de outros serviços -
https://www.nuget.org/packages/Amazon.Lambda.SNSEvents
https://www.nuget.org/packages/Amazon.Lambda.DynamoDBEvents
https://www.nuget.org/packages/Amazon.Lambda.CloudWatchEvents
https://www.nuget.org/packages/Amazon.Lambda.KinesisEvents
https://www.nuget.org/packages/Amazon.Lambda.APIGatewayEvents
Você não está limitado a usar os tipos de eventos definidos pela AWS quando invocar uma função do Lambda. Você mesmo pode criar qualquer tipo de evento. Lembre-se de que a função do Lambda poderá receber qualquer JSON que você enviar
Como a serialização acontece
No caso dos modelos “lambda.”, há um atributo de montagem próximo à parte superior do arquivo Function.cs, que cuida da desserialização do evento de entrada no tipo .NET em seu manipulador de funções. No arquivo.csproj, há uma referência ao pacote Amazon.Lambda.Serialization.SystemTextJson.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
Os modelos “sem servidor.”, funcionam de forma um pouco diferente.
O manipulador de funções é especificado no arquivo serverless.template. Se você estiver implantando uma aplicação serverless.AspNetCoreWebAPI, procure o valor em Resources.AspNetCoreFunction.Properties.Handler. O manipulador para esse tipo de projeto terminará no formato - Assembly::Namespace.LambdaEntryPoint::FunctionHandlerAsync.
A classe LambdaEntryPoint estará em seu projeto e herdará de uma classe que tem um método FunctionHandlerAsync.
O manipulador de funções pode ser configurado para lidar com quatro tipos de eventos diferentes: uma API REST do API Gateway, uma carga da API Gateway HTTP versão 1.0, uma carga da API Gateway HTTP versão 2.0 e um Application Load Balancer.
Ao alterar a classe da qual o LambdaEntryPoint herda, você pode alterar o tipo de evento JSON que o manipulador de funções manipula.
Embora pareça que a função do Lambda esteja respondendo a uma solicitação HTTP enviada por você, esse não é o caso com o JSON que você definiu. Na verdade, a solicitação HTTP é tratada por um gateway ou balanceador de carga, que cria um evento JSON que é então enviado ao manipulador de funções. Esse evento JSON terá os dados originalmente incluídos na solicitação HTTP, tudo desde o endereço IP de origem até os cabeçalhos da solicitação.
Simultaneidade
Há dois tipos de simultaneidade a serem considerados ao trabalhar com funções do Lambda: simultaneidade reservada e simultaneidade provisionada.
Uma conta da AWS tem um limite máximo padrão para o número de execuções simultâneas do Lambda. No momento em que este artigo foi escrito, esse limite era 1.000.
Ao especificar a simultaneidade reservada para uma função, você garante que a função poderá atingir o número especificado de execuções simultâneas. Por exemplo, se sua função tiver uma simultaneidade reservada de 200, você está garantindo que a função poderá atingir 200 execuções simultâneas. Observe que isso deixa 800 execuções simultâneas para outras funções (1000-200=800).
Ao especificar a simultaneidade provisionada, você está inicializando um número específico de ambientes de execução do Lambda. Quando elas forem inicializadas, a função do Lambda poderá responder às solicitações imediatamente, evitando o problema de “inicializações a frio”. Porém, há uma taxa associada ao uso da simultaneidade provisionada.
Para obter mais informações, consulte Gerenciar a simultaneidade reservada do Lambda e Gerenciar a simultaneidade provisionada pelo Lambda.
Inicializações a frio e a quente
Antes que a função do Lambda possa ser invocada, um ambiente de execução deve ser inicializado. Isso é feito em seu nome pelo serviço Lambda. Seu código-fonte é baixado de um bucket do S3 gerenciado pela AWS (para funções que usam runtimes gerenciados e personalizados) ou de um Elastic Container Registry (para funções que usam imagens de contêiner).
A primeira vez em que a função for executada, seu código precisará ser JITed e o código de inicialização será executado (por exemplo, o estruturador). Isso aumenta o tempo de inicialização a frio.
Se a função estiver sendo invocada regularmente, ela permanecerá “aquecida”, ou seja, o ambiente de execução será mantido. As invocações subsequentes da função não sofrerão com o horário de início a frio. As “inicializações a quente” são significativamente mais rápidas do que as “inicializações a frio”.
Se a função não for invocada por um período (a hora exata não foi especificada pelo serviço Lambda), o ambiente de execução será removido. A próxima invocação da função resultará novamente em uma inicialização a frio.
Se você fizer o upload de uma nova versão do código da função, a próxima invocação da função resultará em uma inicialização a frio.
Cada uma das três opções para executar o .NET no Lambda, runtime gerenciado, runtime personalizado e contêiner hospedado tem perfis diferentes de inicialização a frio. O mais lento é o contêiner, o segundo mais lento é o runtime personalizado e o mais rápido é o runtime gerenciado. Se possível, você deve sempre optar pelo runtime gerenciado ao executar as funções do Lambda em .NET.
As inicializações a frio ocorrem com mais frequência em ambientes de teste ou desenvolvimento do que em ambientes de produção. Em uma análise da AWS, as inicializações a frio ocorrem em menos de 1% das invocações.
Se você tiver uma função do Lambda em produção que é usada com pouca frequência, mas precisa responder rapidamente a uma solicitação e deseja evitar inicializações a frio, você poderá usar a simultaneidade provisionada ou um mecanismo para dar um “ping” na função com frequência, a fim de mantê-la aquecida.
Se você estiver interessado em obter mais informações sobre como otimizar a função do Lambda, leia sobre inicializações a frio, inicializações a quente e simultaneidade provisionada no Guia do desenvolvedor do AWS Lambda ou confira a série de blogs Operating Lambda: otimização de desempenho, de James Beswick, parte 1, parte 2 e parte 3.
Trimming e Ready to Run em versões do .NET anteriores ao .NET 7
Se você optou por usar os runtimes personalizados do Lambda para uma versão do .NET anterior à .NET 7, há alguns atributos do .NET que você pode usar para reduzir os tempos de inicialização a frio.
O PublishTrimmed reduzirá o tamanho geral do pacote que você implantar, eliminando as bibliotecas desnecessárias do pacote.
PublishReadyToRun executará uma compilação antecipada do código, reduzindo assim o volume de compilação just-in-time necessário. Porém, isso aumenta o tamanho do pacote que você implanta.
Para um desempenho ideal, você precisará testar a função ao usar essas opções.
PublishTrimmed e PublishReadyToRun podem ser ativados no arquivo.csproj.
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>true</PublishReadyToRun>
Conclusão
Compilação antecipada nativa para .NET 7
dotnet new -i "Amazon.Lambda.Templates::*"dotnet tool update -g Amazon.Lambda.Tools
Verificação de conhecimento
Agora você concluiu o Módulo 2, Ferramentas para o desenvolvimento em .NET com o AWS Lambda. O teste a seguir verificará o que você aprendeu até agora.
1. Quais versões runtimes gerenciados do .NET o serviço Lambda oferece? (selecione dois)
b. NET 6
c. NET 7
d. .NET Core 3.1
e. .NET Framework 4.8
2. A que se refere a inicialização a frio? (selecione uma opção)
b. Uma função do Lambda que usa o armazenamento do AWS S3 Glacier.
c. O tempo necessário para implantar seu código no serviço Lambda.
d. O tempo necessário para atualizar uma função
3. Como você usa o SDK.NET da AWS com suas funções do Lambda no .NET?
a. Adicione uma referência ao pacote do SDK em seu arquivo de projeto.
b. Não é necessário, estão incluídos os modelos da função do Lambda
c. Não é necessário, o kit de ferramentas para os IDEs o inclui
d. Adicione o SDK ao serviço Lambda por meio do Console da AWS
4. Quando você cria um novo projeto lambda.EmptyFunction, qual é o nome do arquivo que especifica a configuração da função?
b. lambda.csproj
c. aws-lambda-tools-defaults.json
5. Quais das seguintes são formas de invocar uma função do Lambda?
b. Solicitações HTTPS
c. Chamadas de outros serviços da AWS
d. Todas as opções acima
Respostas: 1-bd, 2-a, 3-a, 4-c, 5-d