使用 HTTPS 代理在本地测试 AWS Lambdas

​       欢迎来到雲闪世界。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三件事:

  1. template.yaml 文件的位置:这是repo_home 和location的组合, 或者可以提供完全限定的位置。这应该是代码和模板所在的目录。

  2. 堆栈名称:lola需要删除发往 AWS 的 HTTPS 请求中的堆栈名称和环境名称,以便找到特定的资源名称。环境将是静态的 (dev),因此目前是硬编码的。

  3. 资源:CFT template.yaml 文件中应部署或提供的资源名称列表。如果您的 CFT 中有十个 lambda,则可以选择一个、两个或更多个进行构建和测试,而不必构建和部署所有十个。

奔跑的萝拉

LocaLambda 主要做两件事:构建资源并在本地提供服务。您可以单独使用lola构建和提供 lambda ,也可以同时进行这两项操作。构建只需要-b在提供服务需要的地方添加标志-s。要构建和服务,您可以运行:

lola-bs

当使用示例配置文件运行此命令时,您将获得以下输出:

添加图片注释,不超过 140 字(可选)

LocaLambda 构建并提供输出(作者提供的图片)

LocaLambda 将按顺序执行几个步骤,如果您仅构建或部署,则可以跳过其中一些步骤:

  1. (构建)为lola.yaml配置中的每个堆栈创建精简版部署模板,只有选定的资源才会包含在构建中,而不是整个堆栈

  2. (构建)sam build ...为每个精简的模板运行

  3. (服务)对于lola.yaml中定义的每个堆栈,运行 sam local start-lambda ..

  4. (服务)对于lola.yaml中定义的每个堆栈,将中间人的匹配逻辑序列化并将它们放在 Redis 上

  5. (服务)启动中间人,从 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)

  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值