AWS Lambda 上的 .NET 工作负载

模块 3

模块 3:AWS Lambda 上的 .NET

 学习模块

请注意,您可以按照此处提供的示例进行操作,但这不是强制要求。

AWS Lambda 支持多个 .NET 版本,这些版本可以在 x86_64 和 Arm64(Graviton2)架构上,并且您可以自由选择所需架构。您的代码和部署过程不会改变。

由于 Lambda 是一项无服务器服务,因此您只需按实际使用量付费。如果您的 Lambda 函数需要每天运行几次,您只需按实际使用量付费。但是因为 Lambda 函数可以根据您的需求进行扩展,您也可以同时启动一千个实例!

 所需时间

60 分钟 

定价

如上所述,您只需按实际使用量付费。您支付的金额是根据您的 Lambda 函数运行的时间长短(四舍五入到最接近的毫秒)以及您分配给该函数的内存量计算得出的。

请记住,价格是根据分配的内存量计算的,而不是调用期间使用的内存量。因此,这让通过测试函数来评测其在调用期间使用的最大内存量成为了一项有意义的任务。将分配的内存控制在必要的最低数量有助于降低使用 Lambda 函数的成本。有关更多信息,请参阅此页面的 AWS Lambda 定价。

请注意,如果您的 Lambda 函数使用 S3、Kinesis 等其他服务,这些服务也可能会产生费用。

.NET 支持的版本

在 Lambda 平台上运行 .NET 二进制文件有多种方法。最常见、也是首要考虑的方法,是使用 AWS 提供的托管运行时系统。这是最简单、最方便的方法,而且可提供最佳性能。如果您不希望或无法使用托管运行时系统,还有另外两种方法可供选择:自定义运行时系统或容器映像。这两种方法都有各自的优点和缺点,下文将详细讨论。

托管运行时系统

AWS Lambda 服务提供各种热门的运行时系统供您运行代码。AWS 会不断更新 .NET 运行时系统使其一直处于最新状态,并根据需要利用 Microsoft 提供的最新版本进行修补。作为开发人员,在管理代码使用的运行时系统时,除了在熟悉的.csproj 文件中指定要使用的版本外,无需执行任何操作。

目前,AWS 仅为长期支持(LTS)版本的 .NET 运行时系统提供托管运行时系统,截至本文撰写之时,为 .NET 3.1 和 .NET 6。.NET 托管的运行时系统适用于 x86_64 和 arm64 架构,并可在 Amazon Linux 2 上运行。如果您想使用非 AWS 提供的 .NET 版本,则可以构建自己的自定义运行时系统或创建符合您需求的容器映像。

如果您对 .NET 以外的领域感兴趣,那么了解 Lambda 服务也为其他语言(Node.js、Python、Ruby、Java 和 Go)提供托管运行时系统也是有好处的。有关托管运行时系统列表和所支持语言版本的完整详细信息,请参阅此页的“可用运行时系统”。

自定义运行时系统

自定义运行时系统是您自己构建和捆绑的运行时系统。构建和捆绑自定义运行时系统有以下几种原因。最常见的原因是您想要使用 Lambda 服务提供的 .NET 版本,但其未作为托管运行时系统提供。另一个不太常见的原因是,您希望精确控制运行时系统的次要版本和补丁版本。

创建自定义运行时系统非常简单。您要做的就是:

将 --self-contained true 传递给 build 命令。

这可以直接通过 dotnet build 来完成。您也可以通过带有以下参数的 aws-lambda-tools-defaults.json 文件来完成:

"msbuild-parameters": "--self-contained true"

就是这么简单,捆绑 .NET Lambda 函数时的一个简单编译器标志。现在,要部署的软件包将包含您的代码以及您选择的 .NET 运行时系统中所需的文件。

您现在可以按需修补运行时系统和对其进行更新。更新运行时系统需要重新部署函数,因为函数代码和运行时系统是打包在一起的。

与托管运行时系统相比,部署的软件包要大得多,因为它包含所有必要的运行时系统文件。这会对冷启动时间产生不利影响(稍后会详细介绍)。为了帮助减小软件包,可以考虑使用 .NET 编译功能 TrimmingReadyToRun。不过在使用前,请先阅读有关这些功能的文档。

您可以使用在 Linux 上运行的任何版本的 .NET 创建自定义运行时系统。常见的使用案例是使用 .NET 的“当前”或预览版本部署函数。

使用自定义运行时系统时,您可以使用社区提供的各种语言。甚至可以像其他人一样构建自己的自定义运行时系统,来运行 Erlang 和 COBOL 等语言。

容器映像

除了托管运行时系统和自定义运行时系统,AWS Lambda 服务还可以将代码打包到容器映像中并将此映像部署到 Lambda 服务。该选项适合那些有时间构建代码并将其部署到容器的团队,也适合那些需要对运行代码的操作系统和环境加强控制的团队。最高可支持 10GB 大小的映像。

AWS 为 .NET 和 .NET 核心提供了各种基础映像。https://gallery.ecr.aws/lambda/dotnet,该页面的内容将帮助您快速入门。

另一种方法是专门为您的函数创建自定义映像。这是一个比较高级的使用案例,需要您根据需求来编辑 Dockerfile。本课程未介绍这种方法,但是如果您想了解,可以查看以下存储库中的 Dockerfiles – https://github.com/aws/aws-lambda-dotnet/tree/master/LambdaRuntimeDockerfiles/Images

请注意,由于上传文件较大,使用容器更新 Lambda 函数是最慢的。在这三种方法中,容器的冷启动也是最差的。本模块稍后将对此进行详细介绍。

选择适合您的运行时系统

如果您希望启动性能最佳、易于部署、易于上手,并愿意使用 .NET 的 LTS 版本,请选择托管 .NET 运行时系统。

容器映像是个不错的选择,它可以让您使用 AWS 为各种 .NET 版本创建的映像。或者,您可以选择自己的容器映像,然后调整运行代码的操作系统和环境。容器映像还适用于已经广泛使用容器的组织。

如果您对 .NET 及其运行时系统库的版本有非常具体的要求,并且想要自己控制这些要求,请考虑使用自定义运行时系统。但请记住,维护和修补运行时系统需由您自己负责。如果 Microsoft 发布了安全更新,您需要及时了解,并相应地更新您的自定义运行时系统。从性能的角度来看,自定义运行时系统是三种运行时系统中速度最慢的。

Lambda 函数启动后,托管运行时系统、容器映像和自定义运行时系统的性能将非常相似。

适用于 .NET 的 AWS SDK

如果您一直在开发使用 AWS 服务的 .NET 应用程序,那么您可能已经使用了适用于 .NET 的 AWS SDK。SDK 可让 .NET 开发人员轻松地以一致且熟悉的方式调用 AWS 服务。SDK 会随着服务的发布或更新始终保持最新状态。可从 NuGet 下载 SDK。

像许多与 AWS 相关的内容一样,SDK 被分为多个较小的软件包,每个软件包处理一项服务。

例如,如果您想从 .NET 应用程序访问 S3 桶,可以使用 AWSSDK.S3 NuGet 软件包。或者,如果您想从 .NET 应用程序访问 DynamoDB,可以使用 AWSSDK.DynamoDBv2 NuGet 软件包。

不过,您只需添加自己需要的 NuGet 软件包。通过将 SDK 拆分成较小的软件包,可以缩小自己的部署包。

如果您的 Lambda 函数处理程序需要从其他 AWS 服务接收事件,请查找与特定事件相关的 NuGet 软件包,其中包含用于处理事件的相关类型。这些软件包遵循 AWSSDK.Lambda.[SERVICE]Events 命名模式。

例如,如果您的 Lambda 函数:

由传入的 S3 事件触发,请使用 AWSSDK.Lambda.S3Events 软件包

由传入的 Kinesis 事件触发,请使用 AWSSDK.Lambda.KinesisEvents 软件包

由传入的 SNS 通知触发,请使用 AWSSDK.Lambda.SNSEvents 软件包

由传入的 SQS 消息触发,请使用 AWSSDK.Lambda.SQSEvents 软件包

使用 SDK 与 AWS 服务进行交互非常简单。在您的项目中添加对 NuGet 软件包的引用,然后像使用任何其他 .NET 库一样调用该服务。

使用来自 Lambda 函数的 SDK 对您的使用方式没有影响。

请记住,您不一定需要向项目中添加 AWS SDK NuGet 软件包。例如,如果您的 Lambda 函数调用 AWS RDS SQL 服务器,则您只需使用实体框架访问数据库即可。无需额外的 AWS 专用库。但是,如果您想从 Secrets Manager 中检索数据库的用户名/密码,则需要添加 AWSSDK.SecretsManager NuGet 软件包。

关于权限的说明

一般而言,您应该使用执行任务所需的最低权限级别。 在整个课程中,我们将鼓励您并向您展示如何做到这一点。

但是,为了简化本课程的教学,我们建议您使用具有AdministratorAccess策略的 AWS 用户。此策略允许您创建部署 Lambda 函数时所需的角色。不学习课程时,您应该从您的 AWS 用户中删除此策略。

Hello World Style .NET Lambda 函数

正如您在之前的模块中所见,创建、部署和调用 .NET Lambda 函数非常简单。在本节中,您将执行同样的操作,但速度要慢一些,我将解释每个步骤如何操作。将会讨论生成的代码和配置文件。

创建函数

您必须安装所需的工具才能继续执行此操作,有关如何执行此操作的更多详细信息,请参阅模块 3。

如果您现在不想跳转到该模块,请参阅这里的简单提醒。

安装 .NET Lambda 函数模板:

dotnet new -i Amazon.Lambda.Templates

安装用于部署和管理 Lambda 函数的 .NET 工具:

dotnet tool install -g Amazon.Lambda.Tools

现在您已经安装了模板,可以创建新函数了

从命令行运行:

dotnet new lambda.EmptyFunction -n HelloEmptyFunction

这将创建一个名为“HelloEmptyFunction”的新目录。其中还有两个目录,src 和 test。顾名思义,src 目录包含函数的代码,test 目录则包含函数的单元测试。导航到这些目录时,您会发现它们还分别包含另一个目录。这些子目录中包含函数的代码文件和单元测试文件。

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

我们先来看看 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();
    }
}

有几行需要对注释做一些解释:

第 4 行,允许将 JSON 输入转换为 .NET 类。

第 17 行,会将函数的 JSON 输入转换为字符串。

第 17 行,一个 ILambdaContext 对象作为参数传入,它可以用于记录、确定函数的名称、函数运行了多长时间以及其他信息。

如您所见,代码非常简单,任何使用过 C# 的人应该都不陌生。

尽管这里的 FunctionHandler 方法是同步的,但 Lambda 函数可以像任何其他 .NET 方法一样,也可以是异步的。您需要做的就是将 FunctionHandler 更改为

public async Task<string> FunctionHandler(..)

我们来看看 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"
}

第 10 行,具体说明该函数应在发布配置中构建。

第 11 行,向 Lambda 服务指定了要使用的运行时系统。

第 12 行,指定要分配给函数的内存量,在本例中为 256MB。

第 13 行,指定函数的超时时间,在本例中为 30 秒。最多允许超时 15 分钟。

第 14 行,指定函数处理程序。调用此函数时,Lambda 服务将调用此方法。

函数处理程序由三部分组成:

"AssemblyName::Namespace.ClassName::MethodName"

稍后,您将使用该文件中的配置构建此函数并将其部署到 AWS Lambda 服务。

但首先,我们来看看测试项目及其 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);
    }
}

这里的代码相对比较简单,使用了 xUnit 测试框架。您可以像测试任何其他方法一样测试您的 Lambda 函数。

第 14 行,创建了一个函数类的新实例。

第 15 行,创建了一个 TestLambdaContext 类的新实例,该实例将在下一行传递给 Lambda 函数。

第 16 行,调用函数上的 FunctionHandler 方法,传入字符串“hello world”和上下文。它将响应存储在 upperCase 变量中。

第 18 行,断言 upperCase 变量等于“HELLO WORLD”。

您可以从命令行运行该测试,也可以在您最喜欢的 IDE 内运行该测试。还可以对 Lambda 函数执行其他类型的测试,如果您有兴趣了解更多相关信息,请参阅之后的模块,在其中您将学习测试和调试 Lambda 函数的各种方法。

部署函数

现在您拥有一个 Lambda 函数,而且可能已经运行了单元测试,那么就可以将 Lambda 函数部署到 AWS Lambda 服务了。

在命令行中,导航到包含 HelloEmptyFunction.csproj 文件的目录并运行以下命令:

dotnet lambda deploy-function HelloEmptyFunction  

您会看到包含以下内容输出(为了清晰起见,我对其进行了删减):

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

第 1 行,编译并发布项目。请注意,运行时系统为 linux-x64,自包含标志为 false(这意味着该函数将在 Lambda 服务上使用托管的 .NET 运行时系统,而不是自定义运行时系统)。

第 2 行,将已发布的项目压缩为 zip 文件。

第 3-8 行,显示正在压缩的文件。

第 9 行,确认已创建 zip 文件。

接下来,系统将询问您“选择为您的代码提供 AWS 凭证的 IAM 角色:”,您可能会看到之前创建的角色列表,但列表底部将显示“**创建新 IAM 角色***”选项,在该选项旁边输入该数字。

系统将要求您“输入新 IAM 角色的名称:”。输入“HelloEmptyFunctionRole”。

然后,系统将要求您“选择要附加到新角色的 IAM Policy 并授予权限”,并显示策略列表。看起来类似下列内容,但您的显示内容可能更长:

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

选择“AWSLambdaBasicExecutionRole”,它位于列表第 6 位。

片刻之后,您会看到:

Waiting for new IAM Role to propagate to AWS regions
...............  Done
New Lambda function created

现在您可以调用该函数了。

调用函数

命令行

您可以使用 dotnet lambda 工具从您选择的 Shell 调用函数:

dotnet lambda invoke-function HelloEmptyFunction --payload "Invoking a Lambda function"

对于像上面这样的简单 Lambda 函数,没有要逸出的 JSON,但是当您想传递需要解序列化的 JSON 时,则逸出负载将根据您使用的 shell 而有所不同。

您将看到如下所示的输出:

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

输出“Payload:”是 Lambda 函数的响应。

请注意日志尾包含有关 Lambda 函数调用的有用信息,例如它运行的时间以及使用多少内存。对于这样的简单函数,244.83 毫秒可能看起来很多,但这是第一次调用该函数,这意味着需要执行更多的工作,后续调用将更快。如需详细信息,请参阅冷启动部分。

我们来对代码进行一个小改变,以添加一些我们自己的日志语句。

在 FunctionHandler 方法中,在返回语句上方添加以下内容:

context.Logger.LogInformation("Input: " + input);

使用以下方式重新部署:

dotnet lambda deploy-function HelloEmptyFunction

这次将没有关于角色或权限的问题。

部署函数后,您可以再次调用它。

dotnet lambda invoke-function HelloEmptyFunction --payload "Invoking a Lambda function" 

这次,输出将包含一个额外的日志语句。

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

在第 6 行上,是日志语句。Lambda 函数日志也会写入 CloudWatch 日志(前提是您授予 Lambda 函数许可来执行此操作)。

AWS 管理控制台

调用该函数的另一种方法是从 AWS 管理控制台台进行调用。

登录 AWS 管理控制台,然后选择要调用的 Lambda 函数。

单击“测试”选项卡。

向下滚动到“事件 JSON”部分,然后放入 “Invoking a Lambda function”,包括引号。

然后单击“测试”按钮。

您将看到类似以下内容的输出。

请注意,日志输出也可见。

采用 JSON 负载的 .NET Lambda 函数

上一个例子是一个简单的函数,它采用一个字符串并返回一个字符串,是一个开始使用的好例子。

但是您可能想将 JSON 负载传送到 Lambda 函数。事实上,如果另一个 AWS 服务调用您的 Lambda 函数,它将发送 JSON 负载。这些 JSON 负载通常非常复杂,但 NuGet 将提供负载的模型。例如,如果您使用 Lambda 函数处理 Kinesis 事件,则 Amazon.Lambda.KinesisEvents 包具有 KinesisEvent 模型。S3 事件、SQS 事件等也是如此。

您现在不需要使用其中一个模型,而是调用一个新的 Lambda 函数,该函数代表一个人的负载。

{
  "FirstName": "Alan",
  "LastName": "Adams"
}

解序列化 JSON 负载的适当 C# 类是:

public class Person 
{
    public string FirstName { get; init; }
    public string LastName { get; init; }  
}

创建函数

与之前一样,使用以下命令创建服务:

dotnet new lambda.EmptyFunction -n HelloPersonFunction

更改函数

导航到 HelloPersonFunction /src/HelloPersonFunction 中的 Function.cs 文件。

将 FunctionHandler 方法的代码更改为如下所示:
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; }  
}

这与您几分钟前使用的命令相同:

这与您几分钟前使用的命令相同:
dotnet lambda deploy-function HelloPersonFunction

调用函数

命令行
 

现在,您的 Lambda 函数可以接受 JSON 负载,但是您如何调用它取决于您使用的 Shell,因为 JSON 在每个命令界面中逸出的方式可能不同。

如果您使用的是 PowerShell 或 bash,请使用:

dotnet lambda invoke-function HelloPersonFunction --payload '{ \"FirstName\": \"Alan\", \"LastName\": \"Adams\" }'
但是,如果您使用的是 Windows 命令提示符,使用:
dotnet lambda invoke-function HelloPersonFunction --payload "{ \"FirstName\": \"Alan\", \"LastName\": \"Adams\" }"
这只是一个简单的例子。但是,当然,JSON 可以根据需要更加复杂。
AWS 管理控制台
 
调用该函数的另一种方法是从 AWS 管理控制台台进行调用。

登录 AWS 管理控制台,然后选择要调用的 Lambda 函数。
单击“测试”选项卡。
向下滚动到“事件 JSON”部分并输入:
{
  "FirstName": "Alan",
  "LastName": "Adams"
}
然后单击“测试”按钮。

您将看到类似以下内容的输出。
过去的几个例子相当简单,Lambda 函数没有利用任何其他 AWS 服务,但它们是开始使用并熟悉 AWS Lambda 服务、编写 Lambda 函数、部署编译的套件和调用函数的好方法。

在下一节中,您将看到如何部署响应 HTTP 请求的 Lambda 函数。

作为 Lambda 函数建立和运行 Web API 应用程序

在上一个示例中,Lambda 函数都是通过命令行或 AWS 管理控制台中 Lambda 服务的测试功能中调用的。

但是您也可以通过 HTTP 请求调用 Lambda 函数,这是一个非常常见的用例。

适用于 .NET 的 AWS 工具提供了一些模板,您可以使用这些模板来建立托管 Web API 应用程序的简单 Lambda 函数。

最熟悉的可能是 serverless.AspNetCoreWebAPI 模板,这会建立一个可以通过 HTTP 请求调用的简单 Web API 应用程序。项目模板包含 CloudFormation 配置模板,该模板会建立一个 API 网关,以将 HTTP 请求转寄给 Lambda 函数。

部署到 AWS Lambda 时,API 网关会将 HTTP 请求转换为 API 网关事件,并将此 JSON 传送到 Lambda 函数。当 Lambda 函数部署到 Lambda 服务时,没有 Kestrel 服务器在函数中运行。

但是当您在本地运行它时,Kestrel Web 服务器就会启动,这使您可以非常轻松地编写代码并在您熟悉的任何 Web API 应用程序中进行测试。您甚至可以通过行调试执行正常行。这是两者都能兼顾的好方法!

创建函数

从命令行运行以下命令:
dotnet new serverless.AspNetCoreWebAPI -n HelloAspNetCoreWebAPI
这会创建一个名为 HelloAspNetCoreWebAPI 的新目录。其中还有两个目录,src 和 test。顾名思义,src 目录包含函数的代码,test 目录则包含函数的单元测试。导航到这些目录时,您会发现它们还分别包含另一个目录。这些子目录中包含函数的代码文件和单元测试文件。
├───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

部署函数

基于 serverless.* 模板的函数比基于 lambda.* 模板部署函数要复杂一点。

在尝试部署无服务器函数之前,您需要 S3 桶。部署工具将使用此桶来存储 CloudFormation 堆栈。

您可以使用现有的 S3 桶,如果没有,请使用下方说明创建。
创建 S3 桶
 
如果您使用 us-east-1 区域,则可以使用以下命令来创建桶:
aws s3api create-bucket --bucket your-unique-bucket-name1234 
或者如果您使用其他区域
aws s3api create-bucket --bucket your-unique-bucket-name1234 --create-bucket-configuration LocationConstraint=REGION
我在 us-east-1 中创建了一个名为 lambda-course-2022 的桶。
aws s3api create-bucket --bucket lambda-course-2022 
返回部署函数
 
现在您拥有 S3,您可以运行部署命令:
dotnet lambda deploy-serverless
系统会提示您输入 CloudFormation 堆栈名称。
Enter CloudFormation Stack Name: (CloudFormation stack name for an AWS Serverless application)
输入 AspNetCoreWebAPI

然后,系统会要求您输入 S3 桶名称。使用您之前创建的桶的名称,或您要用于此目的的现有桶的名称。

输入之后,开始建置和部署过程。

这会比使用 lambda.* project templates 的示例花费更长时间,因为需要建立和连接更多的基础设施。

输出将分为两个不同的部分。

顶部部分将与您之前部署函数时所看到的相似,即项目的发布和压缩,但这次构件将上传到 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%
之后,将建立 CloudFormation 堆栈,并部署所有所需的资源。
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/

底部就是您可以用来调用 API 的公开 URL。

调用函数

如果在浏览器或 Fiddler/Postman 等中打开该 URL,您会看到一个响应,例如“欢迎在 AWS Lambda 上运行 ASP.NET Core”。

然后尝试打开 https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/Prod/api/values,您将调用值控制器 GET 方法,就像在普通 Web API 应用程序中一样。

请注意,当使用 API Gateway 时,网关会自行设置 29 秒的超时。如果您的 Lambda 函数运行超过此时间,您将不会收到响应。

如果您有兴趣,有几种方法可以检阅已建立的资源。

要查看创建的 AWS 资源,您可以使用:
aws cloudformation describe-stack-resources --stack-name AspNetCoreWebAPI
这将为您提供关于堆栈中每个资源的详细信息。

如果您想要更简洁的输出,请使用:
aws cloudformation describe-stack-resources --stack-name AspNetCoreWebAPI --query 'StackResources[].[{ResourceType:ResourceType, LogicalResourceId:LogicalResourceId, PhysicalResourceId:PhysicalResourceId}]'
另一种方法是打开 AWS 管理控制台并导航至 Lambda 服务。在屏幕左侧,选择应用程序(而不是函数)。


您将看到您创建的应用程序。选择它,然后滚动到打开的页面的底部。您将在此处看到创建的所有资源。
您现在看到了 Lambda 函数的三个例子,第一个是一个简单的 Hello World,采用字符串,第二个也非常简单,但是采用了 JSON 对象,而第三个则更复杂,但仍然很容易建立和部署。

使用这些示例,您可以建立和部署自己的 Lambda 函数。您可能已经了解了一些关于如何调用 .NET Lambda 函数的知识。这就是下一节的主题。

函数 URL – API 网关的替代方案

如果您只需要一个 Lambda 函数来响应简单的 HTTP 请求,则应考虑使用 Lambda 函数 URL。

它们允许您将 HTTPS 端点指派给 Lambda 函数。然后,您可以通过向 HTTPS 端点提出请求来调用 Lambda 函数。有关更多信息,请参阅此博客文章这些文档。

清理您创建的资源

要删除 Lambda 函数,请运行:
dotnet lambda delete-function HelloEmptyFunction  
dotnet lambda delete-function HelloPersonFunction

请注意,上述命令不会删除您创建的角色。

要删除托管 Web API 应用程序的 Lambda 函数以及所有相关资源,请运行:

dotnet lambda delete-serverless AspNetCoreWebAPI

如何调用 .NET Lambda 函数

如您可以从上面的示例中看到,您可以使用简单的字符串、JSON 对象和 HTTP 请求调用 .NET Lambda 函数。Lambda 函数也可以被其他服务调用,例如 S3(发生文件变更时)、Kinesis(当事件到达时)、DynamoDB(在表格上发生变更时)、SMS(当消息到达时)、Step Functions 等。

Lambda 函数如何处理所有这些不同的调用方式?

在幕后,当 Lambda 服务运行函数处理程序并传递 JSON 输入时,会调用这些 Lambda 函数。如果您查看 aws-lambda-tools-defaults.json,您可以看到指定的 "function-handler":。对于 .NET Lambda 函数,处理程序包含 "AssemblyName::Namespace.ClassName::MethodName"。

Lambda 函数也可以通过传递流来调用,但这是一种不常见的情况,请参阅处理流页面了解更多信息。

每个 Lambda 函数都有一个函数处理程序。

除了 JSON 输入之外,Lambda 函数处理程序还可以采用一个可选的 ILambdaContext 对象。这让您访问当前调用的相关信息,例如剩余的时间、函数名称和版本。您也可以通过 ILambdaContext 对象写入日志消息。

所有事件都是 JSON

AWS 服务非常容易调用 .NET Lambda 函数的原因是这些服务会发出 JSON,如上所述,.NET Lambda 函数接受 JSON 输入。不同服务引起的事件都会产生不同形状的 JSON,但 AWS Lambda 事件 NuGet 套件包含所有相关对象类型,以便将 JSON 序列化为对象供您使用。

如需可用 Lambda 套件的清单,请参阅 https://www.nuget.org/packages?packagetype=&sortby=relevance&q=Amazon.Lambda&prerel=True,您必须在这些结果中搜索您感兴趣的事件类型。

例如,如果您想要触发 Lambda 函数来响应 S3 桶上的文件变更,则需要创建一个 Lambda 函数,该函数接受类型 S3Event 的对象。然后,您将 Amazon.Lambda.S3Events 套件添加到您的项目中。然后将函数处理程序方法更改为:

public async string FunctionHandler(S3Event s3Event, ILambdaContext context)
{
    ...
}

这就是处理 S3 事件所需的,您可以以编程方式检查事件,查看在文件上执行了什么操作,它在哪个桶中等。Amazon.Lambda.S3Events 允许您处理事件,而不是使用 S3 本身,如果您想与 S3 服务互动,则需要将 AWSSDK.S3 NuGet 套件新增到您的项目中。稍后的模块将介绍 AWS 服务调用 Lambda 函数的主题。

对于其他类型的事件,则会使用相同的模式,添加 NuGet 套件,将参数更改为函数处理程序,然后您可以使用事件对象。

以下是您可能用来处理来自其他服务的事件的一些常见套件:

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

调用 Lambda 函数时,您不限于使用 AWS 定义的事件类型。您可以自己创建任何类型的事件,请记住 Lambda 函数可以接收您发送的任何 JSON

序列化操作方式

您会记住 Lambda 项目模板有两个大类别,以“lambda.”开头的类别,以及以“serverless.”开头的类别。

在“lambda.”模板的情况下,Function.cs 文件的顶部附近有一个组件属性,该属性负责将传入事件反序列化到函数处理程序中的 .NET 类型。在 .csproj 文件中,有一个引用到 Amazon.Lambda.Serialization.SystemTextJson 套件。
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
您不需要采取其他行动。

对于“serverless.”模板,它的工作方式有些不同。

函数处理程序是在 serverless.template 文件中指定的。如果您要部署 serverless.AspNetCoreWebAPI 应用程序,请在 Resources.AspNetCoreFunction.Properties.Handler 下寻找值。此类型项目的处理程序将以此格式结尾:Assembly::Namespace.LambdaEntryPoint::FunctionHandlerAsync。

LambdaEntryPoint 类将位于您的项目中,它继承具有 FunctionHandlerAsync 方法的类。

函数处理程序可设置为处理四种不同的事件类型:API Gateway REST API、API Gateway HTTP API 负载版本 1.0、API Gateway HTTP API 负载版本 2.0 以及应用程序负载均衡器。

通过更改 LambdaEntryPoint 沿用的类别,您可以更改函数处理程序处理的 JSON 事件类型。

虽然 Lambda 函数看起来正在使用您定义的 JSON 回应您发送的 HTTP 请求,但情况并非如此。实际上,您的 HTTP 请求是由网关或负载均衡器处理,然后创建一个 JSON 事件,该事件发送到您的函数处理程序。此 JSON 事件将包含最初包含在 HTTP 请求中的数据,包括源 IP 地址和请求标头的所有内容。

并发

使用 Lambda 函数时,有两种并发需要考虑:保留并发和预置并发。

AWS 帐户对并发 Lambda 执行数目有默认最大限制。截至本文时,该限制为 1,000。

当您为函数指定保留并发时,您确保该函数将能够达到指定的同时执行数量。例如,如果您的函数的保留并发为 200,则您确保该函数将能够达到 200 个函数同时执行。请注意,这会为其他函数保留 800 个并发执行(1000-200=800)。

当您指定预置并发性时,您会初始化 Lambda 执行环境的指定数目。当它们初始化后,Lambda 函数将能够立即响应请求,避免“冷启动”问题。但是,使用预置并发需要支付费用。

有关更多信息,请参阅管理 Lambda 预留并发性管理 Lambda 预置的并发性

冷启动和热启动

在调用 Lambda 函数之前,必须初始化执行环境,此操作 Lambda 服务会代您完成。您的源代码是从 AWS 托管的 S3 桶(适用于使用托管运行时系统和自定义运行时系统的函数),或从 Elastic Container Registry(适用于使用容器映像的函数)下载的。

当您的函数第一次运行时系统,您的代码需要被 JITed,并运行初始化代码(例如您的构建模块)。这增加了冷启动时间。

如果您的函数定期被调用,它将保持“温暖”,即将维持执行环境。后续调用该函数不会受到冷启动时间的影响。“温启动”比“冷启动”更快。

如果您的函数在一段时间内未调用(Lambda 服务未指定确切的时间),则会移除执行环境。下一次调用该函数将再次导致冷启动。

如果您上传函数代码的新版本,则下次调用函数将导致冷启动。

对于在 Lambda 上运行 .NET,托管的运行时系统、自定义运行时系统以及托管容器的三个选项都有不同的冷启动设置文件。最慢的是容器,稍快的是自定义运行时系统,最快的是托管的运行时系统。如果可能,在运行 .NET Lambda 函数时,您应始终选择托管的运行时系统。

您会发现冷启动在测试或开发环境中发生更频繁,而不是在生产环境中。在 AWS 分析中,低于 1% 的调用中发生冷启动。

如果您在生产中有一个 Lambda 函数不常使用,但需要快速响应请求,并且想避免冷启动,则可以使用预置并发,或使用机制经常“ping”函数以保持函数不被移除。

如果您对有关最佳化 Lambda 函数的更多信息感兴趣,可以在 AWS Lambda 开发人员指南中阅读有关冷启动、温启动和预置并发的信息,或查看 Lambda 操作:性能优化博客系列 James Beswick - 第 1 部分第 2 部分第 3 部分

调整 .NET 7 之前的 .NET 版本并准备运行

如果您选择在 .NET 7 之前的 .NET 版本使用 Lambda 自定义运行时系统,您可以使用一些 .NET 功能来减少冷启动时间。

PublishTrimmed 将通过从包中修剪不必要的库来减少您部署的包的整体大小。

PublishReadyToRun 将提前对您的代码进行编译,通过减少所需的即时编译量来进行提前编译。但它会增加您部署的包的大小。

为了获得最佳性能,您必须在使用这些选项时测试您的函数。

您可以从 .csproj 文件启用 PublishTrimmed 和 PublishReadyToRun。

<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>true</PublishReadyToRun>

结论

此模块已向您介绍如何建立、部署和调用 .NET Lambda 函数。它为您概述了在 AWS Lambda 上运行 .NET 的各种方式,并涵盖了您可能选择一种方式而非另一种方式的一些原因。它还解释了有关“冷启动”和优化的概念。


.NET 7 的原生前期编译

.NET 7 于 2022 年 11 月发布,支持原生的前期(AOT)编译。AWS Lambda 的 .NET 模板工具可让您构建和部署 .NET 7 自定义运行时系统函数。测试中,与 .NET 6 托管运行时系统相比,.NET 7 Lambda 函数的冷启动速度快 86%。
 
要利用此新功能,您需要将 .NET Lambda 项目模板更新为 6.7.0 或更新版本,并将 .NET 的 AWS 扩展更新为 5.6.1 或更新版本。
 
要执行更新,请运行:
dotnet new -i "Amazon.Lambda.Templates::*"dotnet tool update -g Amazon.Lambda.Tools
在编写本文时,Lambda AOT 函数有两个模板:lambda.NativeAOT 和 serverless.NativeAOT。您还需要安装并运行 Docker。

知识测验

现在,您已经完成了模块 2:“使用 AWS Lambda 的 .NET 开发工具”。以下测试可让您检查到目前为止学到的内容。

1.Lambda 服务提供哪些版本的 .NET 托管运行时系统?(选择两个)

a. .NET Core 5

b. .NET 6

c. .NET 7

d. .NET Core 3.1

e. .NET Framework 4.8

2.冷启动是什么意思?(选择一项)

a.启动 Lambda 执行环境和函数初始化代码所需的时间。

b.使用 AWS S3 Glacier 存储的 Lambda 函数。

c.将代码部署到 Lambda 服务所需的时间。

d.更新函数所需的时间

3.您如何将 AWS .NET 软件开发套件与 .NET Lambda 函数一起使用?

a.在项目文件中添加 SDK 套件的引用。

b.您不需要一起使用,它包括 Lambda 函数模板

c.您不需要使用,IDE 的工具套件包括该函数

d.通过 AWS 管理控制台将 SDK 添加至 Lambda 服务

4.当您建立新的 lambda.EmptyFunction 项目时,指定函数配置的文件名是什么?

a. serverless.template

b. lambda.csproj

c. aws-lambda-tools-defaults.json

5.以下哪种方法是调用 Lambda 函数的方法?

a.使用 dotnet lambda tooling 的命令行

b.HTTPS 请求

c.从其他 AWS 服务调用

d.以上都是

答案:1-bd,2-a,3-a,4-c,5-d

结论

此模块已向您介绍如何建立、部署和调用 .NET Lambda 函数。它为您概述了在 AWS Lambda 上运行 .NET 的各种方式,并涵盖了您可能选择一种方式而非另一种方式的一些原因。它还解释了有关“冷启动”和优化的概念。

此页内容对您是否有帮助?

使用其他 AWS 服务