Serverless 简明教程

Serverless - Quick Guide

Serverless - Introduction

What is Serverless?

嗯,这个名字给你提供了一些提示。无需维护服务器就能进行计算 - 这就是无服务器计算(或简称无服务器)的关键。这一概念极具革命性和颠覆性。它已被广泛采用。一些新的应用程序从设计无服务器后端开始,而具有专用服务器的遗留应用程序也在慢慢迁移到无服务器架构。那么,是什么导致了无服务器的广泛采用?与所有事情一样,经济因素使无服务器变得非常有利。

Well, the name gives you quite some hints. Computation without the headache of maintaining a server − that’s the crux of serverless computing (or serverless in short). The concept is quite revolutionary and disruptive. It has been widely adopted. Several fresh applications begin by designing a serverless backend, and the legacy applications with dedicated servers are also slowly migrating to the serverless architecture. So what has led to the widespread adoption of serverless? As with everything, the economics have made serverless quite favorable.

你看,使用无服务器时,你只需为你使用的内容付费。想象一下你每天都需要对数据库执行一些例行维护。此过程每天可能需要花费大约 10 分钟。

You see, with serverless, you only pay for what you use. Imagine you need to perform some routine maintenance on your database every day. This process may take about 10 minutes every day.

现在,在没有无服务器计算的情况下,你的维护 cron 可能驻留在服务器中。除非在剩余时间里让服务器做其他事情,否则你可能最终为一项花费 10 分钟的任务支付 24 小时的费用。相当浪费钱,对吧?如果有人告诉你有一项新服务可以为你执行维护 cron 所需的 10 分钟精确收费,你会不会想简单地切换到那项新服务?这正是无服务器采用如此迅速和广泛的原因。它降低了多个组织的后端费用,还减少了服务器维护遇到的难题。

Now, in the absence of serverless computing, your maintenance cron may be residing in a server. Unless you have something else to do with your server in the remaining time, you may end up paying for 24 hours, for a task that takes 10 minutes. Quite a waste of money, right? What if you were told that there is a new service that will charge you exactly for those 10 minutes that your maintenance cron takes to execute? Won’t you want to simply switch to that new service? That’s exactly why serverless adoption has been so swift and widespread. It has brought down the backend bills for several organizations, and also reduced their server maintenance headache.

云服务提供商(AWS、Azure 等)不必担心在需要时恰好提供所需数量的无服务器应用程序。因此,在高负载期间,你可以调用多个无服务器应用程序,而在正常负载期间,你可以调用单个应用程序。当然,你仅需为高负载期间额外的调用付费。

The cloud service provider (AWS, Azure, etc.) takes the headache of making sure that the serverless application is available exactly when required, and in the quantity required. Thus, during high loads, you may invoke multiple serverless applications, while you may invoke a single application during normal load. And of course,you will pay for the extra invocations only for the duration of the high load.

What is Serverless, again?

上面解释的概念似乎很棒,但你该如何实现它?你为此需要一个框架。它被称为 serverless

The concept explained above seems great, but how do you implement it? You need a framework for that. It is called, uh, serverless.

无服务器框架帮助我们开发和部署设计为以无服务器方式运行的函数/应用程序。该框架向前迈进了一步,并负责部署运行我们的无服务器函数所需的整个堆栈。什么是堆栈?堆栈包括部署、存储和监控无服务器应用程序所需的所有资源。

The serverless framework helps us develop and deploy functions/ applications designed to run in a serverless fashion. The framework goes a step ahead and takes care of the deployment of the entire stack required for our serverless functions to run. What is a stack? Well, the stack comprises of all the resources that you will require for deploying, storing, and monitoring your serverless applications.

它包括实际函数/应用程序、存储容器、监控解决方案等等。例如,在 AWS 的上下文中,你的堆栈将包括你的实际 Lambda 函数、函数文件 S3 存储桶、链接到函数的 Cloudwatch 资源等。无服务器框架为我们创建了整个堆栈。这使我们可以完全专注于我们的功能。无服务器无需维护服务器并无服务器(框架)无需创建和部署运行我们函数所需的堆栈。

It includes the actual function/ application, storage containers, monitoring solutions, and a lot more.For example, in the context of AWS, your stack will consist of your actual Lambda function, S3 bucket for your function files, Cloudwatch resources linked to your function, and so on.The serverless framework creates this entire stack for us. This allows us to focus completely on our function. Serverless takes away the headache of maintaining a server and serverless (framework) takes away the headache of creating and deploying the stack necessary to run our function.

无服务器框架还负责为我们的功能/应用程序分配必要的权限。有些应用程序(我们将在本教程中看到示例)甚至需要将数据库链接到它们。无服务器框架再次负责创建和链接数据库。无服务器如何知道在堆栈中包含什么以及提供哪些权限?所有这些内容都将在 serverless.yml 文件中提及,这是本教程的重点。在后面的章节中会有更多内容。

The serverless framework also takes care of assigning the necessary permissions to our functions/ applications.Some applications (examples of which we will see in this tutorial) even require databases to be linked to them. Serverless framework again takes care of creating and linking the DBs. How does serverless know what to include in the stack and which permissions to provide? All of it is mentioned in the serverless.yml file, which will be our main focus in this tutorial. More on it in the upcoming chapters.

Serverless in AWS

AWS 的许多服务都属于“无服务器计算”的范畴。你可以找到整个有组织的列表 here 。有计算服务、集成服务,甚至还有数据存储服务(是的,AWS 甚至有无服务器数据库)。在整个教程中,我们将重点关注 AWS Lambda 函数。那么什么是 AWS Lambda?AWS Lambda 网站将其定义如下 -

There are many services of AWS that fall under the umbrella of 'serverless computing'. You can find the entire organized list here. There are Compute services, Integration services, even Data Storage services (yes, AWS even has Serverless DBs). We will be focusing on the AWS Lambda functions throughout the tutorial. So what is AWS Lambda? The AWS Lambda website defines it as follows −

AWS Lambda 是一种无服务器计算服务,可让你在无需配置或管理服务器、创建与工作负载感知的集群扩展逻辑、维护事件集成或管理运行时的情况下运行代码。

AWS Lambda is a serverless compute service that lets you run code without provisioning or managing servers, creating workload-aware cluster scaling logic, maintaining event integrations, or managing runtimes.

用外行术语来说,AWS Lambda 是你在 AWS 上进行无服务器计算的窗口。AWS Lambda 正是让无服务器概念如此流行的原因。你只需定义你的函数和触发器,然后你的函数将在你希望它被调用的时间内被调用,并且你只需为你函数执行所需的时间付费就行了。更重要的是,你可以将 AWS Lambda 与 AWS 提供的几乎所有其他服务链接起来 - EC2、S3、dynamoDB 等等。

In layperson terms, AWS Lambda is your window to serverless computing on AWS. It is AWS Lambda that has made the serverless concept so popular. All you need to do is define your function and the trigger to your function, and the function will be invoked exactly when you want it to be invoked, and you will be charged only for the time the function takes to execute. What’s more,you can link AWS Lambda with almost every other service that AWS provides − EC2, S3, dynamoDB, and so on.

因此,如果你已经是 AWS 生态系统的一部分,那么 Lambda 集成很无缝。如果你像我刚了解 AWS Lambda 时那样的 AWS 生态系统新手,它将成为进入 AWS 宇宙的一个好门户。

Therefore, if you have already been a part of the AWS ecosystem, then Lambda integration is quite seamless. If you are new to the AWS ecosystem like I was when I first learned about AWS Lambda, it will act as a good gateway to the AWS universe.

在本教程中,我们将学习使用无服务器框架部署 AWS Lambda 函数。是否感到激动?那么继续阅读下一章节来开始吧。

In this tutorial, we will be learning all about the deployment of AWS Lambda functions using the serverless framework. Are you excited? Then move on to the next chapter to get started.

Serverless - Installing

另一个 tutorialspoint 教程已经介绍了 Serverless 安装。在此重新说明一遍,并作一些修改和补充。

Serverless installation has already been covered in another tutorialspoint tutorial. Reproducing it here, with some modifications and additions.

Step 1 − Install nodejs

首先,您需要先安装 nodejs。您可以通过打开命令提示符并键入 node -v 来检查您的机器上是否安装了 nodejs。如果已安装,您将获得节点版本号。否则,您可以从 here 下载并安装节点。

To begin with, you need to first install nodejs. You can check whether nodejs is installed in your machine or not by opening the Command Prompt and typing node -v. If it is installed, you will get the version number of node. Otherwise, you can download and install node from here.

Install node

Step 2 − Install serverless using the npm command

您可以使用以下命令安装 serverless(npm 代表 node 包管理器) -

You can install serverless using the following command (npm stands for node package manager) −

npm install -g serverless

您可以通过运行 serverless create --help 来检查是否成功安装。如果成功安装了 serverless,您会看到 create 插件的帮助屏幕。

You can check whether it got successfully installed by running serverless create --help. If serverless is successfully installed, you should see the help screen for the create plugin.

install help

请注意,在所有命令中你可以使用简写 sls 代替 serverless

Please note that you can use the shorthand sls instead of serverless in all your commands.

Step 3 − Configure Credentials

你需要从 AWS 获取凭据以配置无服务器配置。为此,要么在 AWS 控制台中创建用户(通过 IAM → 用户 → 添加用户),要么在 IAM → 用户中点击现有用户。如果你创建新用户,则需要附加一些必需的策略(比如 Lambda 访问、S3 访问等),或者为该用户提供管理员访问权限。

You need to obtain credentials from AWS for configuring serverless. For that, either create a user (through IAM → Users → Add user) in the AWS Console or click on an existing User in IAM → Users. If you are creating a new user, you will need to attach some required policies (like Lambda Access, S3 Access, etc.) or provide Administrator access to the user.

set permissions

当你创建用户后,你将能够看到访问秘钥和秘密访问秘钥。请务必将其安全保密。

After you create the user, you will be able to see the access key and secret key. Please keep this very secure and confidential.

secret key

如果你现有的用户,则可以通过执行以下步骤生成新的访问密钥和秘密访问密钥 here

If you are an existing user, you can generate a new Access Key and Secret by following the steps mentioned here.

当你拥有现成的访问密钥和秘密密钥后,你可以使用以下命令在 Serverless 中配置凭证 −

Once you have the access and secret keys handy, you can configure credentials in serverless using the following command −

serverless config credentials --provider aws --key 1234 --secret 5678 --profile custom-profile

profile 区域是可选的。如果你将其留空,则默认配置文件为“aws”。记住你设置的配置文件名,因为在下一教程中我们将看到的 serverless.yml 文件中必须提到它。

The profile field is optional. If you leave it blank, the default profile is 'aws'.Remember what profile name you set because you will have to mention it in the serverless.yml file that we will see in the next tutorial.

如果你已完成上述步骤,则 Serverless 配置已完成。继续阅读下一章,以创建你的第一个 Serverless 项目。

If you’ve completed the above steps, the serverless configuration is complete. Move on to the next chapter to create your first serverless project.

Serverless - Deploying Function

Creating a New Project

导航到新的文件夹,你希望在其中创建第一个要部署到 Serverless 的项目。在该文件夹中,运行以下命令 −

Navigate to a new folder wherein you want to create your first project to be deployed to serverless. Within that folder, run the following command −

sls create --template aws-python3

此命令将创建样板代码,以使用 Serverless 和 Python 运行时部署 Lambda 函数。

This command will create the boilerplate code for deploying lambda functions using serverless and python runtime.

deploying

请注意,你还可以使用其他运行时。运行 sls create --help 以获取所有模板的列表。

Note that you can use other runtimes as well. Run sls create --help to get a list of all templates.

一旦创建了样板代码,你将在文件夹中看到两个文件:handler.py 和 serverless.yml。handler.py 是包含 Lambda 函数代码的文件。serverless.yml 是告诉 AWS 如何创建 Lambda 函数的文件。这是配置文件或设置文件,它将成为本教程中几个章节的重点。让我们首先了解 handler.py 文件。

Once the boilerplate code is created, you will see two files in your folder: handler.py and serverless.yml. handler.py is the file containing the lambda function code. serverless.yml is the file that tells AWS how to create your lambda functions. It is the configuration file or the settings file that is going to be the focus of several chapters of this tutorial. Let us go through the handler.py file first.

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 。此函数接收两个参数:event 和 context。这两个参数都是任何 AWS Lambda 函数的必需参数。每当调用 Lambda 函数时,Lambda 运行时将两个参数传递给该函数 − event 和 context。

It contains one function hello. This function takes in two arguments: event and context. Both of these are required arguments for any AWS Lambda function. Whenever the lambda function is invoked, the lambda runtime passes two arguments to the function − event and context.

event 参数包含要由 Lambda 函数处理的数据。例如,如果你通过 REST API 触发 Lambda 函数,无论你发送到路径参数或 API 正文中的什么数据,都会在事件参数中发送到 Lambda 函数。更多内容请参见后面的章节。需要注意的重要一点是,虽然 event 通常是 Python dict 类型,但也可以是 strfloatintlistNoneType 类型。

The event argument contains the data for the lambda function to process. For instance, if you trigger your lambda function through a REST API, whatever data you send in the path parameters or the body of the API, are sent to the lambda function in the event parameter. More on that in later chapters. The important thing to note is that the event is usually of the python dict type, although can also be of str, float, int, list, or NoneType type.

context 对象是在运行时传递到你的 Lambda 函数的另一个参数。它并不常被使用。官方 AWS 文档指出,此对象提供有关调用、函数和运行时环境的信息的方法和属性。你可以阅读更多有关 eventcontext 对象的信息 here

The context object is another argument passed on to your lambda function at runtime. It is not used very often. The official AWS Documentation states that this object provides methods and properties that provide information about the invocation, function, and runtime environment. You can read more about the event and context objects here.

该函数非常简单。它只返回一条状态码为 200 的消息。底部有一条注释,如果我们不使用带 LAMBDA-PROXY 设置的 HTTP 事件,应该使用这条注释。更多内容请参见 API 触发的 Lambda 章节。

The function is pretty straightforward. It simply returns a message with status code 200. There is a comment at the bottom that should be used if we don’t use the HTTP event with the LAMBDA-PROXY setting.More on that in the API-triggered lambda chapter.

现在,让我们看看 serverless.yml 文件。这是一个很多注释的文件。对于刚开始接触 Serverless 的人来说,这些注释非常有用。我们鼓励你仔细阅读这些注释。在后面的章节中,我们将探讨很多与 serverless.yml 相关的概念。让我们先浏览一下这里的基本概念。

Now, let us look at the serverless.yml file. It is a heavily commented file. The comments are extremely useful for someone starting off with serverless. You are encouraged to go through the comments thoroughly. We will be looking at a lot of concepts related to serverless.yml in the upcoming chapters. Let us just skim through the basic concepts here.

如果你在删除注释后查看 serverless.yml 文件,它将如下所示 −

If you look at the serverless.yml file after removing the comments, this is how it will look like −

service: aws-serverless
frameworkVersion: '2'

provider:
   name: aws
   runtime: python3.8
   lambdaHashingVersion: 20201221
functions:
   hello:
      handler: handler.hello

service 区域确定 CloudFormation 堆栈的名称,你的 Lambda 函数和所有必需的资源都将在其中创建。认为 service 就是你的项目。执行 AWS Lambda 函数所需的一切都将在该 service 中创建。你可以根据自己的喜好设置一个 service 名称。

The service field determines the name of the CloudFormation stack within which your lambda function and all the required resources will be created. Think of the service as your project. Everything required for the AWS Lambda function to execute will be created within that service. You can set a service name of your choice.

framework version 是指 Serverless 框架的版本。它是一个可选区域,通常保留以确保与你分享代码的人使用相同的版本号。如果 serverless.yml 中提到的 frameworkVersion 与你机器中安装的 Serverless 版本不同,则你在部署期间会收到错误。你还可以为 frameworkVersion 指定一个范围,例如 frameworkVersion − >=2.1.0 && <3.0.0 。你可以阅读更多有关 frameworkVersions 的信息 here

The framework version refers to the version of the serverless framework. It is an optional field, ususally kept to ensure that the same version number is used by people with whom you share your code. If ther frameworkVersion mentioned in serverless.yml is different than the version of serverless installed in your machine, you will receive an error during deployment. You can also specify a range for frameworkVersion like frameworkVersion − >=2.1.0 && <3.0.0. You can read more about frameworkVersions here.

下一部分, provider ,可以看作是全局设置集合。我们将在以后的章节中讨论提供者中涵盖的附加参数。在此,我们重点关注现有的参数。 name 字段确定平台环境的名称,在这种情况下为 aws。运行时为 python3.8,因为我们使用了 python3 模板。lambdaHashingVersion 指的是框架应使用的 hash 算法的名称。

The next section, provider, can be considered as a set of global settings. We will be discussing additional parameters covered under provider in later chapters. Here, we will focus on the parameters available. The name field determines the name of your platform environment, which is aws in this case. The runtime is python3.8 because we used the python3 template. The lambdaHashingVersion refers to the name of the hashing algorithm that should be used by the framework.

请注意,如果你为上一章中的“config credentials”步骤添加了自定义配置文件,那么你需要在“provide”中添加“profile”参数。例如,我将我的配置文件名称设置为 yash-sanghvi。因此,我的提供者如下所示 −

Please note that if you’ve added a custom profile in the config credentials step in the previous chapter, you will need to add the profile parameter in provide. For instance, I set my profile name to yash-sanghvi. Therefore, my provider looks like −

provider:
   name: aws
   runtime: python3.8
   lambdaHashingVersion: 20201221
   profile: yash-sanghvi

最后,functions 块定义所有 lambda 函数。这里只有一个函数,在处理程序文件中。该函数的名称是 hello。处理程序字段中提到了该函数的路径。

Finally, the functions block defines all the lambda functions. We have just one function here, in the handler file. The name of the function is hello. The path of the function is mentioned in the handler field.

Deploying the function

要部署该函数,你需要打开命令提示符,导航到包含 serverless.yml 的文件夹,并输入以下命令 −

To deploy the function you need to open the Command Prompt, navigate to the folder containing your serverless.yml, and enter the following command −

sls deploy -v

-v 是指示详细输出的可选参数。它可以帮助你更好地理解后台进程。一旦函数部署完成,你应该能够在 us-east-1 区域(这是默认区域)的 AWS 控制台中看到它。你可以使用“测试”功能从控制台中对其进行调用(你可以保持相同的默认事件,因为我们的 lambda 函数根本不使用事件输入)。你还可以使用以下命令通过命令提示符进行测试 −

The -v is an optional argument that indicates verbose output. It helps you understand the background processes better. Once your function is deployed, you should be able to see it on the AWS Console in the us-east-1 region (which is the default). You can invoke it from the console, using the 'Test' feature (you can keep the same default event since our lambda function is anyway not using the event input). You can also test it using the Command Prompt using −

sls invoke --function hello

请注意,如果你通过诸如 S3 或 dynamoDB 等其他 AWS 服务与该函数进行接口,那么你不能总是本地测试该函数。只有非常基本的函数可以在本地进行测试。

Please note that you cannot always test your function locally if it interfaces with other AWS Services like S3 or dynamoDB.Only the very basic functions can be tested locally.

Deploying function from an existing project

如果你想将现有项目部署到 AWS,请修改现有函数以仅将 eventcontext 作为参数。接下来,在文件夹中添加一个 serverless.yml 文件,并在 serverless.yml 中定义你的函数。然后打开命令提示符,导航到该文件夹,然后点击 sls deploy -v 。这样,你的现有函数也可以部署到 AWS Lambda。

If you want to deploy an existing project to AWS, modify the existing function to take in only the event and context as arguments. Next, add a serverless.yml file in the folder, and define your function within serverless.yml. Then open the command prompt, navigate to the folder, and hit sls deploy -v. That way, your existing function can also be deployed to AWS Lambda.

Serverless - Regions, Memory-size, Timeouts

我们在上一章中了解了如何使用 serverless 来部署我们的第一个函数。在本章中,我们将介绍一些可以对函数执行的配置。我们将主要关注区域、内存大小和超时。

We saw how to deploy our first function using serverless in the previous chapter. In this chapter, we will look at some configurations that we can perform on the function. We will primarily look at the region, the memory-size, and the timeout.

Region

默认情况下,使用 serverless 部署的所有 lambda 函数都是在 us-east-1 区域创建的。如果你希望在其他区域创建 lambda 函数,那么你可以在提供者中指定。

By default, all lambda functions deployed using serverless are created in the us-east-1 region. If you want your lambda functions to get created in a different region, you can specify that in the provider.

provider:
   name: aws
   runtime: python3.6
   region: us-east-2
   profile: yash-sanghvi

不可能在同一个 serverless.yml 文件中为不同函数指定不同的区域。你应该仅在特定的 serverless.yml 文件中包含属于单个区域的函数。属于单独区域的函数可以使用单独的 serverless.yml 文件来部署。

It is not possible to specify different regions for different functions within the same serverless.yml file. You should include only the functions belonging to a single region in a particular serverless.yml file. Function belonging to a separate region can be deployed using a separate serverless.yml file.

MemorySize

AWS Lambda 根据选择的内存按比例分配 CPU。根据最近宣布的更改,你可以为 lambda 函数选择高达 10GB 的 RAM(以前约为 3GB)。

AWS Lambda allocates CPU in proportion to the memory chosen. With the recently announced changes, you can choose up to 10GB of RAM for your lambda function (it was ~3 GB earlier).

选择的 RAM 越大,分配的 CPU 越多,函数执行得越快,执行时间越短。AWS Lambda 会向你收取所用 GB 的费用。因此,如果一个 1GB RAM 的函数执行需要 10 秒,而一个 2GB RAM 的函数执行需要 5 秒,那么这两个调用将收取相同的费用。 时间是否会随着内存的增加而减半很大程度上取决于函数的性质,而且增加内存可能不会给你带来好处。关键要点是,分配的内存量是每个 lambda 函数的重要设置,是你想控制的设置。

The higher the RAM chosen, the higher is the CPU allocated, the faster your function executes, the lower is the execution time. AWS Lambda charges you for the GB-s consumed. Therefore, if a function on 1 GB RAM takes 10 seconds to execute and it takes 5 seconds to execute on a 2 GB RAM, you will be charged the same amount for both the invocations. Whether the time halves on doubling the memory depends a lot on the nature of your function, and you may or may not benefit by increasing the memory. The key takeaway is that the amount of memory allotted is an important setting for each lambda function and one you would like to have control of.

使用 serverless,可以非常轻松地为在 serverless.yml 文件中定义的函数设置内存大小的默认值。还可以为不同的函数定义不同的内存大小。让我们看看如何进行操作。

With serverless, it is quite easy to set the default value of the memory-size for the functions defined within your serverless.yml file. It is also possible to define different memory-sizes for different functions. Let us see how.

Setting default memory-size for all the functions

默认值始终在提供者中提及。该值将被该 serverless.yml 中的所有函数继承。 memorySize 键用于设置此值。值以 MB 为单位表示。

The default values are always mentioned in the provider. This value will be inherited by all the functions within that serverless.yml. The memorySize key is used for setting this value.The value is expressed in MB.

provider:
   name: aws
   runtime: python3.6
   region: us-east-2
   profile: yash-sanghvi
   memorySize: 512 #will be inherited by all functions

如果你未在提供者中或在各个函数中指定 memorySize,那么将考虑默认值 1024。

In case you don’t specify the memorySize in the provider, nor in individual functions, the default value of 1024 will be considered.

Setting custom memory-size for some functions

如果你希望一些函数具有不同于默认内存的值,那么你可以在 serverless.yml 的 functions 部分中指定它。

In case you want some functions to have a value different than the default memory, then you can specify it in the functions section of 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

Timeout

就像 memorySize 一样,超时(以秒为单位)的默认值可以在提供者中设置,并且可以在 functions 部分中为各个函数指定自定义超时。

Just like memorySize, the default value of the timeout (in seconds) can be set in the provider, and custom timeouts for individual functions can be specified in the functions section.

如果你未指定全局超时或自定义超时,那么默认值为 6 秒。

If you don’t specify either the global or custom timeouts, the default value is 6 seconds.

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

请务必保守地保持超时。它不应太小,以至于你的函数经常超时,也不应太大以至于你的函数中的一个 bug 会给你带来一笔巨额账单。

Make sure to keep the timeout at a conservative value. It should not be so small that your function times out very frequently, nor should it be so large that a bug in your function leaves you with an outrageous bill to pay.

Serverless - Service

您不会希望为部署的每个函数创建一个单独的 serverless.yml 文件。那会非常乏味。幸运的是,无服务器提供了在相同的 serverless.yml 文件中部署多个函数的条款。所有这些函数都属于一个名为“服务”的组。服务名称通常是在 serverless.yml 文件中定义的第一件事。

You wouldn’t want to create a separate serverless.yml file for each function that you deploy. That would be very tedious. Luckily, serverless has provisions to deploy multiple functions within the same serverless.yml file. All these functions belong to a single group called 'service'. The service name is often the first thing defined in a serverless.yml file.

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-func1my-first-service-prod-func2 。stage 参数可帮助您区分代码开发的不同阶段。

All the functions within a service, when deployed, take the following name format on the AWS Lambda console − service_name-stage_name-function_name. Thus, the two functions in the example above,when deployed, will take the names − my-first-service-prod-func1 and my-first-service-prod-func2. The stage parameter helps you differentiate within the different stages of code development.

因此,如果您的函数处于开发阶段,您可能使用 stage dev ;如果它处于测试阶段,您可能使用 stage test ;如果它处于生产中,您可能使用 stage prod 。这样,您可以确保对 dev 阶段所做的更改不会影响生产代码。阶段名称没有固定定义。 dev, test, prod 仅是示例。

Thus, if your function is in the development stage, you may use the stage dev; if it is in the testing stage, you may use the stage test; if it is in production, you may use the stage prod. This way, you can ensure that changes made to the dev stage don’t affect the production codes. The stage names are not set in stone. dev, test, prod are just examples.

您可以使用您选择的任何阶段名称。请注意,如果您触发了 API Gateway lambdas(在后面的章节中会有更多信息),则每个阶段的端点都将不同。

You can have any stage name of your choice. Please note that if you have API Gateway triggered lambdas (more on that in a later chapter), then your endpoints will be different for each stage.

此外,如果您转到 AWS Lambda 控制台较少使用的“Applications”部分,您将能够看到整个服务的阶段。

Additionally, if you go to the lesser-used 'Applications' section of the AWS Lambda Console, you will be able to see the entire service with the stage.

aws lambda

如果您单击所选服务的阶段组合,您将能够在一个地方看到服务使用的所有资源 − Lambda 函数、API 网关、Events 规则、日志组、S3 存储桶,一切。

If you click on the service and stage combination of your choice, you will be able to see all the resources used by the service in one place − Lambda functions,API Gateways, Events Rules, Log groups, S3 buckets, everything.

hello world python dev

更有趣的是,您可以转到 Monitoring 选项卡,查看服务整体的性能 → 调用次数、平均持续时间、错误计数等。您可以了解哪些函数对您的账单贡献最大。当您的服务中有多个功能时,监控每个单独功能的性能变得非常困难。服务级别的 Monitoring 选项卡在此处提供了很大帮助。

What’s even more interesting is that you can go to the Monitoring tab and see the performance of your service as a whole → Number of invocations, average duration, error count, etc. You can get an idea of which function is contributing the most to your bill. When you have several functions within your service, monitoring the performance of each individual function becomes quite difficult. The service-level Monitoring Tab helps a lot here.

deployments

最后,Deployments 选项卡可帮助您查看服务的过去所有部署以及部署的状态。

Finally, the Deployments tab helps you see all the past deployments of the service and the status of the deployments.

monitoring

Serverless - Scheduled Lambdas

通常,您需要让您的函数在固定间隔内被调用。它可以是一天一次、一周两次、工作日每分钟一次,依此类推。Serverless 提供两种类型的事件以固定频率调用函数。它们是 Cron 事件和速率事件。

Often, you require your function to be invoked at fixed intervals. It can be once a day, twice a week, once a minute during weekdays, and so on.Serverless provides two types of events to invoke functions at fixed frequencies. They are cron events and rate events.

Cron Event

Cron 事件比速率事件有更大的灵活性。唯一的缺点是它不像速率事件那样易于理解。Cron 表达式的语法定义在 AWS documentation 中 −

The cron event has much more flexibility than the rate event. The only downside is that it is not as straightforward to understand as the rate event. The syntax of the cron expression is defined in the AWS documentation

cron(minutes hours day-of-month month day-of-week year)

可以看到,Cron 表达式由 6 个字段组成。每个字段都可以接受一些已接受的值,还有一些(如 AWS 所称)的通配符。

As can be seen, the cron expression consists of 6 fields. Each field can take in some accepted values, and some, as AWS calls them, wildcards.

让我们首先了解接受的值 −

Let’s have a look at the accepted values first −

  1. minutes − 0-59

  2. hours − 0-23

  3. day-of-month − 1-31

  4. month − 1-12 or JAN-DEC

  5. day-of-week − 1-7 or SUN-SAT

  6. year − 1970-2199

既然接受的值很明显了,让我们了解通配符。在 cron 表达式中有 8 个可能的通配符(一些允许用于所有 6 个字段,而另一些只允许用于特定字段)。在此列出它们 −

Now that the accepted values are clear, let us have a look at the wildcards. There are a total of 8 wildcards possible in the cron expression (some allowed for all 6 fields and some only for specific fields). Listing them down here −

  1. * (asterisk, allowed for all 6 fields) − This is the most popular wildcard. It simply says that all values of the field are included. A * in the hours field would mean that the cron would run every hour. A * in the day-of-month field would mean that the cron will run every day.

  2. , (comma, allowed for all 6 fields) − This is used to specify more than one value. For example. If you want your cron to run at the 5th, 7th, and 9th minute of every hour, your minute field would look like 5,7,9.Similarly, MON,TUE,WED,THU,FRI in the day-of-week field could mean that the cron should run only on weekdays.

  3. - (dash, allowed for all 6 fields) − This wildcard specifies ranges. In the previous wildcard example, in order to specify weekdays, instead of specifying 5 comma-separated-values, we could have simply written MON-FRI

  4. ? (question mark, only allowed for day-of-month and day-of-week) − This is like a don’t-care wildcard. If you have specified MON in the day-of-week field, you don’t care what date Monday falls on. Therefore, you will enter ? in place of day-of-month. Similarly, if you want the cron to run on the 5th of every month, you will enter 5 in the day-of-month field and ? in the day-of-week field, because you don’t care what the day is on the 5th of every month. Note that the AWS documentation clearly states that you cannot use * for both day-of-week and day-of-month fields. If you use * for one, you have to use ? for the other

  5. / (forward slash, allowed for the 5 fields except for day-of-month) − This field specifies increments. If you enter 0/2 in the hours field, this cron will run every even hour (0, 0+2, 0+2+2, and so on). If you specify 1/2 in the hours field, this cron will run every odd hour (1, 1+2, 1+2+2, and so on). As you would have guessed, the value preceding the / is the starting value and the value succeeding it defines the increment.

  6. L (only allowed for day-of-month and day-of-week) − Specifies the last day of the month or the last day of the week

  7. W (only allowed for day-of-month) − This specifies a weekday (Monday to Friday) nearest to that specific day of the month. So if you specify 8W in the day-of-month field, and it corresponds to a weekday, say Tuesday, then the cron will fire on 8th itself. But if 8 corresponds to a weekend day, say Saturday, then the cron will be triggered on 7th (Friday). If 8th corresponds to Sunday, then the cron will fire on 9th (Monday). This is one of the least used wildcards.

  8. # (only allowed for day-of-week) − This is a very special wildcard, best understood by an example. Say you want a cron to run on Mother’s day. Now, Mother’s day falls on 2nd Sunday of May every year.Therefore, your month field would contain MAY or 5. But how do you specify 2nd Sunday? Come in the hashtag. The expression is 0#2. The value preceding the wildcard is the day of the week (0 for Sunday,1 for Monday, and so on). The value succeeding the wildcard specifies the occurrence. Therefore, 2 here refers to the 2nd occurrence of Sunday or the second Sunday.

现在,为您的 lambda 函数定义 cron 触发器,您需要做的就是指定 serverless.yml 中函数 events 键内的 cron 表达式。

Now, to define a cron trigger for your lambda function, all you need to do is specify the cron expression within the events key in the function in serverless.yml.

functions:
   cron_triggered_lambda:
      handler: handler.hello
      events:
         - schedule: cron(10 03 * * ? *) #run at 03:10 (UTC) every day.

Some Examples

以下是 cron 表达式的一些示例 −

Given below are some examples of cron expressions −

  1. cron(30 15 ? * MON-FRI *) − Triggered at 15:30 (UTC) on every weekday

  2. cron(0 9 ? 6 0#3 *) − Triggered at 09:00 (UTC) on the third Sunday of June (Father’s Day)

  3. cron(0/15 * ? * MON *) − Triggered every 15 minutes on Mondays

  4. cron(0/30 9-18 ? * MON-FRI *) − Triggered every 30 minutes from 9 AM to 5:30 PM on weekdays (corresponding to office hours at several places)

Rate Event

与 cron 表达式相比,这样做更容易。语法很简单 rate(value unit) 。例如,rate(5 minutes)。

This is much more straightforward compared to cron expressions. The syntax is simply rate(value unit). For example, rate(5 minutes).

该值可以是任何正整数,允许的单位是分钟、小时、天。

The value can be any positive integer and the allowed units are minute(s), hour(s), day(s).

为 Lambda 函数定义速率触发器类似于定义 cron 触发器。

Defining a rate trigger for your lambda function is similar to defining a cron trigger.

functions:
   rate_triggered_lambda:
      handler: handler.hello
      events:
         - schedule: rate(10 minutes) #run every 10 minutes

Some Examples

  1. rate(2 hours) − Triggered every 2 hours

  2. rate(1 day) − Triggered every day (at 00:00 UTC)

  3. rate(90 minutes) − Triggered every 90 minutes

正如你所意识的那样,速率表达式的简洁性是以降低灵活性为代价的。你可以使用速率来运行每个 N 分钟/小时/天中的 lambda。要执行更复杂的操作,比如仅在工作日触发 lambda,你必须使用 cron 表达式。

As you would have realized, the simplicity of the rate expression comes at the price of reduced flexibility. You can use rate for lambdas running every N minutes/hours/days. To do anything more complex, like triggering your lambda only on the weekdays, you have to use a cron expression.

请注意,如果你的 cron 表达式以某种方式导致触发时间少于一分钟,则不支持该触发器。

Please note that if your cron expression somehow results in trigger times of less than a minute, it won’t be supported.

Serverless - API Gateway Triggered Lambdas

API 网关是触发 Lambda 的另一种流行方法,就像 cron/速率事件一样。基本上,您可以为您的 Lambda 函数获取一个 URL 端点。此 URL 属于连接到您的 Lambda 的 API 网关。无论您在浏览器中还是通过应用程序调用 URL,您的 Lambda 函数都会被调用。在本章中,我们将介绍如何使用 Serverless 框架将 API 网关连接到您的 Lambda 函数,以及如何对其进行测试。

API Gateways are another popular method of triggering lambdas, just like cron/rate events. Basically, you get a URL endpoint for your lambda function. This URL belongs to the API Gateway connected to your lambda. Whenever you invoke the URL,either in the browser or through an application, your lambda function gets invoked. In this chapter, we will see how to connect an API Gateway to your lambda function using the serverless framework, and how to test it.

HTTP Events

要将 API 网关链接到 Lambda 函数,我们需要在 Serverless.yml 中的函数定义中创建 HTTP 事件。以下示例显示了如何将您的 Lambda 函数链接到 REST API 并使用 GET 请求触发它们。

To link an API Gateway to a lambda function, we need to create HTTP events in the function definition in serverless.yml. The following example shows how to link your lambda function(s) to a REST API and trigger it using the GET request.

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)。以下介绍的概念也适用于其他函数。

Let’s unpack the keys one by one. We will only discuss the first function from the above list (user_details_api). The concepts covered below hold for the other function as well.

path 的值指定调用 URL 时端点之后的地址。上面示例中定义的两个函数将共享相同的端点,但一个将使用 endpoint/details/{user_id} 进行调用,而另一个将使用 endpoint/location/{user_id} 进行调用。大括号内的元素是路径参数。我可以在 user_id 的位置发送任意值,而 Lambda 函数可以被编程为返回该特定用户的详细信息(请参见下面的示例函数)。

The value of the path specifies the address after the endpoint for invoking the URL. Both the functions defined in the above example will share the same endpoint, but one will be invoked using endpoint/details/{user_id}, while the other will be invoked by endpoint/location/{user_id} The elements within curly brackets are path parameters. I can send any value in place of user_id and the lambda function can be programmed to return details for that particular user (see the example function below).

method 的值表示请求方法。流行的方法是 get 和 post。还有其他几种方法。深入了解这些方法的详细信息不在本章的讨论范围内。还有另一个您可用来了解详细信息的 post on tutorialspoint

The value of the method indicates the request method. The popular methods are get and post. There are several other methods as well. Diving into the details of these methods is outside the scope of this chapter. There is another post on tutorialspoint which you can refer to for the details.

integration 字段指定 Lambda 函数将如何与 API 网关集成。默认值为 lambda-proxy ,而其他可能选项为 lambda、http、http-proxy、mock。在这两个选项中最广泛使用的选项是 lambda 和 lambda-proxy。用普通人的术语来说, lambda-proxy 将整个控制权交给您的 Lambda 函数,而 lambda 将一些控制权交给 API 网关,另一些控制权交给 Lambda 函数。

The integration field specifies how the lambda function will be integrated with the API Gateway. The default is lambda-proxy, while the other options which are possible are * lambda, http, http-proxy, mock*. The most widely used options out of these two are lambda and lambda-proxy. In layperson terms, lambda-proxy gives the entire control to your lambda function, while lambda gives some control to the API Gateway and some to the lambda function.

如果您选择 lambda-proxy 作为集成类型,那么整个 HTTP 请求将以原始形式传递给您的 Lambda 函数,Lambda 函数发送的响应将原样传递给提出请求的客户端。因此,您必须在您的 Lambda 函数的响应中定义 statusCode 和 headers。

If you choose lambda-proxy as the integration type, then the entire HTTP request will be passed in the raw form to your lambda function, and the response sent by the lambda function is passed without alteration to the client which made the request. Therefore,you have to define the statusCode and headers in the response of your lambda function.

如果您选择 lambda 作为集成类型,您的 API 网关可以在将收到的请求传递给 Lambda 函数之前对其进行更改。类似地,它还可以在将 Lambda 函数发送的响应转发给客户端之前对其进行修改。API 网关将状态代码和 headers 添加到响应中,因此,Lambda 函数只需担心发送正文。这两个选项各有优缺点。

If you choose lambda as the integration type, your API Gateway can make changes to the received request,before passing it on to the lambda function. Similarly, it can also modify the response sent by the lambda function before forwarding it to the client. The API Gateway adds the status code and the headers to the response, and therefore, the lambda function just has to worry about sending the body. Both options have their advantages and disadvantages.

如果您更喜欢简单,您可以使用 lambda-proxy 。如果您对一些复杂性没有意见(因为您必须担心 Lambda 函数的代码和 API 网关的配置),但需要更多的控制权,您可以选择 lambda

You can use lambda-proxy if you prefer simplicity. You can choose lambda if you are okay with some complexity (since you will have to worry about the lambda function’s code as well as the API Gateway’s configuration), but require more control.

您可以阅读 here 了解这两个类型之间的更多区别。在其他集成类型中, httphttp-proxy 用于在将 API 网关与 HTTP 后端集成(而不是 Lambda 函数)时使用,因此与我们无关。mock 用于在不调用后端的情况下测试 API。

You can read more about the difference between these two types here.Out of the other integration types, http and http-proxy are used when integrating the API Gateway with an HTTP backend instead of the lambda function, so irrelevant for us. *mock*is used when you just want to test the API without invoking the backend.

corstrue 配置启用 CORS(跨域资源共享)。用普通人的术语来说,这意味着您允许来自另一个域的服务器的请求。如果没有 corstrue ,则只允许来自同一域的请求。当然,您可以只允许某些特定域,而不是允许所有域。要了解如何执行此操作,请参见 documentation

The corstrue configuration enables CORS (Cross-Origin Resource Sharing). In layperson terms, this means that you allow requests from servers from another domain. Without corstrue, only requests from the same domain will be allowed. Of course, instead of allowing all domains, you can allow only some specific domains. To understand how to do that, see the documentation.

在 Serverless 中,针对由 API 网关触发的 Lambda 函数,有许多可能的配置。强烈建议您仔细阅读 documentation ,或至少将链接加入书签,以便在需要时可以查找。

There are a lot more configurations possible within serverless, for API-Gateway triggered lambdas. You are strongly recommended to go through the documentation, or at least bookmark the link so that you can look it up whenever required.

Sample Lambda Function

此时,您可能想知道您已经创建了由 API 网关触发的函数,但是您如何在 Lambda 函数中访问路径参数?当集成类型为 lambda-proxy 时,以下示例 Python Lambda 函数将回答这个问题。我们基本上使用 'pathParameters' 属性。

At this point, you may be wondering that you created the API Gateway triggered function, but how do you access the path parameters in the lambda function? The following sample lambda function in python will answer that. We basically use the 'pathParameters' property, when the integration type is lambda-proxy.

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

Accessing the Endpoint

现在,您可能有的另一个问题是如何访问端点。有多种方法可以做到这一点。第一种方法是通过 Serverless 部署。无论您通过一项服务部署一个函数还是多个函数,此端点都会在 Serverless 部署的最后显示。

Now, another question that you may be having is how do you access the endpoint. There are multiple ways of doing that. The first way is through serverless deployment. Whenever you deploy a function, or multiple functions through a service, the endpoint is shown at the end of serverless deploy.

第二种方法是通过 Lambda 控制台。如果您在 Lambda 控制台上导航到您的函数,您可以看到附加到它的 API 网关。点击它应该会显示端点。

The second method is through the Lambda console. If you navigate to your function on the lambda console, you can see the API Gateway attached to it. Clicking on it should reveal the endpoint.

apigateway

请注意,如上所述,服务中的所有函数共享相同的端点。path 属性将一个函数的实际触发 URL 与另一个函数的 URL 区分开来。

Please note that, as discussed above, all the functions within a service share the same endpoint.The path property differentiates the actual trigger URL of one function from another.

Serverless - Include/Exclude

我们已经在“部署函数”一章中看到,要想从现有项目向 AWS Lambda 部署函数,我们需要修改函数以将 eventcontext 作为参数,并且需要在项目文件夹中添加一个 serverless.yml 文件,其中定义函数。然后执行 serverless deploy 即可完成这项工作。

We’ve already seen in the 'Deploying a function' chapter that to deploy functions from an existing project to AWS lambda, you need to modify the functions to take event and context as arguments and you need to add a serverless.yml file in the project folder, with the functions defined.Then hitting serverless deploy does the job.

在很多情况下,特别是当需要将某些函数从一个大型现有项目迁移到 AWS Lambda,你就会面临着一个空间挑战。如果你的项目足够大,那么你很可能超过 AWS 对 Lambda 函数设定的空间限制(250 MB,包括应用程序代码及其依赖项)。

Quite frequently, especially if you are asked to migrate some functions from an extensive existing project to AWS Lambda, you are faced with a size challenge.If your project is large enough, you may very well exceed the size limits imposed by AWS on Lambda functions (250 MB, including the application code and its dependencies).

而一些依赖项如 NumPy 本身就占据很多空间。例如,NumPy 约为 80 MB,SciPy 也差不多,以此类推。在这样的情况下,你用于应用程序代码的空间非常有限,而且需要一种方法来从 Lambda 部署包中排除不必要的文件。幸运的是,Serverless 使这项工作变得非常轻松。

And some dependencies like NumPy occupy a lot of space themselves. For instance, NumPy is ~80 MB large, SciPy is also in the same ballpark, and so on.In such scenarios, you have very limited space left for the application code and require a method of excluding unnecessary files from the lambda deployment package. Luckily, serverless makes this quite easy.

Include and exclude fields

你可以用“排除”标签指定希望从部署构建中排除的文件和文件夹,你可能已经猜到这一点。默认情况下,包含排除部分未指定的所有文件/文件夹。那么“包含”标签又有什么用?好吧,如果你希望一个文件夹通常被排除,但希望仅包含该文件夹中的几个文件或子文件夹,你可以通过“包含”标签指定这些文件/子文件夹。这样,该文件夹中的所有其他文件都将被排除,而仅“包含”部分中指定的文件保留。下面的例子将对此进行更好的解释。

As you would have guessed, you can specify files and folders that you want to exclude from the deployment build using the 'exclude' tag. All the files/ folders not specified in the exclude section are included by default. Then what is the use of the 'include' tag? Well, if you want a folder to be excluded in general, but want to include just a few files or sub-folders within that folder, you can specify those files/sub-folders within the 'include' tag. This way, all other files within that folder will be excluded, and only those specified within the 'include' section will remain. The example below will explain this better.

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 * * ? *)

如上方的 serverless.yml 文件所示,我们排除了包含 serverless.yml 的根文件夹中的大多数文件夹。我们甚至排除了 src/models 文件夹。但是,我们希望包含 src/models 中的 2 个子文件夹和 1 个文件。因此,它们已被专门添加到“包含”部分。请注意,默认情况下将包含任何不属于排除部分的文件/文件夹。

As you can from the above severless.yml file, we are excluding most of the folders within the root folder that contains the serverless.yml. We have even excluded the src/models folder. However,we want to include 2 sub-folders and 1 file within src/models. Therefore, those have been specifically added in the 'include' section. Please note that any file/ folder that is not a part of the exclude section will be included by default.

请注意两个 Lambda 函数的路径。它们都位于 src/models。虽然 src/models 默认被排除,但这些函数专门位于 include 部分中提及的子文件夹中。因此它们将毫无问题地执行。如果我添加一个位于 src/data 中的函数,并且该函数将不被允许,因为 src/data 中的所有内容都已被排除。

Note the path of the two lambda functions. They both lie in src/models. While src/models is excluded by default, these functions specifically lie in sub-folders that are mentioned in the include section. Therefore they will execute without any issues. If I added a function lying in, say, src/data, that would not be allowed, because all the contents of src/data have been excluded.

请注意,指定 / * indicates that all content (files/sub-folders) within that folder gets covered. Thus if the docs folder contains 10 sub-folders and 12 files, all of which need to be excluded, then *-docs/ 即可。我们不需要分别提及其中的每个文件/文件夹。

Please note that specifying */ indicates that all content (files/sub-folders) within that folder gets covered. Thus if the docs folder contains 10 sub-folders and 12 files, all of which need to be excluded, then *-docs/ does the job. We don’t need to mention each file/folder separately.

Serverless - Plugins

随着 Serverless 的普及,人们自然会对针对利基用户案例的更多功能提出需求。这些需求由插件来满足。顾名思义,插件是可选的,你只需要安装所需的插件即可。在本篇章中,我们将了解如何访问可用于 Serverless 的多个插件,如何安装这些插件,以及如何在 serverless.yml 中引用这些插件。

As serverless grew in popularity, it was but natural that the demand for a lot more features for niche user-cases would rise. These demands are met by plugins. As the name suggests, plugins are optional and you only need to install the ones that you need. In this chapter, we will see how to access the several plugins available for serverless, how to install these plugins,and how to reference them within serverless.yml.

Browsing the list of plugins

可以在 www.serverless.com/plugins/ 中找到适用于 Serverless 的所有插件

All the plugins available for serverless can be found at www.serverless.com/plugins/

你可以在此处搜索插件。例如,如果你搜索“Python”,你将看到很多专门为 Python 运行时而开发的插件。它们按受欢迎程度排列。

You can search for a plugin here. For example, if you search for 'Python', you will see several plugins specifically developed for the python runtime. They are arranged in the order of popularity.

plugins

让我们来看看最受欢迎的 Python 插件(本文写作时):Python 需求。单击该插件。这将打开与此插件相关的详细文档。

Let’s look at the most popular python plugin (at the time of writing this post): Python Requirements. Click on that plugin. This opens up the detailed documentation related to this plugin.

serverless python requirements

本文件涵盖最重要的两个方面——安装插件并在 serverless.yml 中引用插件。这适用于任何插件。你只需打开其文档即可了解该插件的安装和使用方法。回到 Python 需求插件,文档声明此插件自动捆绑 requirements.txt 中的依赖项,并使其在你的 PYTHONPATH 中可用。

This documentation covers two of the most important aspects − installing the plugin and referencing it within serverless.yml. This is true for any plugin. You only need to open its documentation to know the installation and usage of that plugin. Coming back to the Python Requirements plugin, the documentation states that this plugin automatically bundles dependencies from requirements.txt and makes them available in your PYTHONPATH.

换句话说,如果你的 Lambda 函数需要额外的依赖项,如 pandas、numpy、matplotlib 等,你只需在 requirements.txt 文件中指定这些依赖项,该文件与你的 serverless.yml 文件位于同一个文件夹中。然后,此插件将完成剩下的工作。你甚至可以在 requirements.txt 中指定库的版本号。例如,这是 requirements.txt 样本文件的样子——

In other words, if your lambda function requires additional dependencies like pandas, numpy, matplotlib, etc., you need to just specify these dependencies in a requirements.txt file, that sits in the same folder as your serverless.yml file. Then this plugin will do the rest. You can even specify the version number of the libraries in requirements.txt.For instance, this is how a sample requirements.txt file looks like −

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。因此,务必只包含你实际需要的依赖项。

As you can see, you can either mention just the dependency name, or add the version number along with it (separated by the == sign). Of course, the dependencies, along with the application code, should not exceed 250 MB in size. Therefore, it is essential that you include only those dependencies that you actually need.

现在,让我们回到我们的插件。我们已经准备好了 requirements.txt 文件。下一步是安装插件。打开你的命令提示符并导航到包含 serverless.yml 文件的项目文件夹。然后,按照文档说明,运行以下命令来安装插件 -

Now, let’s come back to our plugin. We have the requirements.txt file ready. The next step would be to install the plugin. Open your Command Prompt and navigate to the project folder containing the serverless.yml file. Then, following the documentation, run the following command to install the plugin −

sls plugin install -n serverless-python-requirements

实际上,如果你将 serverless-python-requirements 替换为任何其他插件名称,则以上命令仍然适用于大多数插件。但是,建议在安装新插件时遵循文档中给出的安装命令。运行上述命令后,你应该看到类似于下图中的消息 -

In fact, if you replace serverless-python-requirements with any other plugin name, the above command should still work for most plugins. However, it is recommended to follow the installation command given in the documentation whenever you install a new plugin. When you run the above command, you should see messages similar to those in the image below −

sls plugin install

如你所见,在项目文件夹中创建了一个 packages.json 文件。如果你的项目文件夹中已经存在一个 packages.json 文件,则会对其进行编辑以包含上述插件。此外,serverless.yml 文件将自动进行编辑以包含已安装的插件。如果你现在打开 serverless.yml 文件,你应该看到添加了以下几行——

As you can see, a packages.json file got created in the project folder. If you had a packages.json file existing in your project folder, it would get edited to include the above plugin. Also,the serverless.yml file would automatically get edited to include the installed plugin. If you now open the serverless.yml file, you should see the following lines added −

plugins:
   - serverless-python-requirements

这意味着 serverless.yml 中对插件的引用是自动完成的。此插件有几个设置,可以在文档中找到。我们将在下一章中介绍与“交叉编译”相关的设置。但现在,让我们看看使用此插件的效果。我已经在我 requirements.txt 中添加了 NumPy。这是我的 handler.py 文件的样子——

This means that the referencing of the plugin in serverless.yml is automatically done. There are several settings related to this plugin which can be found in the documentation. We will take up the setting related to 'Cross Compiling' in the next chapter. But for now, let us just see the effect of using this plugin. I’ve added numpy in my requirements.txt. This is how my handler.py file looks like −

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 依赖项也已打包在一起。

Let me now deploy this to lambda. You should see messages similar to those in the image below. Focus on the size of the package. It is now > 14 MB (this is the size of the zipped package) instead of ~10 kB before the plugin addition, because the numpy dependency is also getting bundled together.

deploy

这证明依赖项现在已与应用程序代码捆绑在一起。你可以使用 sls invoke local -f function_name 在本地测试它。如果你是一个 Windows 或 Mac 用户,很有可能在 AWS Lambda 控制台中测试已部署的 lambda 函数时会抛出一个错误,类似于下面给出的错误 −

This proves that the dependencies are now getting bundled along with the application code. You can test it locally using sls invoke local -f function_name.If you are a Windows or Mac user, chances are that testing out the deployed lambda function on the AWS Lambda Console will throw up an error, similar to the one given below −

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'

继续进入下一章以了解为什么发生此错误以及如何处理它。

Head on to the next chapter to learn more about why this error is occurring and how to deal with it.

Serverless - Packaging Dependencies

在上一个章节中,我们了解了如何将插件与 serverless 结合使用。我们具体介绍了 Python Requirements 插件,了解了如何使用它将 numpy、scipy、pandas 等依赖项与 lambda 函数的应用程序代码捆绑在一起。我们甚至还举例说明了解决 numpy 依赖项所需的函数。我们注意到函数在本地运行得很好,但如果您使用的是 Windows 或 Mac 计算机,则在 AWS Lambda 控制台中运行会遇到错误。让我们来了解一下为什么函数可以在本地运行,但在部署后却无法运行。

In the previous chapter, we saw how to use plugins with serverless. We specifically looked at the Python Requirements plugin and saw how it can be used to bundle dependencies like numpy, scipy, pandas, etc.with your lambda function’s application code. We even saw an example of deploying a function requiring the numpy dependency. We saw that it ran well locally but on the AWS Lambda console, you’d have encountered an error if you are on a Windows or Mac machine. Let’s understand why the function runs locally but doesn’t run after deployment.

如果查看错误信息,您会得到一些提示。我具体指的是一行代码:“导入 numpy C 扩展失败。”如今,numpy、pandas 和 scipy 等诸多重要的 Python 程序包都要求编译 C 扩展。如果在 Windows 或 Mac 计算机上编译这些程序包,那么 Lambda(Linux 环境)在尝试加载它们时会抛出错误。因此,重要的在于,我们可以做些什么来避免此错误。使用 Docker 来解决!

If you look at the error message, you get some hints. I’m specifically referring to one line − 'Importing the numpy C-extensions failed.' Now, many important python packages like numpy, pandas, scipy, etc.require the compilation of C-extensions. If we compile them on a Windows or a Mac machine, then Lambda (linux environment) will throw up an error when trying to load them. So the important question is, what can be done to avoid this error. Come in docker!

What is docker?

根据 Wikipedia 的说法,docker 是一组平台即服务 (PaaS) 产品,它使用操作系统级虚拟化来利用称为容器的程序包交付软件。如果您仔细阅读 Docker 的 Wiki 页面,您会发现其他一些相关说明 – Docker 可以将应用程序及其依赖项打包到虚拟容器中,该容器可以在任何 Linux、Windows 或 macOS 计算机上运行。这使应用程序能够在各种位置中运行,例如:本地、公共云或/和私有云。我认为上述说法应该阐述得非常清楚。我们遇到错误是因为在 Windows/Mac 上编译的 C 扩展无法在 Linux 中运行。

According to Wikipedia, docker is a set of platform as a service (PaaS) products that use OS-level virtualization to deliver software in packages called containers. If you scan the Wikipedia page of docker a bit more, you will come across some more relevant statements − Docker can package an application and its dependencies in a virtual container that can run on any Linux, Windows, or macOS computer. This enables the application to run in a variety of locations, such as on-premises, in a public cloud, and/or in a private cloud. I think it should be very clear after the above statements. We have an error coming up because C-extensions compiled on Windows/Mac don’t work in Linux.

我们可以通过将应用程序打包到可以在任何操作系统中运行的容器中来简单绕过该错误。Docker 在后台执行的操作以实现此操作系统级虚拟化超出了本章节的范围。

We can simply bypass that error by packaging the application in a container that can run on any OS. What docker does in the background to achieve this OS-level virtualization is beyond the scope of this chapter.

Installing docker

您可以前往 https://docs.docker.com/engine/install/ 安装 Docker Desktop。如果您使用的是 Windows 10 家庭版,则 Windows 版本应为 at least 1903 (May 2019 update) 。因此,在安装 Docker Desktop 之前,您可能需要升级 Windows 10 操作系统。Windows 专业版或企业版没有此类限制。

You can head over to https://docs.docker.com/engine/install/ for the installation of Docker Desktop. If you are using Windows 10 Home Edition,the Windows version should be at least 1903 (May 2019 update). Therefore, you may want to upgrade your Windows 10 OS before installing Docker Desktop. No such limitations apply to Windows Pro or Enterprise versions.

Using dockerizePip in serverless

在您的机器上安装了 Docker Desktop 之后,只需对 serverless.yml 文件进行以下补充,即可使用 Docker 来打包应用程序和依赖项 −

Once Docker Desktop has been installed on your machine, you need to make only the following addition to your serverless.yml file to package your applications and dependencies using docker −

custom:
   pythonRequirements:
      dockerizePip: true

请注意,如果您自上一章节起一直关注我的讲解,那么您很可能已经将代码部署到了 Lambda 中。这会在您的本地存储中创建静态缓存。默认情况下,serverless 将使用该缓存来捆绑依赖项,因此不会创建 Docker 容器。因此,为了强制 serverless 创建使用 Docker,我们将向 pythonRequirements 中添加另一个语句 -

Please note, that if you have been following along with me since the previous chapter, it is likely that you have already deployed code to lambda once.This would have created a static cache in your local storage. By default, serverless would use that cache to bundle dependencies, and therefore, docker container won’t be created.Therefore, to force serverless to create use docker, we will add another statement to 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,因为这将节省一些打包时间,这是在您没有对依赖项或依赖项的打包方式进行任何更改的情况下。

This last statement is not necessary if you are deploying to lambda for the first time. In general, you should set useStaticCache to true, since that will save you some packaging time when you haven’t made any changes to the dependencies or the way they have to be packaged.

添加后,serverless.yml 文件现在看起来像:

With these additions, the serverless.yml file now looks like −

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 Desktop 并双击该应用程序。您很快会收到一条消息,提示它正在运行。您也可以通过 Windows 中电池图标旁边的弹出窗口进行验证。如果您可以看到那里的 Docker 图标,则说明它正在运行。

Now, when you run the sls deploy -v command, make sure that docker is running in the background. On Windows, you can just search for Docker Desktop in the Start menu and double click on the app. You will soon get a message that it is running. You can also verify this through the small popup near the battery icon in Windows. If you can see the docker icon there, it is running.

docker

现在,当您在 AWS Lambda 控制台中运行您的函数时,它可以正常工作了。恭喜!!

Now when you run your function on the AWS Lambda console, it would work. Congratulations!!

但是,在 AWS Lambda 控制台中“函数代码”部分中,您会看到一条消息,提示“Lambda 函数“hello-world-python-dev-hello_world”的部署包太大,无法启用内联代码编辑。但是,您仍然可以调用您的函数”。

However, in your 'Function Code' section on the AWS Lambda console, you would be seeing a message saying 'The deployment package of your Lambda function "hello-world-python-dev-hello_world" is too large to enable inline code editing. However, you can still invoke your function.'

function code

看来添加 Numpy 依赖项导致包大小过大,因此,我们甚至无法在 Lambda 控制台中编辑应用程序代码。我们该怎么解决此问题?进入下一章节查找答案。

Seems like the addition of the Numpy dependency has made the bundle size too large and as a result, we cannot even edit our application code in the lambda console. How do we solve that problem? Head on to the next chapter to find out.

Serverless - Layer Creation

What are layers?

层是一种隔离代码块的方法。假设你要在应用程序中导入 NumPy 库。你信任该库,几乎不可能更改该库的源代码。因此,如果你不喜欢 NumPy 的源代码会让你的应用程序工作区混乱,那么你就不会喜欢它。粗略地说,你只想让 NumPy 放在别处,与你的应用程序代码隔离。层允许你执行此操作。你只需将所有依赖项(NumPy、Pandas、SciPy 等)捆绑到一个单独的层中,然后在 serverless 中的 lambda 函数中引用该层。太棒了!该层中捆绑的所有库现在都可以导入到你的应用程序。与此同时,你的应用程序工作区始终保持井井有条。你只需查看应用程序代码进行编辑。

Layers are a way of isolating code blocks. Say you want to import the NumPy library in your application. You trust the library and there’s hardly any chance that you will be making changes in the source code of that library. Therefore, you won’t like it if the source code of NumPy clutters your application workspace. Speaking very crudely, you would simply want NumPy to sit somewhere else, isolated from your application code. Layers allow you to do exactly that. You can simply bundle all your dependencies (NumPy, Pandas, SciPy, etc.) in a separate layer, and then simply reference that layer in your lambda function within serverless. And boom! All the libraries bundled within that layer can now be imported into your application. At the same time, your application workspace remains completely uncluttered. You simply see the application code to edit.

layers

Iva RajovicUnsplash 上的照片表示在层中分离代码

Photo by Iva Rajovic on Unsplash, indicative of code separation in layers

层真正的优点是它们可以跨函数共享。假设你使用包含 NumPy 和 Pandas 的 python-requirements 层部署了一个 lambda 函数。现在,如果另一个 lambda 函数需要 NumPy, 你无需为此函数部署一个单独的层。你可以简单地使用前一个函数的层,它也可以与新函数协同工作。

The really cool thing about layers is that they can be shared across functions. Say you deployed a lambda function with a python-requirements layer that contains NumPy and Pandas. Now, if another lambda function requires NumPy, you need not deploy a separate layer for this function. You can simply use the layer of the previous function and it will work well with the new function as well.

这将为你节省大量部署宝贵时间。毕竟,你只部署应用程序代码。依赖项已经存在于一个现有的层中。因此,多个开发人员将依赖项层保存在一个单独的堆栈中。然后他们在此层中使用所有其他应用程序。通过这种方式,他们不必一次又一次地部署依赖项。毕竟,依赖项相当繁重。NumPy 库本身大约有 80 MB 大小。每当你对应用程序代码(可能只有几 KB)进行更改时,部署依赖项将非常不方便。

This will save you a lot of precious time during deployment. After all, you will be deploying only the application code. The dependencies are already present in an existing layer. Therefore,several developers keep the dependencies layer in a separate stack. They then use this layer in all other applications. This way, they don’t need to deploy the dependencies again and again. After all, the dependencies are quite heavy. NumPy library itself is approx. 80 MB large. Deploying dependencies every time you make changes to your application code (which may measure just a few KBs) will be quite inconvenient.

添加一个依赖项层只是一个例子。还有其他一些用例。例如, serverless.com 上给出的示例涉及使用 FFmpeg 工具创建 GIF。在该示例中,他们将 FFmpeg 工具存储在层中。总而言之,AWS Lambda 允许我们为每个函数添加最多 5 个层。唯一的条件是这 5 个层与应用程序的总大小应小于 250 MB。

And adding a dependencies layer is just one example. There are several other use-cases. For example, the example given on serverless.com concerns the creation of GIFs using the FFmpeg tool. In that example, they have stored the FFmpeg tool in a layer. In all, AWS Lambda allows us to add a maximum of 5 layers per function. The only condition is that the total size of the 5 layers and the application should be less than 250 MB.

Creating python-requirements layer

现在让我们看看如何使用 serverless 创建和部署包含所有依赖项的层。要做到这一点,我们需要 serverless-python-requirements 插件。此插件仅适用于 Serverless 1.34 及更高版本。因此,如果你拥有低于 1.34 的版本,你可能需要升级你的 Serverless 版本。你可以使用以下命令安装插件 −

Now let’s see how the layer containing all the dependencies can be created and deployed using serverless. To do that, we need the serverless-python-requirements plugin.This plugin only works with Serverless 1.34 and above. So you may want to upgrade your Serverless version if you have a version <1.34. You can install the plugin using −

sls plugin install -n serverless-python-requirements

接下来,你在 serverless.yml 的插件部分添加此插件,并在自定义部分中提及其配置 −

Next, you add this plugin in the plugins section of your serverless.yml, and mention it’s configurations in the custom section −

plugins:
   - serverless-python-requirements
custom:
   pythonRequirements:
      dockerizePip: true
      layer: true

在这里, dockerizePiptrue 启用 docker 的使用并允许你将所有依赖项打包到一个 docker 容器中。我们在上一章中讨论了使用 docker 打包。 layertrue 告诉 serverless python 要求应存储在一个单独的层中。现在,此时你可能想知道 serverless 如何理解要打包哪些依赖项?如插件章节中所述,答案就在 requirements.txt 文件中。

Over here, dockerizePiptrue enables the usage of docker and allows you to package all the dependencies in a docker container. We’ve discussed about packaging using docker in the previous chapter. layertrue tells serverless that the python requirements should be stored in a separate layer. Now, at this point, you may be wondering that how does serverless understand which dependencies to package? The answer, as mentioned in the plugins chapter, lies in the requirements.txt file.

在定义了层插件和自定义配置后,你可以将层添加到 serverless 中的各个函数中,如下所示 −

Once the layer plugin and custom configurations have been defined, you can add the layer to your individual functions within serverless as follows −

functions:
   hello:
      handler: handler.hello
      layers:
         - { Ref: PythonRequirementsLambdaLayer }

关键字 PythonRequirementsLambdaLayer 来自 CloudFormation Reference 。通常,它从层的名称派生而来。语法为“LayerNameLambdaLayer”(大写,没有空格)。在我们的例子中,由于层名称是 python requirements,所以引用变为 PythonRequirementsLambdaLayer。如果你不确定 lambda 层的名称,你可以按照以下步骤获取它 −

The keyword PythonRequirementsLambdaLayer comes from the CloudFormation Reference.In general, it is derived from the layer’s name. The syntax is 'LayerNameLambdaLayer' (TitleCased, without spaces). In our case, since the layer name is python requirements, the reference becomes PythonRequirementsLambdaLayer. If you aren’t sure about the name of your lambda layer, you can get it in the following steps −

  1. Run sls package

  2. Open .serverless/cloudformation-template-update-stack.json

  3. Search for 'LambdaLayer'

Using an existing layer from another function in the same region

正如我在开头提到的,层的一大优点是能够在你的函数中使用现有层。通过使用现有层的 ARN 可以轻松地完成此操作。使用 ARN 将现有层添加到函数的语法非常简单 −

Like I mentioned in the beginning, a really cool thing about layers is the ability to use existing layers in your function. This can be done easily by using the ARN of your existing layer.The syntax to add an existing layer to a function using the ARN is quite straightforward −

functions:
   hello:
      handler: handler.hello
      layers:
         - arn:aws:lambda:region:XXXXXX:layer:LayerName:Y

就是这样。现在具有指定 ARN 的层将与你的函数一起工作。如果该层包含 NumPy 库,则你可以继续在你的“hello”函数中调用 import numpy 。它将毫无错误地运行。

That’s it. Now the layer with the specified ARN will work with your function. If the layer contains the NumPy library, you can simply go ahead and call import numpy in your 'hello' function. It will run without any error.

如果你想知道从哪里可以获得 ARN,其实很简单。只需导航到 AWS 控制台中包含该层的函数,然后单击“层”。

If you are wondering from where you can get the ARN, it is quite simple actually. Simply navigate to the function containing the layer in the AWS Console, and click on 'Layers'.

layerarn

当然,如果该层不属于你的帐户,那么它需要公开共享或专门与你的帐户共享。稍后对此进行更多介绍。

Of course, if the layer doesn’t belong to your account, then it either needs to be publicly shared or shared specifically with your account. More on that later.

此外,请记住该层应与你的应用程序兼容。不要期望与 python3.6 运行时中创建的函数一起运行与 node.js 运行时兼容的层。

Also, keep in mind that the layer should be compatible with your application. Don’t expect a layer compatible with node.js runtime to run with a function created in python3.6 runtime.

Non-requirements/ generic layers

如开头所述,层的主要功能是隔离你的代码块。因此,它们不必仅包含依赖项。它们可以包含你指定的任何代码段。在 custom 中的 pythonRequirements 内调用 layer: trueserverless-python-requirements 插件实现的一种快捷方式。但是,要创建一个通用层,serverless.yml 中的语法,如 serverless docs 中所述,如下所示 −

As mentioned in the beginning, the layers serve the main function of isolating your code blocks. Therefore, they don’t need to contain just dependencies. They can contain any piece of code that you specify. Calling layer: true within pythonRequirements within custom is a kind of a shortcut made possible by the serverless-python-requirements plugin. However, to create a generic layer, the syntax within serverless.yml, as explained in the serverless docs, is as follows −

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”之外,所有其他属性都是可选的。path 属性是你希望从应用程序代码中隔离的所选目录的路径。它将被压缩并作为你的层发布。例如,在 example project on serverless 中,他们在层中托管 FFmpeg 工具,他们在名为“layer”的单独文件夹中下载此工具并将其指定在 path 属性中。

The various configuration parameters are self-explanatory thanks to the comments provided. Except for the 'path', all other properties are optional. The path property is a path to a directory of your choice that you want to be isolated from your application code. It will be zipped up and published as your layer. For instance, in the example project on serverless, where they host the FFmpeg tool in a layer, they download the tool in a separate folder called 'layer' and specify that in the path property.

layers:
   ffmpeg:
      path: layer

如前所述,我们可以在 layers 属性内添加多达 5 层。

As told before, we can add up to 5 layers within the layers − property.

为了在函数中使用这些通用层,再次强调,你可以使用 CloudFormation 引用或指定 ARN。

To use these generic layers in your functions, again, you can use either the CloudFormation reference or specify the ARN.

Allowing other accounts to access layers

只需在“allowedAccounts”属性中提及帐号就可让更多帐号访问你的层。例如 −

More accounts can be provided access to your layer by simply mentioning the account numbers in the 'allowedAccounts' property. For example −

layers:
   testLayer:
      path: testLayer
      allowedAccounts:
         - 999999999999 # a specific account ID
         - 000123456789 # a different specific account ID

如果你希望该图层公开可访问,则可以在 allowedAccounts 中添加“*” −

If you want the layer to be publicly accessible, you can add '*' in allowedAccounts −

layers:
   testLayer:
      path: testLayer
      allowedAccounts:
      - '*'

Serverless - REST API with DynamoDB

到目前为止,我们已经学习了与无服务器 lambda 部署相关的多个概念。现在是时候查看一些示例了。在本章节中,我们将查看 Serverless 正式提供的一个示例。正如其名,我们将创建一个 REST API。正如你所猜测的,我们所有 lambda 函数将由 API Gateway 触发。我们的 lambda 函数将与 dynamoDB 表(本质上这是一个待办事项列表)交互,并且用户将能够执行一系列操作,例如创建新项目、获取现有项目、删除项目等,这些操作将使用部署后公开的端点。如果你不熟悉 REST API,那么可以阅读更多内容 here

So far, we have learned several concepts related to serverless lambda deployments. Now it is time to look at some examples. In this chapter, we will look at one of the examples officially provided by Serverless. We will be creating, as the name suggests, a REST API. All our lambda functions, as you would have guessed, will be triggered by an API Gateway. Our lambda functions will interface with a dynamoDB table, which is a to-do list essentially, and the user will be able to perform several operations, like creating a new item, fetching existing items, deleting items, etc. using the endpoints that will be exposed post the deployment.If you are not familiar with REST APIs, then you can read up more about them here.

Code Walkthrough

我们将查看项目结构,讨论一些我们迄今为止尚未看到的新概念,然后完成 serverless.yml 文件演练。所有函数处理器的演练是冗余的。因此,我们将仅完成一个函数处理器演练。你可以将理解其他函数作为练习来执行。

We will have a look at the project structure, discuss some new concepts that we haven’t seen so far, and then perform the walkthrough of the serverless.yml file. The walkthrough of all the function handlers will be redundant. Therefore, we will walk through just one function handler. You can take up understanding the other functions as an exercise.

Project Structure

现在,如果你查看项目结构,lambda 函数处理器都在 todos 文件夹中的不同 .py 文件中。serverless.yml 文件在每个函数处理器的路径中指定了 todos 文件夹。没有外部依赖,因此没有 requirements.txt 文件。

Now, if you look at the project structure, the lambda function handlers are all within separate .py files in the todos folder. The serverless.yml file specifies the todos folder in the path of each function handler. There are no external dependencies, and therefore, no requirements.txt file.

New Concepts

现在,有一些术语可能是你第一次见到。让我们快速浏览一下−

Now, there are a couple of terms that you may be seeing for the first time. Let’s scan these quickly −

  1. dynamoDB − This is a NoSQL (Not only SQL) database provided by AWS. While not exactly accurate, broadly speaking, NoSQL is to SQL what Word is to Excel. You can read more about NoSQL here. There are 4 types of NoSQL databases − Document databases, key-value databases, wide-column stores, and graph databases. dynamoDB is a key-value database, meaning that you can keep inserting key-value pairs into the database. This is similar to redis cache. You can retrieve the value by referencing its key.

  2. boto3 − This is the AWS SDK for Python. If you need to configure, manage, call, or create any service of AWS (EC2, dynamoDB, S3, etc.) within the lambda function, you need the boto3 SDK.You can read up more about boto3 here.

除了这些之外,在 serverless.yml 和处理函数的演练过程中,我们还会遇到一些概念。我们将在那里讨论它们。

Apart from these, there are some concepts that we will encounter during the walkthrough of the serverless.yml and the handler function. We will discuss them there.

serverless.yml Walkthrough

serverless.yml 文件以服务定义开头。

The serverless.yml file begins with the definition of the service.

service: serverless-rest-api-with-dynamodb

然后是通过以下行声明框架版本范围−

That is followed by the declaration of the framework version range through the following line −

frameworkVersion: ">=1.1.0 <=2.1.1"

这类似于检查。如果你的无服务器版本不在该范围内,它将抛出错误。当你共享代码并希望使用此 serverless.yml 文件的所有人使用相同的无服务器版本范围以避免问题时,这将有所帮助。

This acts like a check. If your serverless version doesn’t lie in this range, it will throw up an error. This helps when you are sharing code and would want everyone using this serverless.yml file to use the same serverless version range to avoid problems.

接下来,在提供程序中,我们看到了以前没有遇到的两个额外字段− environmentiamRoleStatements

Next, within the provider, we see two extra fields that we haven’t encountered so far − environment and 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 表名定义为一个环境变量。

Environment, as you would have guessed, is used to define environment variables. All the functions defined within this serverless.yml file can fetch these environment variables. We will see an example in the function handler walkthrough below. Over here, we are defining the dynamoDB table name as an environment variable.

$ 符号表示一个变量。 self 关键字表示 serverless.yml 文件本身,而 opt 表示我们可以在 sls deploy 期间提供的选项。因此,表名将是服务名,后跟连字符,然后是文件找到的第一个阶段参数:在 serverless 部署期间从选项中获得一个,或提供程序阶段,默认是 dev 。因此,在这种情况下,如果你在 serverless 部署期间未提供任何选项,dynamoDB 表名将是 serverless-rest-api-with-dynamodb-dev。你可以阅读有关无服务器变量 here. 的更多内容

The $ sign signifies a variable. The self keyword refers to the serverless.yml file itself, while opt refers to an option that we can provide during sls deploy. Thus, the table name will be the service name followed by a hyphen followed by the first stage parameter that the file finds: either one available from options during serverless deploy, or the provider stage, which is dev by default.Thus, in this case, if you don’t provide any option during serverless deploy, the dynamoDB table name will be serverless-rest-api-with-dynamodb-dev. You can read more about serverless variables here.

iamRoleStatements 定义提供给函数的权限。在这种情况下,我们允许 allowing 函数对 dynamoDB 表执行以下操作− QueryScanGetItemPutItemUpdateItemDeleteItem 。资源名称指定允许这些操作的确切表。如果你在资源名称处输入 " "*,则可以在所有表上进行这些操作。但是,此处,我们只想在其中一张表上允许这些操作,因此此表的 arn(Amazon 资源名称)使用标准 arn 格式提供在资源名称中。此处再次使用了选项区域(在 serverless 部署期间指定)或提供程序中提到的区域(默认是 us-east-1)中的第一个。

iamRoleStatements define permissions provided to the functions. In this case, we are allowing the functions to perform the following operations on the dynamoDB table − Query, Scan, GetItem, PutItem, UpdateItem, and DeleteItem. The Resource name specifies the exact table on which these operations are allowed.If you had entered ""* in place of the resource name, you would have allowed these operations on all the tables. However, here, we want to allow these operations on just one table, and therefore, the arn (Amazon Resource Name) of this table is provided in the Resource name, using the standard arn format. Here again, the first one of either the option region (specified during serverless deploy) or the region mentioned in provider (us-east-1 by default)is used.

在函数部分中,函数按标准格式定义。请注意,get、update、delete 都具有相同的路径,其中 id 作为路径参数。但是,每个方法都不同。

In the functions section, the functions are defined as per the standard format. Notice that get, update, delete all have the same path, with id as the path parameter. However, the method is different for each.

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

之后,我们遇到了之前未见过的另一个块 resources 块。此块基本上可以帮助你指定将在 CloudFormation 模板中创建的函数工作所需的资源。在这种情况下,我们需要为函数工作创建一张 dynamoDB 表。到目前为止,我们已经指定了表名,甚至引用了它的 ARN。但我们尚未创建该表。在 resources 块中指定表的特征将为我们创建该表。

Later on, we come across another block that we haven’t seen before, the resources block. This block basically helps you specify the resources that you will need to create, in a CloudFormation template, for the functions to work. In this case, we need to create a dynamoDB table for the functions to work. So far, we have specified the name of the table, and even referenced its ARN. But we haven’t created the table. Specifying the characteristics of the table in the resources block will create that table for us.

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 表”的表,该表的 TableName(在底部右方提及)等于 provider 中环境变量中定义的 TableName。我们将它的删除策略设置为“保留”,这意味着如果堆栈被删除,则保留该资源。请参见 here 。我们说该表将具有一个名为 id 的属性,并且它的类型将是字符串。我们还指定 id 属性将是一个 HASH 密钥或分区密钥。您可以在 dynamoDB 表中详细了解 KeySchemas here 。最后,我们指定表的读取容量和写入容量。

There are a lot of configurations being defined here, most of them specific to dynamoDB. Briefly, we are asking serverless to create a 'TodosDynamoDbTable', or type 'DynamoDB Table', with TableName (mentioned right at the bottom) equal to the one defined in environment variables in provider. We are setting its deletion policy to 'Retain', which means that if the stack is deleted, the resource is retained. See here. We are saying that the table will have an attribute named id, and its type will be String. We are also specifying that the id attribute will be a HASH key or a partition key. You can read up more about KeySchemas in dynamoDB tables here. Finally, we are specifying the read capacity and write capacity of the table.

就是这样!我们的 serverless.yml 文件现已准备就绪。由于所有函数处理程序或多或少相似,我们将仅介绍一个处理程序,即创建函数的处理程序。

That’s it! Our serverless.yml file is now ready. Now, since all the function handlers are more or less similar, we will walk through just one handler, that of the create function.

Walkthrough of the create function handler

我们从几个导入语句开始

We being with a couple of import statements

import json
import logging
import os
import time
import uuid

接下来,我们导入 boto3,如上所述,它是 python 的 AWS SDK。我们需要 boto3 通过 lambda 函数与 dynamoDB 交互。

Next, we import boto3, which, as described above, is the AWS SDK for python. We need boto3 to interface with dynamoDB from within the lambda function.

import boto3
dynamodb = boto3.resource('dynamodb')

接下来,在实际的函数处理程序中,我们首先检查“events”payload 的内容(create API 使用 post 方法)。如果它的正文不包含“text”键,我们就没有收到要添加到 todo 列表的有效项目。因此,我们引发异常。

Next, in the actual function handler, we first check the contents of the 'events' payload (create API uses the post method). If its body doesn’t contain a 'text' key, we haven’t received a valid item to be added to the todo list. Therefore, we raise an exception.

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)

Considering that we got the 'text' key as expected, we make preparations for adding it to the dynamoDB table. We fetch the current timestamp, and connect to the dynamoDB table. Notice how the environment variable defined in serverless.yml is fetched (using os.environ)

timestamp = str(time.time())
table = dynamodb.Table(os.environ['DYNAMODB_TABLE'])

接下来,我们通过使用 uuid 包生成一个随机 uuid,使用接收的数据作为文本,将 createdAt 和 updatedAt 设置为当前时间戳,并将字段“checked”设置为 False 来创建要添加到表中的项目。“checked”是另一个可以使用更新操作更新的字段,除了文本之外。

Next, we create the item to be added to the table, by generating a random uuid using the uuid package, using the received data as text, setting createdAt and updatedAt to the current timestamp,and setting the field 'checked' to False. 'checked' is another field which you can update, apart from text, using the update operation.

item = {
   'id': str(uuid.uuid1()),
   'text': data['text'],
   'checked': False,
   'createdAt': timestamp,
   'updatedAt': timestamp,
}

最后,我们将项目添加到 dynamoDB 表中,并将创建的项目返回给用户。

Finally, we add the item to the dynamoDB table and return the created item to the user.

# 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 中 bug 的解决方法。json.dumps 默认情况下无法处理小数,因此,已创建 decimalencoder.py 文件来包含处理此内容的 DecimalEncoder 类。

With this walkthrough, I think the other function handlers will be self-explanatory. In some functions, you may see this statement − "body" − json.dumps(result['Item'], cls=decimalencoder.DecimalEncoder). This is a workaround used for a bug in json.dumps. json.dumps can’t handle decimal numbers by default and therefore, the decimalencoder.py file has been created to contain the DecimalEncoder class which handles this.

恭喜您理解您使用无服务器创建的第一个综合项目。该项目创建者还在 README 文件中共享了他的部署端点以及测试这些函数的方法。看一看。前往下一章以查看另一个示例。

Congratulations on understanding your first comprehensive project created using serverless. The creator of the project has also shared the endpoints of his deployment and the ways to test these functions in the README file. Have a look. Head on to the next chapter to see another example.

Serverless - Telegram Echo Bot

这是官方 Serverless 项目列表中提供的另一个有趣的项目。我们基本上在 Telegram 上创建一个新机器人,然后使用 set_webhook 方法将其连接到我们的 Lambda 函数,然后在 Lambda 函数内编写将使机器人回显其收到的任何消息的代码。

This is another interesting project available within the official serverless projects list. We basically create a new bot on Telegram, then use the set_webhook method to connect it to our lambda function, and within the lambda function, write the code that will make the bot echo back whatever message it receives.

Prerequisites

你需要在你的手机或桌面上安装 Telegram App。下载选项可在 here 中找到。Telegram 是一款消息传递应用,有点类似于 WhatsApp 或 Messenger。安装该应用后,你需要在应用内创建一个新机器人。为此,单击“New Message”(圆形铅笔图标,在右下角),然后搜索 BotFather。单击经过验证的账号。

You will need the Telegram App installed on your mobile phone or desktop. The download options can be found here. Telegram is a messaging app,kind of similar to WhatsApp or Messenger. Once the app is installed, you will need to create a new bot within the app. For that, click on the 'New Message' icon (the round pencil icon atthe bottom right), and search for BotFather. Click on the verified account.

telegram

一旦你开始与 BotFather 聊天,创建一个新机器人就是很直观的。你发送 \newbot 命令,输入机器人的名称和用户名,然后你会得到你需要记下的访问令牌。

Once you begin your chat with BotFather, creating a new bot is quite self-explanatory. You send the \newbot command, enter a name and username for the bot, and you get an access token, which you need to note down.

newbot

Code Walkthrough

我们将查看项目结构,然后执行 serverless.yml 文件和 handler.py 文件的演练。

We will have a look at the project structure and then perform the walkthrough of the serverless.yml file and the handler.py file.

Project Structure

我们可以看到,此项目具有列在 requirements.py 中的一个外部依赖项(Python Telegram Bot 库) −

We can see that this project has an external dependency (the python telegram bot library), listed in requirements.py −

python-telegram-bot==8.1.1

package.json 和 serverless.yml 都显示,已使用 serverless-python-requirements 插件来捆绑 Python 要求(在此情况下为 Telegram Bot 库)。因此,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 中提到的版本,该版本可能已过时。

The package.json and the serverless.yml both show that the serverless-python-requirements plugin has been used for bundling the python requirements (the telegram bot library in this case).Therefore, the README.md file also suggests that you perform npm install to install the necessary plugins. I would personally recommend that you delete package.json, and install serverless-python-requirements using sls plugin install -n serverless-python-requirements. This will create the package.json file automatically. This willalso ensure that you install the latest version of serverless-python-requirements. By running npm install, you will be installing the version mentioned in the existing package.jsonwhich may be outdated.

如果你阅读了 README.md 文件,你将发现一个被引用的文件实际上不存在于项目中:serverless.env.yml。系统已要求你创建此文件,并在文件中输入你的 TELEGRAM_TOKEN。这是出于安全原因。TELEGRAM_TOKEN 应该是机密的,你不希望公开分享它。因此,此项目的创建者未在 GitHub 上添加 serverless.env.yml 文件。但你需要在你的本地计算机上创建它。

If you read the README.md file, you will find that one file which is referenced, is not actually present in the project − serverless.env.yml. You have been asked to create this file and enter your TELEGRAM_TOKEN in this file. This has been done for security reasons. The TELEGRAM_TOKEN is supposed to be confidential, and you don’t want to share it publicly. Therefore, the creator of this project has not added the serverless.env.yml file on GitHub. But you will need to create it on your local machine.

serverless.yml Walkthrough

serverless.yml 文件以服务定义开头。

The serverless.yml file begins with the definition of the service.

service: serverless-telegram-bot

接下来,定义提供程序。在此处,再次设置了环境变量。此变量的值(TELEGRAM_TOKEN)从你应该在本地创建的 serverless.env.yml 文件中获取。在此处,再次使用 $ 表示变量。

Next, the provider is defined. Here again, an environment variable has been set. The value for this variable (TELEGRAM_TOKEN) is fetched from the serverless.env.yml file which you are supposed to create locally. Here again, we’ve used $ to signify variables.

provider:
   name: aws
   runtime: python3.6
   profile: ckl
   environment:
      TELEGRAM_TOKEN: ${file(./serverless.env.yml):TELEGRAM_TOKEN, ''}

函数块非常简单。定义了两个函数,它们都是 HTTP 触发的。不过,HTTP 事件参数在此处定义在一行中。

The functions block is quite straightforward. Two functions are defined, both HTTP triggered. The http event parameters, however, are defined in a single line here.

- http:
   path: /set_webhook
   method: post

代替一行执行使用 - httpPOST /set_webhook

Instead of the single line execution used is - httpPOST /set_webhook

此外,请注意 webhookset_webhook 函数都位于同一处理程序文件中。

Also, note that both the webhook and set_webhook functions lie within the same handler file.

functions:
   webhook:
      handler: handler.webhook
      events:
         - http: POST /
set_webhook:
   handler: handler.set_webhook
   events:
      - http: POST /set_webhook

最后,定义 serverless-python-requirements 插件。

Finally, the serverless-python-requirements plugin is defined.

plugins:
   - serverless-python-requirements

Walkthrough of handler.py

我们从几个导入语句开始

We being with a couple of import statements

import json
import telegram
import os
import logging

接下来,定义一个 log 对象,它在本质上帮助我们输入 log 语句。请注意,这对于 python 运行时函数不是必需的。简单的 print 语句也会被记录下来。

Next, a logger object is defined, which basically helps us enter log statements. Please note that this is not required for python runtime functions. Simple print statements also get logged.

唯一的区别是,日志的输出包括 log 级别、时间戳和请求 ID。你可以详细了解日志记录库 here

The only difference is that the output from logger includes log level, timestamp, and request ID. You can read more about the logging library here.

# 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。它们充当函数的返回值。

Next, the JSONs for OK_RESPONSE and ERROR_RESPONSE are defined. These serve as return values of the functions.

OK_RESPONSE = {
   'statusCode': 200,
   'headers': {'Content-Type': 'application/json'},
   'body': json.dumps('ok')
}
ERROR_RESPONSE = {
   'statusCode': 400,
   'body': json.dumps('Oops, something went wrong!')
}

接下来,定义一个 helper 函数,供两个 API 函数使用。该函数使用在 serverless.yml 中以环境变量提供 Token,返回一个 bot 实例。

Next, a helper function, that is used by both the API functions is defined. This function returns a bot instance, using the Token provided as an environment variable in serverless.yml.

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) 函数将其应用于 bot。如果 webhook 设置正确,则设置 OK_RESPONSE;否则,设置 ERROR_RESPONSE。请注意,必须使用 POSTMAN 之类的工具手动触发一次此 set_webhook API。

Next, the handler functions for the two APIs are defined. Let’s look at the set_webhook function first. Over here, the bot instance is obtained from the configure_telegram function that we saw earlier. Next, the host field is extracted from the headers, and the stage field is extracted from the requestContext block of the incoming event. Using these, two fields, the URL of the webhook is constructed. Finally, it is applied to the bot using the bot.set_webhook(url) function. If the webhook is set correctly, the OK_RESPONSE is set, else the ERROR_RESPONSE is set. Please note that this set_webhook API has to be triggered manually once, using a tool like 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 函数的事件中接收到的主机和 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' 触发。因此,set_webhook 事件中的参数可以帮助我们构造 webhook 函数的端点 URL。

Let us understand how the set_webhook function is able to get the correct URL for the webhook. Note that the set_webhook and the webhook functions only have a difference of '/set_webhook' in their path. They share the same host and stage. Therefore, we can derive the endpoint of the webhook function using the host and dev received in the set_webhook function’s event. If your endpoint is 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev', then the host will be 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com' and the stage will be 'dev'. The set_webhook function is triggered by 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev/set_webhook' and the webhook function is triggered by 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev'. Thus, the parameters in set_webhook’s event can help us construct the endpoint URL of the webhook function.

最后,我们来看一下 webhook 函数。它相当简单。它从 configure_telegram helper 函数接收 bot 实例。然后,它检查事件。如果它是一个 POST 事件并包含文本,那么它将从正文中提取聊天 ID 和消息。如果文本是 '/start',表示对话开始,它将使用 bot.sendMessage(chat_id=chat_id, text=text) 命令以标准问候进行回复。否则,它将以接收到的相同文本进行回复。

Finally, let’s look at the webhook function. It is pretty straightforward. It receives the bot instance from the configure_telegram helper function. It then checks the event. If it is a POST event and contains a body, then it extracts the chat ID and message from the body. If the text is '/start', indicating the start of conversation, it replies back with a standard greeting using the bot.sendMessage(chat_id=chat_id, text=text) command. Else, it replies back with the same text that it received.

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 中的 bot 并与它聊天。它将按预期的方式回显消息。

Once you’ve triggered the set_webhook function through a tool like POSTMAN, you can open your bot on Telegram and have a chat with it. It will echo back messages as expected.

set webhook

恭喜你拥有了第一个 Telegram bot!

Congratulations on your first Telegram bot!