Módulo 3: .NET en AWS Lambda
MÓDULO DE APRENDIZAJE
Tenga en cuenta que puede seguir los ejemplos que se presentan aquí, pero no es necesario.
AWS Lambda admite varias versiones de .NET, tanto en las arquitecturas x86_64 como Arm64 (Graviton2), por lo que puede elegir la arquitectura que desee. El código y el proceso de implementación no cambian.
Como Lambda es un servicio sin servidor, solo paga por lo que usa. Si su función de Lambda necesita ejecutarse varias veces al día, eso es todo lo que paga. Pero porque puede escalarse según sus necesidades y también puede iniciar mil instancias de forma simultánea.
Tiempo de realización
60 minutos
Precios
Como se mencionó anteriormente, solo paga por lo que usa. El importe que paga se calcula en función del tiempo durante el que se ejecutó la función de Lambda (redondeado al milisegundo más cercano) y de la cantidad de memoria que asignó a la función.
Tenga en cuenta que en el cálculo del precio se utiliza la cantidad de memoria asignada, no la cantidad utilizada durante una invocación. Esto hace que valga la pena probar las funciones para evaluar la cantidad máxima de memoria que utilizan durante una invocación. Mantener la memoria asignada en la cantidad mínima necesaria ayudará a reducir el costo de usar las funciones de Lambda. Consulte esta página de precios de AWS Lambda para obtener más información.
Tenga en cuenta que si su función de Lambda utiliza otros servicios, como S3 o Kinesis, es posible que también se le cobren cargos por esos servicios.
Versiones compatibles de .NET
Hay varias formas de ejecutar binarios de .NET en la plataforma Lambda. Lo más común, y lo que hay que tener en cuenta primero, es utilizar una versión ejecutable administrada proporcionada por AWS. Es la forma más sencilla de empezar, la más cómoda y ofrece el mejor rendimiento. Si no quiere o no puede usar la versión ejecutable administrada, tiene otras dos opciones: una versión ejecutable personalizada o una imagen de contenedor. Ambas opciones tienen sus propias ventajas y desventajas y se analizarán con más detalle a continuación.
Versiones ejecutables administradas
El servicio AWS Lambda proporciona una variedad de versiones ejecutables populares en las que puede ejecutar su código. En relación con las versiones ejecutables de .NET, AWS mantiene la versión ejecutable actualizada y aplica los parches necesarios con las últimas versiones disponibles de Microsoft. Como desarrollador, no necesita hacer nada en relación con la administración de la versión ejecutable que usará su código, aparte de especificar la versión que desea usar en su conocido archivo .csproj.
En la actualidad, AWS solo ofrece versiones ejecutables administradas para las versiones de soporte a largo plazo (LTS) de .NET Runtime, en el momento de escribir este artículo, es decir, .NET 3.1 y .NET 6. Los versiones ejecutables administradas de .NET están disponibles para las arquitecturas x86_64 y arm64 y se ejecutan en Amazon Linux 2. Si desea utilizar una versión de .NET distinta de las que ofrece AWS, puede crear su propia versión ejecutable personalizada o crear una imagen de contenedor que se adapte a sus necesidades.
Si está interesado en salir del mundo de .NET, le alegrará saber que el servicio Lambda también ofrece versiones ejecutables administradas para otros lenguajes: Node.js, Python, Ruby, Java y Go. Para obtener información completa sobre la lista de versiones ejecutables administradas y las versiones de los idiomas compatibles, consulte esta página sobre las versiones ejecutables disponibles.
Versiones ejecutables personalizadas
Las versiones ejecutables personalizadas son versiones ejecutables que usted mismo crea y agrupa. Hay varias razones por las que lo haría. Lo más habitual es que desee utilizar una versión de .NET que el servicio Lambda no ofrezca como versión ejecutable administrada. Otra razón menos común sería si quisiera tener un control preciso sobre las versiones secundarias y de parches de la versión ejecutable.
Crear la versión ejecutable personalizada requiere muy poco esfuerzo de su parte. Lo único que tiene que hacer es pasar:
“--self-contained true” al comando de compilación.
Esto se puede hacer directamente con dotnet build. También puede hacerlo a través del archivo aws-lambda-tools-defaults.json con el siguiente parámetro:
“msbuild-parameters”: “--self-contained true”
Eso es todo, un simple indicador de compilación al agrupar la función de Lambda para .NET. El paquete que se va a implementar ahora contendrá el código, además de los archivos necesarios de la versión ejecutable de .NET que haya elegido.
Ahora depende de usted parchear y actualizar la versión ejecutable como mejor le parezca. Para actualizar la versión ejecutable es necesario volver a implementar la función, ya que el código de la función y la versión ejecutable se empaquetan juntos.
El paquete que se implementa es considerablemente más grande en comparación con la versión ejecutable administrada porque contiene todos los archivos necesarios de la versión ejecutable. Esto afecta negativamente a los tiempos de arranque en frío (se explicará en detalle más adelante). Para reducir este tamaño, considere la posibilidad de utilizar las características de compilación de .NET, Recortar y ReadyToRun. Sin embargo, lea la documentación sobre estas características antes de hacerlo.
Puede crear una versión ejecutable personalizada con cualquier versión de .NET que se ejecute en Linux. Un caso de uso común es implementar funciones con las versiones “actuales” o de preliminares de .NET.
Al utilizar versiones ejecutables personalizadas, puede utilizar una amplia variedad de lenguajes que ofrece la comunidad. O incluso cree su propia versión ejecutable personalizada, como lo han hecho otros para ejecutar lenguajes como Erlang y COBOL.
Imágenes de contenedor
Además de la versión ejecutable administrada y la versión ejecutable personalizada, el servicio AWS Lambda también le ofrece la posibilidad de empaquetar el código en una imagen de contenedor e implementar esta imagen en el servicio Lambda. La opción es adecuada para los equipos que han invertido tiempo en crear e implementar su código en contenedores, o para aquellos que necesitan más control sobre el sistema operativo y el entorno en el que se ejecuta el código. Se admiten imágenes de hasta 10 GB de tamaño.
AWS proporciona una variedad de imágenes base para .NET y .NET Core. https://gallery.ecr.aws/lambda/dotnet; estas le ayudarán a comenzar rápidamente.
Otra opción es crear una imagen personalizada específicamente para su función. Este es un caso de uso más avanzado y requiere que edite el Dockerfile para adaptarlo a sus necesidades. Este enfoque no se tratará en este curso, pero si sigue ese camino, consulte los Dockerfiles de este repositorio: https://github.com/aws/aws-lambda-dotnet/tree/master/LambdaRuntimeDockerfiles/Images.
Tenga en cuenta que la actualización de la función de Lambda será más lenta con los contenedores debido al tamaño de la carga. Los contenedores también tienen los peores arranques en frío de las tres opciones. Encontrará más información sobre esto más adelante en el módulo.
Cómo elegir la versión ejecutable para usted
Si desea obtener el mejor rendimiento de inicio, facilidad de implementación y facilidad para comenzar y prefiere quedarse con las versiones LTS de .NET, opte por las versiones ejecutables de .NET administradas.
Las imágenes de contenedor son una excelente opción que le permite usar imágenes que AWS ha creado para diversas versiones de .NET. También puede elegir su propia imagen de contenedor y modificar el sistema operativo y el entorno en el que se ejecuta el código. Las imágenes de contenedor también son adecuadas para organizaciones que ya utilizan contenedores de forma extensiva.
Si tiene requisitos muy específicos en relación con las versiones de .NET y sus bibliotecas de versiones ejecutables, y quiere controlarlas usted mismo, considere la posibilidad de utilizar una versión ejecutable personalizada. Sin embargo, tenga en cuenta que depende de usted mantener y corregir la versión ejecutable. Si Microsoft publica una actualización de seguridad, debe conocerla y actualizar su versión ejecutable personalizada de manera adecuada. Desde el punto de vista del rendimiento, la versión ejecutable personalizada es la más lenta de las tres en iniciarse.
Una vez que se inicie la función de Lambda, el rendimiento de la versión ejecutable administrada, la imagen del contenedor y la versión ejecutable personalizada será muy similar.
AWS SDK para .NET
Si ha estado desarrollando aplicaciones .NET que utilizan servicios de AWS, probablemente haya utilizado el AWS SDK para .NET. El SDK permite a los desarrolladores de .NET llamar fácilmente a los servicios de AWS de forma coherente y familiar. El SDK se mantiene actualizado a medida que se lanzan los servicios o cuando se actualizan. El SDK está disponible para su descarga desde NuGet.
Como ocurre con muchas cosas relacionadas con AWS, el SDK se divide en paquetes más pequeños, cada uno de los cuales trata de un único servicio.
Por ejemplo, si desea acceder a los buckets de S3 desde su aplicación .NET, debe utilizar el paquete NuGet AWSSDK.S3. O bien, si quisiera acceder a DynamoDB desde su aplicación .NET, utilizaría el paquete NuGet AWSSDK.DynamoDBv2.
Pero solo agrega los paquetes NuGet que necesita. Al dividir el SDK en paquetes más pequeños, mantiene su propio paquete de implementación más pequeño.
Si su controlador de función de Lambda necesita recibir eventos de otros servicios de AWS, busque paquetes NuGet específicos relacionados con eventos. Contienen los tipos pertinentes para gestionar los eventos. Los paquetes siguen el patrón de nomenclatura de AWSSDK.Lambda.[SERVICIO]Events
Por ejemplo, si la función de Lambda se activa mediante -
eventos de S3 entrantes, utilice el paquete AWSSDK.Lambda.S3Events
eventos entrantes de Kinesis, utilice el paquete AWSSDK.Lambda.KinesisEvents
notificaciones de SNS entrantes, utilice el paquete AWSSDK.Lambda.SNSEvents
mensajes de SQS entrantes, utilice el paquete AWSSDK.Lambda.SQSEvents
Utilizar el SDK para interactuar con los servicios de AWS es muy sencillo. Agregue una referencia al paquete NuGet en su proyecto y, a continuación, llame al servicio como lo haría con cualquier otra biblioteca .NET que pueda usar.
El hecho de que utilice el SDK desde una función de Lambda no afecta a la forma en que lo utilice.
Tenga en cuenta que no es necesario agregar ningún paquete NuGet del SDK de AWS a su proyecto. Por ejemplo, si su función de Lambda llama a un servidor SQL de AWS RDS, simplemente puede utilizar Entity Framework para acceder a la base de datos. No se requieren bibliotecas específicas de AWS adicionales. Sin embargo, si desea recuperar un nombre de usuario o contraseña para la base de datos de Secrets Manager, tendrá que agregar el paquete NuGet AWSSDK.SecretsManager.
Nota sobre los permisos
Como regla general, debe utilizar el nivel más bajo de permisos necesario para realizar una tarea. A lo largo de este curso, se le animará y se le mostrará cómo hacerlo.
Sin embargo, para simplificar la enseñanza de este curso, le sugerimos que utilice un usuario de AWS con la política AdministratorAccess adjunta. Esta política le permite crear las funciones necesarias al implementar funciones de Lambda. Cuando no esté trabajando en el curso, debe eliminar esta política de su usuario de AWS.
Una función de Lambda para .NET del estilo de “Hello World”
Como se vio en un módulo anterior, es muy fácil crear, implementar e invocar una función de Lambda para .NET. En esta sección hará lo mismo, pero más despacio, y le explicaré lo que sucede en cada paso. Se analizarán el código generado y los archivos de configuración.
Creación de la función
Debe instalar las herramientas necesarias para continuar con esto; consulte el módulo 3 para obtener más detalles sobre cómo hacerlo.
Si no quiere hacerlo ahora mismo, aquí está un recordatorio rápido.
Instale las plantillas de funciones de Lambda para .NET:
dotnet new -i Amazon.Lambda.Templates
Instale las herramientas de .NET para implementar y administrar las funciones de Lambda:
dotnet tool install -g Amazon.Lambda.Tools
Ahora que tiene las plantillas instaladas, puede crear una nueva función
Desde la línea de comandos, ejecute:
dotnet new lambda.EmptyFunction -n HelloEmptyFunction
Esto crea un nuevo directorio llamado HelloEmptyFunction. En su interior hay dos directorios más, src y test. Como sugieren los nombres, el directorio src contiene el código de la función y el directorio test contiene las pruebas unitarias de la función. Cuando navegue por estos directorios, verá que cada uno contiene otro directorio. Dentro de estos subdirectorios se encuentran los archivos de código de la función y los archivos de prueba unitaria.
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
Primero echemos un vistazo al archivo 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();
}
}
Algunas notas sobre las líneas que necesitan una pequeña explicación:
La línea 4 permite la conversión de la entrada JSON en una clase .NET.
En la línea 17, la entrada JSON de la función se convertirá en una cadena.
En la línea 17, se pasa un objeto ILambdaContext como parámetro, que se puede usar para registrar, determinar el nombre de la función, cuánto tiempo lleva ejecutándose la función y otra información.
Como puede ver, el código es muy simple y debería resultarle familiar a cualquiera que haya trabajado con C#.
Aunque el método FunctionHandler aquí es sincrónico, las funciones de Lambda pueden ser asincrónicas como cualquier otro método .NET. Lo único que necesita hacer es cambiar el FunctionHandler a
public async Task<string> FunctionHandler(..)
Echemos un vistazo al archivo 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"
}
La línea 10 especifica que la función debe crearse en la configuración de la versión.
La línea 11 especifica al servicio Lambda qué versión ejecutable utilizar.
La línea 12 especifica la cantidad de memoria que se va a asignar a la función, en este caso 256 MB.
La línea 13 especifica el tiempo de espera de la función, en este caso 30 segundos. El tiempo de espera máximo permitido es de 15 minutos.
La línea 14 especifica el controlador de la función. Este es el método que invocará el servicio Lambda cuando llame a esta función.
El controlador de la función se compone de tres partes:
“AssemblyName::Namespace.ClassName::MethodName”
Un poco más adelante, creará e implementará esta función en el servicio AWS Lambda mediante la configuración de este archivo.
Pero primero, echemos un vistazo al proyecto de prueba y su archivo 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);
}
}
El código aquí es relativamente sencillo y utiliza el marco de pruebas xUnit. Puede probar las funciones de Lambda del mismo modo que probaría cualquier otro método.
La línea 14 crea una nueva instancia de la clase Function.
La línea 15 crea una nueva instancia de la clase TestLambdaContext, que se pasará a la función de Lambda de la siguiente línea.
La línea 16 invoca el método FunctionHandler en la función y pasa la cadena “hello world” y el contexto. Almacena la respuesta en la variable upperCase.
La línea 18 afirma que la variable upperCase es igual a “HELLO WORLD”.
Puede ejecutar esta prueba desde la línea de comandos o dentro de su IDE favorito. Hay otros tipos de pruebas que se pueden realizar en las funciones de Lambda. Si está interesado en obtener más información al respecto, consulte un módulo posterior en el que aprenderá sobre diversas formas de probar y depurar las funciones de Lambda.
Implementación de la función
Ahora que tiene una función de Lambda y es posible que haya realizado una prueba unitaria, es hora de implementar la función de Lambda en el servicio AWS Lambda.
Desde la línea de comandos, navegue hasta el directorio que contiene el archivo HelloEmptyFunction.csproj y ejecute el siguiente comando:
dotnet lambda deploy-function HelloEmptyFunction
Verá un resultado que incluye lo siguiente (lo recorté para mayor claridad):
... 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).
La línea 1 compila y publica el proyecto. Tenga en cuenta que la versión ejecutable es linux-x64 y el indicador “self-contained” es falso (lo que significa que la función utilizará la versión ejecutable de .NET administrada en el servicio Lambda, en lugar de una versión ejecutable personalizada).
La línea 2 comprime el proyecto publicado en un archivo zip.
Las líneas 3 a 8 muestran los archivos que se van a comprimir.
La línea 9 confirma que se ha creado el archivo zip.
A continuación, se le preguntará “Seleccione el rol de IAM para proporcionar las credenciales de AWS a su código”. Es posible que se le presente una lista de los roles que creó anteriormente, pero al final de la lista aparecerá la opción “*** Crear un nuevo rol de IAM ***”, escriba ese número junto a esa opción.
Se le pedirá que “Introduzca el nombre del nuevo rol de IAM:”. Escriba “HelloEmptyFunctionRole”.
A continuación, se le pedirá que “Seleccione la política de IAM para adjuntarla a la nueva función y conceder permisos” y se mostrará una lista de políticas. Se verá algo similar a lo siguiente, pero el suyo puede ser más largo:
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 ...)
Seleccione “AWSLambdaBasicExecutionRole”, es el número 6 de mi lista.
Después de un momento, verá:
Waiting for new IAM Role to propagate to AWS regions
............... Done
New Lambda function created
Ahora puede invocar la función.
Invocar la función
Línea de comandos
Puede utilizar las herramientas de Lambda de dotnet para invocar la función desde el shell de su elección:
dotnet lambda invoke-function HelloEmptyFunction --payload "Invoking a Lambda function"
En el caso de las funciones de Lambda sencillas como las anteriores, no hay ningún JSON que escapar, pero cuando quiera pasar un JSON que deba deserializarse, el escape de la carga útil variará según el shell que utilice.
Verá una salida similar a la siguiente:
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
La salida “Payload:” es la respuesta de la función de Lambda.
Observe cómo la cola de registro contiene información útil sobre la invocación de la función de Lambda, como cuánto tiempo se ejecutó y cuánta memoria se utilizó. Para una función simple como esta, 244,83 ms puede parecer mucho, pero esta es la primera vez que se invoca la función, lo que significa que es necesario realizar más trabajo y las invocaciones posteriores habrían sido más rápidas. Consulte la sección sobre arranques en frío para obtener más información.
Hagamos un pequeño cambio en el código para agregar algunas de nuestras propias instrucciones de registro.
Agregue lo siguiente encima de la instrucción return en el método FunctionHandler:
context.Logger.LogInformation("Input: " + input);
Vuelva a realizar la implementación mediante:
dotnet lambda deploy-function HelloEmptyFunction
Esta vez no habrá preguntas sobre el rol o los permisos.
Una vez implementada la función, puede volver a invocarla.
dotnet lambda invoke-function HelloEmptyFunction --payload "Invoking a Lambda function"
Esta vez, la salida contendrá una instrucción de registro adicional.
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
Ahí, en la línea 6, está la instrucción de registro. Los registros de funciones de Lambda también se escriben en los registros de CloudWatch (siempre que haya otorgado permisos a la función de Lambda para hacerlo).
Consola de AWS
Otra forma de invocar la función es desde la Consola de AWS.
Inicie sesión en la Consola de AWS y seleccione la función de Lambda que desea invocar.
Haga clic en la pestaña Prueba.
Desplácese hacia abajo hasta la sección JSON del evento y escriba “Invocar una función de Lambda”, incluidas las comillas.
A continuación, haga clic en el botón Probar.
Verá una salida similar a la siguiente.
Tenga en cuenta que la salida del registro también está visible.
Una función de Lambda para .NET que toma una carga JSON
El ejemplo anterior era una función simple que tomaba una cadena y la devolvía; es un buen ejemplo para empezar.
Sin embargo, es probable que desee enviar cargas JSON a las funciones de Lambda. De hecho, si otro servicio de AWS invoca su función de Lambda, enviará una carga JSON. Esas cargas JSON suelen ser bastante complejas, pero NuGet dispondrá de un modelo para la carga útil. Por ejemplo, si gestiona eventos de Kinesis con la función de Lambda, el paquete Amazon.Lambda.KinesisEvents tiene un modelo KinesisEvent. Lo mismo ocurre con los eventos de S3, los eventos de SQS, etc.
En lugar de utilizar uno de esos modelos en este momento, va a invocar una nueva función de Lambda con una carga útil que represente a una persona.
{
"FirstName": "Alan",
"LastName": "Adams"
}
La clase de C# adecuada para deserializar la carga JSON es:
public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
Cree la función
Como antes, cree una función nueva con el siguiente comando:
dotnet new lambda.EmptyFunction -n HelloPersonFunction
Altere la función
Cambie el código del método FunctionHandler para que tenga este aspecto:
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; }
}
Este es el mismo comando que utilizó hace unos minutos:
dotnet lambda deploy-function HelloPersonFunction
Invoque la función
Ahora, su función de Lambda puede aceptar una carga JSON, pero la forma en que la invoque depende del shell que utilice, debido a la forma en que JSON se escapa en cada shell.
Si usa PowerShell o bash, use:
dotnet lambda invoke-function HelloPersonFunction --payload '{ \"FirstName\": \"Alan\", \"LastName\": \"Adams\" }'
dotnet lambda invoke-function HelloPersonFunction --payload "{ \"FirstName\": \"Alan\", \"LastName\": \"Adams\" }"
Inicie sesión en la Consola de AWS y seleccione la función de Lambda que desea invocar.
{
"FirstName": "Alan",
"LastName": "Adams"
}
Verá una salida similar a la siguiente.
En la siguiente sección, verá cómo implementar una función de Lambda que responda a las solicitudes HTTP.
Creación y ejecución de una aplicación de API web como una función de Lambda
Pero también puede invocar la función de Lambda mediante una solicitud HTTP, y ese es un caso de uso muy común.
Las herramientas de AWS para .NET ofrecen algunas plantillas que puede utilizar para crear una función de Lambda sencilla que aloje una aplicación de API web.
La más familiar probablemente sea la plantilla Serverless.AspNetCoreWebAPI, que crea una aplicación de API web simple que se puede invocar mediante una solicitud HTTP. La plantilla del proyecto incluye una plantilla de configuración de CloudFormation que crea una puerta de enlace de API que reenvía las solicitudes HTTP a la función de Lambda.
Cuando se implementa en AWS Lambda, la puerta de enlace de API traduce la solicitud HTTP a un evento de puerta de enlace de API y envía este JSON a la función de Lambda. No se ejecuta ningún servidor de Kestrel en la función de Lambda cuando se implementa en el servicio Lambda.
Pero cuando se ejecuta localmente, se inicia un servidor web de Kestrel, lo que hace que sea muy fácil escribir el código y probarlo con la misma facilidad que lo haría con cualquier aplicación de API web. Incluso puede hacer la depuración normal línea por línea. ¡Obtiene lo mejor de los dos mundos!
Cree la función
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
Implemente la función
Antes de intentar implementar la función sin servidor, necesita un bucket de S3. Las herramientas de implementación lo utilizarán para almacenar una pila de CloudFormation.
Puede usar un bucket de S3 existente o, si no tiene uno, siga las instrucciones que se indican a continuación.
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)
A continuación, se le pedirá el nombre del bucket de S3. Use el nombre del bucket que creó anteriormente o un bucket existente que quiera usar para este fin.
Una vez introducido esto, comience el proceso de creación e implementación.
Esto llevará más tiempo que los ejemplos que utilizan plantillas de proyectos lambda.* porque hay más infraestructura que crear y conectar.
La salida se dividirá en dos secciones distintas.
La sección superior será similar a la que vio al implementar funciones anteriormente, una publicación y un zip del proyecto, pero esta vez el artefacto se carga en 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/
Justo en la parte inferior está la URL pública que puede usar para invocar la API.
Invocar la función
A continuación, intente abrir https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/Prod/api/values e invocará el método GET del controlador de valores, tal como lo haría en una aplicación de API web normal.
Tenga en cuenta que, al usar una puerta de enlace de API, la puerta de enlace impone su propio tiempo de espera de 29 segundos. Si la función de Lambda se ejecuta durante más tiempo, no recibirá ninguna respuesta.
Si está interesado, hay varias maneras de revisar los recursos que se crearon.
Para revisar los recursos de AWS creados, puede utilizar:
aws cloudformation describe-stack-resources --stack-name AspNetCoreWebAPI
Si desea una salida más concisa, utilice:
aws cloudformation describe-stack-resources --stack-name AspNetCoreWebAPI --query 'StackResources[].[{ResourceType:ResourceType, LogicalResourceId:LogicalResourceId, PhysicalResourceId:PhysicalResourceId}]'
Con estos ejemplos, puede crear e implementar sus propias funciones de Lambda. Y es posible que haya aprendido un poco sobre cómo se invocan las funciones de Lambda para .NET. Este es el tema de la siguiente sección.
URL de funciones: una alternativa a las puertas de enlace de API
Si todo lo que necesita es una función de Lambda que responda a una solicitud HTTP simple, debería considerar el uso de las URL de la función de Lambda.
Permiten asignar un punto de conexión HTTPS a una función de Lambda. A continuación, invoque la función de Lambda mediante una solicitud al punto de conexión HTTPS. Para obtener más información, consulte esta publicación de blog y estos documentos.
Limpiar los recursos que creó
dotnet lambda delete-function HelloEmptyFunction
dotnet lambda delete-function HelloPersonFunction
Tenga en cuenta que los comandos anteriores no eliminan el rol que creó.
Para eliminar la función de Lambda que alojaba la aplicación de API web y todos los recursos asociados, ejecute:
dotnet lambda delete-serverless AspNetCoreWebAPI
Cómo se invoca una función de Lambda para .NET
Como puede ver en los ejemplos anteriores, puede invocar una función de Lambda para .NET con una cadena simple, un objeto JSON y una solicitud HTTP. Otros servicios también pueden invocar funciones de Lambda, como S3 (cuando se produce un cambio en un archivo), Kinesis (cuando llega un evento), DynamoDB (cuando se produce un cambio en una tabla), SMS (cuando llega un mensaje), Step Functions, etc.
¿Cómo maneja una función de Lambda todas estas formas diferentes de invocación?
En segundo plano, estas funciones de Lambda se invocan cuando el servicio de Lambda ejecuta el controlador de la función y le pasa una entrada JSON. Si observa aws-lambda-tools-defaults.json, puede ver el “function-handler”: especificado. Para las funciones de Lambda para .NET, el controlador incluye “AssemblyName::Namespace.ClassName::MethodName”.
Las funciones de Lambda también se pueden invocar pasándoles un flujo, pero este es un escenario menos común; consulte la página sobre el control de flujos para obtener más información.
Cada función de Lambda tiene un único controlador de función.
Junto con la entrada JSON, el controlador de la función de Lambda también puede tomar un objeto ILambdaContext opcional. Esto le da acceso a información sobre la invocación actual, como el tiempo que le queda para completarse, el nombre y la versión de la función. También puede escribir mensajes de registro en el objeto mediante ILambdaContext.
Todos los eventos son JSON
Lo que hace que sea muy fácil para un servicio de AWS invocar una función de Lambda para .NET es que estos servicios emiten JSON y, como se ha descrito anteriormente, la función de Lambda para .NET acepta entradas JSON. Todos los eventos generados por los diferentes servicios producen un JSON con formas diferentes, pero los paquetes NuGet de eventos de AWS Lambda incluyen todos los tipos de objetos relevantes necesarios para volver a serializar el JSON en un objeto con el que pueda trabajar.
Consulte https://www.nuget.org/packages?packagetype=&sortby=relevance&q=Amazon.Lambda&prerel=True para obtener una lista de los paquetes de Lambda disponibles; tendrá que buscar en esos resultados el tipo de evento que le interesa.
Por ejemplo, si desea activar una función de Lambda en respuesta a un cambio de archivo en un bucket de S3, debe crear una función de Lambda que acepte un objeto de tipo S3Event. A continuación, agregue el paquete Amazon.Lambda.S3Events a su proyecto. A continuación, cambie el método del controlador de la función a:
public async string FunctionHandler(S3Event s3Event, ILambdaContext context)
{
...
}
Eso es todo lo que necesita para gestionar el evento de S3; puede examinar el evento mediante programación, ver qué acción se realizó en el archivo, en qué bucket se encontraba, etc. Amazon.Lambda.S3Events le permite trabajar con el evento, no con el propio S3. Si desea interactuar con el servicio S3, también debe agregar el paquete NuGet AWSSDK.S3 a su proyecto. Un módulo posterior abordará el tema de los servicios de AWS que invocan funciones de Lambda.
Se sigue el mismo patrón para otros tipos de eventos: agregue el paquete NuGet, cambie el parámetro al controlador de la función y, a continuación, podrá trabajar con el objeto de evento.
Estos son algunos de los paquetes comunes que puede utilizar para gestionar eventos de otros servicios:
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
No está limitado a utilizar tipos de eventos definidos por AWS al invocar una función de Lambda. Puede crear cualquier tipo de evento usted mismo; recuerde que la función de Lambda puede tomar cualquier JSON que le envíe
Cómo ocurre la serialización
En el caso de las plantillas “lambda.”, hay un atributo de ensamblaje cerca de la parte superior del archivo Function.cs, que se encarga de deserializar el evento entrante al tipo .NET en el controlador de la función. En el archivo .csproj, hay una referencia al paquete Amazon.Lambda.Serialization.SystemTextJson.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
Para las plantillas “serverless.”, funciona de forma un poco diferente.
El controlador de la función se especifica en el archivo serverless.template. Si va a implementar una aplicación Serverless.AspNetCoreWebAPI, busque el valor en Resources.AspNetCoreFunction.Properties.Handler. El controlador para este tipo de proyecto tendrá la forma Assembly::Namespace.LambdaEntryPoint::FunctionHandlerAsync.
La clase LambdaEntryPoint estará en su proyecto y hereda de una clase que tiene un método FunctionHandlerAsync.
El controlador de la función se puede configurar para gestionar cuatro tipos de eventos diferentes: una API de REST de puerta de enlace de API, una carga útil de API HTTP de puerta de enlace de API versión 1.0, una carga útil de API HTTP de puerta de enlace de API versión 2.0 y un equilibrador de carga de aplicación.
Al cambiar la clase de la que hereda LambdaEntryPoint, puede cambiar el tipo de evento JSON que gestiona el controlador de la función.
Aunque parezca que la función de Lambda responde a una solicitud HTTP que usted le envía, con el JSON que usted defina, ese no es el caso. En realidad, la solicitud HTTP la gestiona una puerta de enlace o un equilibrador de carga, que luego crea un evento JSON que se envía al controlador de la función. Este evento JSON incluirá los datos originalmente incluidos en la solicitud HTTP, hasta la dirección IP de origen y los encabezados de la solicitud.
Simultaneidad
Hay dos tipos de simultaneidad que se deben tener en cuenta al trabajar con funciones de Lambda: la simultaneidad reservada y la simultaneidad aprovisionada.
Una cuenta de AWS tiene un límite máximo predeterminado para el número de ejecuciones simultáneas de Lambda. Al momento de escribir este artículo, ese límite es de 1000.
Al especificar la simultaneidad reservada para una función, se garantiza que la función podrá alcanzar el número especificado de ejecuciones simultáneas. Por ejemplo, si su función tiene una simultaneidad reservada de 200, se asegura de que la función podrá alcanzar las 200 ejecuciones simultáneas. Tenga en cuenta que esto deja 800 ejecuciones simultáneas para otras funciones (1000 – 200 = 800).
Cuando especifica la simultaneidad aprovisionada, está inicializando un número específico de entornos de ejecución de Lambda. Cuando se hayan inicializado, la función de Lambda podrá responder a las solicitudes de forma inmediata, evitando el problema de los “arranques en frío”. Sin embargo, hay una tarifa asociada al uso de la simultaneidad aprovisionada.
Para obtener más información, consulte Administración de la simultaneidad reservada de Lambda y Administración de la simultaneidad aprovisionada de Lambda.
Arranques en frío y arranques en caliente
Antes de poder invocar la función de Lambda, se debe inicializar un entorno de ejecución; esto lo hace el servicio Lambda en su nombre. El código fuente se descarga de un bucket de S3 administrado por AWS (para las funciones que utilizan versiones ejecutables administradas y versiones ejecutables personalizadas) o de un Elastic Container Registry (para las funciones que utilizan imágenes de contenedor).
La primera vez que se ejecute la función, el código debe estar JITed y se ejecutará el código de inicialización (por ejemplo, su constructor). Esto aumenta el tiempo de arranque en frío.
Si su función se invoca con regularidad, permanecerá “caliente”, es decir, se mantendrá el entorno de ejecución. Las invocaciones posteriores de la función no se verán afectadas por la hora de arranque en frío. Los “arranques en caliente” son significativamente más rápidos que los “arranques en frío”.
Si la función no se invoca durante un periodo (el servicio Lambda no especifica la hora exacta), se elimina el entorno de ejecución. La siguiente invocación de la función volverá a provocar un arranque en frío.
Si sube una nueva versión del código de la función, la próxima invocación de la función provocará un arranque en frío.
Las tres opciones para ejecutar .NET en Lambda, la versión ejecutable administrada, la versión ejecutable personalizada y el alojamiento en contenedores tienen perfiles de arranque en frío diferentes. El más lento es el contenedor, el siguiente más lento es la versión ejecutable personalizada y el más rápido es la versión ejecutable administrada. Si es posible, siempre debe optar por la versión ejecutable administrada al ejecutar funciones de Lambda para .NET.
Se descubrió que los arranques en frío ocurren con más frecuencia en entornos de prueba o desarrollo que en entornos de producción. En un análisis de AWS, los arranques en frío se producen en menos del 1 % de las invocaciones.
Si tiene una función de Lambda en producción que se usa con poca frecuencia, pero necesita responder rápidamente a una solicitud, y quiere evitar arranques en frío, puede usar la simultaneidad aprovisionada o usar un mecanismo para “hacer ping” a su función con frecuencia para mantenerla actualizada.
Si desea obtener más información sobre la optimización de su función de Lambda, puede leer sobre los arranques en frío, los arranques en caliente y la simultaneidad aprovisionada en la Guía para desarrolladores de AWS Lambda o consultar la serie de blogs sobre optimización del rendimiento de Lambda Operating Lambda: James Beswick, parte 1, parte 2 y parte 3.
Recorte y listo para ejecutarse en versiones de .NET anteriores a .NET 7
Si optó por utilizar versiones ejecutables personalizadas de Lambda para una versión de .NET anterior a .NET 7, hay un par de características de .NET que puede utilizar para reducir los tiempos de arranque en frío.
PublishTrimmed reducirá el tamaño total del paquete que implemente al eliminar las bibliotecas innecesarias del paquete.
PublishReadyToRun compilará su código con antelación, reduciendo así la cantidad de compilaciones justo a tiempo que se requiere. Sin embargo, aumenta el tamaño del paquete que implementa.
Para conseguir un rendimiento óptimo, tendrá que probar su función al utilizar estas opciones.
PublishTrimmed y PublishReadyToRun se pueden activar desde el archivo .csproj.
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>true</PublishReadyToRun>
Conclusión
Compilación anticipada nativa para .NET 7
dotnet new -i "Amazon.Lambda.Templates::*"dotnet tool update -g Amazon.Lambda.Tools
Pruebas de conocimientos
Ya completó el módulo 2, Herramientas para el desarrollo de .NET con AWS Lambda. El siguiente test le permitirá comprobar lo que ha aprendido hasta ahora.
1. ¿Qué versiones ejecutables administradas de .NET ofrece el servicio Lambda? (seleccione dos)
b. .NET 6
c. .NET 7
d. .NET Core 3.1
e. .NET Framework 4.8
2. ¿A qué se refiere el arranque en frío? (seleccione uno)
b. Una función de Lambda que usa el almacenamiento de AWS S3 Glacier.
c. El tiempo que lleva implementar el código en el servicio Lambda.
d. El tiempo que se tarda en actualizar una función.
3. ¿Cómo se utiliza el AWS .NET SDK con sus funciones de Lambda para .NET?
a. Se agrega una referencia al paquete del SDK en el archivo del proyecto.
b. No es necesario, se incluyen las plantillas de funciones de Lambda.
c. No es necesario, el kit de herramientas para los IDE lo incluye.
d. Se agrega el SDK al servicio Lambda a través de la Consola de AWS.
4. Al crear un nuevo proyecto lambda.EmptyFunction, ¿cuál es el nombre del archivo que especifica la configuración de la función?
b. lambda.csproj
c. aws-lambda-tools-defaults.json
5. ¿Cuáles de las siguientes son formas de invocar una función de Lambda?
b. Solicitudes HTTPS
c. Llamadas desde otros servicios de AWS
d. Todas las anteriores
Respuestas: 1-bd, 2-a, 3-a, 4-c, 5-d