无服务器 – 快速指南
无服务器 – 快速指南
无服务器 – 简介
什么是无服务器?
嗯,这个名字给了你一些提示。无需为维护服务器而头疼的计算——这是无服务器计算(或简称无服务器)的关键。这个概念非常具有革命性和破坏性。它已被广泛采用。几个新的应用程序从设计无服务器后端开始,带有专用服务器的遗留应用程序也在慢慢迁移到无服务器架构。那么是什么导致了无服务器的广泛采用?与所有事情一样,经济学使无服务器变得非常有利。
你看,使用无服务器,你只需为你使用的东西付费。想象一下,您需要每天对数据库执行一些日常维护。这个过程每天可能需要大约 10 分钟。
现在,在没有无服务器计算的情况下,您的维护 cron 可能驻留在服务器中。除非您在剩余时间内有其他事情与您的服务器有关,否则您最终可能会为一项需要 10 分钟的任务支付 24 小时的费用。太浪费钱了吧?如果您被告知有一项新服务会针对您的维护 cron 执行所需的 10 分钟向您收费,该怎么办?您不想简单地切换到该新服务吗?这正是无服务器采用如此迅速和广泛的原因。它降低了几个组织的后端费用,也减少了他们的服务器维护难题。
云服务提供商(AWS、Azure 等)头疼地确保无服务器应用程序在需要时准确可用,并且数量充足。因此,在高负载期间,您可能会调用多个无服务器应用程序,而在正常负载期间您可能会调用单个应用程序。当然,您只需为高负载期间的额外调用付费。
什么是无服务器?
上面解释的概念看起来很棒,但你如何实现它呢?你需要一个框架。它被称为,呃,无服务器。
无服务器框架帮助我们开发和部署旨在以无服务器方式运行的功能/应用程序。该框架向前迈进了一步,负责部署我们的无服务器功能运行所需的整个堆栈。什么是栈?好吧,该堆栈包含部署、存储和监控无服务器应用程序所需的所有资源。
它包括实际的函数/应用程序、存储容器、监控解决方案等等。例如,在 AWS 的上下文中,您的堆栈将由您的实际 Lambda 函数、用于您的函数文件的 S3 存储桶、链接到您的 Cloudwatch 资源的 Cloudwatch 资源组成。函数,等等。无服务器框架为我们创建了整个堆栈。这使我们能够完全专注于我们的功能。无服务器消除了维护服务器的麻烦,而无服务器(框架)消除了创建和部署运行我们的功能所需的堆栈的麻烦。
无服务器框架还负责为我们的功能/应用程序分配必要的权限。一些应用程序(我们将在本教程中看到的示例)甚至需要将数据库链接到它们。无服务器框架再次负责创建和链接数据库。无服务器如何知道堆栈中包含哪些内容以及提供哪些权限?所有这些都在 serverless.yml 文件中提到,这将是我们在本教程中的主要关注点。在接下来的章节中有更多关于它的内容。
AWS 中的无服务器
AWS 的许多服务都属于“无服务器计算”的范畴。您可以在此处找到完整的组织列表。有计算服务、集成服务,甚至数据存储服务(是的,AWS 甚至有无服务器数据库)。我们将在整个教程中重点介绍 AWS Lambda 函数。那么什么是 AWS Lambda?AWS Lambda 网站将其定义如下 –
AWS Lambda 是一种无服务器计算服务,可让您运行代码而无需预置或管理服务器、创建工作负载感知集群扩展逻辑、维护事件集成或管理运行时。
通俗地说,AWS Lambda 是您在 AWS 上进行无服务器计算的窗口。正是 AWS Lambda 让无服务器概念如此流行。您需要做的就是定义您的函数和您的函数的触发器,并且该函数将在您希望它被调用的时候准确地被调用,并且您只需为该函数执行所需的时间付费。更重要的是,您可以将 AWS Lambda 与 AWS 提供的几乎所有其他服务相关联——EC2、S3、dynamoDB 等。
因此,如果您已经是 AWS 生态系统的一部分,那么 Lambda 集成是非常无缝的。如果您像我第一次了解 AWS Lambda 时一样刚接触 AWS 生态系统,它将成为通往 AWS 世界的良好门户。
在本教程中,我们将学习有关使用无服务器框架部署 AWS Lambda 函数的所有信息。你兴奋吗?然后继续下一章开始。
无服务器 – 安装
无服务器安装已在另一个 tutorialspoint 教程中介绍。在这里复制它,并进行一些修改和添加。
第 1 步 – 安装 nodejs
首先,您需要先安装 nodejs。您可以通过打开命令提示符并输入node -v来检查您的机器中是否安装了 nodejs 。如果已安装,您将获得节点的版本号。否则,您可以从这里下载并安装节点。
第 2 步 – 使用 npm 命令安装无服务器
您可以使用以下命令安装无服务器(npm 代表节点包管理器) –
npm install -g serverless
您可以通过运行serverless create –help来检查它是否已成功安装。如果无服务器安装成功,您应该会看到创建插件的帮助屏幕。
请注意,您可以在所有命令中使用速记sls而不是serverless。
第 3 步 – 配置凭据
您需要从 AWS 获取凭证以配置无服务器。为此,请在 AWS 控制台中创建用户(通过 IAM -> 用户 -> 添加用户)或单击 IAM -> 用户中的现有用户。如果您要创建新用户,则需要附加一些必需的策略(如 Lambda 访问、S3 访问等)或为用户提供管理员访问权限。
创建用户后,您将能够看到访问密钥和秘密密钥。请保持这个非常安全和保密。
如果您是现有用户,则可以按照此处提到的步骤生成新的访问密钥和密钥。
一旦您掌握了访问密钥和密钥,您就可以使用以下命令在无服务器中配置凭据 –
serverless config credentials --provider aws --key 1234 --secret 5678 --profile custom-profile
配置文件字段是可选的。如果您将其留空,则默认配置文件为“aws”。请记住您设置的配置文件名称,因为您将不得不在我们将在下一个教程中看到的 serverless.yml 文件中提及它。
如果您已完成上述步骤,则无服务器配置已完成。继续下一章以创建您的第一个无服务器项目。
无服务器 – 部署功能
创建新项目
导航到要在其中创建要部署到无服务器的第一个项目的新文件夹。在该文件夹中,运行以下命令 –
sls create --template aws-python3
此命令将创建样板代码,用于使用无服务器和 Python 运行时部署 lambda 函数。
请注意,您也可以使用其他运行时。运行sls create –help以获取所有模板的列表。
创建样板代码后,您将在文件夹中看到两个文件:handler.py 和 serverless.yml。handler.py 是包含 lambda 函数代码的文件。serverless.yml 是告诉 AWS 如何创建 lambda 函数的文件。配置文件或设置文件将成为本教程几章的重点。让我们先浏览一下 handler.py 文件。
import json def hello(event, context): body = { "message": "Go Serverless v1.0! Your function executed successfully!", "input": event } response = { "statusCode": 200, "body": json.dumps(body) } return response # Use this code if you don't use the http event with the LAMBDA-PROXY # integration """ return { "message": "Go Serverless v1.0! Your function executed successfully!", "event": event } """
它包含一个函数hello。这个函数接受两个参数:事件和上下文。这两个都是任何 AWS Lambda 函数的必需参数。每当调用 lambda 函数时,lambda 运行时都会将两个参数传递给函数 – 事件和上下文。
该事件的参数包含了lambda函数来处理数据。例如,如果您通过 REST API 触发您的 lambda 函数,则您在路径参数或 API 主体中发送的任何数据都将发送到事件参数中的 lambda 函数。在后面的章节中会有更多的介绍。需要注意的重要一点是,事件通常是 python dict类型,但也可以是str、float、int、list或NoneType类型。
该上下文对象是在运行时传递到您的lambda表达式的另一个理由。它不经常使用。AWS 官方文档指出,此对象提供了提供有关调用、函数和运行时环境信息的方法和属性。您可以在此处阅读有关事件和上下文对象的更多信息。
该功能非常简单。它只是返回一条状态代码为 200 的消息。如果我们不使用带有 LAMBDA-PROXY 设置的 HTTP 事件,则应使用底部的注释。更多关于 API 触发的 lambda 章节。
现在,让我们看看 serverless.yml 文件。这是一个被大量评论的文件。这些评论对于开始使用无服务器的人非常有用。我们鼓励您彻底阅读评论。在接下来的章节中,我们将研究很多与 serverless.yml 相关的概念。让我们简单地浏览一下这里的基本概念。
如果您在删除注释后查看 serverless.yml 文件,它会是这样的 –
service: aws-serverless frameworkVersion: '2' provider: name: aws runtime: python3.8 lambdaHashingVersion: 20201221 functions: hello: handler: handler.hello
service 字段确定 CloudFormation 堆栈的名称,您的 lambda 函数和所有必需的资源将在其中创建。将服务视为您的项目。执行 AWS Lambda 函数所需的一切都将在该服务中创建。您可以设置您选择的服务名称。
框架版本是指无服务器框架的版本。这是一个可选字段,通常保留以确保与您共享代码的人使用相同的版本号。如果 serverless.yml 中提到的 frameworkVersion 与您机器中安装的 serverless 版本不同,您将在部署过程中收到错误消息。您还可以为 frameworkVersion 指定一个范围,例如frameworkVersion − >=2.1.0 && <3.0.0。您可以在此处阅读有关 frameworkVersions 的更多信息。
下一部分provider可以被视为一组全局设置。我们将在后面的章节中讨论 provider 中涵盖的其他参数。在这里,我们将重点介绍可用的参数。该名称字段用于确定您的平台环境,这是在这种情况下,AWS的名称。运行时是 python3.8,因为我们使用了 python3 模板。lambdaHashingVersion 指的是框架应该使用的散列算法的名称。
请注意,如果您在上一章的配置凭据步骤中添加了自定义配置文件,则需要在提供中添加配置文件参数。例如,我将我的个人资料名称设置为 yash-sanghvi。因此,我的提供者看起来像 –
provider: name: aws runtime: python3.8 lambdaHashingVersion: 20201221 profile: yash-sanghvi
最后,functions 块定义了所有的 lambda 函数。我们这里只有一个函数,在处理程序文件中。函数的名字是你好。函数的路径在处理程序字段中提到。
部署函数
要部署您需要打开命令提示符的功能,导航到包含 serverless.yml 的文件夹,然后输入以下命令 –
sls deploy -v
所述-v是一个可选参数,表示详细的输出。它可以帮助您更好地了解后台进程。部署您的函数后,您应该能够在 us-east-1 区域(这是默认值)的 AWS 控制台上看到它。您可以使用“测试”功能从控制台调用它(您可以保留相同的默认事件,因为我们的 lambda 函数无论如何都不使用事件输入)。您还可以使用命令提示符进行测试 –
sls invoke --function hello
请注意,如果您的函数与 S3 或 dynamoDB 等其他 AWS 服务接口,则您无法始终在本地测试您的函数。只能在本地测试非常基本的函数。
从现有项目部署功能
如果要将现有项目部署到 AWS,请修改现有函数以仅接受事件和上下文作为参数。接下来,在文件夹中添加一个 serverless.yml 文件,并在 serverless.yml 中定义您的函数。然后打开命令提示符,导航到该文件夹,然后点击sls deploy -v。这样,您现有的函数也可以部署到 AWS Lambda。
无服务器 – 区域、内存大小、超时
我们在上一章中看到了如何使用无服务器部署我们的第一个函数。在本章中,我们将查看可以对函数执行的一些配置。我们将主要查看区域、内存大小和超时。
地区
默认情况下,所有使用无服务器部署的 lambda 函数都在 us-east-1 区域中创建。如果您希望您的 lambda 函数在不同的区域中创建,您可以在提供程序中指定。
provider: name: aws runtime: python3.6 region: us-east-2 profile: yash-sanghvi
无法在同一个 serverless.yml 文件中为不同的函数指定不同的区域。您应该只在特定的 serverless.yml 文件中包含属于单个区域的函数。属于单独区域的函数可以使用单独的 serverless.yml 文件进行部署。
内存大小
AWS Lambda 根据所选内存按比例分配 CPU。通过最近宣布的更改,您可以为 lambda 函数选择最多 10GB 的 RAM(之前约为 3GB)。
选择的 RAM 越高,分配的 CPU 越高,函数执行得越快,执行时间越短。AWS Lambda 按消耗的 GB 数向您收费。因此,如果一个函数在 1 GB RAM 上执行需要 10 秒,而在 2 GB RAM 上执行需要 5 秒,则您将被收取相同的费用。将内存加倍所花费的时间是否减半在很大程度上取决于您的函数的性质,您可能会也可能不会通过增加内存而受益。关键的一点是,分配的内存量是每个 lambda 函数的重要设置,也是您想要控制的一个。
使用无服务器,很容易为 serverless.yml 文件中定义的函数设置内存大小的默认值。也可以为不同的功能定义不同的内存大小。让我们看看如何。
为所有函数设置默认内存大小
默认值总是在提供者中提及。该值将由 serverless.yml 中的所有函数继承。该memorySize键用于设置此价值。值MB表示。
provider: name: aws runtime: python3.6 region: us-east-2 profile: yash-sanghvi memorySize: 512 #will be inherited by all functions
如果您没有在提供程序中指定 memorySize,也没有在单个函数中指定 memorySize,则将考虑默认值 1024。
为某些函数设置自定义内存大小
如果您希望某些函数具有与默认内存不同的值,则可以在 serverless.yml 的函数部分中指定它。
functions: custom_memory_func: #will override the default memorySize handler: handler.custom_memory memorySize: 2048 default_memory_func: #will inherit the default memorySize from provider handler: handler.default_memory
超时
就像 memorySize 一样,可以在提供程序中设置超时的默认值(以秒为单位),并且可以在函数部分中指定各个函数的自定义超时。
如果您未指定全局或自定义超时,则默认值为 6 秒。
provider: name: aws runtime: python3.6 region: us-east-2 profile: yash-sanghvi memorySize: 512 #will be inherited by all functions timeout: 50 #will be inherited by all functions functions: custom_timeout: #will override the default timeout handler: handler.custom_memory timeout: 30 default_timeout_func: #will inherit the default timeout from provider handler: handler.default_memory
确保将超时保持在一个保守的值。它不应该小到你的函数经常超时,也不应该大到你的函数中的一个错误让你付出巨大的代价。
无服务器 – 服务
您不希望为您部署的每个函数创建单独的 serverless.yml 文件。那会非常乏味。幸运的是,无服务器提供了在同一个 serverless.yml 文件中部署多个功能的规定。所有这些功能都属于一个称为“服务”的组。服务名称通常是 serverless.yml 文件中定义的第一件事。
service: my-first-service provider: name: aws runtime: python3.6 stage: prod region: us-east-2 profile: yash-sanghvi functions: func1: handler: handler1.func1 func2: handler: handler2.func2
服务中的所有函数在部署时在 AWS Lambda 控制台上采用以下名称格式 – service_name-stage_name-function_name。因此,上面示例中的两个函数在部署时将采用名称 – my-first-service-prod-func1和my-first-service-prod-func2。stage 参数可帮助您区分代码开发的不同阶段。
因此,如果您的功能处于开发阶段,您可以使用阶段dev;如果是在测试阶段,可以使用阶段测试;如果它正在生产中,您可以使用 stage prod。这样,您可以确保对开发阶段所做的更改不会影响生产代码。艺名不是一成不变的。dev、test、prod只是示例。
您可以选择任何艺名。请注意,如果您有 API Gateway 触发的 lambdas(在后面的章节中有更多内容),那么每个阶段的端点都会不同。
此外,如果您转到 AWS Lambda 控制台的较少使用的“应用程序”部分,您将能够看到整个服务与舞台。
如果您单击您选择的服务和阶段组合,您将能够在一个地方看到该服务使用的所有资源 – Lambda 函数、API 网关、事件规则、日志组、S3 存储桶等等。
更有趣的是,您可以转到“监控”选项卡并查看整个服务的性能 -> 调用次数、平均持续时间、错误计数等。您可以了解哪个功能对服务的贡献最大你的账单。当您的服务中有多个功能时,监控每个功能的性能就变得非常困难。服务级别监控选项卡在这里有很大帮助。
最后,“部署”选项卡可帮助您查看服务的所有过去部署和部署状态。
无服务器 – 计划的 Lambda
通常,您需要以固定的时间间隔调用您的函数。可以是一天一次、一周两次、工作日一分钟一次等等。Serverless 提供了两种类型的事件以固定频率调用函数。它们是 cron 事件和 rate 事件。
定时事件
cron 事件比 rate 事件具有更大的灵活性。唯一的缺点是它不像利率事件那样容易理解。cron 表达式的语法在AWS 文档中定义–
cron(minutes hours day-of-month month day-of-week year)
可以看出,cron 表达式由 6 个字段组成。每个字段都可以接受一些可接受的值,还有一些,如 AWS 所称,通配符。
让我们先看看接受的值 –
-
分钟– 0-59
-
小时– 0-23
-
月中的某天– 1-31
-
月– 1-12 或 JAN-DEC
-
星期几– 1-7 或 SUN-SAT
-
年– 1970-2199
现在接受的值很清楚,让我们看看通配符。cron 表达式中总共可能有 8 个通配符(有些允许所有 6 个字段,有些仅适用于特定字段)。在这里列出它们 –
-
*(星号,所有 6 个字段都允许) – 这是最流行的通配符。它只是说包括该字段的所有值。小时字段中的 * 表示 cron 每小时运行一次。月份字段中的 * 表示 cron 将每天运行。
-
,(逗号,允许所有 6 个字段) – 这用于指定多个值。例如。如果你想让你的 cron 在每小时的第 5、7 和 9 分钟运行,你的分钟字段看起来像 5、7、9。同样,星期几的 MON、TUE、WED、THU、FRI字段可能意味着 cron 应该只在工作日运行。
-
-(破折号,允许所有 6 个字段) – 此通配符指定范围。在前面的通配符示例中,为了指定工作日,而不是指定 5 个逗号分隔值,我们可以简单地编写 MON-FRI
-
? (问号,只允许用于月份和星期几) – 这就像一个无关紧要的通配符。如果您在星期几字段中指定了 MON,您就不必关心星期一的日期。因此,您将进入?代替月份的日期。同样,如果您希望 cron 在每个月的 5 号运行,您将在 day-of-month 字段中输入 5 和 ? 在星期几字段中,因为您不在乎每个月的 5 号是哪一天。请注意,AWS 文档明确指出您不能将 * 用于星期几和月份字段。如果你使用 * 作为一个,你必须使用 ? 对于另一个
-
/(正斜杠,允许除日期以外的 5 个字段) – 此字段指定增量。如果您在小时字段中输入 0/2,则此 cron 将每隔偶数小时运行一次(0、0+2、0+2+2 等)。如果您在 hours 字段中指定 1/2,则此 cron 将每隔奇数小时运行一次(1、1+2、1+2+2 等)。正如您所猜到的,/ 前面的值是起始值,它后面的值定义了增量。
-
L(仅允许用于月份和星期几) – 指定月份的最后一天或一周的最后一天
-
W(仅适用于某天) – 这指定了最接近该月特定日期的工作日(周一至周五)。因此,如果您在 day-of-month 字段中指定 8W,并且它对应于工作日,例如星期二,那么 cron 将在 8 号本身触发。但是如果 8 对应一个周末,比如说星期六,那么 cron 将在 7 日(星期五)被触发。如果 8th 对应于星期日,那么 cron 将在 9th(星期一)启动。这是最少使用的通配符之一。
-
#(仅允许用于星期几) – 这是一个非常特殊的通配符,最好通过示例来理解。假设您希望在母亲节运行一个 cron。现在,母亲节是每年 5 月的第二个星期日。因此,您的月份字段将包含 MAY 或 5。但是如何指定第二个星期日?进入主题标签。表达式为 0#2。通配符前面的值是星期几(星期日为 0,星期一为 1,依此类推)。通配符后面的值指定出现的次数。因此,这里的 2 指的是第二个星期日或第二个星期日。
现在,要为您的 lambda 函数定义 cron 触发器,您需要做的就是在 serverless.yml 中函数的 events 键中指定 cron 表达式。
functions: cron_triggered_lambda: handler: handler.hello events: - schedule: cron(10 03 * * ? *) #run at 03:10 (UTC) every day.
一些例子
下面给出了一些 cron 表达式的例子 –
-
cron(30 15 ? * MON-FRI *) – 每个工作日的 15:30 (UTC) 触发
-
cron(0 9 ? 6 0#3 *) – 在六月的第三个星期日(父亲节)的 09:00 (UTC) 触发
-
cron(0/15 * ? * MON *) – 在星期一每 15 分钟触发一次
-
cron(0/30 9-18 ? * MON-FRI *) – 从工作日的上午 9 点到下午 5:30 每 30 分钟触发一次(对应多个地方的办公时间)
评价事件
与 cron 表达式相比,这要简单得多。语法很简单rate(value unit)。例如,速率(5 分钟)。
该值可以是任何正整数,允许的单位是分钟、小时、天。
为 lambda 函数定义速率触发器类似于定义 cron 触发器。
functions: rate_triggered_lambda: handler: handler.hello events: - schedule: rate(10 minutes) #run every 10 minutes
一些例子
-
rate(2 hours) – 每 2 小时触发一次
-
rate(1 day) – 每天触发(UTC 时间 00:00)
-
rate(90 分钟) – 每 90 分钟触发一次
正如您所意识到的,速率表达式的简单性是以降低灵活性为代价的。您可以为每 N 分钟/小时/天运行一次的 lambda 使用 rate。要执行更复杂的操作,例如仅在工作日触发 lambda,您必须使用 cron 表达式。
请注意,如果您的 cron 表达式以某种方式导致不到一分钟的触发时间,它将不受支持。
参考
无服务器 – API 网关触发的 Lambda
API 网关是另一种触发 lambda 的流行方法,就像 cron/rate 事件一样。基本上,您会获得 lambda 函数的 URL 端点。此 URL 属于连接到您的 lambda 的 API 网关。每当您在浏览器中或通过应用程序调用 URL 时,您的 lambda 函数都会被调用。在本章中,我们将看到如何使用无服务器框架将 API 网关连接到您的 lambda 函数,以及如何对其进行测试。
HTTP 事件
要将 API 网关链接到 lambda 函数,我们需要在 serverless.yml 的函数定义中创建 HTTP 事件。以下示例展示了如何将 lambda 函数链接到 REST API 并使用 GET 请求触发它。
functions: user_details_api: handler: handler.send_user_details events: - http: path: details/{user_id} method: get integration: lambda-proxy cors: true location_api: handler: handler.send_location events: - http: path: location/{user_id} method: get integration: lambda-proxy cors: true
让我们一一拆开钥匙。我们将只讨论上述列表中的第一个函数 (user_details_api)。下面介绍的概念也适用于其他功能。
path 的值指定了调用 URL 的端点之后的地址。上面例子中定义的两个函数将共享同一个端点,但一个会使用 endpoint/details/{user_id} 调用,而另一个会使用 endpoint/location/{user_id} 调用 大括号内的元素是路径参数. 我可以发送任何值来代替 user_id,并且可以对 lambda 函数进行编程以返回该特定用户的详细信息(请参阅下面的示例函数)。
method 的值表示请求方法。流行的方法是 get 和 post。还有其他几种方法。深入研究这些方法的细节超出了本章的范围。还有另一篇关于tutorialspoint的帖子,你可以参考它的详细信息。
集成字段指定 lambda 函数将如何与 API 网关集成。默认是lambda-proxy,而其他可能的选项是lambda, http, http-proxy, mock。这两个选项中使用最广泛的选项是 lambda 和 lambda-proxy。用外行的话来说,lambda-proxy将整个控制权交给 lambda 函数,而lambda将一些控制权交给 API Gateway,一些控制权交给 lambda 函数。
如果您选择lambda-proxy作为集成类型,那么整个 HTTP 请求将以原始形式传递给您的 lambda 函数,并且由 lambda 函数发送的响应将不加更改地传递给发出请求的客户端。因此,您必须在 lambda 函数的响应中定义 statusCode 和标头。
如果您选择lambda作为集成类型,您的 API 网关可以对接收到的请求进行更改,然后再将其传递给 lambda 函数。同样,它也可以在将 lambda 函数发送的响应转发给客户端之前对其进行修改。API 网关将状态代码和标头添加到响应中,因此,lambda 函数只需担心发送正文。这两种选择都有其优点和缺点。
如果您喜欢简单,可以使用lambda 代理。如果您可以接受一些复杂性,您可以选择lambda(因为您将不得不担心 lambda 函数的代码以及 API 网关的配置),但需要更多控制。
你可以在这里阅读更多关于这两种类型之间的区别。 在其他集成类型中,将 API 网关与 HTTP 后端集成时使用的是http和http-proxy,而不是 lambda 函数,因此与我们无关。当您只想在不调用后端的情况下测试 API 时使用模拟。
该CORS –真正的配置使CORS(跨来源资源共享)。用外行的话来说,这意味着您允许来自另一个域的服务器的请求。如果没有cors – true,则只允许来自同一域的请求。当然,您可以只允许某些特定的域,而不是允许所有域。要了解如何做到这一点,请参阅文档。
对于 API-Gateway 触发的 lambda,无服务器中有更多可能的配置。强烈建议您阅读文档,或者至少将链接添加为书签,以便您可以在需要时查找。
示例 Lambda 函数
说到这里,你可能会疑惑,你创建了 API 网关触发函数,但是如何访问 lambda 函数中的路径参数呢?以下 Python 中的示例 lambda 函数将回答这个问题。当集成类型为lambda-proxy时,我们基本上使用‘pathParameters’属性。
import json def lambda_handler(event, context): # TODO implement # print(event) #helps you see the entire input request. The printed output can be found in CloudWatch logs user = event['pathParameters']['user_id'] return { 'statusCode': 200, 'body': json.dumps('Hello ' + str(user)) }
访问端点
现在,您可能遇到的另一个问题是如何访问端点。有多种方法可以做到这一点。第一种方法是通过无服务器部署。每当您通过服务部署一个函数或多个函数时,端点都会显示在无服务器部署的末尾。
第二种方法是通过 Lambda 控制台。如果您在 lambda 控制台上导航到您的函数,您可以看到附加到它的 API 网关。单击它应该会显示端点。
请注意,如上所述,一个服务中的所有函数共享同一个端点。路径属性将一个函数的实际触发器 URL 与另一个函数区分开来。
参考
无服务器 – 包括/排除
我们已经在“部署函数”一章中看到,要将函数从现有项目部署到 AWS lambda,您需要修改函数以将事件和上下文作为参数,并且需要在项目文件夹,定义了功能。然后点击无服务器部署就可以了。
非常频繁,特别是如果您被要求将一些函数从广泛的现有项目迁移到 AWS Lambda,您将面临大小挑战。如果您的项目足够大,您很可能会超过 AWS 对 Lambda 函数施加的大小限制(250 MB,包括应用程序代码及其依赖项)。
而一些像 NumPy 这样的依赖本身就占据了很大的空间。例如,NumPy 大约有 80 MB 大,SciPy 也在同一范围内,等等。在这种情况下,您留给应用程序代码的空间非常有限,并且需要一种从 lambda 部署包中排除不必要文件的方法。幸运的是,无服务器使这变得非常容易。
包含和排除字段
正如您所猜到的,您可以使用“排除”标记指定要从部署构建中排除的文件和文件夹。排除部分中未指定的所有文件/文件夹都默认包含在内。那么’include’标签有什么用呢?好吧,如果您希望一般排除某个文件夹,但只想在该文件夹中包含几个文件或子文件夹,您可以在“包含”标签中指定这些文件/子文件夹。这样,该文件夹中的所有其他文件都将被排除,只有在“包含”部分中指定的文件才会保留。下面的例子将更好地解释这一点。
service: influx-archive-pipeline provider: name: aws runtime: python3.6 stage: prod region: us-east-2 profile: yash-sanghvi timeout: 900 memorySize: 1024 # you can add packaging information here package: include: - src/models/config.py - src/models/lambda_apis/** - src/models/scheduled_lambdas/** exclude: - docs/** - models/** - notebooks/** - references/** - reports/** - src/data/** - src/visualization/** - src/models/** - utils/** functions: user_details_api: handler: src/models/lambda_apis/user_details_api.sync_user_details events: - http: path: details/{user_id} method: get integration: lambda cors: true monitoring_lambda: handler: src/models/scheduled_lambdas/monitoring_lambda.periodic_monitoring events: - schedule: cron(15 16 * * ? *)
正如您可以从上面的 severless.yml 文件中看到的,我们排除了根文件夹中包含 serverless.yml 的大部分文件夹。我们甚至排除了 src/models 文件夹。但是,我们希望在 src/models 中包含 2 个子文件夹和 1 个文件。因此,那些已特别添加到“包含”部分中。请注意,默认情况下将包含不属于排除部分的任何文件/文件夹。
注意两个 lambda 函数的路径。它们都位于 src/models 中。虽然默认情况下排除 src/models,但这些函数特别位于包含部分中提到的子文件夹中。因此,它们将毫无问题地执行。如果我在 src/data 中添加了一个函数,那将是不允许的,因为 src/data 的所有内容都已被排除。
请注意,指定/**表示该文件夹中的所有内容(文件/子文件夹)都被覆盖。因此,如果 docs 文件夹包含 10 个子文件夹和 12 个文件,所有这些都需要排除,那么-docs/**就可以完成这项工作。我们不需要单独提及每个文件/文件夹。
无服务器 – 插件
随着无服务器越来越流行,对利基用户案例的更多功能的需求会增加,这是很自然的。这些需求由插件来满足。顾名思义,插件是可选的,您只需要安装您需要的插件。在本章中,我们将看到如何访问可用于 serverless 的几个插件,如何安装这些插件,以及如何在 serverless.yml 中引用它们。
浏览插件列表
所有可用于无服务器的插件都可以在www.serverless.com/plugins/上找到
您可以在此处搜索插件。例如,如果您搜索“Python”,您将看到几个专门为 Python 运行时开发的插件。它们是按受欢迎程度排列的。
让我们看看最流行的 Python 插件(在撰写本文时):Python 要求。单击该插件。这将打开与此插件相关的详细文档。
本文档涵盖了两个最重要的方面 – 安装插件并在 serverless.yml 中引用它。对于任何插件都是如此。您只需打开其文档即可了解该插件的安装和使用情况。回到 Python 需求插件,文档说明该插件会自动捆绑来自 requirements.txt 的依赖项,并使它们在您的 PYTHONPATH 中可用。
换句话说,如果您的 lambda 函数需要其他依赖项,如 pandas、numpy、matplotlib 等,您只需在 requirements.txt 文件中指定这些依赖项,该文件与 serverless.yml 文件位于同一文件夹中。然后这个插件将完成剩下的工作。您甚至可以在 requirements.txt 中指定库的版本号。例如,这是一个示例 requirements.txt 文件的样子 –
aws-psycopg2==1.2.1 boto boto3==1.7.62 botocore==1.10.62 numpy==1.14.5 pandas==0.25.0 scipy==1.5.2 sqlalchemy==1.2.15
如您所见,您可以仅提及依赖项名称,也可以添加版本号(以 == 符号分隔)。当然,依赖项以及应用程序代码的大小不应超过 250 MB。因此,您必须仅包含您实际需要的那些依赖项。
现在,让我们回到我们的插件。我们已准备好requirements.txt 文件。下一步是安装插件。打开命令提示符并导航到包含 serverless.yml 文件的项目文件夹。然后,按照文档,运行以下命令来安装插件 –
sls plugin install -n serverless-python-requirements
事实上,如果您将serverless-python-requirements替换为任何其他插件名称,上述命令仍然适用于大多数插件。但是,无论何时安装新插件,都建议按照文档中给出的安装命令进行操作。当您运行上述命令时,您应该会看到类似于下图中的消息 –
如您所见,在项目文件夹中创建了一个 packages.json 文件。如果您的项目文件夹中有一个 packages.json 文件,它将被编辑以包含上述插件。此外,serverless.yml 文件将自动进行编辑以包含已安装的插件。如果您现在打开 serverless.yml 文件,您应该看到添加了以下几行 –
plugins: - serverless-python-requirements
这意味着 serverless.yml 中插件的引用是自动完成的。有几个与此插件相关的设置,可以在文档中找到。我们将在下一章中介绍与“交叉编译”相关的设置。但现在,让我们看看使用这个插件的效果。我在我的 requirements.txt 中添加了 numpy。这是我的 handler.py 文件的样子 –
import time import numpy def hello(event, context): print("second update!") time.sleep(4) print(numpy.random.randint(100)) return("hello")
现在让我将它部署到 lambda。您应该会看到类似于下图中的消息。关注包裹的大小。它现在 > 14 MB(这是压缩包的大小)而不是添加插件之前的 ~10 kB,因为 numpy 依赖项也被捆绑在一起。
这证明依赖项现在与应用程序代码捆绑在一起。您可以使用sls invoke local -f function_name 进行本地测试。如果您是 Windows 或 Mac 用户,在 AWS Lambda 控制台上测试部署的 lambda 函数可能会引发错误,类似于下面给出的错误 –
Unable to import module 'handler': IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE! Importing the numpy C-extensions failed. This error can happen for many reasons, often due to issues with your setup or how NumPy was installed. We have compiled some common reasons and troubleshooting tips at: https://numpy.org/devdocs/user/troubleshooting-importerror.html Please note and check the following: * The Python version is: Python3.8 from "/var/lang/bin/python3.8" * The NumPy version is: "1.19.4" and make sure that they are the versions you expect. Please carefully study the documentation linked above for further help. Original error was: No module named 'numpy.core._multiarray_umath'
继续阅读下一章,详细了解发生此错误的原因以及如何处理。
无服务器 – 打包依赖
在上一章中,我们看到了如何在无服务器中使用插件。我们专门研究了 Python 需求插件,并了解了它如何用于将 numpy、scipy、pandas 等依赖项与 lambda 函数的应用程序代码捆绑在一起。我们甚至看到了部署需要 numpy 依赖项的函数的示例。我们看到它在本地运行良好,但在 AWS Lambda 控制台上,如果您使用的是 Windows 或 Mac 计算机,则会遇到错误。让我们了解为什么该函数在本地运行但在部署后不运行。
如果您查看错误消息,您会得到一些提示。我特别指的是一行 – “导入 numpy C 扩展失败。” 现在,很多重要的python包,如numpy、pandas、scipy等,都需要编译C-extensions。如果我们在 Windows 或 Mac 机器上编译它们,那么 Lambda(linux 环境)在尝试加载它们时会抛出错误。所以重要的问题是,可以做些什么来避免这个错误。进来码头!
什么是码头工人?
根据维基百科,docker 是一组平台即服务 (PaaS) 产品,它使用操作系统级虚拟化以称为容器的包形式交付软件。如果你多扫描一下 docker 的维基百科页面,你会发现一些更相关的陈述 – Docker 可以将应用程序及其依赖项打包在一个可以在任何 Linux、Windows 或 macOS 计算机上运行的虚拟容器中。这使应用程序能够在各种位置运行,例如本地、公共云和/或私有云。看完上面的说法我想应该就很清楚了。我们出现错误,因为在 Windows/Mac 上编译的 C 扩展在 Linux 中不起作用。
我们可以通过将应用程序打包在可以在任何操作系统上运行的容器中来简单地绕过该错误。docker 在后台做什么来实现这个 OS 级别的虚拟化超出了本章的范围。
安装泊坞窗
您可以前往https://docs.docker.com/engine/install/安装 Docker Desktop。如果您使用的是 Windows 10 家庭版,则 Windows 版本应至少为 1903(2019 年 5 月更新)。因此,您可能需要在安装 Docker Desktop 之前升级您的 Windows 10 操作系统。此类限制不适用于 Windows Pro 或 Enterprise 版本。
在无服务器中使用 dockerizePip
在您的机器上安装 Docker Desktop 后,您只需在 serverless.yml 文件中添加以下内容即可使用 docker 打包您的应用程序和依赖项 –
custom: pythonRequirements: dockerizePip: true
请注意,如果您从上一章开始就一直跟着我,很可能您已经将代码部署到 lambda 一次。这会在您的本地存储中创建一个静态缓存。默认情况下,无服务器将使用该缓存来捆绑依赖项,因此不会创建 docker 容器。因此,为了强制无服务器创建使用 docker,我们将在 pythonRequirements 中添加另一条语句 –
custom: pythonRequirements: dockerizePip: true useStaticCache: false #not necessary if you will be deploying the code to lambda for the first time.
如果您是第一次部署到 lambda,则不需要最后一条语句。通常,您应该将 useStaticCache 设置为 true,因为当您没有对依赖项或它们必须打包的方式进行任何更改时,这将为您节省一些打包时间。
通过这些添加,serverless.yml 文件现在看起来像 –
service: hello-world-python provider: name: aws runtime: python3.6 profile: yash-sanghvi region: ap-south-1 functions: hello_world: handler: handler.hello timeout: 6 memorySize: 128 plugins: - serverless-python-requirements custom: pythonRequirements: dockerizePip: true useStaticCache: false #not necessary if you will be deploying the code to lambda for the first time.
现在,当您运行sls deploy -v命令时,请确保 docker 正在后台运行。在 Windows 上,您只需在“开始”菜单中搜索 Docker 桌面并双击该应用程序即可。您很快就会收到一条消息,表明它正在运行。您还可以通过 Windows 中电池图标附近的小弹出窗口来验证这一点。如果您可以在那里看到泊坞窗图标,则它正在运行。
现在,当您在 AWS Lambda 控制台上运行您的函数时,它会起作用。恭喜!!
但是,在 AWS Lambda 控制台的“函数代码”部分,您会看到一条消息,指出“您的 Lambda 函数“hello-world-python-dev-hello_world”的部署包太大,无法启用内联代码编辑。但是,您仍然可以调用您的函数。
似乎添加 Numpy 依赖项使包大小过大,因此我们甚至无法在 lambda 控制台中编辑我们的应用程序代码。我们如何解决这个问题?前往下一章找出答案。
参考
无服务器 – 层创建
什么是图层?
层是隔离代码块的一种方式。假设您要在应用程序中导入 NumPy 库。您信任该库,并且几乎不可能对该库的源代码进行更改。因此,如果 NumPy 的源代码弄乱了您的应用程序工作区,您将不会喜欢它。粗略地说,您只是希望 NumPy 位于其他地方,与您的应用程序代码隔离。图层允许您做到这一点。您可以简单地将所有依赖项(NumPy、Pandas、SciPy 等)捆绑在一个单独的层中,然后在无服务器中的 lambda 函数中简单地引用该层。和繁荣!该层中捆绑的所有库现在都可以导入到您的应用程序中。同时,您的应用程序工作区保持完全整洁。
由Iva Rajovic在Unsplash上拍摄的照片,表示层中的代码分离
层真正酷的地方在于它们可以跨功能共享。假设您使用包含 NumPy 和 Pandas 的 python-requirements 层部署了一个 lambda 函数。现在,如果另一个 lambda 函数需要 NumPy,则无需为此函数部署单独的层。您可以简单地使用前一个函数的图层,它也可以很好地与新函数配合使用。
这将在部署期间为您节省大量宝贵的时间。毕竟,您将只部署应用程序代码。依赖项已经存在于现有层中。因此,一些开发人员将依赖层保留在单独的堆栈中。然后他们在所有其他应用程序中使用该层。这样,他们就不需要一次又一次地部署依赖项。毕竟,依赖是相当沉重的。NumPy 库本身大约是。80 MB 大。每次更改应用程序代码(可能只有几 KB)时都部署依赖项将非常不方便。
添加依赖层只是一个例子。还有其他几个用例。例如,serverless.com 上给出的示例涉及使用 FFmpeg 工具创建 GIF。在那个例子中,他们将 FFmpeg 工具存储在一个层中。总之,AWS Lambda 允许我们为每个函数添加最多 5 个层。唯一的条件是 5 层和应用程序的总大小应小于 250 MB。
创建 python-requirements 层
现在让我们看看如何使用无服务器创建和部署包含所有依赖项的层。为此,我们需要serverless -python-requirements插件。该插件仅适用于 Serverless 1.34 及更高版本。因此,如果您的版本低于 1.34,您可能需要升级无服务器版本。您可以使用安装插件 –
sls plugin install -n serverless-python-requirements
接下来,您在 serverless.yml 的 plugins 部分中添加此插件,并在自定义部分中提及它的配置 –
plugins: - serverless-python-requirements custom: pythonRequirements: dockerizePip: true layer: true
在这里,dockerizePip – true启用了docker的使用,并允许您将所有依赖项打包到docker容器中。我们在上一章讨论过使用 docker 打包。layer – true告诉无服务器,python 需求应该存储在一个单独的层中。现在,此时,您可能想知道无服务器如何理解要打包哪些依赖项?正如插件章节中提到的,答案就在 requirements.txt 文件中。
定义层插件和自定义配置后,您可以将层添加到无服务器中的各个功能中,如下所示 –
functions: hello: handler: handler.hello layers: - { Ref: PythonRequirementsLambdaLayer }
关键字PythonRequirementsLambdaLayer来自CloudFormation Reference。通常,它派生自层的名称。语法为“LayerNameLambdaLayer”(TitleCased,无空格)。在我们的例子中,由于层名称是 python 要求,引用变为 PythonRequirementsLambdaLayer。如果您不确定 lambda 层的名称,您可以通过以下步骤获取它 –
-
运行sls 包
-
打开 .serverless/cloudformation-template-update-stack.json
-
搜索“LambdaLayer”
使用同一区域中另一个函数的现有图层
就像我在开头提到的那样,层的一个非常酷的地方是能够在您的函数中使用现有层。这可以通过使用现有层的 ARN 轻松完成。使用 ARN 将现有层添加到函数的语法非常简单 –
functions: hello: handler: handler.hello layers: - arn:aws:lambda:region:XXXXXX:layer:LayerName:Y
就是这样。现在,具有指定 ARN 的层将与您的函数一起使用。如果该层包含 NumPy 库,您可以简单地继续并在您的“hello”函数中调用import numpy。它将运行而不会出现任何错误。
如果您想知道从哪里可以获得 ARN,实际上很简单。只需导航到 AWS 控制台中包含层的函数,然后单击“层”。
当然,如果图层不属于您的帐户,则需要公开共享或专门与您的帐户共享。稍后再谈。
此外,请记住该层应与您的应用程序兼容。不要期望与 node.js 运行时兼容的层与在 python3.6 运行时中创建的函数一起运行。
非需求/通用层
正如开头所提到的,这些层的主要功能是隔离您的代码块。因此,它们不需要只包含依赖项。它们可以包含您指定的任何代码段。调用layer: true在自定义中的pythonRequirements中是一种由serverless -python-requirements插件实现的快捷方式。然而,要创建一个通用层,serverless.yml 中的语法,如serverless docs 中所述,如下 –
layers: hello: path: layer-dir # required, path to layer contents on disk name: ${opt:stage, self:provider.stage, 'dev'}-layerName # optional, Deployed Lambda layer name description: Description of what the lambda layer does # optional, Description to publish to AWS compatibleRuntimes: # optional, a list of runtimes this layer is compatible with - python3.8 licenseInfo: GPLv3 # optional, a string specifying license information # allowedAccounts: # optional, a list of AWS account IDs allowed to access this layer. # - '*' # note: uncommenting this will give all AWS users access to this layer unconditionally. retain: false # optional, false by default. If true, layer versions are not deleted as new ones are created
由于提供的注释,各种配置参数是不言自明的。除了“路径”,所有其他属性都是可选的。path 属性是您要与应用程序代码隔离的所选目录的路径。它将被压缩并发布为您的图层。例如,在serverless 上的示例项目中,他们在一个层中托管 FFmpeg 工具,他们将该工具下载到名为“layer”的单独文件夹中,并在 path 属性中指定。
layers: ffmpeg: path: layer
如前所述,我们最多可以在图层– 属性中添加 5 个图层。
要在您的函数中使用这些通用层,您可以再次使用 CloudFormation 参考或指定 ARN。
允许其他帐户访问图层
只需在“allowedAccounts”属性中提及帐号,就可以为更多帐户提供访问您的图层的权限。例如 –
layers: testLayer: path: testLayer allowedAccounts: - 999999999999 # a specific account ID - 000123456789 # a different specific account ID
如果您希望该图层可公开访问,您可以在 allowedAccounts 中添加“*” –
layers: testLayer: path: testLayer allowedAccounts: - '*'
参考
无服务器 – 带有 DynamoDB 的 REST API
到目前为止,我们已经学习了几个与无服务器 lambda 部署相关的概念。现在是看一些例子的时候了。在本章中,我们将查看 Serverless 官方提供的示例之一。顾名思义,我们将创建一个 REST API。正如您所猜到的,我们所有的 lambda 函数都将由 API 网关触发。我们的 lambda 函数将与 dynamoDB 表进行交互,该表本质上是一个待办事项列表,用户将能够使用端点执行多项操作,例如创建新项目、获取现有项目、删除项目等。在部署后公开。如果您不熟悉 REST API,那么您可以在此处阅读有关它们的更多信息。
代码演练
代码可以在 GitHub 上找到 – https://github.com/serverless/examples/tree/master/aws-python-rest-api-with-dynamodb
我们将看一下项目结构,讨论一些我们目前还没有看到的新概念,然后执行 serverless.yml 文件的演练。所有函数处理程序的演练将是多余的。因此,我们将只介绍一个函数处理程序。您可以将理解其他功能作为练习。
项目结构
现在,如果您查看项目结构,则 lambda 函数处理程序都位于 todos 文件夹中的单独 .py 文件中。serverless.yml 文件在每个函数处理程序的路径中指定了 todos 文件夹。没有外部依赖项,因此没有 requirements.txt 文件。
新概念
现在,您可能第一次看到几个术语。让我们快速扫描这些 –
-
dynamoDB – 这是 AWS 提供的 NoSQL(不仅是 SQL)数据库。虽然不完全准确,但从广义上讲,NoSQL 之于 SQL,就像 Word 之于 Excel。您可以在此处阅读有关 NoSQL 的更多信息。NoSQL 数据库有 4 种类型 – 文档数据库、键值数据库、宽列存储和图形数据库。dynamoDB 是一个键值数据库,这意味着您可以不断地将键值对插入到数据库中。这类似于redis缓存。您可以通过引用其键来检索该值。
-
boto3 – 这是适用于 Python 的 AWS 开发工具包。如果您需要在 lambda 函数中配置、管理、调用或创建 AWS 的任何服务(EC2、dynamoDB、S3 等),则需要 boto3 SDK。您可以在此处阅读有关 boto3 的更多信息。
除此之外,我们在演练 serverless.yml 和处理程序函数的过程中还会遇到一些概念。我们将在那里讨论它们。
serverless.yml 演练
serverless.yml 文件以服务的定义开始。
service: serverless-rest-api-with-dynamodb
然后通过以下行声明框架版本范围 –
frameworkVersion: ">=1.1.0 <=2.1.1"
这就像支票一样。如果您的无服务器版本不在此范围内,则会引发错误。当您共享代码并希望使用此 serverless.yml 文件的每个人都使用相同的 serverless 版本范围以避免出现问题时,这会有所帮助。
接下来,在提供程序中,我们看到了两个迄今为止尚未遇到的额外字段 – environment和iamRoleStatements。
provider: name: aws runtime: python3.8 environment: DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} iamRoleStatements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}: *:table/${self:provider.environment.DYNAMODB_TABLE}"
Environment,如您所料,用于定义环境变量。此 serverless.yml 文件中定义的所有函数都可以获取这些环境变量。我们将在下面的函数处理程序演练中看到一个示例。在这里,我们将 dynamoDB 表名称定义为环境变量。
该$符号表示的变量。该自我关键字指serverless.yml文件本身,而选择是指一个选项,我们可以在提供SLS部署。因此,表名将是服务名称,后跟一个连字符,后跟文件找到的第一个阶段参数:无服务器部署期间的选项可用,或提供者阶段,默认情况下为dev。因此,在这种情况下,如果您在无服务器部署期间不提供任何选项,则 dynamoDB 表名称将为 serverless-rest-api-with-dynamodb-dev。您可以在此处阅读有关无服务器变量的更多信息。
iamRoleStatements定义提供给函数的权限。在这种情况下,我们允许函数对 dynamoDB 表执行以下操作 – Query、Scan、GetItem、PutItem、UpdateItem和DeleteItem。资源名称指定允许这些操作的确切表。如果您输入了“*”代替资源名称,您将允许对所有表进行这些操作。但是,在这里,我们希望只允许对一个表进行这些操作,因此,使用标准 arn 格式在资源名称中提供了该表的 arn(Amazon 资源名称)。这里再次使用选项区域(在无服务器部署期间指定)或提供程序中提到的区域(默认为 us-east-1)中的第一个区域。
在函数部分,函数是按照标准格式定义的。请注意,get、update、delete 都具有相同的路径,其中 id 作为路径参数。但是,每个方法都不同。
functions: create: handler: todos/create.create events: - http: path: todos method: post cors: true list: handler: todos/list.list events: - http: path: todos method: get cors: true get: handler: todos/get.get events: - http: path: todos/{id} method: get cors: true update: handler: todos/update.update events: - http: path: todos/{id} method: put cors: true delete: handler: todos/delete.delete events: - http: path: todos/{id} method: delete cors: true
后来,我们遇到了另一个我们以前没有见过的块,资源块。此块基本上可以帮助您在 CloudFormation 模板中指定需要创建的资源,以使函数正常工作。在这种情况下,我们需要为函数创建一个 dynamoDB 表。到目前为止,我们已经指定了表的名称,甚至引用了它的 ARN。但是我们还没有创建表。在资源块中指定表的特征将为我们创建该表。
resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DYNAMODB_TABLE}
这里定义了很多配置,其中大多数特定于 dynamoDB。简而言之,我们要求无服务器创建一个“TodosDynamoDbTable”,或输入“DynamoDB Table”,其中 TableName(在底部提到)等于在 provider 的环境变量中定义的那个。我们将其删除策略设置为“保留”,这意味着如果删除堆栈,则保留资源。见这里。我们说该表将有一个名为id的属性,其类型将为 String。我们还指定 id 属性将是 HASH 键或分区键。您可以在此处阅读有关 dynamoDB 表中 KeySchemas 的更多信息。最后,我们指定表的读取容量和写入容量。
就是这样!我们的 serverless.yml 文件现已准备就绪。现在,由于所有函数处理程序或多或少相似,我们将只介绍一个处理程序,即 create 函数的处理程序。
创建函数处理程序的演练
我们有几个导入语句
import json import logging import os import time import uuid
接下来,我们导入 boto3,如上所述,它是适用于 Python 的 AWS 开发工具包。我们需要 boto3 从 lambda 函数内与 dynamoDB 接口。
import boto3 dynamodb = boto3.resource('dynamodb')
接下来,在实际的函数处理程序中,我们首先检查“事件”有效负载的内容(create API 使用 post 方法)。如果它的主体不包含“文本”键,我们还没有收到要添加到待办事项列表的有效项目。因此,我们提出了一个例外。
def create(event, context): data = json.loads(event['body']) if 'text' not in data: logging.error("Validation Failed") raise Exception("Couldn't create the todo item.")
考虑到我们按预期获得了 ‘text’ 键,我们准备将其添加到 dynamoDB 表中。我们获取当前时间戳,并连接到 dynamoDB 表。注意 serverless.yml 中定义的环境变量是如何获取的(使用 os.environ)
timestamp = str(time.time()) table = dynamodb.Table(os.environ['DYNAMODB_TABLE'])
接下来,我们通过使用 uuid 包生成随机 uuid,使用接收到的数据作为文本,将 createdAt 和 updatedAt 设置为当前时间戳,并将字段 ‘checked’ 设置为 False 来创建要添加到表中的项目。‘checked’ 是另一个可以使用更新操作更新的字段,除了文本之外。
item = { 'id': str(uuid.uuid1()), 'text': data['text'], 'checked': False, 'createdAt': timestamp, 'updatedAt': timestamp, }
最后,我们将项目添加到 dynamoDB 表并将创建的项目返回给用户。
# write the todo to the database table.put_item(Item=item) # create a response response = { "statusCode": 200, "body": json.dumps(item) } return response
通过本演练,我认为其他函数处理程序将不言自明。在某些函数中,您可能会看到此语句 – “body” – json.dumps(result[‘Item’], cls=decimalencoder.DecimalEncoder)。这是用于json.dumps 中的错误的解决方法。json.dumps 默认不能处理十进制数,因此,decimalencoder.py文件已经被创建来包含处理这个的 DecimalEncoder 类。
恭喜您了解您使用无服务器创建的第一个综合项目。该项目的创建者还在README文件中分享了他的部署端点以及测试这些功能的方法。看一看。前往下一章查看另一个示例。
无服务器 – Telegram Echo Bot
这是官方无服务器项目列表中的另一个有趣项目。我们基本上在 Telegram 上创建了一个新的机器人,然后使用 set_webhook 方法将它连接到我们的 lambda 函数,并在 lambda 函数中编写代码,使机器人回显它收到的任何消息。
先决条件
您需要在手机或台式机上安装 Telegram 应用程序。可以在此处找到下载选项。Telegram 是一个消息应用程序,有点类似于 WhatsApp 或 Messenger。安装应用程序后,您需要在应用程序中创建一个新机器人。为此,单击“新消息”图标(右下角的圆形铅笔图标),然后搜索 BotFather。单击已验证的帐户。
一旦你开始与 BotFather 聊天,创建一个新的机器人就很容易理解了。您发送\newbot命令,输入机器人的名称和用户名,您将获得一个访问令牌,您需要记下该令牌。
代码演练
代码可以在 GitHub 上找到 – https://github.com/serverless/examples/tree/master/aws-python-telegram-bot
我们将查看项目结构,然后执行 serverless.yml 文件和 handler.py 文件的演练。
项目结构
我们可以看到这个项目有一个外部依赖(python 电报机器人库),列在 requirements.py 中 –
python-telegram-bot==8.1.1
package.json 和 serverless.yml 都表明 serverless-python-requirements 插件已经用于捆绑 python 需求(在这种情况下是电报机器人库)。因此,README.md 文件也建议您执行npm install安装必要的插件。我个人建议您删除 package.json,并使用sls plugin install -n serverless-python-requirements 安装 serverless-python-requirements。这将自动创建 package.json 文件。这还将确保您安装最新版本的 serverless-python-requirements。通过运行npm install,您将安装现有 package.json 中提到的版本,该版本可能已过时。
如果您阅读 README.md 文件,您会发现引用的一个文件实际上并不存在于项目中 – serverless.env.yml。您被要求创建此文件并在此文件中输入您的 TELEGRAM_TOKEN。这样做是出于安全原因。TELEGRAM_TOKEN 应该是机密的,您不想公开共享。因此,该项目的创建者并未在 GitHub 上添加 serverless.env.yml 文件。但是您需要在本地机器上创建它。
serverless.yml 演练
serverless.yml 文件以服务的定义开始。
service: serverless-telegram-bot
接下来,定义提供者。这里再次设置了一个环境变量。此变量 (TELEGRAM_TOKEN) 的值是从应该在本地创建的 serverless.env.yml 文件中获取的。在这里,我们再次使用$来表示变量。
provider: name: aws runtime: python3.6 profile: ckl environment: TELEGRAM_TOKEN: ${file(./serverless.env.yml):TELEGRAM_TOKEN, ''}
功能块非常简单。定义了两个函数,都是 HTTP 触发的。但是,http 事件参数在这里定义在一行中。
- http: path: /set_webhook method: post
而不是使用单行执行– http – POST /set_webhook
另外,请注意webhook和set_webhook函数位于同一个处理程序文件中。
functions: webhook: handler: handler.webhook events: - http: POST / set_webhook: handler: handler.set_webhook events: - http: POST /set_webhook
最后,定义了 serverless-python-requirements 插件。
plugins: - serverless-python-requirements
handler.py 演练
我们有几个导入语句
import json import telegram import os import logging
接下来定义了一个logger对象,它基本上可以帮助我们输入日志语句。请注意,这对于 python 运行时函数不是必需的。简单的打印语句也会被记录。
唯一的区别是 logger 的输出包括日志级别、时间戳和请求 ID。您可以在此处阅读有关日志记录库的更多信息。
# Logging is cool! logger = logging.getLogger() if logger.handlers: for handler in logger.handlers: logger.removeHandler(handler) logging.basicConfig(level=logging.INFO)
接下来,定义 OK_RESPONSE 和 ERROR_RESPONSE 的 JSON。这些用作函数的返回值。
OK_RESPONSE = { 'statusCode': 200, 'headers': {'Content-Type': 'application/json'}, 'body': json.dumps('ok') } ERROR_RESPONSE = { 'statusCode': 400, 'body': json.dumps('Oops, something went wrong!') }
接下来,定义了两个 API 函数都使用的辅助函数。此函数使用 serverless.yml 中作为环境变量提供的 Token 返回一个机器人实例。
def configure_telegram(): """ Conimages the bot with a Telegram Token. Returns a bot instance. """ TELEGRAM_TOKEN = os.environ.get('TELEGRAM_TOKEN') if not TELEGRAM_TOKEN: logger.error('The TELEGRAM_TOKEN must be set') raise NotImplementedError return telegram.Bot(TELEGRAM_TOKEN)
接下来,定义两个 API 的处理程序函数。我们先来看看set_webhook函数。在这里,bot 实例是从我们之前看到的 configure_telegram 函数中获取的。接下来,从头中提取主机字段,从传入事件的 requestContext 块中提取 stage 字段。使用这两个字段,构建 webhook 的 URL。最后,它使用bot.set_webhook(url)函数应用于机器人。如果 webhook 设置正确,则设置 OK_RESPONSE,否则设置 ERROR_RESPONSE。请注意,此 set_webhook API 必须使用 POSTMAN 之类的工具手动触发一次。
def set_webhook(event, context): """ Sets the Telegram bot webhook. """ logger.info('Event: {}'.format(event)) bot = configure_telegram() url = 'https://{}/{}/'.format( event.get('headers').get('Host'), event.get('requestContext').get('stage'), ) webhook = bot.set_webhook(url) if webhook: return OK_RESPONSE return ERROR_RESPONSE
让我们了解 set_webhook 函数如何能够获取 webhook 的正确 URL。请注意,set_webhook 和 webhook 函数的路径只有 ‘/set_webhook’ 不同。他们共享同一个主持人和舞台。因此,我们可以使用在 set_webhook 函数的事件中接收到的 host 和 dev 来导出 webhook 函数的端点。如果您的端点是“https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev”,则主机将为“https://abcdefghijk.execute-api.us-east-1”。 amazonaws.com”,舞台将是“dev”。set_webhook 函数由“https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev/set_webhook”触发,webhook 函数由“https://abcdefghijk.execute-api.us”触发-east-1.amazonaws.com/dev’。因此,
最后,我们来看一下 webhook 函数。这很简单。它从configure_telegram辅助函数接收机器人实例。然后它检查事件。如果它是一个 POST 事件并且包含一个正文,那么它会从正文中提取聊天 ID 和消息。如果文本是“/start”,表示对话开始,它会使用bot.sendMessage(chat_id=chat_id, text=text)命令回复标准问候语。否则,它会用收到的相同文本回复。
def webhook(event, context): """ Runs the Telegram webhook. """ bot = configure_telegram() logger.info('Event: {}'.format(event)) if event.get('httpMethod') == 'POST' and event.get('body'): logger.info('Message received') update = telegram.Update.de_json(json.loads(event.get('body')), bot) chat_id = update.message.chat.id text = update.message.text if text == '/start': text = """Hello, human! I am an echo bot, built with Python and the Serverless Framework. You can take a look at my source code here: https://github.com/jonatasbaldin/serverless-telegram-bot. If you have any issues, please drop a tweet to my creator: https://twitter.com/jonatsbaldin. Happy botting!""" bot.sendMessage(chat_id=chat_id, text=text) logger.info('Message sent') return OK_RESPONSE return ERROR_RESPONSE
一旦您通过 POSTMAN 之类的工具触发了 set_webhook 功能,您就可以在 Telegram 上打开您的机器人并与其聊天。它将按预期回显消息。
祝贺你的第一个 Telegram 机器人!