欢迎来到雲闪世界。AWS Lambda 通常是在云中部署和执行代码的最简单方法之一,尤其是在使用sam CLI部署代码时。无服务器资源定义的简单性加上在本地打包资源并确保它们在 AWS 上运行的能力,提供了美妙的开发体验。
但有时,当构建和打包步骤增加到十分钟、十五分钟或(哇)更多分钟时,这个漂亮的流程可能会变成一个可怕的部署过程。在一些地方,sam build/sam deploy范式会崩溃并开始导致部署时间失控:
-
部署中的 Lambda 数量变得很大(例如超过六个)
-
您的部分或全部 Lambda 需要安装大量资源和/或 Lambda 包大小较大(增加了构建时间和sam将代码放到 S3 上的时间)
-
您正在同时对两个 Lambda 进行协调更改(在测试之前,需要在 AWS 中更新多个堆栈)
-
多个人同时在同一个 Lambda 堆栈上进行开发
-
你的网速很慢(再次延长将 Lambda 包放入 S3 的时间)
由于sam将所有资源部署在 CFT 中,因此您无法有选择地选择要逐步更新哪些资源。其影响是,您需要为要在云中测试的每个部署构建并将所有代码上传到 AWS。当您将代码推送到 AWS 后才意识到您拼错了一个字符串并且需要重新部署整个堆栈时,这种情况会变得特别烦人。
添加图片注释,不超过 140 字(可选)
是的,sam确实提供了一种在本地调用 lambda 的方法,但当您想要测试 Lambda 之间的协调更改时,这无济于事。使用sam您无法在本地部署两个 lambda并在它们之间路由流量(据我所知)。当然,可能有更好的方法来构建和部署您的代码以防止单片部署问题,但总会有权衡。单个 Lambda 中应该包含多少功能代码?如何选择何时将函数分离到不同的堆栈中?如何围绕堆栈组织存储库?
如果您的打包和部署步骤很长,但又想加快构建和测试 Lambda 所需的时间,该怎么办?或者,如果您有来自不同堆栈的 lambda 相互调用,而您想同时在本地测试它们,该怎么办?
使用代理重新路由流量
使用代理重新路由流量的基本原理是,我们希望捕获发往 AWS 的传输中请求,然后将其发送回本地计算机,而不是允许其进入云端。这个过程通常称为中间人 (MITM)。这个名字因被广泛用于黑客攻击而声名狼藉,但它在帮助减少测试 Lambda 所需的时间方面具有实际用途。
添加图片注释,不超过 140 字(可选)
这里的关键是,我们只想将预配置的端点重新路由回我们的本地机器。例如,如果我们部署一个有五个 Lambda 的堆栈,并且我们只对其中一个 Lambda 进行更改,那么其他四个 Lambda 的流量应该继续流向 AWS。
捕获流向 AWS 的流量
当您使用boto3或其他 AWS 程序包调用 AWS Lambda 时,您只是通过 HTTPS 调用 API;AWS 程序包会为您处理身份验证、端点创建和序列化。由于所有这些流量都是通过 HTTPS 发送的,因此您可以通过代理查看计算机的所有 HTTPS 传出流量!
如果您使用的是 Mac,则可以在系统偏好设置下更新 HTTPS 代理设置,然后network > advanced > proxies。在这里,我已为端口 9090 打开了本地代理。
添加图片注释,不超过 140 字(可选)
如果您下载Proxyman之类的工具,然后从本地机器调用 lambdas,您就可以自己看到流经流量的流量。
添加图片注释,不超过 140 字(可选)
Proxyman 中显示的对 AWS 的 HTTPS 请求的屏幕截图(图片来自作者)
匹配来自端点的流量以在本地重新路由它们
现在 HTTPS 流量正在通过我们的代理,我们有能力对其进行操纵。这是通过中间人程序完成的。此 MITM 将读取通过代理的所有事件,并可选择对它们应用某些操作。这是在本地测试我们的 Lambda 的关键。
我正在使用mitmproxy,这是一个功能非常强大的工具,它允许将脚本逻辑应用于通过代理的任何记录(又称流)。mitmproxy cli 接受一个 python 文件作为参数,它将 HTTPS 事件提供给 python 脚本以采取行动。这是一个脚本示例
此代码可以通过以下命令运行(注意 - 9090 是我的 HTTPS 代理正在运行的端口):
import json
import time
from mitmproxy import http
class MatchedFlow(object):
def __init__(self, from_route, to_route):
self.from_route = from_route
self.to_route = to_route
def matches(self, route_url: str) -> bool:
if self.from_route in route_url:
return True
return False
def replace_url(self, flow: http.HTTPFlow) -> None:
env_url = flow.request.url.replace(self.from_route, '')
replacement_route = self.to_route + env_url[env_url.index('/'):]
flow.request.url = replacement_route
class FlowMatcher(object):
"""Matches specific flows (URLs) that are being sent to AWS and routes them to internally
running services
"""
def __init__(self, match_flows):
self.route_confs = None
self.match_flows = match_flows
def match_update_url(self, flow: http.HTTPFlow) -> None:
"""Matches a request against the set of configuraitons provided to see if the request
should be rerouted.
:param flow: A mitmproxy http flow object containing the full request details
:return: None
"""
for match_flow in self.match_flows:
if match_flow.matches(flow.request.pretty_url):
match_flow.replace_url(flow)
break
mf1 = MatchedFlow(from_route='https://google.com', to_route='https://bing.com')
mf2 = MatchedFlow(from_route='https://reddit.com', to_route='https://medium.com')
matched_flows = [mf1, mf2]
FM = FlowMatcher(matched_flows)
def request(flow: http.HTTPFlow) -> None:
FM.match_update_url(flow)
此代码可以通过以下命令运行(注意 - 9090 是我的 HTTPS 代理正在运行的端口):
mitmdump -p 9090 重新路由流量.py
重新路由 Lambda 流量
您可能已经在上面的屏幕截图中注意到,向 AWS 发出的所有请求都遵循相同的命名约定;区域、lambda 版本日期和 lambda 函数名称被连接在一起以构建端点。当您运行时,sam local start-lambda …本地也会发生类似的过程!AWS 在您的计算机上创建一个映射到特定端口的本地端点。您甚至可以像这样指定该端口:
sam local start-lambda -p 54321 --template /path/to/template.yaml
您的 Lambda 将在本地运行,您可以看到本地端点:
添加图片注释,不超过 140 字(可选)
到目前为止,我们已经学习了如何通过中间人捕获和重新路由流量,以及如何在本地启动 lambda 并选择其运行的端口。剩下要做的唯一一件事就是构建一个映射,从 AWS 中的端点指向运行此 Lambda 的本地端口。这可能会让你感到棘手。在本地启动 lambda 时,你只能使用资源名称sam调用 lambda ,但正如我们上面看到的,Lambda URL 包含函数名称。
添加图片注释,不超过 140 字(可选)
CFT Lambda 定义(作者提供图片)
我总是为我的 Lambda 使用标准化的命名约定,我的团队也非常严格地遵循它,如上所示,命名约定是“ {StackName}-{ResourceName}-{Env} ”。这样,在将流量路由到本地运行的 Lambda 时,我只需从函数名称中剥离堆栈名称和环境即可。这允许中间人脚本使用此基本逻辑替换流!
reroute_flow = {
'from_route':'my-stack-MyLambda1-dev',
'to_route':'MyLambda1'
}
mf = MatchedFlow(**reroute_flow)
FM = FlowMatcher([mf])
瞧!一旦插入中间人脚本,我们现在就可以将流量路由到本地 Lambda。我们甚至可以定义多个重新路由,以便在本地版本的 Lambda 之间路由流量!
reroute_flow1 = {
'from_route':'my-stack-MyLambda1-dev',
'to_route': 'MyLambda1'
}
reroute_flow2 = {
'from_route':'my-stack-MySecondLambda-dev',
'to_route': 'MySecondLambda'
}
mf1 = MatchedFlow(**reroute_flow1)
mf2 = MatchedFlow(**reroute_flow2)
FM = FlowMatcher([mf1, mf2])
LocaLambda — 一个简化事情的 CLI
为了在本地测试 Lambda,有很多事情要做:设置 HTTPS 代理、定义匹配规则、在本地提供 Lambda 等。LocaLambda (简称lola)是一个轻量级 CLI,可以简化此过程。Lola 提供三种功能:
-
设置:设置所需的资源和配置文件,以使lola运行,这是安装lola后的一次性活动
-
构建:lola将构建lola.yaml文件中定义的特定资源。它通过使用仅包含指定资源的精简版本复制实际的 template.yaml 文件来实现此目的
-
服务:lola针对配置文件中定义的每个 Lambda运行sam local start-lambda... ,并跟踪所使用的特定端口。然后,它将自动启动您的本地 HTTPS 代理服务器并执行中间人脚本。应该注意的是,中间人运行的可执行文件与lola 不同。为了告诉中间人要应用什么匹配逻辑,它会序列化本地部署资源的映射并将它们放在 Redis 上(您也必须在本地运行它)。
LocaLambda 设置
LocaLambda 可在pypi上使用,可以通过以下方式安装:
pip 安装 localambda
首次安装时,LocaLambda 需要设置一些特定于您计算机的资源和配置文件。您可以让 LocaLambda 指导您完成一次性设置:
lola——设置
输出结果是一个.lolarc位于您的主目录中的资源文件和一个lola位于您选择的位置的新目录。您需要lola.yaml在目录中创建一个文件(例如下面的文件),该文件将告诉 LocaLambda 要构建和提供哪些资源。此文件也可以在 GitHub 存储库的示例lola部分中找到。
repo_home: "/your/path/to/base/git/directory"
region: us-east-1
stacks:
- MyLambdaFunctionTest: # This item is the minimum configurations for a resource
location: deployable_lambda
stack_name: my-lambda-stack
resources:
- MyLambdaFunction # CFT Resource Name
- MySecondLambda:
location: deployable_lambda
stack_name: my-second-lambda-stack
template: template.yml # Specify your own template name, default is template.yaml
region: us-east-1 # Optionally override the default region
resources:
- MySecondLambdaFunction # CFT Resource Name
这些配置中的很多都是基于我们的使用情况,您的需求可能会略有不同。由于我对 Lambdas 使用了标准化的命名约定,因此我只需要告诉lola三件事:
-
template.yaml 文件的位置:这是repo_home 和location的组合, 或者可以提供完全限定的位置。这应该是代码和模板所在的目录。
-
堆栈名称:lola需要删除发往 AWS 的 HTTPS 请求中的堆栈名称和环境名称,以便找到特定的资源名称。环境将是静态的 (dev),因此目前是硬编码的。
-
资源:CFT template.yaml 文件中应部署或提供的资源名称列表。如果您的 CFT 中有十个 lambda,则可以选择一个、两个或更多个进行构建和测试,而不必构建和部署所有十个。
奔跑的萝拉
LocaLambda 主要做两件事:构建资源并在本地提供服务。您可以单独使用lola构建和提供 lambda ,也可以同时进行这两项操作。构建只需要-b在提供服务需要的地方添加标志-s。要构建和服务,您可以运行:
lola-bs
当使用示例配置文件运行此命令时,您将获得以下输出:
添加图片注释,不超过 140 字(可选)
LocaLambda 构建并提供输出(作者提供的图片)
LocaLambda 将按顺序执行几个步骤,如果您仅构建或部署,则可以跳过其中一些步骤:
-
(构建)为lola.yaml配置中的每个堆栈创建精简版部署模板,只有选定的资源才会包含在构建中,而不是整个堆栈
-
(构建)sam build ...为每个精简的模板运行
-
(服务)对于lola.yaml中定义的每个堆栈,运行 sam local start-lambda ..
-
(服务)对于lola.yaml中定义的每个堆栈,将中间人的匹配逻辑序列化并将它们放在 Redis 上
-
(服务)启动中间人,从 Redis 读取配置并开始匹配 HTTPS 事件
关闭 SSL 验证
由于我们正在捕获 HTTPS 流量并将其重新路由到其他主机,因此我们将不可避免地遇到 SSL 证书问题,因为您的本地计算机无法拥有涵盖 AWS 域的证书。为了解决这个问题,AWS Lambda 背后的开发人员提供了一种关闭 SSL 验证的巧妙方法。设置 Lambda 客户端时,只需将 verify 标志设置为False,如 boto3 所示:
导入 boto3 lambda_client = boto3.client('lambda',verify = False)
实际上,您不想硬编码此值,因为在所有其他环境中,您都希望让 lambda 客户端验证证书。这里的一个建议是只使用环境变量(例如 local、dev、staging、prod 等),并且仅在本地部署时将其设置为验证。
测试以确保事件被拦截和重新路由
现在一切都已设置完毕并开始运行,我们可以测试流量是否正确路由到本地 Lambda。当lola为您的本地资源提供服务时,可以在 shell 中运行以下脚本。
import json
import boto3
lambda_client = boto3.client('lambda', verify=False)
user_address = lambda_client.invoke(
FunctionName='my-lambda-stack-MyLambdaFunction-dev',
InvocationType='RequestResponse',
Payload=json.dumps({'some': 'input'})
我在后台进行了设置,以便此 Lambda 部署在本地,并调用部署在 AWS 中的另一个 lambda。您可以在此处看到,流量被路由到本地提供的第一个 Lambda,而不是 AWS!
LocaLambda 重新路由事件输出(作者提供图片)
结束语
LocaLambda 可以成为您工具箱中一款出色的字符串工具,可加快您的开发过程,但它绝不是万灵药。它比localstack更轻量,但如果只需要一两分钟即可构建并将代码推送到 AWS,它可能仍然有点太重了。它有几个地方有改进的空间(并且始终欢迎反馈/ PR):
-
允许函数名称到资源映射的自定义匹配逻辑
-
处理层集成,包括本地层和从 AWS 导入的层
-
管理 ssh 隧道;许多需要访问私有网络中数据库的 lambda 也部署在私有网络中,或者至少部署在可以访问数据库的安全组中。能够快速测试需要数据库访问的 lambda,而无需手动配置隧道,这将是一个巨大的进步。
-
能够使用 repo 中的模板的不同结构进行部署(例如 template.yaml 不在 repo 根目录、多个模板、嵌入式模板等)
-
允许动态链接和取消链接不同的lola.yaml配置文件,以避免需要更新单个配置
总而言之,这是一个巧妙而有趣的小工具,可以满足我们团队不时遇到的利基用例的需求。我希望你也可以从这个项目中获得一些价值!
感谢关注雲闪世界。(Aws解决方案架构师vs开发人员&GCP解决方案架构师vs开发人员)
订阅频道(https://t.me/awsgoogvps_Host)
TG交流群(t.me/awsgoogvpsHost)