Python 无服务器架构的正确实现方式™(第一部分)
注:本文作者为迈克尔·拉弗斯
想必您已经有所耳闻,自 2013 年 11 月 AWS Lambda 发布以来,无服务器架构发展迅猛。无需管理服务器、只需为实际使用的计算资源付费以及开箱即用的横向扩展功能,这些听起来确实非常吸引人,但究竟该从何入手呢?作为一名 Python 开发者,您或许听说过Serverless Framework或Zappa;但面对像 Node.js 这样陌生的生态系统(在 Serverless 框架中),您可能感到畏惧,或者您并不清楚这些工具的具体功能,以及它们对于初学者来说是否过于复杂。如果是这样,那么您来对地方了。在这篇博文中,我们将简要介绍无服务器架构在 Python 中的应用,并提供一些实用技巧,帮助您快速入门。
我的应用/工作负载适合无服务器架构吗?
这可能是你了解无服务器架构时需要问的最重要的问题。虽然无服务器架构有很多优势,但它并非适用于所有应用/工作负载。例如,AWS Lambda 函数调用的当前最大持续时间为五分钟。这意味着,如果你的应用/工作负载无法拆分成五分钟的片段,那么无服务器架构可能并非最佳选择。例如,如果你的应用使用 WebSocket,需要与服务器保持持久连接,那么在 AWS Lambda 上,该连接每五分钟就会关闭并需要重新建立。这也意味着,你为实际上只是保持一个 WebSocket 连接打开的工作负载支付了所有计算时间。但是,如果你的应用基本上是无状态的,例如 REST 或 GraphQL API,那么无服务器架构可能非常适合,因为 HTTP 请求很少会超过 30 秒,更不用说五分钟了。
无服务器架构的真正优势在于能够应对负载高峰和低谷期。仔细想想,这很可能适用于你开发过的绝大多数应用/工作负载。当你的应用/工作负载出现高峰时,AWS Lambda 可以提供强大的横向扩展能力,默认情况下可处理 1000 个并发请求(此限制可以提高)。而当应用/工作负载处于低谷期时,其资源消耗不会达到峰值,从而可以节省大量运营成本。想想看,大多数应用/工作负载都服务于不同的时区,那么为什么要在客户休息时支付全额费用来运行应用/工作负载呢?
如果您仍然不确定您的应用程序/工作负载是否适合,这里有一个方便的计算器,可以比较 AWS Lambda 和 EC2。
Python 2 还是 Python 3?
过去十年,Python 生态系统经历了诸多变革——其中最显著的莫过于 Python 3 的发布以及众多代码库从 Python 2.x 向 3.x 的迁移。在无服务器领域,我们给出的建议与其他任何 Python 项目一样:如果是新项目,请使用 Python 3.6。虽然 Python 2.7 一直以来都运行良好,但它将在 2020 年后停止更新。因此,如果您尚未开始迁移到 3.6,您还有两年的时间来完成这项工作。
如果您有一个现有的 Python 2.7 项目,那您很幸运,因为 AWS Lambda 支持 2.7 版本,但您应该认真考虑尽快将代码移植到 Python 3.6。这里有一份非常有用的速查表,教您如何编写 Python 2 和 3 兼容的代码。与速查表一样,本文中的建议也适用于 2.7 和 3.6 版本。
Web API 还是 Worker?
在介绍 Python 可用的无服务器工具之前,让我们先深入了解一下我们的应用程序/工作负载。如果您有一个 Web 应用程序,其前端包含多个 Web 资源(HTML、JavaScript、CSS、图像等),那么您不应该通过 AWS Lambda 函数来提供这些资源。这并不是说您不能这样做,而是说您不应该这样做。请记住,AWS Lambda 是按函数运行时间付费的。把这些时间浪费在提供 Web 资源上并不划算。事实上,由于您的前端可能包含大量 Web 资源,因此这种方式对于原本简单的任务来说可能成本较高。要提供 Web 资源,您应该考虑使用 CDN(内容分发网络)。AWS 提供了一项专门用于此目的的服务,名为 CloudFront,这里有一份关于如何将其与 S3 结合使用的指南。
但这实际上只涵盖了 Web 应用的前端。或者,你的应用/工作负载可能根本没有前端。我们将这些工作负载分为两类:Web API(REST、GraphQL 等)和工作进程。你可能已经熟悉 Web API,AWS Lambda 允许我们使用名为 API Gateway 的服务来提供这些服务(稍后会详细介绍)。另一类,我称之为“工作进程”,是指你的应用可能需要执行的所有其他任务,例如:发送电子邮件、上传文件、推送通知等等。Serverless 可以处理这些工作负载,甚至提供了类似 cron 作业的服务来处理定时任务。
我之所以要区分这两类工作负载,是因为虽然在无服务器架构中,这些工作负载看起来可能很相似(或者说难以区分,因为它们本质上都是函数),但有些工具更擅长或更适合处理其中一种工作负载。在本指南中,我将指出具体情况。希望您已经考虑过应用程序中的哪些部分将通过 Web API 提供服务,哪些部分可以作为后台运行的工作任务,这样您就可以选择合适的工具来完成任务。
无服务器框架
在无服务器领域,Serverless Framework无疑是领军者,这绝非偶然。其背后的团队投入了大量时间和精力来优化开发者体验,使其成为目前最直观易用、最便捷的无服务器工具之一。它还提供了一套首屈一指的功能集,除了支持 AWS Lambda 之外,还支持多种云平台,并拥有不断壮大的插件生态系统。对于 Python 开发者而言,这无疑是理想的起点,而且也的确如此。
但 Serverless 框架并非完美无缺,它也存在一些 Python 特有的注意事项。首先,Serverless 是用 Node.js 编写的,这可能并非你的理想选择。但就像任何优秀的工具一样,如果使用得当,你甚至不会注意到它是由什么语言实现的,Serverless 在这方面确实可以做到。话虽如此,你仍然需要安装 Node 和 NPM,但之后你就可以npm install -g serverless开始使用 Serverless CLI 作为你的主要界面了。你不需要了解任何 JavaScript,尽管你的配置文件默认是 YAML 格式。
我们来试一试。
npm install -g serverless
serverless可以使用 `cd`或 `cd` 简写来访问 CLI sls。我更喜欢 `cd` sls,所以让我们创建一个项目:
mkdir ~/my-serverless-project
cd ~/my-serverless-project
sls create -n my-serverless-project -t aws-python3
这里我创建了一个名为 `<directory>` 的目录my-serverless-project,并使用 `<project>` 创建了一个项目sls create。我还使用 `<template>` 指定了一个模板-t aws-python3。Serverless 捆绑了几个模板,这些模板会在serverless.yml你运行 `<project>` 命令时创建的 `<project>` 文件中设置一些合理的默认值sls create。在本例中,我指定了适用于 Python 3.6 的 AWS 模板。aws-python2如果你的项目是 Python 2.7,还有一个 `<template>` 可供选择。还有其他语言和云平台的模板,但这超出了本指南的范围。`<service>`-n my-serverless-project指定了一个服务名称,你可以将其更改为你想要的任何项目名称。运行这些命令后,serverless.yml你的目录中应该会有一个my-serverless-project`<project>` 文件。让我们来看看它的内容:
cat serverless.yml
你会发现,这serverless.yml是一个 YAML 文件,其中包含许多有用的注释,解释了配置的每个部分。此时,我建议你阅读这些注释,这在后面会很有帮助。阅读完毕后,让我们编写一个相当于 Lambda 函数的 Hello World 示例:
def handler(event, context):
return {"message": "hi there"}
并将其保存到我们的my-serverless-project目录中hello.py。这个名称handler只是 Lambda 函数的常用名称,您可以根据自己的喜好为函数命名。现在我们有了一个函数,让我们通过将其添加到我们的配置文件中来让 Serverless 识别它serverless.yml。首先打开您的配置文件serverless.yml并找到 `<serverless_config_file>`functions部分。然后将该部分替换为以下内容:
functions:
hello:
handler: hello.handler
现在保存您的设置serverless.yml。要部署此函数,我们需要确保已配置 AWS 凭证。假设一切就绪,那么只需运行以下命令:
sls deploy
部署需要一些时间。简而言之,Serverless 正在执行以下步骤:
- 基于以下内容创建 CloudFormation 模板
serverless.yml - 将 CloudFormation 模板和您的文件压缩
hello.py到一个 zip 压缩包中。 - 创建一个 S3 存储桶并将 zip 压缩包上传到该存储桶。
- 执行 CloudFormation 模板,其中包括配置 AWS Lambda 函数并将其指向 S3 zip 压缩包。
你可以手动完成所有这些步骤,但为什么要这样做呢?部署完成后,我们可以使用以下命令进行测试:
sls invoke -f hello
这-f hello是我们在文件中指定的函数名称serverless.yml。该sls invoke命令会调用该函数。函数调用是调用函数的另一种说法。当我们调用此函数时,它应该返回:
{"message": "hi there"}
如果你看到的是这样的,恭喜你,你已经创建了你的第一个 AWS Lambda 函数。但这似乎没什么用处。如果我们想做一些更有挑战性的事情,比如发送 HTTP 请求并返回结果呢?让我们创建一个名为 `.js` 的新文件httprequest.py,并添加以下内容:
import requests
def handler(event, context):
r = requests.get("https://news.ycombinator.com/news")
return {"content": r.content}
更新functions您的以下部分serverless.yml:
functions:
hello:
handler: hello.handler
httprequest:
handler: httprequest.handler
然后重新部署并调用我们的新函数:
sls deploy
sls invoke -f httprequest
现在你应该会看到一个错误ImportError。这是因为requests尚未安装。使用 AWS Lambda 时,我们需要将要使用的所有库打包到函数中。我们可以通过多种方式实现这一点,例如pip运行以下命令:
# Don't run this just yet, keep reading
pip install requests -t .
requests这将把wheel 文件(及其依赖项)安装到我们的my-serverless-project目录中。但这里有个问题。AWS Lambda 运行在 64 位 Linux 系统上。由于requests它包含一些编译后的依赖项,如果您运行的不是 64 位 Linux 系统,那么requests您刚刚安装的这个版本将无法在 AWS Lambda 上运行。那么,如果您运行的是 macOS、Windows 或 FreeBSD 系统该怎么办呢?总之,您应该明白我的意思。
Serverless 插件(以及 Docker)来帮忙了。
值得庆幸的是,Serverless 拥有一个插件生态系统来弥补各种不足,而对于 Python 来说,编译后的代码包恰好就是其中一个不足之处。这里相关的插件名为serverless-python-requirements,安装方法如下:
npm install -g serverless-python-requirements
并在文件末尾添加以下几行serverless.yml:
plugins:
- serverless-python-requirements
现在,这个插件会立即启用支持。因此,我们现在只需要在项目目录中添加一个文件,requirements.txt而不是像上面那样添加代码,下次运行时,所需的依赖项就会自动安装和打包。现在就来添加:piprequirements.txtsls deploy
echo "requests" >> requirements.txt
但我们还没有解决编译问题。为此,我们需要custom在配置文件中添加一个部分serverless.yml。这个部分用于放置自定义配置选项,插件也会从中查找自身的配置选项。新的custom部分应该如下所示:
custom:
pythonRequirements:
dockerizePip: true
这样做会指示serverless-python-requirements插件在将 Python 包打包到 zip 压缩包之前,先在 Docker 容器中编译它们,以确保它们是为 64 位 Linux 系统编译的。您还需要安装 Docker才能使此功能生效,但安装完成后,该插件会自动处理您在文件中定义的依赖项requirements.txt。是不是很棒?
现在让我们重新部署并重新调用我们的函数:
sls deploy
sls invoke -f httprequest
即使你运行的是 64 位 Linux 系统,这种方法也更简洁,你不觉得吗?
在我们继续之前,您可能有一些疑问。什么是这些event?context这些对我有什么用?
AWS Lambda 函数是事件驱动的,因此当您调用一个函数时,实际上是在 AWS Lambda 中触发一个事件。Lambda 函数的第一个参数包含触发该函数的事件,该事件在 AWS Lambda 中以 JSON 对象的形式表示,但传递给 Python 的是该对象的字典。当您运行 `Lambda.py` 时sls invoke -f hello,传递给 Lambda 函数的是一个空字典。但如果是 API 请求,它将包含整个 HTTP 请求,并以字典的形式表示。换句话说,该event字典充当 Lambda 函数的输入参数,而 Lambda 函数的返回值则是输出。使用 AWS Lambda 时,您的输出也需要是可序列化的 JSON 对象。以下是一些您可能会在 AWS Lambda 中看到的示例事件。
第二个参数是 AWS Lambda 上下文,它是一个 Python 对象,包含有关 Lambda 函数和当前调用的有用元数据。例如,每次调用都有一个 `_INCTION_SIMCAR`aws_request_id属性,如果您想在日志中追踪特定调用发生的情况,这将非常有用。点击此处查看有关上下文对象的更多信息。您可能暂时不需要关注上下文对象,但最终会在调试时发现它很有用。
那么这有什么用呢?如果你的应用/工作负载能够处理 JSON 序列化的输入并生成 JSON 序列化的输出,那么它就可以直接接入 AWS Lambda 函数。回到我们之前提到的两种工作负载类型:Web API 和 worker,我们已经基本实现了 worker 所需的功能。假设我们想让函数httprequest每十分钟运行一次,那么我们只需要在代码中添加以下内容serverless.yml:
functions:
httprequest:
handler: httprequest.handler
events:
- schedule: rate(10 minutes)
并重新部署:
sls deploy
好了,完成了。现在httprequest它每十分钟自动触发一次。如果您需要更精细的控制,也可以指定函数触发的具体时间。您还可以使用 SNS、SQS 或其他 AWS 服务构建更复杂的工作流。
那么 Web API 呢?还记得我们之前讨论过事件,并提到过 HTTP 请求可以表示为一个事件吗?对于 Web API,Amazon 的 API Gateway 服务会为我们触发这些事件。此外,API Gateway 还会提供一个主机名来接收 HTTP 请求,将这些 HTTP 请求转换为事件对象,调用我们的 Lambda 函数,收集响应并将其作为 HTTP 响应传递给请求者。这听起来可能很复杂,但幸运的是,Serverless 框架已经为我们抽象化了其中的大部分工作。让我们添加一个 HTTP 端点serverless.yml:
functions:
webapi:
handler: webapi.handler
events:
- http:
path: /
method: get
这看起来很像我们之前安排的工作任务,对吧?就像在之前的计划任务中一样,这里我们配置这个处理程序来处理http事件,并指定 `a` 和 ` pathb` method。正如你可能已经猜到的,`a`path是我们的 HTTP 请求路径,`b`method是这个处理程序将要处理的 HTTP 方法。你还会注意到我们添加了一个新的 `a` handler,现在让我们在 `a` 中创建它webapi.py:
import json
def handler(event, context):
return {"statusCode": 200, "body": json.dumps({"message": "I'm an HTTP response"})}
在这里,我们的处理程序会接收来自 API 网关的事件,并以一个可序列化的 JSON 字典作为响应。该字典包含两个键:第一个键statusCode是 HTTP 状态码,我们希望 API 网关返回该状态码;第二个键是响应的 HTTP 正文。您还会注意到,我们body也将其序列化为 JSON。这样做的原因是 API 网关期望 HTTP 响应正文为字符串。因此,如果我们希望 Web API 以 JSON 格式响应,则需要在将其返回给 API 网关之前对其进行序列化。这也意味着,如果您需要支持其他格式的序列化,例如 XML、YAML 或其他格式,也可以这样做。
现在让我们部署它:
sls deploy
完成后,Serverless框架将为我们提供一个名为“endpoint”的端点:
endpoints:
GET - https://XXXXXXXXXX.execute-api.us-east-1.amazonaws.com/dev
您的端点看起来会略有不同。刚才发生了什么?简而言之,Serverless 创建了我们新的 AWS Lambda 函数,然后配置了一个 AWS API Gateway 指向该 Lambda 函数。您在上方看到的端点是由 API Gateway 提供的。
让我们来测试一下我们的终点:
curl https://XXXXXXXXXX.execute-api.us-east-1.amazonaws.com/dev
您应该会看到以下响应:
{"message": "I'm an HTTP response"}
恭喜,您刚刚创建了您的第一个无服务器 Web API。现在您可能已经注意到,API 网关提供的 URL 格式相当难看。如果它能像这样更易读就好了https://api.mywebapi.com/。嗯,也有相应的插件可以实现这一点。
清理
在这篇文章中,我们创建了三个 Lambda 函数和一个 API 网关。但这些只是示例,旨在帮助您初步了解无服务器架构。您可能需要进行一些清理工作,只需运行以下命令即可:
sls remove
剩下的工作就交给 Serverless 框架来处理吧。
总结
本文介绍了 AWS Lambda 的一些基础知识,并使用 Serverless 框架创建了一个简单的 Web API 和 worker。在第二部分中,我们将探讨一些更高级的主题,包括两个专为 Python 构建的 Serverless 框架:Zappa和Chalice 。我们还将演示如何使用这些工具快速构建 Web API,并提供使用Django和Flask等流行 Web 框架的示例。
文章来源:https://dev.to/adjohn/the-right-way-to-do-serverless-in-python-part-1-353o