首页 > 编程语言 >Python-无服务器微服务构建指南-全-

Python-无服务器微服务构建指南-全-

时间:2024-05-20 16:57:35浏览次数:30  
标签:指南 服务 Python AWS DynamoDB API dynamo 服务器 Lambda

Python 无服务器微服务构建指南(全)

原文:zh.annas-archive.org/md5/3c97e70c885487f68835a4d0838eee09

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

这本书将让您对微服务和无服务器计算有很好的理解,以及它们与现有架构相比的优缺点。您将对部署完整的无服务器堆栈的威力有所认识,不仅在节省运行成本方面,还在支持维护和升级方面。这有效地使您的公司能够更快地推出任何新产品,并在这个过程中击败竞争对手。您还将能够创建、测试和部署一个可扩展的无服务器微服务,其成本是按使用量支付的,而不是按其正常运行时间支付的。此外,这将允许您根据请求的数量进行自动扩展,同时安全性是由 AWS 本地构建和支持的。因此,既然我们知道前方有什么,让我们立即开始阅读这本书吧。

这本书是为谁准备的

如果您是一名具有 Python 基础知识的开发人员,并且想要学习如何构建、测试、部署和保护微服务,那么这本书适合您。不需要先前构建微服务的知识。

本书涵盖的内容

第一章,无服务器微服务架构和模式,提供了单片和微服务架构的概述。您将了解设计模式和原则,以及它们与无服务器微服务的关系。

第二章,创建您的第一个无服务器数据 API,讨论了安全性及其重要性。我们将讨论 IAM 角色,并概述一些安全概念和原则,涉及到保护您的无服务器微服务,特别是关于 Lambda、API Gateway 和 DynamoDB。

第三章,部署您的无服务器堆栈,向您展示如何仅使用代码和配置部署所有基础设施。您将了解不同的部署选项。

第四章,测试您的无服务器微服务,涵盖了测试的概念。我们将探讨许多类型的测试,从使用模拟进行单元测试,使用 Lambda 和 API Gateway 进行集成测试,本地调试 Lambda,并使本地端点可用,到负载测试。

第五章,保护您的微服务,涵盖了如何使您的微服务安全的重要主题。

充分利用本书

一些先前的编程知识将会有所帮助。

所有其他要求将在各自章节的相关点中提到。

下载示例代码文件

您可以从您在www.packt.com的账户中下载本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packt.com/support并注册,以便文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. www.packt.com上登录或注册。

  2. 选择“支持”选项卡。

  3. 点击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

下载文件后,请确保使用最新版本的解压缩或提取文件夹:

  • Windows 的 WinRAR/7-Zip

  • Mac 的 Zipeg/iZip/UnRarX

  • Linux 的 7-Zip/PeaZip

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Building-Serverless-Microservices-in-Python。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有来自我们丰富书籍和视频目录的其他代码包,可在github.com/PacktPublishing/上找到。查看一下!

下载彩色图片

本书中使用的屏幕截图/图表的彩色图像也可以在 PDF 文件中找到。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789535297_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这里有一个例子:“在这里,您可以看到我们将 EventId 作为资源 1234,以及格式为 YYYYMMDD 的 startDate 参数。”

代码块设置如下:

  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

{
  "firstName": "John",
  "lastName": "Smith",
  "age": 27,
  "address": {

任何命令行输入或输出都以以下方式编写:

$ cd /mnt/c/

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单中的单词或对话框中的单词会在文本中以这种方式出现。这里有一个例子:“在 DynamoDB 导航窗格中,选择 Tables,然后选择 user-visits。”

警告或重要说明会以这种方式出现。

提示和技巧会出现在这样。

第一章:无服务器微服务架构和模式

微服务架构基于服务。您可以将微服务视为 SOA 的轻量级版本,但受到更近期架构的丰富,例如事件驱动架构,其中事件被定义为感兴趣的状态变化。在本章中,您将了解单片多层架构和单片面向服务的架构SOA)。我们将讨论这两种架构的优缺点。我们还将研究微服务的背景,以了解其增长背后的原因,并比较不同的架构。

我们将介绍设计模式和原则,并介绍无服务器微服务集成模式。然后,我们将涵盖通信样式和分解微服务模式,包括同步和异步通信。

然后,您将学习如何在 AWS 中使用无服务器计算快速部署基于事件驱动的计算和云中的微服务。我们通过设置您的无服务器 AWS 和开发环境来结束本章。

在本章中,我们将涵盖以下主题:

  • 理解不同的架构类型和模式

  • 虚拟机、容器和无服务器计算

  • 微服务集成模式概述

  • 通信样式和分解微服务模式

  • AWS 中的无服务器计算

  • 设置您的无服务器环境

理解不同的架构类型和模式

在本节中,我们将讨论不同的架构,如单片和微服务,以及它们的优缺点。

单片多层架构和单片面向服务的架构

在我职业生涯的早期,当我为 Capgemini 的全球财富 500 强客户工作时,我们倾向于使用多层架构,其中您可以创建不同的物理分离层,可以独立更新和部署。例如,如下所示的三层架构图表中,您可以使用表示领域逻辑数据存储层:

表示层,您有用户界面元素和任何与表示相关的应用程序。在领域逻辑中,您有所有业务逻辑和与从表示层传递数据有关的任何内容。领域逻辑中的元素还涉及将数据传递给存储或数据层,其中包括数据访问组件和任何数据库元素或文件系统元素。例如,如果您想要将数据库技术从 SQL Server 更改为 MySQL,您只需更改数据访问组件,而不是修改表示层或领域逻辑层中的元素。这使您能够将存储类型与表示和业务逻辑解耦,从而能够更容易地通过交换数据存储层来更改数据库技术。

几年后在 Capgemini,我们使用 SOA 来实施客户项目,这比多层架构更加细粒度。基本上是有标准化的服务契约和注册表,允许自动化和抽象:

与 SOA 相关的四个重要服务属性:

  • 每项服务都需要有一个与企业活动相关联的清晰业务活动。

  • 使用服务的任何人都不需要了解内部工作原理。

  • 所有信息和系统都是自包含的和抽象的。

  • 为了支持其可组合性,服务可能由其他基础服务组成

以下是一些重要的 SOA 原则:

  • 标准化

  • 松散耦合的

  • 摘要

  • 无状态的

  • 细粒度的

  • 可组合的

  • 可发现的

  • 可重用的

第一个原则是存在标准化的服务合同。这基本上是在企业级别定义的通信协议,因此当您使用服务时,您确切地知道是哪个服务,传递消息的合同以及您将得到什么回报。这些服务是松散耦合的。这意味着它们可以自主工作,但您也可以从企业网络中的任何位置访问它们。它们还提供抽象版本,这意味着这些服务是一个黑匣子,内部逻辑实际上是隐藏的,但它们也可以独立于其他服务工作。

一些服务也将是无状态的。这意味着,如果调用一个服务,传递一个请求,您将得到一个响应,如果服务或有效负载出现问题,您还将得到一个异常。在 SOA 中,粒度也非常重要。服务的粒度需要足够小,以便不会被低效地调用或多次调用。因此,我们希望规范化服务的级别和粒度。如果某些服务被其他服务重用,可以将其分解,或者可以将服务合并并规范化以最小化冗余。服务还需要可组合,因此您可以将它们合并成更大的服务或将它们拆分。

有一套标准化的合同,但服务还需要可发现。可发现意味着有一种自动发现可用的服务、可用的端点以及解释它们的方法。最后,合理的元素,重用对于 SOA 非常重要,这是指逻辑可以在代码库的其他部分中重用。

单体架构的好处

在 SOA 中,架构是松散耦合的。企业的所有服务都在一个存储库中定义。这使我们能够清楚地看到可用的服务。此外,还有一个全局数据模型。通常,有一个数据存储库,我们在其中存储所有数据源,每个单独的服务实际上都会写入或读取它。这使得它可以在全局层面上集中。

另一个好处是通常只有少量大型服务,这些服务由明确的业务目标驱动。这使它们易于理解,并且对我们的组织来说是一致的。一般来说,服务之间的通信是通过智能管道或某种中间件解耦的。

单体架构的缺点

单体架构的缺点是通常只有一个技术堆栈。这意味着应用服务器、Web 服务器或数据库框架在整个企业中是一致的。过时的库和代码可能很难升级,因为这取决于单一堆栈,几乎就像所有服务都需要在相同版本的库上对齐。

另一个缺点是在单一堆栈上通常有非常庞大的代码库,这意味着构建和部署代码需要很长的时间。服务部署在单个或大型应用服务器和 Web 服务器上。这意味着,为了扩展,您需要扩展整个服务器,这意味着无法独立部署和扩展应用程序。要扩展应用程序,您需要扩展托管应用程序的 Web 应用程序或应用程序服务器。

另一个缺点是通常有一个中间件编排层或集成逻辑是集中的。例如,服务将使用业务流程管理BPM)框架来控制工作流程,您将使用企业服务总线ESB),它允许您在中心路由消息,或者您将有某种中间件来处理服务之间的集成。许多这些逻辑都集中在一起,当您更改该中心逻辑的配置时,您必须非常小心,以免破坏任何服务之间的通信。

微服务概述

微服务一词起源于 2011 年的一个研讨会,当时不同的团队描述了他们使用的一种架构风格。2012 年,Netflix 的 Adrien Cockcroft 实际上将微服务描述为一种在 Web 规模上开创的细粒度 SOA。

例如,如果我们在物联网IoT)设备上有传感器,如果温度发生变化,我们将发出事件作为可能的下游警告。这就是所谓的事件流处理复杂事件处理。基本上,整个架构都由事件驱动。

微服务中使用的另一种设计类型称为领域驱动设计DDD)。这基本上是领域专家和开发人员之间有一个共同的语言。DDD 中的另一个重要组成部分是有界上下文,这是每个服务都依赖其边界的严格一致性模型。例如,如果这是一个处理客户开票的服务,该服务将是唯一可以处理、写入或更新客户开票的中心点和唯一位置。好处是在有界上下文之外的系统中不会有关于数据访问责任的混淆。

您可以将微服务看作是围绕着使用 JSON 标准的 REST 端点或应用程序编程接口构建的。很多逻辑可以内置到服务中。这就是所谓的愚蠢管道但是聪明的端点,您可以在图表中看到原因。我们有一个处理客户支持的服务,如下:

例如,端点将更新客户支持详细信息,添加新的工单,或使用特定标识符获取客户支持详细信息。我们有一个本地客户支持数据存储,因此所有关于客户支持的信息都存储在该数据存储中,您可以看到微服务发出客户支持事件。这些事件通过发布-订阅机制或使用其他发布事件框架发送出去,例如命令查询责任分离CQRS)。您可以看到这符合有界上下文。在这个有界上下文中有一个单一的责任。因此,这个微服务控制着关于客户支持的所有信息。

微服务架构的优缺点

有界上下文和这是一个非常小的代码库,允许您非常频繁地构建和部署。此外,您可以独立扩展这些服务。通常每个微服务都有一个应用服务器或 Web 服务器。您可以很快地扩展它,只需针对您想要的特定服务。此外,您可以经常构建并更频繁地进行测试,并且可以使用任何类型的语言、数据库或 Web 应用程序服务器。这使其成为一个多边形系统。有界上下文非常重要,因为您可以对一个领域进行建模。功能可以非常快速地发布,因为例如,客户服务微服务实际上可以控制对数据的所有更改,因此您可以更快地部署这些组件。

然而,使用微服务架构也存在一些缺点。首先,在分布式开发和测试方面存在很多复杂性。此外,服务之间的通信更多,因此网络流量更大。延迟和网络在微服务中变得非常重要。DevOps 团队必须维护和监控从另一个服务获取响应所需的时间。此外,责任的变化是另一个复杂因素。例如,如果我们将一个有界上下文分成几种类型的子有界上下文,你需要考虑这在团队内如何运作。通常还需要一个专门的 DevOps 团队,他们基本上是为了支持和维护整个组织中更多的服务和机器。

SOA 与微服务

现在我们对两者有了很好的理解,我们将比较 SOA 和微服务架构。在通信本身方面,SOA 和微服务都可以使用同步和异步通信。SOA 通常依赖于简单对象访问协议(SOAP)或 Web 服务。微服务倾向于更现代化,广泛使用表述状态转移(REST)API。

我们将从以下图表开始,比较 SOA 和微服务:

编排是一个重大的区别所在。在 SOA 中,一切都围绕着 BPM、ESB 或某种中间件集中。所有服务和数据流之间的集成都是在中央控制的。这使你可以在一个地方配置任何更改,这有一些优势。

微服务的方法是使用更基于协作的方法。这是一个个体服务更加智能,即一个智能端点但一个愚蠢的管道。这意味着服务知道要调用谁以及他们将得到什么数据,并且他们在微服务内管理这个过程。这给了我们在微服务集成方面更多的灵活性。在 SOA 世界或三层架构中,灵活性较少,因为通常是单一的代码库,集成是一组大型的单体发布和用户界面或后端服务的部署。这可能限制企业的灵活性。然而,对于微服务来说,这些系统要小得多,可以独立部署,更加细粒度。

最后,在架构方面,SOA 在企业级别运作,那里会有一个企业架构师或解决方案架构师模型和控制中央存储库中所有服务的发布。微服务更加灵活。微服务讨论的是在项目级别工作,他们说团队只由一些开发人员或非常少的开发人员组成,可以坐在一起分享披萨。因此,这使你在项目级别更加灵活地做出决策,而不必在企业级别达成一致。

虚拟机、容器和无服务器计算

现在我们对单体和微服务架构有了更好的理解,让我们看看用于创建无服务器微服务的亚马逊网络服务(AWS)构建模块。

但首先我们将介绍虚拟机、容器和无服务器计算,这是托管在公共云中的任何应用程序或服务的基本构建模块。

虚拟机是公共云和网络托管站点的最初提供的服务,容器是独立的轻量级镜像,无服务器计算是云提供商完全管理资源的情况。你将了解每种方法的优缺点,我们将最后进行详细比较。

虚拟机

在传统数据中心中,您必须购买或租赁物理机器,并具备额外的容量来处理额外的网络或用户流量。在新世界中,虚拟机是最早的公共云服务之一。您可以将其视为类似于物理盒子,您可以在其中安装操作系统,通过 SSH 或 RDP 进行远程连接,并安装应用程序和服务。我认为虚拟机一直是初创公司成功的关键构建模块之一。它使他们能够以较小的资本投资进入市场,并随着其网络流量和用户量的增加而扩展。这是以前只有大型组织才能负担得起的,因为物理硬件的大量前期成本。

虚拟机的优势在于按使用量付费、实例类型选择和动态存储分配,使您的组织完全灵活地在几分钟内租用硬件,而不是等待购买物理硬件。虚拟机还提供由云提供商管理的安全性。此外,它们提供多区域自动扩展和负载平衡,同样由云提供商管理,几乎可以通过点击按钮获得。有许多虚拟机可用,例如 Amazon EC2、Azure VM 和 Google Compute Engine。

然而,它们也有一些缺点。主要缺点是扩展需要几分钟的时间。因此,需要启动的任何机器都需要几分钟的时间,这使得在请求时几乎不可能快速扩展。在配置方面也需要一些努力,需要像 Chef 或 Puppet 这样的配置管理工具。例如,操作系统需要保持最新。

另一个缺点是您仍然需要编写逻辑来轮询或订阅其他托管服务,例如流分析服务。此外,您仍然需要支付空闲机器时间。例如,当您的服务未运行时,虚拟机仍然运行,并且即使它们没有被积极使用,您仍然需要支付时间费用。

容器

使用虚拟机的旧方法是在主机操作系统上部署应用程序,并使用诸如 Chef 或 Puppet 之类的配置管理工具。这样做的好处是管理应用程序构件的库和生命周期,并尝试操作特定的操作系统,无论是 Linux 还是 Windows。容器是出于这种限制而产生的,其思想是将代码和依赖项打包到一个可移植容器中,在这里您可以进行完整的操作系统级虚拟化。实际上,您可以更好地利用机器上可用的资源。

这些容器可以非常快速地启动,并且基本上是不可变的,也就是说,操作系统、库版本和配置都不能更改。基本思想是将代码和依赖项放入这个可移植容器中,并且可以通过配置在本地或服务器上重新创建环境。另一个重要方面是编排引擎。这是管理容器的关键。因此,您将有由 Kubernetes 或 Amazon EC2 容器服务(ECS)管理、部署和扩展的 Docker 镜像。

这些容器的缺点是它们通常在几秒钟内扩展,这仍然太慢了,无法实际上每个请求调用一个新容器。因此,您需要它们预热并且已经可用,这是有成本的。此外,集群和镜像配置确实需要一些 DevOps 工作。

最近,AWS 推出了 AWS Fargate 和 Elastic Kubernetes Service(EKS),这些都有助于减轻一些配置管理和支持工作,但您仍然需要一个 DevOps 团队来支持它们。

另一个缺点是与托管服务的集成工作。例如,如果您正在处理流分析服务,仍然需要编写轮询和订阅代码,将数据拉入您的应用程序或服务。

最后,与虚拟机一样,即使 Kubernetes 协助,您仍然需要支付正在运行的任何容器的费用。它们可以在 EC2 实例上运行,因此即使未使用,您仍需要支付实际机器的运行时间。

无服务器计算

您可以将服务计算视为专注于业务逻辑,而不是围绕服务的所有基础设施配置管理和集成。在无服务器计算中,仍然存在服务器,只是您不管理服务器本身、操作系统或硬件,所有的可伸缩性都由云提供商管理。您无法访问原始机器,也就是说,您无法 SSH 到该机器。

优势在于您可以真正专注于业务逻辑代码,而不是基础设施或入站集成代码,这是您作为组织为客户和客户添加的业务价值。

此外,安全性再次由云提供商管理,自动扩展和高可用性选项也由云提供商管理。例如,您可以根据请求的数量动态地启动更多实例。费用是按执行时间而不是按空闲时间计算。

不同的公共云无服务器提供。Google、Azure、AWS 和阿里巴巴云都有函数即服务(FaaS)的概念。这是您在函数中部署业务逻辑代码的地方,周围的一切,如安全性和可伸缩性,都由云提供商管理。

缺点是这些是无状态的,这意味着它们的寿命非常短。几分钟后,函数内部维护的任何状态都会丢失,因此必须在外部进行持久化。它不适用于长时间运行的进程。它还具有有限的实例类型和持续时间。例如,AWS Lambda 在终止之前有 15 分钟的持续时间。对于外部库的实际大小或任何自定义库,也存在限制,因为这些 Lambda 需要非常快速地启动。

比较虚拟机、容器和无服务器

让我们比较基础设施即服务(IaaS),容器即服务(CaaS)和函数即服务(FaaS)。将 IaaS 视为虚拟机,CaaS 视为 Docker 容器池,FaaS 的一个示例将是 Lambda 函数。这是 IaaS、CaaS 和 FaaS 之间的比较:

绿色元素由用户管理,蓝色元素由云服务提供商管理。因此,在左侧,您可以看到 IaaS,如虚拟机一样,用户承担了很多责任。在 CaaS 中,操作系统级由提供商管理,但您可以看到容器和运行时实际上是由用户管理的。最后,在右侧,FaaS,您可以看到核心业务逻辑代码和应用程序配置由用户管理。

那么,在 AWS 世界中,您如何在 AWS Lambda 容器和 EC2 实例之间进行选择?请查看以下图表:

如果我们将虚拟机与容器和 Lambda 函数进行比较,您会发现在维护方面需要一些配置工作,使其具有高可用性和管理性。对于 Lambda 函数,这实际上是在预请求的基础上完成的。也就是说,它是请求驱动的。例如,如果您的网站受到更多流量的影响,AWS 将会启动更多的 Lambda 以使其高度可用(HA)。

就灵活性而言,您可以完全访问虚拟机和容器,但在 AWS Lambda 中,您只有默认的硬件、默认的操作系统,没有图形处理单元(GPU)可用。好处是您不需要对 Lambda 进行升级或维护。

就可扩展性而言,您需要提前规划虚拟机和容器。您需要预配容器或实例,并决定如何进行扩展。在 AWS Lambda 函数中,扩展是根据请求的数量或数据量隐式进行的,因为您会自然地获得更多或更少的并行执行的 Lambda 函数。

虚拟机的启动通常需要几分钟,可能会持续几周。容器可以在几秒钟内启动,并可能在几分钟或几小时内保持运行,然后被处理掉。然而,Lambda 函数可以在大约 100 毫秒内启动,并且通常会持续几秒钟或几分钟。

就状态而言,虚拟机和容器可以保持状态,即使这通常不是扩展的最佳实践。Lambda 函数始终是无状态的,当它们终止执行时,内存中的任何内容都会被处理掉,除非它在外部持久化,例如在 DynamoDB 表或 S3 存储桶中。

虚拟机和 Docker 容器需要与 AWS 服务进行自定义集成。然而,在 Lambda 函数中,事件源可以使用内置集成与其他 AWS 服务(如 Kinesis、S3 和 API Gateway)将数据推送到 Lambda 函数。您只需订阅 Lambda 事件源到 Kinesis 流,数据就会被推送到您的 Lambda 函数中,带有其业务逻辑代码,这使您能够决定如何处理和分析这些数据。然而,对于 EC2 虚拟机和 ECS 容器,您需要使用 AWS SDK 或其他方式构建自定义的入站集成逻辑。

最后,就定价而言,EC2 实例按秒计费。它们还有一个使用市场价格的竞价实例,比按需实例便宜得多。容器也是如此,只是您可以在一个 EC2 实例上运行多个容器。这样可以更好地利用资源,成本更低,因为您可以在 EC2 实例之间灵活地分配不同的容器。对于 AWS Lambda 函数,定价是按 100 毫秒、调用次数和所需的随机存取内存(RAM)计费。

微服务集成模式概述

在本节中,我们将讨论设计模式、设计原则,以及微服务架构模式与传统微服务模式的关系,以及如何应用于无服务器微服务。这些主题将帮助您了解不同的集成模式。

设计模式

模式是可重复使用的蓝图,是其他人面临类似问题的解决方案,已经在各种生产环境中得到广泛审查、测试和部署。

遵循这些模式意味着您将受益于最佳实践和技术人员的智慧。您还将与其他开发人员或架构师说同样的语言,这使您能够更快地交换想法,更轻松地与其他系统集成,并更有效地进行员工交接。

模式为什么有用?

有用的应用几乎从不孤立存在。它们几乎总是集成在更广泛的生态系统中,这对于微服务来说尤其如此。换句话说,集成规范和要求需要被其他开发人员和架构师沟通和理解。

使用模式时,您有一个在技术人员中间通用的语言,使您能够被理解。这实际上是更好地协作,与其他人合作,交换想法,解决问题。

模式的主要目的是在实现新服务时节省时间和精力,因为您有一个标准的术语和蓝图来构建东西。在某些情况下,它们可以帮助您避免陷阱,因为您可以从他人的经验中学习,并应用最佳实践、软件、设计模式和原则。

软件设计模式和原则

您可能会在您的微服务或 Lambda 代码中使用面向对象(OO)或函数式编程,因此让我们简要讨论与它们相关的模式。

在面向对象编程中,编码时可以使用许多最佳实践模式或原则,例如 GRASP 或 SOLID。我不会深入讨论,因为这需要一整本书,但我想强调一些对微服务很重要的原则:

  • SOLID:这有五个原则。一个例子是单一责任原则(SRP),其中您定义每个都有单一责任和因此单一变更原因的类,减少服务的大小并增加其稳定性。

  • 包内聚性:例如,共同的闭包原则类一起变化。因此,当业务规则发生变化时,开发人员只需要在少量的包中更改代码。

  • 包耦合:例如,无环依赖原则,它规定包或组件的依赖图不应该有循环。

让我们简要地介绍一些微服务的有用设计模式:

  • 创建模式:例如,工厂方法创建多个派生类的实例。

  • 结构模式:例如,装饰器动态地为对象添加额外的责任。

  • 行为模式:例如,命令模式将请求封装为对象,使得提取参数、排队和记录请求更容易。基本上,您将创建命令的参数与执行命令的参数解耦。

  • 并发模式:例如,反应器对象为必须同步处理的资源提供了异步接口。

根据您的编码经验,您可能熟悉这些。如果不熟悉,值得阅读以提高代码的可读性、管理性和稳定性,以及您的生产力。以下是一些参考资料,您可以在其中了解更多:

  • 《SOLID 面向对象设计》,Sandi Metz(2009)

  • 《设计模式:可复用的面向对象软件元素》,Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides(1995)

  • 《Head First 设计模式》,Eric T Freeman,Elisabeth Robson,Bert Bates,Kathy Sierra(2004)

  • 《敏捷软件开发,原则,模式和实践》,Robert C. Martin(2002)

无服务器微服务模式类别

除了我们刚刚讨论的软件设计模式和原则之外,还有微服务模式。根据我的经验,有许多我推荐的与无服务器微服务相关的微服务模式,如下图所示:

我创建了这个图表来总结和说明我们将在本书中讨论的无服务器微服务模式:

  • 通信风格:服务之间和外部之间的通信方式。

  • 分解模式:创建一个通过业务能力或有界上下文松耦合的服务。

  • 数据管理:处理本地和共享数据存储。

  • 查询和消息传递:查看微服务之间发送的事件和消息,以及如何有效地查询服务。

  • 部署:理想情况下,我们希望统一和独立的部署,您也不希望开发人员为每个有界上下文或微服务重新创建新的流水线。

  • 可观察性和发现:能够了解服务是否正常运行,监视和记录活动,使您能够在出现问题时进行深入分析。您还希望了解和监视当前运行的内容,例如出于成本和维护原因。

  • 安全性:这对于合规性、数据完整性、数据可用性和潜在的财务损失至关重要。重要的是要建立不同的加密、身份验证和授权流程。

接下来,我们将首先看一下通信风格和分解模式。

通信风格和分解微服务模式

在本节中,我们将讨论两种微服务模式,称为通信风格和分解,并提供足够详细的内容,以便您能够与其他开发人员、架构师和 DevOps 讨论它们。

通信风格

由于微服务应用程序本质上是分布式的,因此它们严重依赖于授权网络。这使得了解可用的不同通信风格非常重要。这些可以用于彼此之间的通信,也可以用于与外部世界的通信。以下是一些示例:

  • 远程过程调用:以前,Java 使用远程方法调用RMI)很流行,这是客户端和服务器之间紧密耦合的一种非标准协议,这是一种限制。此外,网络不可靠,因此应避免传统的 RMI。其他方法,如 SOAP 接口和从Web 服务定义语言WSDL)生成的客户端,更好,但与REpresentational State TransferREST)API 相比,它们被视为过重,而 REST API 已被广泛应用于微服务。

  • 同步通信:它更简单易懂,也更容易实现;您发出请求并获得响应。然而,在等待响应的同时,您可能也会阻塞连接插槽和资源,从而限制其他服务的调用:

  • 异步通信:通过异步通信,您发出请求,然后稍后收到响应,有时是无序的。这些可以使用回调、async/await或 Node.js 或 Python 中的promise来实现。然而,在使用async时有许多设计考虑,特别是如果需要监视失败。与大多数同步调用不同,这些是非阻塞的:

在处理通信时,您还需要考虑您的调用是阻塞还是非阻塞。例如,使用阻塞调用将网页客户端的指标写入 NoSQL 数据库可能会减慢您的网站。

您需要考虑如何处理接收到的太多请求并对其进行限流,以免过度压倒您的服务,并查看失败,如重试、延迟和错误。

使用 Lambda 函数时,您可以从 AWS 构建的事件源中受益,并针对每个请求或微批量数据启动一个 Lambda。在大多数情况下,即使在规模上,同步代码也足够使用,但在设计系统时了解架构和服务之间的通信非常重要,因为它受带宽限制,网络连接可能会失败。

一对一通信微服务模式

在单个微服务级别上,数据管理模式由一套小型服务组成,具有自己的本地数据存储,通过 REST API 或通过发布/订阅进行通信:

API 网关是所有客户端的单一入口点,并为它们量身定制,允许对其进行解耦,这对于面向外部服务特别有用。

一对一的请求/响应可以是同步或异步。如果它们是同步的,它们可以对每个请求进行响应。如果通信是异步的,它们可以有异步响应或异步通知。通常更喜欢异步,因为它不会保持开放的连接(非阻塞),并且更好地利用了中央处理单元CPU)和输入/输出I/O)操作。

我们将在本书的后面更详细地讨论数据管理模式,届时我们将看看微服务如何在更广泛的生态系统中集成。

多对多通信微服务模式

对于多对多的通信,我们使用发布/订阅,这是一种消息模式。发送者称为发布者的消息不会直接发送到特定的接收者;相反,接收者需要订阅消息。这是一种高度可扩展的模式,因为两者是解耦的:

异步消息允许服务消费和响应事件,并且作为一种非常可扩展的模式,因为您已经解耦了两个服务:发布者和订阅者。

按业务能力分解模式

如何创建和设计微服务?如果您正在迁移现有系统,您可能会考虑将单体或应用程序分解为微服务。即使对于新的绿地项目,您也需要考虑所需的微服务:

首先,您需要识别业务能力,即组织为了产生价值而做的事情,而不是如何。也就是说,您需要分析目的、结构和业务流程。一旦您确定了业务能力,您就为每个能力或能力组定义一个服务。然后,您需要添加更多细节,以了解服务通过定义可用的方法或操作所做的事情。最后,您需要设计服务之间的通信方式。

这种方法的好处是相对稳定,因为它与您的业务提供的内容相关。此外,它与流程和地位相关。

缺点是数据可能跨多个服务,可能不是最佳的通信或共享代码,并且需要一个集中的企业语言模型。

按有界上下文分解模式

应用有界上下文分解模式有三个步骤:首先,识别领域,即组织所做的事情。然后识别子域,即根据实际功能将交织在一起的模型分割成逻辑上分离的子域。最后,找到有界上下文,标记出领域模型中每个术语的含义都被充分理解的地方。有界上下文不一定只属于单个子域。这三个步骤如下:

这种模式的好处如下:

  • 在与领域专家合作时使用普遍语言,有助于更广泛的沟通。

  • 团队拥有、部署和维护服务,使他们在有界上下文中具有灵活性和更深入的理解。这是很好的,因为其中的服务最有可能相互通信。

  • 团队与代表性领域专家一起理解领域。有一个接口,可以将其他团队的许多实现细节抽象出来。

也有一些缺点:

  • 需要领域专业知识。

  • 这是一个迭代的过程,需要进行持续集成CI)。

  • 对于简单的领域来说过于复杂,依赖于普遍语言和领域专家。

  • 如果使用了多语言方法,可能没有人再知道技术栈。幸运的是,微服务应该更小更简单,因此可以重新编写这些服务。

更多细节可以在以下书籍中找到:

  • 构建微服务,Sam Newman(2015)

  • 领域驱动设计:应对软件核心的复杂性,Eric Evans(2003)

  • 实施领域驱动设计,Vaughn Vernon(2013)

AWS 中的无服务器计算

AWS 中的无服务器计算允许您快速在云中部署事件驱动的计算。使用无服务器计算,仍然有服务器,但您不必管理它们。AWS 会自动为您管理所有计算资源,以及任何触发机制。例如,当对象被写入存储桶时,会触发一个事件。如果另一个服务向 Amazon DynamoDB 表写入新记录,那可能会触发一个事件或调用一个端点。

使用事件驱动计算的主要思想是,它可以轻松地将数据转换为到达云端时,或者我们可以执行数据驱动的审计分析通知、转换或解析物联网(IoT)设备事件。无服务器还意味着您不需要始终运行的服务来执行,实际上可以根据事件触发它。

AWS 中一些关键的无服务器服务概述

以下是 AWS 中一些关键的无服务器服务的解释:

  • Amazon 简单存储服务(S3):分布式的网络规模对象存储,具有高度可扩展性、高度安全性和可靠性。您只需支付实际消耗的存储空间,这在定价方面非常有利。它还支持加密,您可以提供自己的密钥,或者可以使用 AWS 提供的服务器端加密密钥。

  • Amazon DynamoDB:由 AWS 管理的完全托管的 NoSQL 存储数据库服务,允许您专注于将数据写入数据存储。它具有高度的耐用性和可用性。它已经在游戏和其他需要低延迟的高性能活动中使用。它在内部使用 SSD 存储,并为高可用性提供了分区。

  • Amazon 简单通知服务(SNS):一种推送通知服务,允许您向其他订阅者发送通知。这些订阅者可以是电子邮件地址、SNS 消息或其他队列。消息将被推送到 SNS 服务的任何订阅者。

  • Amazon 简单队列服务(SQS):一个完全托管和可扩展的分布式消息队列,具有高可用性和耐用性。SQS 队列通常订阅到 SNS 主题以实现分布式发布-订阅模式。您根据请求的数量付费。

  • AWS Lambda:主要思想是您编写业务逻辑代码,并且它基于您配置的事件源进行触发。美妙之处在于,您只在代码实际执行时付费,最低到 100 毫秒。它会自动扩展并具有高可用性。这是 AWS 无服务器生态系统的关键组件之一。

  • Amazon API 网关:一个托管的 API 服务,允许您构建、发布和管理 API。它可以扩展,并允许您执行缓存、流量限制和边缘位置的缓存,这意味着它们基于用户所在的位置进行本地化,最大限度地减少总体延迟。此外,它与 AWS Lambda 函数进行本地集成,允许您专注于解析请求或数据的核心业务逻辑代码。

  • AWS 身份和访问管理(IAM):所有安全性的核心组件是 IAM 角色和策略,这基本上是由 AWS 管理的一种机制,用于集中安全性并将其联合到其他服务。例如,您可以限制 Lambda 仅读取特定的 DynamoDB 表,但无法写入相同的 DynamoDB 表,也可以拒绝对其他表的读/写访问。

  • 亚马逊 CloudWatch:用于监控服务的中央系统。例如,您可以监视各种资源的利用率,记录自定义指标,并托管应用程序日志。它还非常有用,可以创建规则,当特定事件或异常发生时触发通知。

  • AWS X-Ray:允许您跟踪服务请求并分析来自各种来源的延迟和跟踪。它还生成服务地图,因此您可以看到依赖关系以及请求中花费最多时间的地方,并对性能问题和错误进行根本原因分析。

  • 亚马逊 Kinesis Streams:允许您捕获每秒数百万事件的流服务,以便您可以进一步分析。主要思想是,例如,成千上万的物联网设备直接写入 Kinesis Streams,将数据捕获在一个管道中,然后用不同的消费者进行分析。如果事件数量增加,需要更多容量,您可以简单地添加更多分片,每个分片的写入容量为每秒 1000 次。添加更多分片很简单,没有停机时间,也不会中断事件捕获。

  • 亚马逊 Kinesis Firehose:允许您持久保存和加载流数据的系统。它允许您写入一个端点,该端点会在内存中缓冲事件长达 15 分钟,然后将其写入 S3。它支持大量数据,并与云中的数据仓库 Amazon Redshift 集成。它还与 Elasticsearch 服务集成,允许您查询自由文本、网络日志和其他非结构化数据。

  • 亚马逊 Kinesis Analytics:允许您使用结构化查询语言SQL)分析 Kinesis Streams 中的数据。它还具有发现数据模式的能力,以便您可以在流上使用 SQL 语句。例如,如果您捕获网络分析数据,您可以计算每日页面查看数据,并按特定的pageId进行聚合。

  • 亚马逊 Athena:允许您直接使用读取模式的模式查询 S3 的服务。它依赖于 AWS Glue 数据目录来存储表模式。您可以创建一个表,然后直接从 S3 查询数据,没有启动时间,它是无服务器的,并且可以以非常灵活和具有成本效益的方式探索大规模数据集。

在所有这些服务中,AWS Lambda 是 AWS 中最广泛使用的无服务器服务。我们将在下一节中更多地讨论这个。

AWS Lambda

AWS 中的关键无服务器组件称为AWS Lambda。Lambda 基本上是一些业务逻辑代码,可以由事件源触发:

数据事件源可以是将对象放入或获取对象从 S3 存储桶。流事件源可以是已添加到 DynamoDB 表的新记录,触发了 Lambda 函数。其他流事件源包括 Kinesis Streams 和 SQS。

端点请求的一个例子是 Alexa 技能,来自 Alexa Echo 设备。另一个常见的是 Amazon API Gateway,当您调用一个将调用 Lambda 函数的端点。此外,您可以使用 AWS CodeCommit 或 Amazon Cloud Watch 的更改。

最后,您可以基于 SNS 或不同的定时事件触发不同的事件和消息。这些可以是常规事件,也可以是通知事件。

主要思想是,事件源和 Lambda 之间的集成完全由 AWS 管理,因此您只需要编写业务逻辑代码,即函数本身。一旦函数运行,您可以运行转换或一些业务逻辑代码,实际上写入图表右侧的其他服务。这些可以是数据存储或调用其他端点。

在无服务器世界中,您可以更轻松地使用 AWS Lambda 实现sync/asyc请求、消息传递或事件流处理。这包括我们刚刚讨论的微服务通信风格和数据管理模式。

Lambda 有两种类型的事件源类型,非流事件源和流事件源:

  • 非流事件源:Lambda 可以异步或同步调用。例如,SNS/S3 是异步的,但 API Gateway 是同步的。对于同步调用,客户端负责重试,但对于异步调用,如果配置了,它将在发送到死信队列DLQ)之前多次重试。有这个重试逻辑和 AWS 事件源的集成和支持是非常好的,因为这意味着更少的代码和更简单的架构。

  • 流事件源:Lambda 被微批量数据调用。在并发方面,对于 Kinesis Streams,每个分片并行调用一个 Lambda,对于 DynamoDB Stream,每个分区调用一个 Lambda。在 Lambda 内部,您只需要迭代传入的 Kinesis Streams、DynamoDB 或 SQS 数据作为 JSON 记录。此外,您可以从 AWS 内置的流集成中受益,Lambda 将轮询流并按顺序检索数据,并在失败时重试,直到数据过期,对于 Kinesis Streams,数据可以保存长达七天。而且,有了这个重试逻辑内置,而不需要编写一行代码,这是非常好的。如果您必须使用 AWS Consumer 或 Kinesis SDK 自己构建 EC2 或容器的一组,那么这将需要更多的工作:

实质上,AWS 负责调用并传递事件数据给 Lambda,您负责处理 Lambda 的响应。

无服务器计算实现微服务模式

以下是 AWS 上可用的一些无服务器和托管服务的概述图:

利用 AWS 托管服务确实意味着额外的供应商锁定,但有助于减少非业务差异化的支持和维护成本。同时也可以更快地部署应用程序,因为基础设施可以在几分钟内进行配置或销毁。在某些情况下,使用 AWS 托管服务来实现微服务模式,不需要太多的代码,只需要配置。

我们有以下服务:

  • 事件、消息和通知:用于异步发布/订阅和协调组件

  • API 和 Web:为您的无服务器微服务创建 API 并将其暴露给 Web

  • 数据和分析:存储、共享和分析您的数据

  • 监控:确保您的微服务和堆栈正常运行

  • 授权和安全:确保您的服务和数据安全,并且只能被授权的人访问

在中心是 AWS Lambda,用于连接服务的粘合剂,同时也是您部署业务逻辑源代码的关键位置之一。

示例用例 - 无服务器文件转换器

以下是一个示例用例,让您了解不同的托管 AWS 系统如何作为解决方案组合在一起。要求是第三方供应商每天在随机时间向我们发送一个小的 10MB 文件,我们需要转换数据并将其写入 NoSQL 数据库,以便可以实时查询。如果第三方数据出现任何问题,我们希望在几分钟内发送警报。您的老板告诉您,他们不想为这个任务专门保留一台始终开机的机器,第三方没有 API 开发经验,而且预算有限。安全主管也了解到这个项目,并增加了另一个限制。他们不希望将第三方访问您的 AWS 帐户超出一个被封锁的 S3 存储桶:

这可以作为一个事件驱动的无服务器堆栈来实现。在左边,我们有一个 S3 存储桶,第三方可以访问并放置他们的文件。当创建新对象时,会触发 Lambda 调用,通过内置的事件源映射。Lambda 执行代码来转换数据,例如,从对象中提取关键记录,如user_iddateevent类型,并将它们写入 DynamoDB 表。Lambda 发送转换的摘要自定义指标,例如转换并写入 CloudWatch 指标的记录数。此外,如果有转换错误,Lambda 会发送包含转换问题摘要的 SNS 通知,这可能会生成一封邮件给管理员和第三方提供商,以便他们调查问题。

设置您的无服务器环境

如果您已经拥有 AWS 帐户并在本地配置了它,您可以跳过此部分,但出于安全原因,我建议您为控制台访问启用多因素身份验证MFA),并且不要使用根用户帐户密钥进行课程。

有三种访问 AWS 资源的方式:

  • AWS 管理控制台是一个用于管理您的服务和计费的基于 Web 的界面。

  • AWS 命令行界面是一个统一的工具,用于管理和自动化所有 AWS 服务。

  • Python、JavaScript、Java、.NET 和 GO 中的软件开发工具包,允许您以编程方式与 AWS 进行交互。

设置您的 AWS 帐户

设置帐户非常简单;您只需要大约五分钟、一个智能手机和一张信用卡:

  1. 创建帐户。AWS 帐户包括 12 个月的免费使用:aws.amazon.com/free/

  2. 输入您的姓名和地址。

  3. 提供付款方式。

  4. 验证您的电话号码。

这将创建一个根帐户,我建议您仅将其用于计费,而不是开发

设置 MFA

我建议您使用 MFA,因为它在用户名和密码之上增加了额外的保护层。使用您的手机作为虚拟 MFA 设备是免费的 (aws.amazon.com/iam/details/mfa/)。执行以下步骤进行设置:

  1. 登录 AWS 管理控制台:console.aws.amazon.com

  2. 在左侧菜单中选择仪表板。

  3. 在安全状态下,展开在根帐户上激活 MFA。

  4. 选择激活 MFA 或管理 MFA。

  5. 在向导中,选择虚拟 MFA 设备,然后选择继续。

  6. 安装 MFA 应用程序,如 Authy (authy.com/)。

  7. 选择显示 QR 码,然后用您的智能手机扫描 QR 码。点击帐户并生成 Amazon 的六位数字令牌。

  8. 在 MFA 代码 1 框中输入六位数字令牌。

  9. 等待您的手机生成一个新的令牌,每 30 秒生成一次。

  10. 在 MFA 代码 2 框中输入六位数字令牌。

  11. 选择分配 MFA:

设置新用户和密钥

出于安全原因,我建议您仅将根帐户用于计费!因此,第一件事是创建另一个权限较低的用户:

按照以下步骤创建用户:

  1. 登录 AWS 管理控制台 (console.aws.amazon.com/)。

  2. 选择安全、身份和合规性 > IAM 或在“查找服务”下搜索 IAM。

  3. 在 IAM 页面上,选择添加用户。

  4. 对于用户名,在设置用户详细信息窗格上输入新用户。

  5. 对于选择 AWS 访问类型,选择旁边的复选框,即编程访问、AWS 控制台访问。可选择自动生成的密码和需要密码重置。

  6. 选择下一步:权限:

按照以下步骤为新用户设置权限:

  1. 选择创建组。

  2. 在“创建组”对话框中,输入Administrator作为新组名称。

  3. 在策略列表中,选择 AdministratorAccess 旁边的复选框(请注意,对于非概念验证或非开发 AWS 环境,我建议使用更受限制的访问策略)。

  4. 选择创建组。

  5. 选择刷新并确保管理员旁边的复选框被选中。

  6. 选择下一步:标签。

  7. 选择下一步:审核。

  8. 选择创建用户。

  9. 选择下载.csv 并记下密钥和密码。您将需要这些来以编程方式访问账户并登录为此用户。

  10. 选择关闭。

有关创建新用户的更多详细信息,请访问docs.aws.amazon.com/IAM/latest/UserGuide/getting-started_create-admin-group.html

与 root 账户一样,我建议您启用 MFA:

  1. 在管理控制台中,选择 IAM | 用户并选择新用户。

  2. 选择安全凭据选项卡,然后选择未分配的 MFA 设备旁边的管理。

  3. 选择虚拟 MFA 设备并选择继续。

  4. 安装诸如 Authy (authy.com/)之类的 MFA 应用程序。

  5. 选择显示 QR 码,然后用您的智能手机扫描 QR 码。点击账户并生成 Amazon 的六位令牌。

  6. 在 MFA 代码 1 框中输入六位令牌。

  7. 等待手机生成一个新的令牌,每 30 秒生成一个。

  8. 在 MFA 代码 2 框中输入六位令牌。

  9. 选择分配 MFA。

使用代码管理您的基础设施

在 AWS 管理控制台中可以通过 Web 界面完成很多工作。这是一个很好的开始,可以帮助您了解您正在构建的内容,但通常不建议用于生产部署,因为它耗时且容易出错。最佳实践是仅使用代码和配置来部署和管理基础设施。我们将在本书中使用 AWS 命令行界面CLI)、bash shell 脚本和 Python 3,所以现在让我们设置这些。

在 Windows 10 上安装 bash

如果您不使用 Windows,请跳过此步骤。

在部署和管理无服务器堆栈时,使用 bash(Unix shell)可以让您的生活变得更加轻松。我认为所有分析师、数据科学家、架构师、管理员、数据库管理员、开发人员、DevOps 和技术人员都应该了解一些基本的 bash,并能够运行 shell 脚本,这些通常用于 Linux 和 Unix(包括 macOS 终端)。

或者,您可以调整脚本以使用 MS-DOS 或 PowerShell,但鉴于 bash 现在可以作为 Windows 10 上的应用程序本地运行,并且在线上有更多的 bash 示例,我不建议这样做。

请注意,我已经去掉了\r或回车符,因为它们在 shell 脚本中是非法的。如果您想正确查看文件中的回车符,可以在 Windows 上使用 Notepad++ (notepad-plus-plus.org/)。如果您使用传统的 Windows 记事本,新行可能根本不会呈现,因此请使用 Notepad++、Sublime (www.sublimetext.com/)、Atom (atom.io/)或其他编辑器。

有关如何在 Windows 10 上安装 Linux Bash shell 的详细指南,请访问www.howtogeek.com/249966/how-to-install-and-use-the-linux-bash-shell-on-windows-10/。主要步骤如下:

  1. 导航至控制面板|程序|打开或关闭 Windows 功能。

  2. 在列表中选择 Windows 子系统的复选框,然后选择确定。

  3. 导航至 Microsoft Store | 在 Windows 上运行 Linux 并选择 Ubuntu。

  4. 启动 Ubuntu 并设置一个带有用户名和密码的 root 账户,Windows C:\和其他驱动器已经挂载,您可以使用终端中的以下命令访问它们:

$ cd /mnt/c/

干得好,您现在可以完全访问 Windows 上的 Linux!

更新 Ubuntu,安装 Git 和 Python 3

Git 将在本书的后面使用:

$ sudo apt-get update
$ sudo apt-get -y upgrade
$ apt-get install git-core

Lambda 代码是用 Python 3.6 编写的。pip是一个用于安装和管理 Python 包的工具。还有其他流行的 Python 包和依赖管理器可用,例如 Conda(conda.io/docs/index.html)或 Pipenv(pipenv.readthedocs.io/en/latest/),但我们将使用 pip,因为它是从 Python 包索引 PyPI(pypi.org/)安装包的推荐工具,并且得到了最广泛的支持。

$ sudo apt -y install python3.6
$ sudo apt -y install python3-pip

检查 Python 版本:

$ python --version

你应该使用 Python 版本 3.6+。

在每个项目文件夹的requirements.txt中列出了运行、测试和部署无服务器微服务所需的依赖包,并且可以使用pip进行安装:

$ sudo pip install -r /path/to/requirements.txt

这将安装本地开发所需的依赖库,例如 Boto3,这是 Python AWS 软件开发工具包SDK)。

在一些项目中,有一个名为lambda-requirements.txt的文件,其中包含 Lambda 部署时所需的第三方包。我们创建了另一个requirements文件,因为当 Lambda 部署到 AWS 时,Boto3 包已经包含在内,并且部署的 Lambda 不需要与测试相关的库,例如noselocust,这些库会增加包的大小。

安装和设置 AWS CLI

AWS CLI 用于打包和部署 Lambda 函数,以及以可重复的方式设置基础架构和安全性:

$ sudo pip install awscli --upgrade

您之前创建了一个名为newuser的用户,并且有一个名为crednetials.csv的文件,其中包含 AWS 密钥。通过运行aws configure输入它们:

$ aws configure
AWS Access Key ID: <the Access key ID from the csv>
AWS Secret Access Key: <the Secret access key from the csv>
Default region name: <your AWS region such as eu-west-1>
Default output format: <optional>

有关设置 AWS CLI 的更多详细信息,请参阅 AWS 文档(docs.aws.amazon.com/lambda/latest/dg/welcome.html)。

要选择 AWS 区域,请参考 AWS 区域和终端点(docs.aws.amazon.com/general/latest/gr/rande.html)。通常,美国用户使用us-east-1,欧洲用户使用eu-west-1

总结

在本章中,我们概述了单片和微服务架构。然后我们讨论了设计模式和原则以及它们与无服务器微服务的关系。我们还看到了如何设置 AWS 和开发环境,这将在本书中使用。

在下一章中,我们将创建一个无服务器微服务,该微服务公开了一个 REST API,并能够查询使用 API Gateway,Lambda 和 DynamoDB 构建的 NoSQL 存储。

第二章:创建您的第一个无服务器数据 API

在本章中,我们将构建一个完整的无服务器微服务,通过 REST API 可访问,并能够查询 NoSQL 数据库。我们将首先讨论并创建亚马逊网络服务AWS)安全基础设施,以确保对 AWS 资源的受限访问。然后,我们将使用管理控制台,然后使用 Python 创建、添加记录并查询 NoSQL 数据库。然后,我们将讨论 Python 中 Lambda 函数中使用的代码和 API 网关集成。最后,我们将部署它并测试 API 是否正常工作。

本章将涵盖以下主题:

  • AWS 安全概述

  • 保护您的无服务器微服务

  • 构建无服务器微服务数据 API

  • 在 AWS 管理控制台中设置 Lambda 安全

  • 使用 AWS 创建和写入名为 DynamoDB 的 NoSQL 数据库

  • 使用 Python 创建和写入名为 DynamoDB 的 NoSQL 数据库

  • 创建一个用于查询 DynamoDB 的 Lambda

  • 设置 API 网关并将其与 Lambda 代理集成

  • 连接 API 网关、Lambda 和 DynamoDB

  • 清理

AWS 安全概述

我们将从讨论安全性以及如何在 AWS 中正确设置它开始。

为什么安全性很重要?

您可能最近听说过勒索软件、网络攻击或安全漏洞,您不希望您的组织受到这些影响。以下是其中一些:

系统配置不正确、缺少更新或使用不安全的通信可能导致系统被黑客攻击或遭受勒索软件要求。这可能导致诉讼费用、数据丢失或泄露以及对您的组织的财务成本。

确保系统安全的原因有很多,包括以下几点:

  • 合规性:遵守法律、法规和标准,例如欧盟通用数据保护条例GDPR)、健康信息便携和责任法案HIPAA)和联邦贸易委员会法案

  • 数据完整性:如果系统不安全,数据可能会被剥离或篡改,这意味着您不能再信任客户数据或财务报告。

  • 个人可识别信息(PII):消费者和客户了解您的隐私政策。数据应该得到安全保护,在不再需要时进行匿名化和删除。

  • 数据可用性:数据对授权用户可用,但是,例如,如果您的数据中心发生自然灾害,那么在访问数据方面会发生什么?

AWS 中的许多安全性源于配置和正确的架构,因此了解以下重要安全相关术语的子集非常重要:

  • 传输中的安全:例如,HTTPS SSL——把它看作是浏览器上的挂锁

  • 静态安全:例如,数据加密,只有拥有密钥的用户才能读取数据存储中的数据

  • 身份验证:例如,确认用户或系统是否是其应该是的过程

  • 授权:例如,访问特定资源的权限和控制机制

安全设计原则

有许多安全标准、原则、认证和指导,可能足以填满几本书。以下是我发现实用和有用的一个,来自开放式 Web 应用安全项目OWASPwww.owasp.org。 OWASP 安全设计原则(www.owasp.org/index.php/Security_by_Design_Principles)适用于任何系统、应用程序或服务,有助于通过设计使它们更加安全,包括无服务器计算。即使无需管理无服务器的服务器,您仍需要确保您的架构、集成、配置和代码遵循以下原则:

  • 最小化攻击面积:每增加一个功能都是一个风险——确保它们是安全的,例如,删除不再使用的任何 Lambda。

  • 建立安全默认值:这些默认值适用于每个用户、身份和访问管理策略和无服务器堆栈组件。

  • 最小特权原则:账户或服务具有执行其业务流程所需的最少特权,例如,如果一个 Lambda 只需要对表进行读取访问,则它不应该拥有更多的访问权限。

  • 深度防御原则:具有不同的验证层和集中审计控制。

  • 安全失败:这确保了如果请求或转换失败,它仍然是安全的。

  • 不要相信服务:特别是第三方、外部服务或库,例如,感染恶意软件的 JavaScript 和 Node.js 库。

  • 职责分离:为不同的任务使用不同的角色,例如,管理员不应该是用户或系统用户。

  • 避免通过混淆来保护安全性:这通常是一个坏主意和一个薄弱的安全控制。与其依赖于架构或源代码是秘密,不如依赖于其他因素,如良好的架构、限制请求和审计控制。

  • 保持安全简单:不要过度设计;使用简单的架构和设计模式。

  • 正确修复安全问题:及时修复问题并添加新的测试。

在构建任何无服务器微服务时,请牢记这些原则。

AWS 身份和访问管理

身份和访问管理(IAM)是一个中心位置,您可以在其中管理用户的安全凭据,例如密码、访问密钥和权限策略,以控制对 AWS 服务和资源的访问。我们将讨论最相关的 IAM 资源——策略、用户、组和角色——但首先,我们将讨论 IAM 策略中使用的 JSON(www.json.org/)格式。

JavaScript 对象表示法

JSON,或 JavaScript 对象表示法,是在 REST API 和微服务中使用的标准数据格式。它可以被人类阅读,也可以被机器自动解析。数据对象由属性-值对和数组数据类型组成。支持的数据类型值包括数字、字符串、布尔值、数组、对象和 null,如下面的代码所示:

{
  "firstName": "John",
  "lastName": "Smith",
  "age": 27,
  "address": {
    "city": "New York",
    "postalCode": "10021"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "mobile",
      "number": "123 456-7890"
    }
  ]
}

前面的代码是与 John Smith 相关的详细信息的示例。您可以看到名字是键,字符串值是 John。这样,您有两个由冒号分隔的字符串。因此,您可以看到 John Smith 今年 27 岁,他居住的城市是纽约,邮政编码是 10021,您可以看到他有两个电话号码。一个是他的家庭电话号码,另一个是移动电话号码。JSON 非常描述性,可以很容易地以编程方式解析。

您可以看到它也不必是扁平的。它也可以是分层的,并且键内置到数据中。您还可以很容易地向 JSON 数据添加新的电话号码并扩展模式,而不会破坏模型,不像其他格式,如逗号分隔变量(CSV)文件。JSON 在标准 Python JSON 库中得到了本地支持,但您也可以使用其他库。我喜欢它的原因是它与 Python 数据类型有本地映射。

IAM 策略

IAM 策略是定义效果、操作、资源和条件的 JSON 文档,如下面的代码所示:

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": [  
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query"],
        "Resource": "arn:aws:dynamodb:eu-west-
                     1:123456789012:table/Books",
        "Condition": {
            "IpAddress": {
                "aws: SourceIp": "10.70.112.23/16"
            }
        }
    }
}

这是一个 JSON 文档的示例,只有在请求来自特定的 CIDR(无类域间路由选择)10.70.112.23/16时,才会授予对名为Books的 DynamoDB 表的读取访问权限,即在 IP 地址版本 4 范围内从10.70.0.010.70.255.255

还有一个可视化编辑器,允许您创建这些,或者您可以通过编辑实际的 JSON 文档来手动创建。例如,我们在本书的前面创建了一个新用户,并使用 AWS 托管策略赋予了他们管理员权限,但您也可以像所示的那样创建自己的。我的建议是在可能的情况下使用 AWS 托管策略,除非是可以或应该更受限制的资源,比如 DynamoDB 表或 S3 存储桶。

IAM 用户

IAM 用户是与 AWS 交互的人或服务。我们实际上在第一章中设置了一个新用户,无服务器微服务架构和模式。他们可以通过多因素身份验证的密码访问 AWS 管理控制台,和/或者可以使用命令行界面或 AWS 软件开发工具包SDK)进行编程访问。您可以直接向用户附加一个或多个 IAM 策略,以授予他们对资源或服务的访问权限。策略可以像我们刚刚向您展示的那样,用于授予特定来源 IP 范围对名为Books的 DynamoDB 表的读取访问权限。

IAM 组

IAM 组用于在组织组中模仿此安全功能。您可以将其视为活动目录组。例如,在您的组织中,您将拥有管理员、开发人员和测试人员。要创建一个组,您可以使用 AWS 管理控制台、SDK 或 CLI。创建组后,您可以将其附加到用户,或者在创建新用户时也可以创建一个。我倾向于将 IAM 策略附加到组,然后将组分配给用户,因为这样更容易管理并标准化访问权限。例如,我可以将数据科学组分配给团队的新成员,知道它与其他用户相同。同样,如果有人离开,那么他们的神奇策略也不会随着他们被删除!

IAM 角色

IAM 角色类似于 IAM 用户,它们可以附加策略,但可以由需要在所谓的受信实体中获得访问权限的任何人承担。通过这种方式,您可以委托访问权限给用户、应用程序或服务,而无需给他们一个新的 AWS 密钥,因为他们通过这个受信实体使用临时安全令牌。例如,无需共享任何密钥,纯粹使用角色,您可以仅授予第三方对 S3 存储桶的读取访问权限,而不允许其访问 AWS 环境中的其他任何内容。

保护您的无服务器微服务

在这一部分,我们将详细讨论安全性。

Lambda 安全性

正如我们之前讨论的,AWS Lambda 是无服务器堆栈中的核心组件,或者是与您的自定义代码集成连接器,由 AWS 托管服务之间的事件触发。Lambda 函数始终与执行 IAM 角色相关联,并且使用附加到该角色的策略是拒绝或授予其访问其他 AWS 资源的最佳且最安全的方式之一。美妙的是,对于许多 AWS 托管服务,如 S3、DynamoDB 和 Kinesis Stream,无需管理或交换任何密钥或密码。也有一些例外,比如一些 Amazon 关系型数据库服务RDS),比如 SQL Server,但 MySQL 或 PostgreSQL 支持 IAM 数据库身份验证。以下图显示了 Lambda 函数的工作原理:

如前图所示,在 Lambda 函数中通常涉及两个 IAM 角色:

  • 调用 Lambda,例如,从 API 网关或 AWS Step Functions

  • 授予对 AWS 资源的读取和写入访问权限,例如,授予 Lambda 对 DynamoDB 表的读取访问权限

此外,请注意以下内容:

  • 密钥管理服务KMS)可用于对 DynamoDB 或 RDS 中的静态数据进行加密/解密,也可用于加密密码或密钥,例如,如果您需要与第三方 API 或数据库集成。

  • Lambda 默认在安全的虚拟私有云VPC)中启动。但是,如果有需要访问的资源,例如 ElastiCache 集群或 RDS,您也可以在自己的私有 VPC 中运行它。您还可以这样做以增加另一层安全性。

API Gateway 安全性

让我们看一下以下图表:

API Gateway 可以用于创建一个无需身份验证的面向公众的 API,但有时您会希望限制访问。以下是控制谁可以调用请求授权 API 的三种不同方式:

  • IAM 角色和策略可用于授予对 API 的访问权限,API Gateway 会透明地验证请求的调用者的签名。

  • Amazon Cognito 用户池控制谁可以访问 API。用户或服务必须首先登录才能访问 API。

  • API Gateway 自定义授权器请求,例如,一个承载令牌,并使用 Lambda 函数检查客户端是否被授权调用 API。

API Gateway 中,请注意以下内容:

  • 如果您收到的请求来自 API 自己的域之外的域,您必须启用跨域资源共享CORS)。

  • 还支持客户端 SSL 证书,例如允许后端系统验证 HTTP 请求是否来自 API Gateway 而不是其他系统。

  • API Gateway 可能还需要通过 IAM 角色授予访问权限,例如,如果需要向 Kinesis Streams 写入记录或调用 Lambda 函数。

  • 使用计划允许您为客户创建 API 密钥,从而限制和监视使用情况。例如,这可以让您为客户创建按使用量付费的 API。

DynamoDB 安全性

现在让我们看一下以下图表:

DynamoDB 是 AWS 托管的服务,授权通过 IAM 权限策略进行管理。授予或拒绝对 DynamoDB 的访问的 IAM 策略附加到特定的 IAM 用户或角色,然后可以访问它。如果您想在一个 AWS 账户中承担角色,我们还可以选择委派相同的权限,以便它们可以访问不同 AWS 账户中的 DynamoDB 表。在这种情况下的好处是不需要交换密钥。

我建议您在为 DynamoDB 创建这些策略时应用最小权限原则,尽可能地限制它们,这意味着避免对表访问使用通配符星号,例如使用"Resource": "*"。例如,在策略文档中,除非绝对必要,避免给予对所有表的读写访问权限。在可能的情况下,最好明确列出特定操作、表名和约束。

监控和警报

现在考虑以下图表:

总的来说,监视系统中的任何可疑活动或发现系统的任何性能问题都很重要。API Gateway、DynamoDB 和 Lambda 函数都内置了 CloudWatch 和 X-Ray 的支持。CloudWatch 允许您跟踪指标和监视日志文件,设置特定的警报,并自动对 AWS 资源的更改做出反应。X-Ray 是一个跟踪请求的服务,还可以生成特定的服务地图。这些免费系统的组合为您提供了非常好的洞察力,可以直接了解您的无服务器系统的性能。CloudTrail 是另一个服务,允许您监视所有 API 和任何用户或系统对资源的访问。

了解更多

现在您对 AWS 中的安全性有了更深入的了解,以及为什么对您的组织来说很重要。

如果您想了解更多信息,以下是一些白皮书和最佳实践指南的链接。我建议阅读以下白皮书:

构建无服务器微服务数据 API

在本节中,我们将研究构建无服务器微服务的架构和要求。本章的其余部分在 AWS 管理控制台中进行配置,同时也涉及 Python 代码。Python 代码遵循基本的设计模式,并且保持简单,以便您可以理解并轻松地为自己的用例进行调整。

无服务器微服务数据 API 要求

我们想要创建一个微服务,能够为特定事件提供网站访问次数的总用户数。

查询字符串

对于传入请求的特定EventId,我们希望作为响应检索每日网站访问次数(已由另一个系统收集)。我们将通过EventIdstartDate进行查询;我们希望检索startDate之后的所有网站访问次数。URL 及其参数将如下所示:

在这里,您可以看到我们有EventId作为资源1234,并且startDate参数格式为YYYYMMDD格式。在这种情况下,它是20180102。这就是请求。

我们可以在浏览器中或以编程方式输入此请求,我们希望得到的响应来自 NoSQL 数据库,其中存储了实时数据。实际的响应格式将以 JSON 的形式从数据库返回,通过 Lambda 函数呈现给用户或以编程方式查询 API 的其他服务。我们还希望这个 API 能够轻松扩展并且成本效益很高;也就是说,我们不希望一直运行一个机器,因为那样会花钱并且需要维护。我们只想在实际请求时支付费用。

现在以下图表:

这是我们感兴趣的时间序列数据的一个示例,我们有一个EventId,用于事件324,我们有格式为EventDay的日期,这是 2017 年 10 月,我们在表格的右侧列中有一个名为EventCount的总EventCount。您可以看到 2017 年 10 月 10 日,EventId324EventCount2,这意味着在那一天对于该事件的总访问次数为2。第二天是0,因为没有第 11 天的条目。然后,它在 12 日增加到10,13 日为10,然后下降到6062。请注意,当表中没有特定日期的数据时,它为0

这是我们希望 API 以 JSON 格式提供的数据作为响应,以便另一个客户端应用程序可以绘制它,如下图所示:

例如,如果我们在 Excel 中绘制数据,您将看到这种类型的图表,其中EventCount2开始,在 10 月 11 日有一个间隙,这个特定用户没有访问,然后增加到10106,然后在 10 月 15 日有一个间隙,然后在 10 月 16 日再次增加到6

数据 API 架构

现在我们知道了我们想要返回的数据的要求和形状,我们将讨论整体架构。同样,整个堆栈将依赖于 JSON 格式进行所有服务之间的数据交换。

以下图表显示了我们在请求中拥有的内容:

请求流程如下:

  1. 我们在互联网上有一个客户端浏览器、移动客户端或后端服务。

  2. 这将查询我们的 API Gateway,将带有EventIdstartDate作为可选 URL 参数的请求传递。

  3. 这将通过 AWS IAM 角色进行身份验证。

  4. 然后将启动 Lambda 函数。

  5. Lambda 函数将使用角色访问 DynamoDB。

  6. Lambda 将查询 Dynamo 以搜索特定的EventId。可选地,查询还将包括与EventDay进行比较的特定startDate。如果EventDay大于startDate,则将返回记录。

以下图表显示了我们在响应中的内容:

响应流程如下:

  1. 数据将从 DynamoDB 返回,如前图表右下角所示

  2. 这将通过与 Lambda 函数关联的相同 IAM 角色进行

  3. 来自 DynamoDB 的 JSON 记录将返回给 Lambda 函数,Lambda 函数将其解析为 JSON 响应

  4. 这将通过 Lambda 函数通过 API Gateway 角色传递

  5. 它被传回 API Gateway

  6. 最终,它将返回给客户端浏览器移动客户端,或者发出初始请求的后端服务,以便可以绘制图表

我们还配置了 Amazon CloudWatch 来监视请求,通过提供指标和日志的仪表板。

在 AWS 管理控制台中设置 Lambda 安全性

我们将登录到 AWS 管理控制台。我们首先使用管理控制台的原因是为了让您更好地了解 Lambda 函数的工作原理,以及它们如何与其他 AWS 服务集成,例如 API Gateway 和 DynamoDB。在后面的章节中,我们将向您展示如何使用 AWS CLI 部署 Lambda 函数。如果您是 Lambda 的初学者,那么我总是发现首先在管理控制台中手动创建完整的无服务器堆栈对于获得比如说有一个魔术命令启动完整的 AWS 基础设施更好和更深入的理解是有用的!

我们将首先使用 AWS 管理控制台创建 Lambda IAM 角色和策略,以便 Lambda 函数可以访问 DynamoDB,并将任何日志或任何状态写入 CloudWatch。我们之前在第一章中使用的管理控制台,无服务器微服务架构和模式,允许您集中控制所有 AWS 服务,创建角色,甚至创建 Lambda 函数。就无服务器微服务的架构而言,我们首先从以下图表的右侧开始,并逐步构建其余部分。

以下图表显示了数据 API Lambda IAM:

接下来创建两个 IAM 策略并将它们附加到新的 Lambda IAM 角色。

创建 IAM 策略

我们将创建 Lambda 函数和 IAM 角色和策略。您需要做的第一件事是登录到 AWS 管理控制台。在 IAM 中,我们要创建实际的策略本身。您可以单击创建策略,我们将使用 JSON 编辑器。

DynamoDB IAM 策略

首先,我们需要一个策略,允许 Lambda 函数从 DynamoDB 中读取记录。我们可以通过以下方式来做:

  1. 登录到https://console.aws.amazon.com/的 AWS 管理控制台。

  2. 选择安全性、身份和合规性| IAM,或在查找服务下搜索 IAM。

  3. 在 IAM 导航窗格中,选择策略。

  4. 选择创建策略。

  5. 选择 JSON 选项卡。

您也可以使用或切换到可视化编辑器来创建策略,而不是使用JSON视图,但我更喜欢JSON视图,因为代码可以进行源控制,并且可以像我们稍后使用 AWS CLI 那样进行程序化部署。

  1. 输入或粘贴以下 JSON 策略文档:
      {
          "Version": "2012-10-17",
          "Statement": [
              {
                 "Effect": "Allow",
                 "Action": [
                     "dynamodb:BatchGetItem",
                     "dynamodb:DescribeTable",
                     "dynamodb:GetItem",
                     "dynamodb:Query",
                     "dynamodb:Scan"
                 ],
                  "Resource": [
                     "arn:aws:dynamodb:<your-region>:<your-aws-
                      accountid>:table/user-visits"                 
                 ]
             }
         ]
     }

更新<your-region>为您的 AWS 区域,例如us-east-1,并将<your-aws-accountid>更新为您的 AWS 账户 ID。

如果您不知道您的 AWS 账号,您可以在 AWS 管理控制台顶部的 Support | Support Center 菜单中找到它,如下面的屏幕截图所示:

  1. 选择 Review Policy。

  2. 在 Review Policy 页面,为名称输入dynamo-readonly-user-visits

  3. 选择创建策略。

这个名为 dynamo-readonly-user-visits 的 IAM 策略现在将在筛选策略下作为客户管理的策略可用。

我们谈到了安全性非常重要,确保安全的一种方法是应用 OWASP 安全设计原则,比如之前谈到的最小特权原则。在这里,我们通过使用策略来限制表访问来实现这一点。您会注意到,我将其限制在一个特定的名称,dynamo表。对于策略名称,应尽可能描述和细化,以便更容易维护。我倾向于在可能的情况下为每个 AWS 资源使用一个策略。我使用前缀dynamo-readonly,这样就很明显,您只能从一个名为user-visits的特定表中读取。

Lambda IAM 策略

创建一个策略,以便能够将日志写入和推送指标到 CloudWatch:

  1. 登录 AWS 管理控制台,并在console.aws.amazon.com/iam/中打开 IAM 控制台,如果您尚未登录。

  2. 在 IAM 导航窗格中,选择策略。

  3. 选择创建策略。

  4. 选择 JSON 选项卡。

  5. 输入或复制以下 JSON 文档:

     {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Action": [
             "logs:CreateLogGroup",
             "logs:CreateLogStream",
             "logs:PutLogEvents",
             "logs:DescribeLogStreams"
          ],
            "Resource": [
              "arn:aws:logs:*:*:*"
         ]
        },
       {
           "Effect": "Allow",
           "Action": [
             "cloudwatch:PutMetricData"
           ],
           "Resource": "*"
         }
      ]
     }

这个策略的主要目的是允许 Lambda 函数创建 CloudWatch 日志组和流,并将日志事件添加到这些流中,然后描述它们。我还添加了另一个允许您放置指标的声明,如果您想要推送自定义监视指标,则需要这样做。

  1. 选择 Review Policy。

  2. 在 Review Policy 中,为名称输入lambda-cloud-write

  3. 选择创建策略。

创建 Lambda IAM 角色

现在我们有了两个 IAM 策略,我们将创建一个新的 Lambda IAM 角色,并将这两个策略附加到它:

  1. 登录 AWS 管理控制台,并在console.aws.amazon.com/iam/中打开 IAM 控制台

  2. 在导航窗格中,选择角色

  3. 选择创建角色

  4. 选择 AWS 服务,然后在下面选择 Lambda

  5. 选择下一步:权限

  6. 在附加权限策略 | 筛选策略下,输入dynamo-readonly-user-visits-api

  7. 选择复选框以选择 dynamo-readonly-user-visits-api

  8. 在附加权限策略 | 筛选策略下,输入lambda-cloud-write

  9. 选择 lambda-cloud-write 的复选框

  10. 选择下一步:标签

  11. 选择下一步:审核

  12. 在 Review 页面,为角色名称输入lambda-dynamo-data-api

  13. 选择创建角色

您已创建了两个 IAM 策略,并将它们附加到一个新的 Lambda 执行角色上,稍后我们将将其与 Lambda 函数关联。

使用 AWS 创建和写入名为 DynamoDB 的 NoSQL 数据库

我们将创建一个 DynamoDB 表,从硬编码值向表中写入数据,从文件写入数据记录,然后我们将展示两种不同的查询表的方法:

在 AWS 中创建 DynamoDB

以下步骤显示如何创建 DynamoDB:

  1. 您需要先登录 AWS 管理控制台,然后在console.aws.amazon.com/dynamodb/中打开 AWS DynamoDB 控制台。

  2. 选择创建表,或者在 DynamoDB 导航窗格中,选择表,然后选择创建表。

  3. 在创建 DynamoDB 表窗口中,执行以下步骤:

  4. 在表名下,输入user-visits

  5. 在 Partition key 的主键中,输入EventId并选择 String

  6. 勾选添加排序键框

  7. 在排序键中,输入EventDay并选择 Number

分区键和哈希键可以互换使用,就像排序键和范围键一样。主键可以是仅分区键,也可以是具有分区键和排序键的复合键。

使用 AWS 将数据写入 DynamoDB

执行以下步骤:

  1. 登录到 AWS 管理控制台,并在console.aws.amazon.com/dynamodb/中打开 DynamoDB 控制台。

  2. 在 DynamoDB 导航窗格中,选择 Tables 并选择 user-visits。

  3. 在 user-visits 窗格中,选择 Items*选项卡。

  4. 选择创建项目。

  5. 在创建项目弹出窗口中:

  6. 在 EventId String 下,输入324

  7. 在 EventDay Number 下,输入20171001

  8. 选择+ > 追加 > 数字,对于字段,输入EventCount,对于数字,输入3

  9. 选择保存

现在,您会看到在右下窗格的 Items 选项卡中添加了一个新记录,因为扫描也已自动完成。

DynamoDB 是一个托管的 NoSQL 数据库,这意味着每行可以有不同的列,列的名称,即属性,是区分大小写的。

使用 AWS 查询 DynamoDB

在 DynamoDB 上有两种类型的搜索;ScanQueryScan从表中检索所有记录。Query利用主键有效地检索一组记录。两者都允许您具有可选的一致性、分页、过滤器、条件,并选择要返回的属性。一般来说,Scan适用于检索所有数据或跨许多不同主键的数据,而Query应该在您有主键并且想要检索所有或经过筛选的相关记录时使用。

在 AWS 管理控制台中的 DynamoDB 扫描

执行以下步骤:

  1. 登录到 AWS 管理控制台,并在console.aws.amazon.com/dynamodb/中打开 DynamoDB 控制台。

  2. 在 DynamoDB 导航窗格中,选择 Tables 并选择 user-visits

  3. 在 user-visits 窗格中选择 Items*选项卡

  4. 从下拉菜单中选择 Scan

  5. 可选择+Add Filter 以过滤查询结果

  6. 选择开始搜索

现在,您应该在右下窗格的表中看到结果,其中包含 EventId、EventDay 和 EventCount 列。

在 AWS 管理控制台中的 DynamoDB 查询

执行以下步骤:

  1. 登录到 AWS 管理控制台,并在console.aws.amazon.com/dynamodb/中打开 DynamoDB 控制台。

  2. 在 DynamoDB 导航窗格中,选择 Tables 并选择 user-visits

  3. 在 user-visits 窗格中,选择 Items*选项卡

  4. 从下拉菜单中选择查询

  5. 在分区键下,输入324

  6. 在 Sort Key 下,选择>并输入20171001

  7. 选择开始搜索

您会看到没有返回结果,因为我们正在寻找 EventDay 大于20171001的记录,而当前表中没有这样的记录。

修改以下内容以查找记录:

  1. 在 Sort Key 下,选择>=并输入20171001

  2. 选择开始搜索

现在您会看到我们添加的记录可见,因为它符合查询搜索条件。

修改以下内容以查找记录:

  1. 在 Sort Key 下,选择 between 并输入2017093020171002

  2. 选择开始搜索

在这里,我们使用 between 条件来检索记录。

这种查询灵活性使您能够以非常低的延迟检索数据。但是,您会注意到条件表达式的分区键始终固定为=,并且必须提供给所有Query操作-这在许多 NoSQL 数据库中很常见。如果您没有或不知道主键,那么您需要使用Scan

使用 AWS 删除 DynamoDB

让我们删除表,因为我们将使用 Python 重新创建它。执行以下步骤:

  1. 登录到console.aws.amazon.com/dynamodb/控制台

  2. 从左侧 DynamoDB 菜单中选择 Tables

  3. 选择 user-visits

  4. 选择删除表

  5. 选择删除

使用 Python 创建和写入名为 DynamoDB 的 NoSQL 数据库

现在我们了解了如何创建表、添加数据和使用 AWS 控制台查询 DynamoDB,我们将看看如何只使用 Python 代码来实现这一点。

我们建议您使用 Python 集成开发环境IDE),如 Eclipse PyDev (www.pydev.org/download.html)或 PyCharm (www.jetbrains.com/pycharm/)。您不需要使用 IDE,但我建议您这样做。如果您真的想要,您可以使用 VI,例如在 Linux 上实际编辑您的代码。但是使用 IDE 允许您运行调试或在本地设置单元测试并逐步执行,这使得开发更容易和更高效。

首先在 Python 中使用Boto3 https://boto3.readthedocs.io/创建表。在 PyCharm 或您喜欢的文本编辑器中运行以下部分的代码。

使用 Python 创建 DynamoDB 表

以下是创建表的通用 Python 代码。创建一个名为dynamo_table_creation.py的 Python 脚本,其中包含以下代码:

import boto3

def create_dynamo_table(table_name_value, enable_streams=False,
                        read_capacity=1,
                        write_capacity=1,
                        region='eu-west-1'):
    table_name = table_name_value
    print('creating table: ' + table_name)
    try:
        client = boto3.client(service_name='dynamodb', 
                              region_name=region)
        print(client.create_table(TableName=table_name,
                                  AttributeDefinitions=[{'AttributeName': 'EventId',
                                                         'AttributeType': 'S'},
                                                        {'AttributeName': 'EventDay',
                                                         'AttributeType': 'N'}],
                                  KeySchema=[{'AttributeName': 'EventId',
                                              'KeyType': 'HASH'},
                                             {'AttributeName': 'EventDay',
                                              'KeyType': 'RANGE'},
                                             ],
                                  ProvisionedThroughput={'ReadCapacityUnits': read_capacity,
                                                         'WriteCapacityUnits': write_capacity}))
    except Exception as e:
        print(str(type(e)))
        print(e.__doc__)

def main():
    table_name = 'user-visits'
    create_dynamo_table(table_name, False, 1, 1)

if __name__ == '__main__':
    main()

与在 AWS 控制台中创建 DynamoDB 表不同,这里我们使用 Python SDK Boto3 来创建它。main()调用名为create_dynamo_table()的方法,该方法接受与我们要创建的表相关的各种参数,table_name是第一个。忽略enable_streams参数,我们稍后会使用。另外两个与初始读取和写入容量相关联。这将直接影响成本,以及表的大小和检索的数据。这就是为什么我默认将它们设置为1。区域参数应该是您的 AWS 区域。

然后创建一个boto3.client(),这是一个代表 DynamoDB 的低级客户端。然后我们使用它来使用client.create_table()创建一个表,传入我们的create_dynamo_table()中传入的参数,以及分区键名EventId,其数据类型String,表示为S,和排序键名EventDay,其数据类型编号表示为N。所有其他属性都是可选的和任意的。

您会注意到 DynamoDB 在管理控制台和 Boto3 描述之间的关键术语有所变化,但它们是同义词:Partition key (AWS Console) = Hash key (Boto3)Sort key (AWS Console) = Range key (Boto3)

两者一起,作为复合键,称为主键。

使用 Python 写入 DynamoDB

以下代码将三条记录写入 DynamoDB。使用以下 Python 代码创建另一个名为dynamo_modify_items.py的文件:

from boto3 import resource

class DynamoRepository:
    def __init__(self, target_dynamo_table, region='eu-west-1'):
        self.dynamodb = resource(service_name='dynamodb', 
                        region_name=region)
        self.target_dynamo_table = target_dynamo_table
        self.table = self.dynamodb.Table(self.target_dynamo_table)

    def update_dynamo_event_counter(self, event_name, 
                event_datetime, event_count=1):
        return self.table.update_item(
            Key={
                'EventId': event_name,
                'EventDay': event_datetime
            },
            ExpressionAttributeValues={":eventCount": event_count},
            UpdateExpression="ADD EventCount :eventCount")

def main():
    table_name = 'user-visits'
    dynamo_repo = DynamoRepository(table_name)
    print(dynamo_repo.update_dynamo_event_counter('324', 20171001))
    print(dynamo_repo.update_dynamo_event_counter('324', 20171001, 2))
    print(dynamo_repo.update_dynamo_event_counter('324', 20171002, 5))

if __name__ == '__main__':
    main()

在这里,我们使用 Boto3 的resource(),这是一个具有存储库模式的更高级别的服务资源。我们在DynamoRepository()类中将所有与 DynamoDB 相关的代码抽象出来,该类实例化为dynamo_repo,并使用table_nameself.dynamodb.Table()基于table_name创建一个表资源。这将在稍后调用update_dynamo_event_counter()更新 DynamoDB 记录时使用。

self.table.update_item()中,我首先使用ExpressionAttributeValues声明一个名为eventCount的变量。我在 DynamoDB 高级Update Expressionsdocs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)中使用它,这是我在 DynamoDB 中最喜欢的功能之一。为什么?因为并非所有 NoSQL 数据库都可以在没有类似信号量锁并且客户端进行重试的情况下执行类似操作。它在一个原子语句中执行以下三个操作,同时避免可能的并发违规,以最终一致性为代价:

  1. 读取与给定的EventId=event_nameEventDay=event_datetime匹配的记录

  2. 如果不存在,则创建一个新项目,设置EventCount=1

  3. 如果已存在,则通过event_count增加EventCount

第一个函数调用dynamo_repo.update_dynamo_event_counter('324', 20171001),将EventCount设置为1;第二个函数调用dynamo_repo.update_dynamo_event_counter('324', 20171001, 2),将EventCount增加2,现在为3。第三个函数调用添加了一个新记录,因为EventCount或主键不同。

使用 Python 查询 DynamoDB

现在我们已经创建了一个表并添加了数据,我们只需要编写一些代码来查询它。这将成为稍后在 Lambda 函数中使用的代码的一部分。

创建一个名为dynamo_query_table.py的 Python 脚本,其中包含以下代码:

import decimal
import json

from boto3 import resource
from boto3.dynamodb.conditions import Key

class DecimalEncoder(json.JSONEncoder):
    """Helper class to convert a DynamoDB item to JSON
    """
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            if o % 1 > 0:
                return float(o)
            else:
                return int(o)
        return super(DecimalEncoder, self).default(o)

class DynamoRepository:
    def __init__(self, target_dynamo_table, region='eu-west-1'):
        self.dynamodb = resource(service_name='dynamodb', region_name=region)
        self.dynamo_table = target_dynamo_table
        self.table = self.dynamodb.Table(self.dynamo_table)

    def query_dynamo_record_by_parition(self, parition_key, 
        parition_value):
        try:
            response = self.table.query(
                KeyConditionExpression=
                Key(parition_key).eq(parition_value))
            for record in response.get('Items'):
                print(json.dumps(record, cls=DecimalEncoder))
            return

        except Exception as e:
            print('Exception %s type' % str(type(e)))
            print('Exception message: %s ' % str(e))

    def query_dynamo_record_by_parition_sort_key(self, 
          partition_key, partition_value, sort_key, sort_value):
        try:
            response = self.table.query(
                KeyConditionExpression=Key(partition_key)
                .eq(partition_value)
                & Key(sort_key).gte(sort_value))
            for record in response.get('Items'):
                print(json.dumps(record, cls=DecimalEncoder))
            return

        except Exception as e:
            print('Exception %s type' % str(type(e)))
            print('Exception message: %s ' % str(e))
def main():
    table_name = 'user-visits'
    partition_key = 'EventId'
    partition_value = '324'
    sort_key = 'EventDay'
    sort_value = 20171001

    dynamo_repo = DynamoRepository(table_name)
    print('Reading all data for partition_key:%s' % partition_value)
    dynamo_repo.query_dynamo_record_by_parition(partition_key, 
        partition_value)

    print('Reading all data for partition_key:%s with date > %d' 
           % (partition_value, sort_value))
    dynamo_repo.query_dynamo_record_by_parition_sort_key(partition_key,                                                         
          partition_value, sort_key, sort_value)
if __name__ == '__main__':
    main()

就像我之前做的那样,我创建了DynamoRepository类,它抽象了与 DynamoDB 的所有交互,包括连接和查询表。以下是用于使用 DynamoDB 的self.table.query()查询表的两种方法:

  • query_dynamo_record_by_parition()方法,通过partition_key查询记录,也称为哈希键,在这种情况下是EventId。在这里,我们在query()中仅使用相等条件,显示为KeyConditionExpression=Key(partition_key).eq(parition_value))

  • query_dynamo_record_by_parition_sort_key()方法,通过partition_keysort_key查询记录,也称为range key,在这种情况下是EventDate。在这里,我们在query()中仅使用相等条件和大于或等于条件,如KeyConditionExpression=Key(partition_key).eq(partition_value) & Key(sort_key).gte(sort_value))。这使您能够快速按特定日期范围进行过滤,例如,检索最近 10 天的事件数据以在仪表板中显示。

然后我们解析查询返回的记录并将其打印到控制台。这个 JSON 将是 Lambda 在下一部分作为响应返回给 API Gateway 的内容。

创建一个用于查询 DynamoDB 的 Lambda

现在我们已经设置了securityuser-visits表,并且知道如何编写代码来查询 DynamoDB 表,我们将编写 Lambda Python 代码。

创建 Lambda 函数

现在我们已经有了附加了两个 IAM 策略的 IAM 角色,创建 Lambda 函数本身。在这里,我们正在从头开始创建一个函数,因为我们希望深入了解创建无服务器堆栈所涉及的全部细节。以下图表显示了涉及 CloudWatch、DynamoDB、IAM 和 Lambda 的数据 API 架构:

执行以下步骤:

  1. 登录 AWS 管理控制台,在console.aws.amazon.com/lambda/上打开 AWS Lambda 控制台。

  2. 选择创建函数,或者在 AWS Lambda 导航窗格中选择函数,然后选择创建函数。

  3. 在创建函数页面上,选择从头开始创建,按以下步骤操作:

  4. 对于名称,键入lambda-dynamo-data-api

  5. 在运行时中,选择 Python 3.7

  6. 在角色中,保留选择现有角色

  7. 在现有角色中,选择 lambda-dynamo-data-api

  8. 在功能代码下,已创建一个名为lambda_function.py的新文件。将以下代码复制并粘贴到 lambda_function 选项卡下,覆盖现有代码:

      import json
      import decimal

      from boto3 import resource
      from boto3.dynamodb.conditions import Key

      class HttpUtils: 
        def init(self):
         pass

      @staticmethod
      def parse_parameters(event):
          try:
              return_parameters = 
              event['queryStringParameters'].copy()
          except Exception:
              return_parameters = {}
          try:
              resource_id = event.get('path', '').split('/')[-1]
              if resource_id.isdigit():
                  return_parameters['resource_id'] = resource_id
              else:
                  return {"parsedParams": None, "err":
                      Exception("resource_id not a number")}
          except Exception as e:
              return {"parsedParams": None, "err": e}  
              # Generally bad idea to expose exceptions
          return {"parsedParams": return_parameters, "err": None}

      @staticmethod
      def respond(err=None, err_code=400, res=None):
          return {
              'statusCode': str(err_code) if err else '200',
              'body': '{"message":%s}' % json.dumps(str(err)) 
                  if err else
              json.dumps(res, cls=DecimalEncoder),
              'headers': {
                  'Content-Type': 'application/json',
                  'Access-Control-Allow-Origin': '*'
              },
          }

      @staticmethod
      def parse_body(event):
          try:
              return {"body": json.loads(event['body']), 
                      "err": None}
          except Exception as e:
              return {"body": None, "err": e}

      class DecimalEncoder(json.JSONEncoder): def default(self, o): 
      if isinstance(o, decimal.Decimal): if o % 1 > 0: 
          return float(o) 
      else: return int(o) return super(DecimalEncoder, 
          self).default(o)

      class DynamoRepository: def init(self, table_name): 
      self.dynamo_client = resource(service_name='dynamodb',
      region_name='eu-west-1') self.table_name = 
          table_name self.db_table =        
      self.dynamo_client.Table(table_name)

      def query_by_partition_and_sort_key(self, 
          partition_key, partition_value,
          sort_key, sort_value):
          response = self.db_table.query(KeyConditionExpression=                                      
                     Key(partition_key).eq(partition_value)
                     & Key(sort_key).gte(sort_value))

          return response.get('Items')

      def query_by_partition_key(self, partition_key, 
          partition_value):
          response = self.db_table.query(KeyConditionExpression=                                   
              Key(partition_key).eq(partition_value))
          return response.get('Items')

      def print_exception(e): try: print('Exception %s type' % 
      str(type(e))) print('Exception message: %s ' 
                          % str(e)) except Exception: pass

      class Controller(): def init(self): pass

      @staticmethod
      def get_dynamodb_records(event):
          try:
              validation_result = HttpUtils.parse_parameters(event)
              if validation_result.get(
              'parsedParams', None) is None:
                  return HttpUtils.respond(
                      err=validation_result['err'], err_code=404)
              resource_id = str(validation_result['parsedParams']
                            ["resource_id"])
              if validation_result['parsedParams']
                  .get("startDate") is None:
                  result = repo.query_by_partition_key(
                           partition_key="EventId", 
                           partition_value=resource_id)
              else:
                  start_date = int(validation_result['parsedParams']
                               ["startDate"])
                  result = repo.query_by_partition_and_sort_key(
                            partition_key="EventId", 
                            partition_value=resource_id,
                            sort_key="EventDay", 
                            sort_value=start_date)
                            return HttpUtils.respond(res=result)

                  except Exception as e:
                  print_exception(e)
              return HttpUtils.respond(err=Exception('Not found'), 
                  err_code=404)

      table_name = 'user-visits' repo = 
                    DynamoRepository(table_name=table_name)

      def lambda_handler(event, context): response = 
      Controller.get_dynamodb_records(event) return response
  1. 选择保存。

Lambda 函数始终具有一个handlerdocs.aws.amazon.com/lambda/latest/dg/python-programming-model-handler-types.html),并且传入主要的event,其中包含事件源数据。在这里,这将是一个 API Gateway GET 请求。另一个参数是contextdocs.aws.amazon.com/lambda/latest/dg/python-context-object.html),它提供了诸如 Lambda 的内存或生存时间等详细信息。

您将从之前的示例中识别class DynamoRepository(),它处理连接和查询。新的HttpUtils类是一组用于解析查询字符串和正文并返回响应的实用方法。另一个新的Controller()类控制主流程。在这里,它假定 API 网关请求是GET方法,因此它调用函数来解析请求,查询 DynamoDB,并将响应返回给 API 网关。

异常流程是防御性构建的,因此捕获了所有异常(通常最佳做法是仅捕获特定命名的异常并引发其余异常),而不是引发异常。这是因为我们希望 API 能够在发生异常时返回 4XX 或 5XX。出于调试目的,我们也返回异常。在生产环境中,您不会返回异常,只会记录它,因为它可能会暴露代码中的漏洞。类比一下,您可能还记得在浏览器中看到那些带有源错误和完整堆栈跟踪的 SQL Server 错误,它们是在白色背景上以黄色显示的,来自一个安全性较差的 Web 应用程序。

我建议您在本地开发 Lambda 代码,但 AWS 最近收购了一个名为 Cloud9 的在线 IDE 编辑器,您已经在其中粘贴了 Python 代码,如下面的屏幕截图所示:

测试 Lambda 函数

现在我们已经部署了 Lambda 代码,可以在 AWS 管理控制台中测试它是否正常工作。执行以下步骤:

  1. 登录到 AWS 管理控制台,并在console.aws.amazon.com/lambda/上打开 AWS Lambda 控制台。

  2. 在 AWS Lambda 导航窗格中,选择函数。

  3. 选择 lambda-dynamo-data-api。

  4. 选择测试。

  5. 在配置测试事件中,在事件名称下,键入requestApiGatewayGetValid,并在 JSON 文档中键入或复制并粘贴以下代码,覆盖旧代码:

      {
       "body": "{"test":"body"}",
       "resource": "/{proxy+}",
       "requestContext": {
         "resourceId": "123456",
         "apiId": "1234567890",
         "resourcePath": "/{proxy+}",
         "httpMethod": "GET",
         "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
         "accountId": "123456789012",
         "identity": {
           "apiKey": null,
           "userArn": null,
           "cognitoAuthenticationType": null,
           "caller": null,
           "userAgent": "Custom User Agent String",
           "user": null, "cognitoIdentityPoolId": null,
           "cognitoIdentityId": null,
           "cognitoAuthenticationProvider": null,
           "sourceIp": "127.0.0.1",
           "accountId": null
         },
         "stage": "prod"
       },
       "queryStringParameters": {
         "StartDate": "20171009"
       },
       "headers": {
         "Via": "1.1 08f323deadbeefa7af34e5feb414ce27
                 .cloudfront.net (CloudFront)",
         "Accept-Language": "en-US,en;q=0.8",
         "CloudFront-Is-Desktop-Viewer": "true",
         "CloudFront-Is-SmartTV-Viewer": "false", 
         "CloudFront-Is-Mobile-Viewer": "false",
         "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
         "CloudFront-Viewer-Country": "US", "Accept":        
         "text/html,application/xhtml+xml,application/xml;q=0.9,
              image/webp,*/*;q=0.8",
         "Upgrade-Insecure-Requests": "1",
         "X-Forwarded-Port": "443", "Host": "1234567890
              .execute-api.us-east-1.amazonaws.com",
         "X-Forwarded-Proto": "https",
         "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-
              9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
         "CloudFront-Is-Tablet-Viewer": "false",
         "Cache-Control": "max-age=0",
         "User-Agent": "Custom User Agent String",
         "CloudFront-Forwarded-Proto": "https", "Accept-Encoding": 
              "gzip, deflate, sdch"
       },
       "pathParameters": {
         "proxy": "path/to/resource"
       },
       "httpMethod": "GET",
       "stageVariables": {
         "baz": "qux"
       },
       "path": "/path/to/resource/324"
      }
  1. 以下是 API 网关GET请求 JSON 的一些重要部分:
  • 请求使用GET方法从"httpMethod": "GET"

  • 资源或EventID324,来自"path": "/path/to/resource/324"

  • 查询参数来自"queryStringParameters": { "StartDate": "20171009"}

  1. 选择创建。

  2. 选择要运行测试的测试。

您应该从执行结果中看到,使用示例 API 网关GET请求成功通过了测试。这包括持续时间,内存使用和日志输出。展开详细信息以查看将发送到 DynamoDB 的响应。它应该类似于以下代码:

{
  "statusCode": "200",
  "body": "[{"EventCount": 3, "EventDay": 20171001, "EventId": 
          "324"}]",
  "headers": { "Content-Type": "application/json", "Access-Control-
              Allow-Origin": "*"
 }
}

如果出现错误,请检查详细信息的日志输出,可能与 IAM 角色/策略,DynamoDB 名称或 Lambda 代码有关。

设置 API 网关并将其与 Lambda 代理集成

现在我们知道 Lambda 函数可以使用一些 API 网关测试数据,并返回具有statusCode200的标头和正文,我们只需要添加将调用 Lambda 函数的 API 网关,如下图所示:

执行以下步骤:

  1. 登录到 AWS 管理控制台,并在console.aws.amazon.com/apigateway/上打开 API 网关控制台。

  2. 选择开始使用,或者在亚马逊 API 网关导航窗格中,选择 API 并选择创建 API。

  3. 在创建页面上,执行以下步骤:

  4. 在选择协议中,选择 REST

  5. 在创建新 API 中,选择新 API

  6. 在设置下,输入 API 名称为metrics

  7. 选择区域作为端点类型

  8. 选择创建 API

  9. 从操作下拉菜单中选择创建资源。

  10. 在新的子资源窗口中,执行以下步骤:

  11. 在资源名称中,键入visits

  12. 在资源路径中,键入visits

  13. 选择启用 API 网关 CORS

  14. 选择创建资源。

  15. 选择/visits资源,并从操作下拉菜单中选择创建资源。

  16. 在新的子资源窗口中,执行以下步骤:

  17. 在资源名称中,键入{resourceId}

  18. 在资源路径中,键入{resourceId},替换默认的-resourceId-

  19. 检查启用 API 网关 CORS

  20. 选择创建资源

  21. 选择/Vists/{resourceId}资源,并从操作下拉菜单中选择创建方法。

  22. 选择下拉菜单中的 GET,然后选择其右侧的复选标记。

  23. 选择/visits/{resourceId} - GET - Setup窗口中的 GET 资源方法:

  24. 在集成类型中,选择 Lambda 函数

  25. 检查使用 Lambda 代理集成

  26. 在 Lambda 区域中,从下拉菜单中选择您的区域

    1. 在 Lambda 函数中,键入lambda-dynamo-data-api
  27. 检查使用默认超时

  28. 选择保存

  29. 在添加权限到 Lambda 函数中选择 OK。这将允许 API 网关调用 Lambda 函数。

现在您应该有一个看起来像以下截图的 API 网关 GET - 方法执行:

最后,通过执行以下步骤进行快速测试以确保其正常工作:

  1. 在左侧的资源菜单中选择/visits/{resourceId} - GET

  2. 选择测试

  3. 在路径{resourceId}下键入324

  4. 选择测试

您应该看到状态 200、延迟、日志和 JSON 响应正文,如下代码所示:

[ 
   {
     "EventCount": 3,
     "EventDay": 20171001,
     "EventId": "324"
   },
   {
     "EventCount": 5,
     "EventDay": 20171002,
     "EventId": "324"
   }
]

如果您没有收到2XX状态代码,那么请查看日志,这将帮助您诊断问题。它可能与安全 IAM 角色有关。

连接 API 网关、Lambda 和 DynamoDB

现在我们知道 API 网关与 Lambda 函数的集成工作正常,我们将部署它并获取 URL。架构如下图所示:

这种架构的工作原理如下:

  1. 登录到 AWS 管理控制台,并在console.aws.amazon.com/apigateway/上打开 API 网关控制台。

  2. 在 Amazon API Gateway 导航窗格中,选择 API 和指标。

  3. 在指标下选择资源和/Vists/{resourceId},然后从操作下拉菜单中选择部署 API。

  4. 在部署 API 弹出窗口中,执行以下步骤:

  5. 在部署阶段中,选择[新阶段]

  6. 在阶段名称中,键入prod

  7. 在阶段描述中,键入prod

  8. 选择“部署”

  9. 指标下的阶段应自动在左侧菜单上选择。

  10. 选择prod/visits/{resourceId}/GET下的 GET 以获取调用 URL。调用 URL 应如下所示:https://{restapi_id}.execute-api.{region}.amazonaws.com/prod/visits/{resourceId}

  11. 在新的浏览器选项卡中粘贴调用 URL:https://{restapi_id}.execute-api.{region}.amazonaws.com/Prod/visits/{resourceId}

  • 响应正文将是{"message": "Internal server error"}

  • 这是因为我们在查询 DynamoDB 之前在 URLparse_parameters()函数中验证了resource_id,以确保它是一个数字

  1. 在新的浏览器选项卡中粘贴调用 URL:https://{restapi_id}.execute-api.{region}.amazonaws.com/prod/visits/324。因为我们在内部使用了正确的resourceIdEventId,您应该在浏览器选项卡中看到以下代码:
      [{
           "EventCount": 3,
           "EventDay": 20171001,
           "EventId": "324"
      },
      {
           "EventCount": 5,
           "EventDay": 20171002,
           "EventId": "324"
      }]
  1. 在新的浏览器选项卡中粘贴调用 URL:https://{restapi_id}.execute-api.{region}.amazonaws.com/Prod/visits/324?startDate=20171002。因为我们添加了startDate=20171002参数,您应该在浏览器选项卡中看到以下代码:
      [{"EventCount": 5, "EventDay": 20171002, "EventId": "324"}]

这是使用 Lambda 中的query_by_partition_and_sort_key()方法和startDate

现在我们有一个完全可用的无服务器数据 API,能够在 DynamoDB 上运行不同类型的查询。

清理

您需要手动删除资源。我建议您使用 AWS 控制台进行操作。执行以下步骤:

  1. 删除 API 网关:

  2. 登录到控制台console.aws.amazon.com/apigateway/

  3. 在左侧 API 菜单下选择资源

  4. 从操作下拉菜单中选择删除 API

  5. 在确认此操作文本框中键入 API 的名称

  6. 选择删除 API

  7. 删除 DynamoDB 表:

  8. 登录到console.aws.amazon.com/dynamodb/控制台

  9. 在左侧 DynamoDB 菜单上选择表

  10. 选择用户访问

  11. 选择删除表

  12. 选择删除

  13. 删除 Lambda 函数:

  14. 登录到console.aws.amazon.com/lambda/控制台

  15. 在左侧 AWS Lambda 菜单上选择函数

  16. 选择 lambda-dynamo-data-api

  17. 在操作菜单下选择删除函数

  18. 选择删除

  19. 删除 IAM 角色和策略:

  20. 登录到console.aws.amazon.com/iam/控制台

  21. 在 IAM 导航窗格中选择角色

  22. 选择 lambda-dynamo-data-api

  23. 在右上角选择删除角色

  24. 选择是

  25. 在 IAM 导航窗格中选择策略

  26. 在筛选策略下选择客户管理

  27. 选择 dynamo-readonly-user-visits 旁边的单选按钮,并在策略操作菜单下选择删除

  28. 在弹出窗口中选择删除

  29. 选择 lambda-cloud-write 旁边的单选按钮,并在策略操作菜单下选择删除

  30. 在弹出窗口中选择删除

摘要

在本章中,我们讨论了安全性以及其重要性。应用 OWASP 安全设计原则是确保您的无服务器堆栈安全的良好第一步。然后,我们讨论了 IAM 角色,并概述了策略,解释了它们是确保对 AWS 资源受限访问的关键文档。然后,我们概述了一些关于保护您的无服务器微服务安全性概念和原则,特别是关于 Lambda,API Gateway 和 DynamoDB 的安全性。

然后,我们构建了一个可扩展的无服务器微服务,具有 RESTful 数据 API。我们首先创建了一个 DynamoDB 表,然后向其中添加数据,并进行了查询,首先是在 AWS 控制台手动操作,然后使用 Python Boto3 SDK。然后,我们构建了一个简单的 Lambda 来解析请求 URL 参数,查询 DynamoDB,并将记录作为响应主体的一部分返回。然后,我们查看了 Lambda 和 API Gateway 之间的集成设置。然后通过部署 API 将所有内容连接在一起。我们创建了一个完全可扩展的 API,您可以非常轻松地调整以适应自己的用例,并且非常具有成本效益。在不到 30 分钟的时间内,您已经创建了一个具有 API 的高度可扩展的无服务器微服务。API Gateway 和 Lambda 成本是按使用量付费的。对于 DynamoDB,您实际上可以非常轻松地更改读取和写入容量,设置根据负载自动扩展读取和写入容量,或者甚至通过按实际使用量付费的按需容量模式付费,使其完全按 API 使用量和存储的数据付费,避免传统的容量规划或过度配置。

我们在 AWS 控制台中做了很多工作,但在后续章节中,我们将使用 AWS CLI 或使用代码部署流水线来完成大部分工作。但是,使用 AWS 控制台应该让您对在 AWS 中可以做什么以及 Lambda 如何与 DynamoDB 和 API Gateway 集成有很好的理解。当我们使用配置、脚本和代码自动化大部分创建和配置时,这种坚实的基础非常有用。在接下来的章节中,我们将添加更多功能,自动化测试和部署流水线,并实施微服务模式。

在您的组织中,您将开发大量源代码,并且不希望像我们在本章中所做的那样手动部署它。您将首先希望自动测试代码,以确保其按预期工作,然后以可重复的方式部署堆栈。这对于在生产中使用的持续集成或持续交付系统是必需的。

在下一章中,我们将讨论如何使用代码和配置部署您的无服务器微服务,以使该过程更具重复性和可扩展性。

第三章:部署您的无服务器堆栈

在上一章中,我们使用 API Gateway、Lambda 和 DynamoDB 创建了一个完全功能的无服务器数据 API,并使用 IAM 角色进行了测试。然而,大部分的代码和配置都是手动部署的;这个过程容易出错,不可重复,也不可扩展。

在本章中,我们将向您展示如何仅使用代码和配置来部署所有这些基础设施。涵盖的主题如下:

  • 无服务器堆栈构建和部署选项概述

  • 创建配置文件、S3 存储桶、IAM 策略和 IAM 角色资源

  • 使用 API Gateway、Lambda 和 DynamoDB 构建和部署

无服务器堆栈构建和部署选项概述

在本节中,我们将讨论手动配置基础设施、基础设施即代码、使用无服务器应用程序模型构建和部署,以及使用替代选项构建和部署时面临的挑战。

手动配置基础设施

配置基础设施的挑战在于它曾经是一个非常手动的过程。例如,管理员会按照手册中描述的步骤点击用户界面上的项目,运行一系列命令,或者登录服务器并编辑配置文件。随着云计算和开始扩展的 Web 框架的增长,这变得越来越具有挑战性。这可以通过单片架构和它们共享的 Web 服务器或应用服务器来完成。然而,使用微服务架构,使用不同语言开发的不同 Web 服务器和数据库,以及运行的数千个服务需要独立测试、构建和部署。

手动部署服务在成本方面需要付出很多努力,也很难在规模上维护这样的配置。服务的部署变得更加缓慢,也更难从任何错误中恢复,因为您可能需要管理员通过 SSH 远程连接到您的服务器,重新启动机器,或者尝试理解问题所在,并多次更改多台机器的配置。测试和使任何过程可重复也非常困难。无论是使用用户界面还是编辑一个服务器上的配置文件进行的任何配置更改,都不太可重复,也容易出现人为错误或配置错误。例如,在之前的章节中我们使用了 AWS 管理控制台。如果您在任何配置中出现错误,您将不得不返回诊断问题并进行修复,这将非常耗时。

在下一节中,我们将讨论基础设施即代码以及它如何帮助解决手动配置基础设施或部署服务时遇到的问题。

基础设施即代码

基础设施即代码基本上是通过定义文件或代码来管理和配置资源的过程。它提供了一种集中管理配置的方式,涉及实施和版本控制。突然之间,资源管理和配置变得更像是软件开发生命周期中的敏捷过程。所有更改都经过验证、测试,并作为发布过程的一部分进行配置,并使用标准的部署流程。这也提供了将用于在一个区域部署基础设施的配置复制到另一个区域的能力。

例如,假设您使用代码和配置在北弗吉尼亚地区部署基础设施,您可以轻松修改它以使其在爱尔兰地区运行。这使您能够快速扩展基础设施周围的配置,从而导致了术语 DevOps 的发展。这是开发人员在配置方面更加参与,特别是在基础设施周围,而运维团队(或运维团队)在开发过程中更加参与。以下图表显示了基础设施即代码的不同优势:

使用基础设施即代码有许多好处。第一个是成本降低,因为您在简单和重复的任务上花费的精力要少得多。在扩展或部署类似基础设施时,您还可以降低成本。

在构建任何系统时,我通常喜欢构建它以便它可以在两个环境中运行。一旦它在两个环境中运行,它就可以在许多环境中运行。例如,如果您使用命名约定作为每个环境的前缀或变量构建代码,例如dev代表developmentstg代表staging,并在部署时进行替换,那么我们以后可以轻松地为production添加prd前缀。始终强烈建议使用标准命名约定。另一个例子可能是始终将三个字符作为约定或规则,这样您就不会陷入具有多个前缀的情况,例如 prod 或 production,这可能会引入意外错误。在配置文件中,将被替换的环境变量可能看起来像${env}

另一个要点是执行速度;也就是说,您的管理员或 DevOps 团队实际上可以比以前更快地发布基础设施和服务。此外,通过在每个步骤进行跟踪、验证和测试,有助于减少错误和问题的数量。总的来说,这有助于降低风险并提高安全性。由于有了这种可追溯性,您可以了解部署了什么,以及它是否成功,或者它是否导致问题并应该回滚。

使用无服务器应用程序模型(SAM)构建和部署

最近出现的一个工具是由 AWS 维护的 SAM(github.com/awslabs/serverless-application-model)。它允许您构建和部署无服务器堆栈。它提供了一种简化的方式来定义和部署任何无服务器资源或应用程序。在其基础上,它采用了云形成,但使用的源代码行数比使用 AWS 命令行要少。使用 SAM 模板文件的基本概念是它可以是包含所有无服务器配置的 JSON 或 YAML 文件,其吉祥物是松鼠 SAM。

使用备选选项构建和部署

部署 AWS 无服务器堆栈有替代选项。第一个选项是 AWS 命令行界面(CLI)。当您的组织不想为所有内容或部分无服务器堆栈使用云形成堆栈时,AWS CLI 是一个选择。AWS CLI 在功能发布方面通常领先于 SAM。因此,在本书中,我使用一些命令来补充 SAM 中尚未构建的部分。

Serverless Framework,最初称为 JAWS,是使用 Node.js 技术构建的。它在最初发布时领先于时代,但现在随着 AWS SAM 的出现,它是由第三方维护的 AWS 顶层附加层。然而,它确实允许您使用其他云提供商的其他功能,例如 Google 和 Azure,这是一个很棒的功能,但我个人质疑在不同云提供商之间重用函数代码的事件源、安全性和数据形状都是不同的。

Chalice 和 Zappa 是基于 Python 的 AWS 框架,类似于 Python Flask 和 Bottle 微型 Web 框架,但它们又是 AWS 的另一种抽象。您需要等待任何改进通过。

此外,还存在一种风险,即依赖这些框架当 AWS 功能被弃用时。您需要与它们保持同步,或者依赖于这些其他框架的开源贡献者来实际进行更改或直接贡献。如果我必须选择一个,我会选择 SAM,但我也接受一些人更喜欢无服务器。

SAM 需要一个 S3 存储桶来进行包部署,Lambda 需要 IAM 策略和 IAM 角色。接下来让我们来看看这些。

创建配置文件、S3 存储桶、IAM 策略和 IAM 角色资源

我们首先设置一个 S3 存储桶,用于保存 Lambda 部署包的源代码。IAM 策略和角色允许 API Gateway 调用 Lambda,并允许 Lambda 访问 DynamoDB。我们使用 AWS 管理控制台设置它们;在这里,我们将使用 AWS CLI 和 SAM。

本章中使用的代码、shell 脚本和配置文件都可以在./serverless-microservice-data-api/文件夹下找到。

创建 AWS 凭据配置文件

按照以下步骤创建 AWS 凭据配置文件:

  1. 创建名为demo的 AWS 配置文件:
$ aws configure --profile demo
  1. Chapter 1中重新输入与newuser相关的相同的 AWS aws_access_key_idaws_secret_access_key详细信息。

或者,您可以通过复制[default]来复制[default]配置文件,并创建一个[demo]的新条目,如下所示:

 $ vi ~/.aws/credentials
      [default]
      aws_access_key_id =
      AAAAAAAAAAAAAAAAAAAA
      aws_secret_access_key =
      1111111111111111111111111111111111111111

      [demo]
      aws_access_key_id =
      AAAAAAAAAAAAAAAAAAAA
      aws_secret_access_key =
      1111111111111111111111111111111111111111

本书提供的代码需要一个配置文件名称(这里是demo)来使用正确的密钥;如果您使用其他配置文件名称,请在每个项目的 shell 脚本common-variables.sh中更改它。

创建一个 S3 存储桶

要部署 Lambda 源代码,您需要使用现有的 S3 存储桶或创建一个新的——使用以下代码来创建一个:

$ aws s3api create-bucket --bucket <you-bucket-name> --profile demo --create-bucket-configuration LocationConstraint=<your-aws-region> --region <your-aws-region>

确保<your-bucket-name>是可寻址的,它必须遵循 DNS 命名约定。要选择您的 AWS 区域,请参考 AWS 区域和终端点(docs.aws.amazon.com/general/latest/gr/rande.html)。通常,美国用户可以使用us-east-1,欧洲用户可以使用eu-west-1

为您的 AWS 账户设置配置文件

我已经为./bash/下的每个无服务器项目创建了一个名为common-variables.sh的配置文件,该文件创建了 AWS CLI 和 SAM 使用的环境变量。您需要使用您的 AWS 账户详细信息对它们进行修改。这样做是为了为在多个地区支持多个 AWS 账户打下基础。以下是common-variables.sh的示例:

#!/bin/sh
export profile="demo"
export region="<your-aws-region>"
export aws_account_id=$(aws sts get-caller-identity --query 'Account' --profile $profile | tr -d '\"')
# export aws_account_id="<your-aws-accountid>"
export template="lambda-dynamo-data-api"
export bucket="<you-bucket-name>"
export prefix="tmp/sam"

# Lambda settings
export zip_file="lambda-dynamo-data-api.zip"
export files="lambda_return_dynamo_records.py"

让我们试着理解这段代码:

  • 使用您的 AWS 区域(例如us-east-1)更新<your-aws-region>

  • 我正在动态确定aws_account_id,但您也可以像注释中所示那样硬编码它,在这种情况下,请取消注释该行,并将<your-aws-accountid>更新为您的 AWS 账户 ID。如果您不知道它,您可以在 AWS 管理控制台|支持|支持中心屏幕中找到您的账户号码。

  • template是我们将使用的 SAM 模板的名称。

  • bucketprefix定义了部署的 Lambda 包的位置。

更新策略和假定角色文件

您需要更改存储在./IAM文件夹下的 IAM 策略文档中的 AWS aws_account_id(当前设置为000000000000)。此外,当前设置为eu-west-1的区域也必须更改。

要替换您的aws_account_id(假设您的 AWS aws_account_id111111111111),您可以手动执行,也可以运行以下命令:

$ find ./ -type f -exec sed -i '' -e 's/000000000000/111111111111/' {} \;

创建 IAM 角色和策略

我们在 AWS 管理控制台中手动创建了 IAM 策略和角色。现在我们将看看如何使用 AWS CLI 创建这些。

以下是我们在./IAM/目录下创建的 JSON 策略dynamo-readonly-user-visits.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:BatchGetItem",
                "dynamodb:DescribeTable",
                "dynamodb:GetItem",
                "dynamodb:Query",
                "dynamodb:Scan"
            ],
            "Resource": [
                "arn:aws:dynamodb:eu-west-1:000000000000:
                 table/user-visits",
                "arn:aws:dynamodb:eu-west-1:000000000000:
                 table/user-visits-sam"         
            ]
        }
    ]
}

总结一下策略,它表示我们对两个名为user-visits的 DynamoDB 表具有QueryScan访问权限,这些表可以手动创建或在 Python 中创建,以及我们将在本章中使用 SAM 创建的user-visits-sam

创建一个允许 Lambda 函数将日志写入 CloudWatch 日志的策略。创建一个名为lambda-cloud-write.json的文件,内容如下:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogStreams"
    ],
      "Resource": [
        "arn:aws:logs:*:*:*"
    ]
  },
  {
      "Effect": "Allow",
      "Action": [
        "cloudwatch:PutMetricData"
      ],
      "Resource": "*"
    }
 ]
}

创建 IAM 角色时,还需要指定它可以承担的 IAM 角色类型。我们创建了一个名为assume-role-lambda.json的文件,这被称为受信任的实体:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

将上述内容定义为 JSON 代码使我们能够在 AWS 中对安全性和权限进行版本控制。此外,如果有人错误地删除了它们,我们可以在 AWS 中简单地重新创建它们。

我们现在将创建一个名为create-role.sh的 shell 脚本,在./bash文件夹下,以创建一个 Lambda IAM 角色和三个 IAM 策略,并将它们附加到 IAM 角色:

#!/bin/sh
#This Script creates a Lambda role and attaches the policies

#import environment variables
. ./common-variables.sh

#Setup Lambda Role
role_name=lambda-dynamo-data-api
aws iam create-role --role-name ${role_name} \
    --assume-role-policy-document file://../../IAM/assume-role-lambda.json \
    --profile $profile || true

sleep 1
#Add and attach DynamoDB Policy
dynamo_policy=dynamo-readonly-user-visits
aws iam create-policy --policy-name $dynamo_policy \
    --policy-document file://../../IAM/$dynamo_policy.json \
    --profile $profile || true

role_policy_arn="arn:aws:iam::$aws_account_id:policy/$dynamo_policy"
aws iam attach-role-policy \
    --role-name "${role_name}" \
    --policy-arn "${role_policy_arn}"  --profile ${profile} || true

#Add and attach cloudwatch_policy
cloudwatch_policy=lambda-cloud-write
aws iam create-policy --policy-name $cloudwatch_policy \
    --policy-document file://../../IAM/$cloudwatch_policy.json \
    --profile $profile || true

role_policy_arn="arn:aws:iam::$aws_account_id:policy/$cloudwatch_policy"
aws iam attach-role-policy \
    --role-name "${role_name}" \
    --policy-arn "${role_policy_arn}"  --profile ${profile} || true

使用./create-role.sh执行脚本。它将创建一个 IAM 角色和三个 IAM 策略,并将它们附加到 IAM 角色。请注意,此处的代码是有意幂等的,因为策略更改需要谨慎管理,因为它们可能会影响其他人。

请注意,还可以在 SAM 模板中创建 IAM 角色,但是使用 AWS CLI 意味着在删除无服务器堆栈时可以重用角色和策略,而不是删除它们。如果将它们检入 Git 标准命名约定,则可以添加版本控制,并通过集中创建来帮助支持团队。

检查 IAM 角色和策略

AWS CLI 会在创建 IAM 角色和策略时给您反馈,但您也可以在 AWS 管理控制台中检查:

  1. 登录 AWS 管理控制台,并在console.aws.amazon.com/iam/上打开 IAM 控制台。

  2. 在 IAM 导航窗格中,选择角色。

  3. 从角色列表中选择lambda-dynamo-data-api

  4. 在权限选项卡下选择显示更多权限策略。

您应该看到以下三个附加的策略:

使用 API Gateway,Lambda 和 DynamoDB 构建和部署

部署无服务器堆栈涉及三个步骤:

  1. 将 Lambda 构建为 ZIP 包

  2. 使用 SAM 和 CloudFormation 打包您的无服务器堆栈

  3. 使用 SAM 和 CloudFormation 部署您的无服务器堆栈

将 Lambda 构建为 ZIP 包

如果尚未安装 ZIP,请安装 ZIP。对于 Ubuntu/Debian,您可以使用sudo apt-get install zip -y。创建一个名为create-lambda-package.sh的文件,内容如下:

#!/bin/sh
#setup environment variables
. ./common-variables.sh

#Create Lambda package and exclude the tests to reduce package size
(cd ../../lambda_dynamo_read;
mkdir -p ../package/
zip -FSr ../package/"${zip_file}" ${files} -x *tests/*)

这将仅在源代码发生更改时创建 Lambda 代码的 ZIP 文件。这是将部署到 AWS 的内容,并且在需要打包第三方库时,将这些命令分开具有优势。

SAM YAML 模板

我们将使用 SAM 模板创建无服务器堆栈。SAM 使用 YAML 或 JSON,并允许您定义 Lambda 函数和 API Gateway 设置,以及创建 DynamoDB 表。模板如下所示:

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: >-
  This Lambda is invoked by API Gateway and queries DynamoDB.
Parameters:
    AccountId:
        Type: String
Resources:
  lambdadynamodataapi:
    Type: AWS::Serverless::Function
    Properties:
      Handler: lambda_return_dynamo_records.lambda_handler
      Runtime: python3.6
      CodeUri: ../../package/lambda-dynamo-data-api.zip
      FunctionName: lambda-dynamo-data-api-sam
      Description: >-
        This Lambda is invoked by API Gateway and queries DynamoDB.
      MemorySize: 128
      Timeout: 3  
      Role: !Sub 'arn:aws:iam::${AccountId}:
                  role/lambda-dynamo-data-api'
      Environment:
        Variables:
          environment: dev
      Events:
        CatchAll:
          Type: Api
          Properties:
            Path: /visits/{resourceId}
            Method: GET
  DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: user-visits-sam
      SSESpecification:
        SSEEnabled: True
      AttributeDefinitions:
        - AttributeName: EventId
          AttributeType: S
        - AttributeName: EventDay
          AttributeType: N
      KeySchema:
        - AttributeName: EventId
          KeyType: HASH
        - AttributeName: EventDay
          KeyType: RANGE
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1

从上到下,我们首先指定模板类型,描述,并传递一个字符串参数AccountId。然后,我们指定 Lambda 的细节,例如Handler,这是入口点,ZIP 代码的位置,并为函数指定名称和描述。然后,我们选择 128 MB 的 RAM,因为这是一个概念验证,我们不需要更多的内存;我们为超时指定3。之后,即使 Lambda 仍在运行,它也会终止;这限制了成本,并且是合理的,因为我们期望同步响应。然后,我们有 IAM Lambda 执行角色,其中包含${AccountId}参数,该参数在部署无服务器堆栈时传递。

我们看到如何添加将在 Lambda 函数中可用的环境变量。变量是environment: dev

然后我们有 Lambda 函数的触发器或事件源。在这里,我们创建了一个 API Gateway,其中包含/visits/{resourceId}路径的资源,使用GET方法调用一个带有resourceId的 Lambda 函数,该resourceId将是EventId

最后,我们使用 Python 创建了一个 DynamoDB 表,其中EventId的哈希数据类型为stringEventDay的范围数据类型为number。为了降低成本(或免费),我将读取和写入容量设置为1

因此,在一个 SAM YAML 文件中,我们已经配置了 Lambda,具有其 Lambda 集成的 API Gateway,并创建了一个新的 DynamoDB 表。

对于 DynamoDB,我强烈建议在 SAM 创建的资源末尾附加sam,以便知道其来源。我还建议,如果 DynamoDB 表在服务之间共享,最好使用 Boto3 或 AWS CLI 进行创建。这是因为删除一个无服务器堆栈可能意味着该表对所有服务都被删除。

打包和部署您的无服务器堆栈

一旦具有策略的 IAM 角色,具有 Lambda 代码的 ZIP 包和 SAM 模板都创建好了,您只需要运行两个 CloudFormation 命令来打包和部署您的无服务器堆栈。

第一个命令将 Lambda 代码与 SAM 模板打包并推送到 S3:

$ aws cloudformation package --template-file $template.yaml \
    --output-template-file ../../package/$template-output.yaml \
    --s3-bucket $bucket --s3-prefix backend \
    --region $region --profile $profile

Successfully packaged artifacts and wrote output template to file ../../package/lambda-dynamo-data-api-output.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /mnt/c/serverless-microservice-data-api/package/lambda-dynamo-data-api-output.yaml --stack-name <YOUR STACK NAME>

第二个命令将其部署到 AWS:

$ aws cloudformation deploy --template-file ../../package/$template-output.yaml \
    --stack-name $template --capabilities CAPABILITY_IAM \
    --parameter-overrides AccountId=${aws_account_id} \
    --region $region --profile $profile

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - lambda-dynamo-data-api

SAM 中的一个很棒的功能是能够使用参数。在这里,当我们使用--parameter-overrides AccountId=${aws_account_id}部署堆栈时就可以实现。好处是我们可以在多个环境中重用相同的 SAM 模板,例如 AWS 帐户和区域,以及任何其他参数。

您可以通过检查 AWS 管理控制台来验证堆栈是否已正确部署到 AWS:

  1. 登录到 AWS 管理控制台console.aws.amazon.com/cloudformation/

  2. 选择管理和治理| CloudFormation 或在“查找服务”下搜索 CloudFormation。

  3. 在 CloudFormation 窗格中,选择 lambda-dynamo-data-api。

  4. 选择事件。这显示了不同的事件,并且在部署堆栈时非常有用。通常,这将是命名冲突(例如,具有相同名称的 DynamoDB 表存在)或 IAM 相关问题(例如,角色不存在)。

  5. 选择资源。这显示了由此 CloudFormation 堆栈管理的资源:

您还可以直接检查 AWS 管理控制台,以确保 API Gateway,Lambda 函数和 DynamoDB 表已正确创建。

例如,这是我们使用 Python 创建的相同 Lambda,但由 SAM 部署和管理。除非您正在进行概念验证,建议进一步的更改通过配置更改而不是 AWS 管理控制台中的更改进行管理,因为这将破坏基础架构作为代码和自动化:

将所有内容放在一起

在本章中,我们部署了一个完全可工作的无服务器堆栈,无需使用用户界面配置任何设置。这是部署基础设施和代码的推荐方式,因为它更具可重复性,可扩展性,并且不太容易出错。它还允许您在一切都在 Git 中进行版本控制时执行诸如还原配置之类的操作。

./serverless-microservice-data-api/bash文件夹下提供的 shell 脚本:

  • common-variables.sh:其他脚本使用的环境变量

  • create-role.sh:Lambda IAM 角色创建并附加了三个策略

  • lambda-dynamo-data-api.yaml:定义 SAM YAML 模板

  • create-lambda-package.sh:创建 Lambda ZIP 包

  • build-package-deploy-lambda-dynamo-data-api.sh:编排 Lambda ZIP 的构建,打包和部署

以下是build-package-deploy-lambda-dynamo-data-api.sh的内容,当您修改 Lambda 代码或其他 SAM 配置设置时,可以运行它:

#!/usr/bin/env bash

# Variables
. ./common-variables.sh

#Create Zip file of your Lambda code (works on Windows and Linux)
./create-lambda-package.sh

#Package your Serverless Stack using SAM + Cloudformation
aws cloudformation package --template-file $template.yaml \
    --output-template-file ../../package/$template-output.yaml \
    --s3-bucket $bucket --s3-prefix backend \
    --region $region --profile $profile

#Deploy your Serverless Stack using SAM + Cloudformation
aws cloudformation deploy --template-file ../../package/$template-output.yaml \
    --stack-name $template --capabilities CAPABILITY_IAM \
    --parameter-overrides AccountId=${aws_account_id} \
    --region $region --profile $profile

手动测试无服务器微服务

测试步骤如下:

  1. 登录到 AWS 管理控制台,在console.aws.amazon.com/apigateway/打开 API Gateway 控制台。

  2. 在 Amazon API Gateway 导航窗格中,选择 API | lambda-dynamo-data-api | Stages。

  3. Prod/visits/{resourceId}/GET下选择 GET 以获取调用 URL,应该看起来像https://{restapi_id}.execute-api.{region}.amazonaws.com/Prod/visits/{resourceId}

  4. 打开一个新的浏览器选项卡,输入https://{restapi_id}.execute-api.{region}.amazonaws.com/Prod/visits/{resourceId} URL。您将获得{"message":"resource_id not a number"}响应正文。这是因为我们在查询 DynamoDB 之前通过parse_parameters() URL 函数验证了resource_id,以确保它是一个数字。

  5. 打开一个新的浏览器选项卡,输入https://{restapi_id}.execute-api.{region}.amazonaws.com/Prod/visits/324 URL。由于我们使用了正确的resourceId,您应该在浏览器选项卡中看到[ ]。

为什么我们没有得到数据?

好吧,没有数据加载到user-visits-sam DynamoDB 表中,这就是为什么!

运行python3 ./aws_dynamo/dynamo_modify_items.py将一些记录加载到user-visits-sam DynamoDB 表中。

以下是dynamo_modify_items.py的内容:

from boto3 import resource

class DynamoRepository:
    def __init__(self, target_dynamo_table, region='eu-west-1'):
        self.dynamodb = resource(service_name='dynamodb', region_name=region)
        self.target_dynamo_table = target_dynamo_table
        self.table = self.dynamodb.Table(self.target_dynamo_table)

    def update_dynamo_event_counter(self, event_name, 
            event_datetime, event_count=1):
        return self.table.update_item(
            Key={
                'EventId': event_name,
                'EventDay': event_datetime
            },
            ExpressionAttributeValues={":eventCount": event_count},
            UpdateExpression="ADD EventCount :eventCount")

def main():
    table_name = 'user-visits-sam'
    dynamo_repo = DynamoRepository(table_name)
    print(dynamo_repo.update_dynamo_event_counter('324', 20171001))
    print(dynamo_repo.update_dynamo_event_counter('324', 20171001, 2))
    print(dynamo_repo.update_dynamo_event_counter('324', 20171002, 5))

if __name__ == '__main__':
    main()

现在在浏览器中转到相同的端点,您应该会收到以下数据:

[{"EventCount": 3, "EventDay": 20171001, "EventId": "324"}, {"EventCount": 5, "EventDay": 20171002, "EventId": "324"}]

打开一个新的浏览器选项卡,输入https://{restapi_id}.execute-api.{region}.amazonaws.com/Prod/visits/324?startDate=20171002 URL。由于我们添加了startDate=20171002参数,您应该在浏览器选项卡中看到以下内容:

[{"EventCount": 5, "EventDay": 20171002, "EventId": "324"}]

进行代码和配置更改

代码很少保持静态,因为会出现新的需求和业务请求。为了说明对更改的良好支持,假设我们对 Lambda 函数 Python 代码进行了一些更改,现在想要使用 Python 3.7,而不是 Python 3.6。

我们可以通过三个步骤更新代码,配置和堆栈:

  1. 更改lambda_return_dynamo_records.py的 Python 代码,使其符合 Python 3.7。

  2. 更改lambda-dynamo-data-api.yaml的 SAM 模板如下:

      Resources:
        lambdadynamodataapi:
          Type: AWS::Serverless::Function
          Properties:
            Handler: lambda_return_dynamo_records.lambda_handler
            Runtime: python3.7
  1. 运行./build-package-deploy-lambda-dynamo-data-api.sh。这将重新构建 Lambda ZIP 包,因为代码已更改。打包和部署代码和 SAM 配置,然后 CloudFormation 将管理和部署更改。

删除无服务器堆栈

当您不再需要您的无服务器堆栈时,您可以在 AWS 管理控制台的 CloudFormation 下删除它:

  1. 登录到 AWS 管理控制台,在console.aws.amazon.com/cloudformation/打开 CloudFormation 控制台。

  2. 从列表中选择 lambda-dynamo-data-api。

  3. 选择操作,然后选择删除堆栈。

  4. 在提示时选择是,删除。

或者,您可以使用./delete-stack.sh运行以下 shell 脚本:

#!/usr/bin/env bash
. ./common-variables.sh
aws cloudformation delete-stack --stack-name $template --region $region --profile $profile

摘要

现在您对手动部署无服务器堆栈以可重复和一致的方式有了更深入的理解和一些实际经验,使用基础设施即代码原则。您可以根据组织的无服务器微服务需求进行调整。您了解了服务部署选项,并使用 AWS CLI 创建了存储桶、IAM 角色和 IAM 策略,以及使用 AWS SAM 部署了 API Gateway、Lambda 和 DynamoDB。您还看到了如何轻松修改 SAM 模板文件以在整个堆栈中传播更改。本书提供了完整的 Python 源代码、IAM 策略、角色、Linux 和 shell 脚本,因此您可以根据自己的需求进行调整。现在,您可以利用它们,而无需手动使用 AWS 管理控制台 GUI,并且只需要在部署其他无服务器微服务时修改脚本。

现在我们已经向您展示了如何部署堆栈,非常重要的是您知道代码是否按预期运行和执行,特别是随着代码库的增长,并且将在生产环境中使用。我们还没有涵盖自动部署和测试的机制。因此,在下一章中,我们将讨论并介绍您在无服务器微服务上应该使用的不同类型的测试。

第四章:测试您的无服务器微服务

在上一章中,我们使用 API Gateway、Lambda 和 DynamoDB 创建了一个完全功能的无服务器数据 API,并将其部署到了 AWS CLI。我们展示的测试是在 AWS 管理控制台和浏览器中进行的,这对于少量简单代码开发作为概念验证是可以的,但不建议用于开发或生产系统。

对于开发人员来说,首先在本地开发和测试要高效得多,对于持续交付来说,自动化测试至关重要。本章就是关于测试的。

测试可能很容易覆盖整本书,但我们将保持非常实用的方式,并专注于测试您的无服务器代码和我们在第三章中部署的数据 API,部署您的无服务器堆栈。这将包括单元测试、模拟、本地调试、集成测试,在 Docker 容器中本地运行 Lambda 或无服务器 API 的 HTTP 服务器,以及负载测试。

在本章中,我们将涵盖以下主题:

  • 对 Python Lambda 代码进行单元测试

  • 在本地运行和调试您的 AWS Lambda 代码

  • 使用真实测试数据进行集成测试

  • AWS 无服务器应用程序模型SAM)CLI

  • 规模化加载和端到端测试

  • 减少 API 延迟的策略

  • 清理

对 Python Lambda 代码进行单元测试

在本节中,我们将讨论为什么测试很重要,以及我们可以用于测试、单元测试和模拟的示例数据。

为什么测试很重要?

想想在不同国家的大型分布式开发团队中进行的协作和团队合作,想象一下他们想要在同一个源代码仓库上进行协作,并在不同的时间检查代码更改。对于这些团队来说,理解代码并能够在本地测试以查看其工作原理非常重要,他们的更改是否会影响现有服务,以及代码是否仍然按预期工作。

测试对于确保交付或用户体验中有质量很重要。通过进行大量测试,您可以及早发现缺陷并加以修复。例如,如果检测到了重大错误,您可以决定不发布最近的更新,并在发布之前修复问题。

另一个重要的点是可用性。例如,您的客户可能有性能或非功能性要求。例如,想象一个电子商务网站,您可以在其中添加商品,但必须等待整整一分钟才能将其添加到购物篮中。在大多数情况下,这是不可接受的,用户会失去对平台的信任。理想情况下,您将有一个测试流程,以确保延迟仍然很低,网站响应迅速。需要测试的其他示例包括不按预期工作的功能或用户界面缺陷,这些缺陷会阻止用户完成他们想要做的任务。

拥有更短的发布周期很重要。使用自动化,您可以自动且一致地运行一千次测试,而不需要人工手动测试站点的不同部分,手动测试 API,或在任何发布之前严格检查代码。在每次发布到生产环境之前,您都会运行这一千次测试,这样您就会更有信心,一切都按预期工作,如果您在生产中发现了这一千次测试忽略的问题,您可以修复它并为该场景添加一个新的测试。

测试类型

测试可以手动完成,就像我们在 AWS 管理控制台中所做的那样,这容易出错且不可扩展。通常,测试是使用预先编写的测试套件自动化的,并且对于持续集成和持续交付至关重要。

有许多可用的软件测试定义和类型;涵盖它们可能需要整本书。在这里,我们将专注于对我们的无服务器堆栈相关的三种主要类型:

  • 单元测试:对单个软件模块进行低级别测试,通常由开发人员完成,并在测试驱动开发TDD)中使用。这些类型的测试通常执行速度很快。

  • 集成测试:验证集成后所有组合服务是否正常工作。这些通常更昂贵,因为需要运行许多服务。

  • 负载测试:这是一种非功能性测试,用于检查系统在重负载下的性能。有时也被称为性能或压力测试,因为它有助于了解平台的可用性和可靠性。

单元测试 Lambda Python 代码

在 AWS 管理控制台中进行调试并不容易;在本地调试代码并稍后自动化该过程要更有建设性。

我们从之前的章节中知道,Lambda 事件源是 API Gateway 的GET请求。由于我们只关注数据的一个子集,因此完整的 JSON 有效负载也可以用几行 Python 代码模拟出来。

样本测试数据

在这里,我们有一个带有setUp()方法的测试用例,该方法在测试套件开始时运行一次,以及一个tearDown()方法,在测试结束时运行。

以下是在serverless-microservice-data-api/test/test_dynamo_get.py顶部的测试设置和拆卸的内容的子集:

import unittest
import json

class TestIndexGetMethod(unittest.TestCase):
    def setUp(self):
        self.validJsonDataNoStartDate = json.loads('{"httpMethod": 
        "GET","path": "/path/to/resource/324","headers": ' \ 'null} ')
        self.validJsonDataStartDate = 
        json.loads('{"queryStringParameters": {"startDate":      
        "20171013"},' \ '"httpMethod": "GET","path": "/path/to/resource
        /324","headers": ' \ 'null} ')
        self.invalidJsonUserIdData =   
        json.loads('{"queryStringParameters": {"startDate": 
        "20171013"},' \ '"httpMethod": "GET","path": "/path/to/resource
        /324f","headers": ' \ 'null} ')
        self.invalidJsonData = "{ invalid JSON request!} "
    def tearDown(self):
        pass

我创建了四个不同的 JSON Python 字典:

  • self.validJsonDataNoStartDate: 没有StartDate过滤器的有效GET请求

  • self.validJsonDataStartDate: 具有StartDate过滤器的有效GET请求

  • self.invalidJsonUserIdData: 一个无效的UserId,不是一个数字

  • self.invalidJsonData: 无法解析的无效 JSON

单元测试

以下是可以在serverless-microservice-data-api/test/test_dynamo_get.py中找到的单元测试:

    def test_validparameters_parseparameters_pass(self):
        parameters = lambda_query_dynamo.HttpUtils.parse_parameters(
                     self.validJsonDataStartDate)
        assert parameters['parsedParams']['startDate'] == u'20171013'
        assert parameters['parsedParams']['resource_id'] == u'324'     

    def test_emptybody_parsebody_nonebody(self):
        body = lambda_query_dynamo.HttpUtils.parse_body(
               self.validJsonDataStartDate)         
        assert body['body'] is None

    def test_invalidjson_getrecord_notfound404(self):
        result = lambda_query_dynamo.Controller.get_dynamodb_records(
                 self.invalidJsonData)
        assert result['statusCode'] == '404'

    def test_invaliduserid_getrecord_invalididerror(self):            
        result = lambda_query_dynamo.Controller.get_dynamodb_records(
                 self.invalidJsonUserIdData)
        assert result['statusCode'] == '404'
        assert json.loads(result['body'])['message'] == 
             "resource_id not a number" 

我使用了test的前缀,这样 Python 测试套件可以自动检测它们作为单元测试,并且我使用了三元单元测试命名约定来命名测试方法:方法名、测试状态和预期行为。测试方法如下:

  • test_validparameters_parseparameters_pass(): 检查参数是否被正确解析。

  • test_emptybody_parsebody_nonebody(): 在GET方法中我们没有使用 body,所以我们希望确保如果没有提供 body,它仍然可以正常工作。

  • test_invalidjson_getrecord_notfound404(): 检查 Lambda 对无效的 JSON 有效负载的反应。

  • test_invaliduserid_getrecord_invalididerror(): 检查 Lambda 对无效的非数字userId的反应。

前面的内容并不查询 DynamoDB 记录。如果我们想要这样做,我们应该让 DynamoDB 运行起来,使用新的 DynamoDB 本地(docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html),或者我们可以模拟 DynamoDB 调用,这是我们接下来要看的。

模拟

有一个名为 Moto 的 Python AWS 模拟框架(docs.getmoto.org/en/latest/),但我更喜欢使用一个名为mock的通用框架,它在 Python 社区得到了更广泛的支持,并且从 Python 3.3 开始已经包含在 Python 标准库中。

以下模拟代码可以在serverless-microservice-data-api/test/test_dynamo_get.py底部找到:

from unittest import mock

     mock.patch.object(lambda_query_dynamo.DynamoRepository,
                      "query_by_partition_key",
                       return_value=['item'])
     def test_validid_checkstatus_status200(self, 
         mock_query_by_partition_key):
        result = lambda_query_dynamo.Controller.get_dynamodb_records(
                 self.validJsonDataNoStartDate)
        assert result['statusCode'] == '200'

    @mock.patch.object(lambda_query_dynamo.DynamoRepository,
                       "query_by_partition_key",
                        return_value=['item'])
     def test_validid_getrecord_validparamcall(self, 
         mock_query_by_partition_key):         
lambda_query_dynamo.Controller.get_dynamodb_records(
self.validJsonDataNoStartDate)         mock_query_by_partition_key.assert_called_with(
     partition_key='EventId',                                                                      
     partition_value=u'324')

    @mock.patch.object(lambda_query_dynamo.DynamoRepository,
                       "query_by_partition_and_sort_key",
                        return_value=['item'])
    def test_validid_getrecorddate_validparamcall(self, 
        mock_query_by_partition_and_sort_key):
           lambda_query_dynamo.Controller.get_dynamodb_records(
               self.validJsonDataStartDate)
          mock_query_by_partition_and_sort_key.assert_called_with(partition_key='   
    EventId',                                                                      
    partition_value=u'324',                                                                 
    sort_key='EventDay',                                                                 
    sort_value=20171013)

从这段代码中得出的关键观察结果如下:

  • @mock.patch.object()是一个装饰器,用于对我们从DynamoRepository()类中模拟的query_by_partition_key()query_by_partition_and_sort_key()方法。

  • test_validid_checkstatus_status200(): 我们模拟对query_by_partition_key()的调用。如果查询有效,我们会得到一个'200'状态码。

  • test_validid_getrecords_validparamcall(): 我们模拟对query_by_partition_key()的调用,并检查该方法是否使用了正确的参数进行调用。请注意,我们不需要检查较低级别的boto3 self.db_table.query()方法是否有效。

  • test_validid_getrecordsdate_validparamcall(): 我们模拟对query_by_partition_and_sort_key()的调用,并检查该方法是否使用正确的参数进行了调用。

您不是在这里测试现有的第三方库或 Boto3,而是测试您的代码和与它们的集成。模拟允许您用模拟对象替换测试中的代码部分,并对方法或属性进行断言。

运行单元测试

现在我们有了所有的测试套件,而不是在 IDE(如 PyCharm)中运行它们,您可以使用以下 bash 命令从根文件夹运行测试:

$ python3 -m unittest discover test 

unittest会自动检测所有测试文件必须是项目顶层目录可导入的模块或包。在这里,我们只想从以test_为前缀的测试文件夹中运行测试。

我在serverless-microservice-data-api/bash/apigateway-lambda-dynamodb/unit-test-lambda.sh下创建了一个 shell 脚本:

#!/bin/sh (cd ../..; python3 -m unittest discover test) 

代码覆盖率

我们不会深入讨论,但代码覆盖率是软件工程中使用的另一个重要度量标准。代码覆盖率衡量了测试套件覆盖的代码程度。主要思想是,覆盖率百分比越高,测试覆盖的代码就越多,因此创建未检测到的错误的可能性就越小,服务应该按预期运行。这些报告可以帮助开发人员提出额外的测试或场景,以增加覆盖率百分比。

与测试覆盖率相关的 Python 包包括coveragenose和较新的nose2,它们可以提供覆盖率报告。例如,您可以运行以下命令,使用nosenose2获取 Lambda 代码的测试覆盖分析报告:

$ nosetests test/test_dynamo_get.py --with-coverage --cover-package lambda_dynamo_read -v
$ nose2 --with-coverage 

当我们开始编写自己的测试时,我们可以选择使用一组额外的工具来进行测试。这些工具被称为代码覆盖工具。Codecov 和 Coveralls 就是这样的工具的例子。当我们想要分析通过 GitHub 等托管服务编写的代码时,这些工具非常有用,因为它们提供了完整的分析,以确定哪些行已经进行了测试。

在本地运行和调试 AWS Lambda 代码

有时,您希望使用本地 Lambda 模拟 API Gateway 负载,针对 AWS 中托管的真实远程 DynamoDB 进行调试。这样可以使用真实数据进行调试和构建单元测试。此外,我们将看到这些稍后可以用于集成测试。

批量加载数据到 DynamoDB

我们将首先讨论如何从名为sample_data/dynamodb-sample-data.txt逗号分隔值CSV)文件中批量加载数据到 DynamoDB。与为每个项目插入单个语句不同,这是一个更高效的过程,因为数据文件与 Python 代码是解耦的。

EventId,EventDay,EventCount
324,20171010,2
324,20171012,10
324,20171013,10
324,20171014,6
324,20171016,6
324,20171017,2
300,20171011,1
300,20171013,3
300,20171014,30 

添加另一个名为update_dynamo_event_counter()的方法,该方法使用DynamoRepository类更新 DynamoDB 记录。

以下是serverless-microservice-data-api/aws_dynamo/dynamo_insert_items_from_file.py Python 脚本的内容:

from boto3 import resource

class DynamoRepository:
    def __init__(self, target_dynamo_table, region='eu-west-1'):
        self.dynamodb = resource(service_name='dynamodb', region_name=region)
        self.target_dynamo_table = target_dynamo_table
        self.table = self.dynamodb.Table(self.target_dynamo_table)     

    def update_dynamo_event_counter(self, event_name, 
        event_datetime, event_count=1):
        response = self.table.update_item(
            Key={
                'EventId': str(event_name),
                'EventDay': int(event_datetime)
            },
            ExpressionAttributeValues={":eventCount": 
                int(event_count)},
            UpdateExpression="ADD EventCount :eventCount")
        return response 

在这里,我们有一个DynamoRepository类,在__init__()中实例化了与 DynamoDB 的连接,并且有一个update_dynamo_event_counter()方法,如果记录存在则更新 DynamoDB 记录,如果不存在则使用传入的参数添加新记录。这是一个原子操作。

以下是serverless-microservice-data-api/aws_dynamo/dynamo_insert_items_from_file.py Python 脚本的后半部分:

 import csv
table_name = 'user-visits-sam'
input_data_path = '../sample_data/dynamodb-sample-data.txt'
dynamo_repo = DynamoRepository(table_name)
with open(input_data_path, 'r') as sample_file:
    csv_reader = csv.DictReader(sample_file)
    for row in csv_reader:
        response = dynamo_repo.update_dynamo_event_counter(row['EventId'],                                                            row['EventDay'],                                                            row['EventCount'])
        print(response) 

这段 Python 代码打开 CSV 文件,提取标题行,并解析每一行,同时将其写入名为user-visits-sam的 DynamoDB 表中。

现在我们已经将一些数据行加载到 DynamoDB 表中,我们将通过调试本地 Lambda 函数来查询表。

在本地运行 Lambda

这是一个完整的 API 网关请求示例,serverless-microservice-data-api/sample_data/request-api-gateway-valid-date.json,代理 Lambda 函数将作为事件接收。这些可以通过打印 Lambda 作为事件源传递给 CloudWatch 日志的真实 API 网关 JSON 事件来生成:

{
  "body": "{\"test\":\"body\"}",
  "resource": "/{proxy+}",
  "requestContext": {
    "resourceId": "123456",
    "apiId": "1234567890",
    "resourcePath": "/{proxy+}",
    "httpMethod": "GET",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
    "accountId": "123456789012",
    "identity": {
      "apiKey": null,
      "userArn": null,
      "cognitoAuthenticationType": null,
      "caller": null,
      "userAgent": "Custom User Agent String",
      "user": null,
      "cognitoIdentityPoolId": null,
      "cognitoIdentityId": null,
      "cognitoAuthenticationProvider": null,
      "sourceIp": "127.0.0.1",
      "accountId": null
    },
    "stage": "prod"
  },
  "queryStringParameters": {
    "foo": "bar"
  },
  "headers": {
    "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net 
            (CloudFront)",
    "Accept-Language": "en-US,en;q=0.8",
    "CloudFront-Is-Desktop-Viewer": "true",
    "CloudFront-Is-SmartTV-Viewer": "false",
    "CloudFront-Is-Mobile-Viewer": "false", 
    "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
    "CloudFront-Viewer-Country": "US",
    "Accept": "text/html,application/xhtml+xml,application/xml;
               q=0.9,image/webp,*/*;q=0.8",
    "Upgrade-Insecure-Requests": "1",
    "X-Forwarded-Port": "443",
    "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
    "X-Forwarded-Proto": "https",
    "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-
                    9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
    "CloudFront-Is-Tablet-Viewer": "false",
    "Cache-Control": "max-age=0",
    "User-Agent": "Custom User Agent String",     
    "CloudFront-Forwarded-Proto": "https",
    "Accept-Encoding": "gzip, deflate, sdch"
  },
  "pathParameters":{
    "proxy": "path/to/resource"
  },
  "httpMethod": "GET",
  "stageVariables": {
    "baz": "qux"
  },
  "path": "/path/to/resource/324"
} 

与依赖于另一个第三方框架进行本地调试(例如 SAM CLI)不同,您可以通过使用 JSON Dict事件直接调用 Lambda 函数来直接调试 Lambda 函数。这意味着您无需任何额外的库来运行,而且它是本机 Python。

serverless-microservice-data-api/test/run_local_api_gateway_lambda_dynamo.py的内容是使用 AWS 中的服务(例如 DynamoDB)本地调试 Lambda 函数的示例。

import json

from lambda_dynamo_read import lambda_return_dynamo_records as lambda_query_dynamo

with open('../sample_data/request-api-gateway-valid-date.json', 'r') as sample_file:
     event = json.loads(sample_file.read())
print("lambda_query_dynamo\nUsing data: %s" % event)
print(sample_file.name.split('/')[-1]) response = lambda_query_dynamo.lambda_handler(event, None)
print('Response: %s\n' % json.dumps(response)) 

我们打开样本GET文件,将 JSON 解析为Dict,然后将其作为参数传递给lambda_query_dynamo.lambda_handler()。由于我们没有模拟 DynamoDB,它将查询table_name = 'user-visits-sam'Lambda 函数中指定的表。然后它将捕获输出响应,可能如下所示:

Response: {"statusCode": "200", "body": "[{\"EventCount\": 3, \"EventDay\": 20171001, \"EventId\": \"324\"}, {\"EventCount\": 5, \"EventDay\": 20171002, \"EventId\": \"324\"}, {\"EventCount\": 4, \"EventDay\": 20171010, \"EventId\": \"324\"}, {\"EventCount\": 20, \"EventDay\": 20171012, \"EventId\": \"324\"}, {\"EventCount\": 10, \"EventDay\": 20171013, \"EventId\": \"324\"}, {\"EventCount\": 6, \"EventDay\": 20171014, \"EventId\": \"324\"}, {\"EventCount\": 6, \"EventDay\": 20171016, \"EventId\": \"324\"}, {\"EventCount\": 2, \"EventDay\": 20171017, \"EventId\": \"324\"}]", "headers": {"Content-Type": "application/json", "Access-Control-Allow-Origin": "*"}} 

正文与我们在第三章中在浏览器中看到的内容相同,部署您的无服务器堆栈。因此,您可以直接使用真实数据调试不同的集成场景,并在使用真实数据的 Lambda 代码中构建更完整的测试套件。

使用真实测试数据进行集成测试

现在我们了解了真实测试数据,我们将看看如何测试已部署的 Lambda 函数。首先,您需要安装和设置 AWS CLI,并按照第一章末尾显示的方式配置 AWS 凭据:

$ sudo pip3 sudo install awscli 
$ aws configure 

我们将重新部署在第三章中部署的无服务器微服务堆栈,部署您的无服务器堆栈,以便我们可以测试它。使用以下命令:

$ cd ./serverless-microservice-data-api/bash/apigateway-lambda-dynamodb
$ ./build-package-deploy-lambda-dynamo-data-api.sh

这将重新构建 Lambda ZIP 包作为代码(如果有任何更改)。然后它将打包和部署代码和 SAM 配置。最后,它将创建 API 网关、Lambda 函数和 DynamoDB 表。

对于测试,我们将使用 AWS CLI,它可以调用所有 AWS 托管的服务。在这里,我们对lambda (docs.aws.amazon.com/cli/latest/reference/lambda/index.html)和apigateway (docs.aws.amazon.com/cli/latest/reference/apigateway/index.html)服务感兴趣。

测试 Lambda 是否已正确部署

测试部署的 Lambda,您可以运行以下命令:

$ aws lambda invoke --invocation-type Event \
 --function-name lambda-dynamo-data-api-sam  --region eu-west-1 \
 --payload file://../../sample_data/request-api-gateway-get-valid.json \ outputfile.tmp 

为了自动化,我们可以将以下代码放入一个 shell 脚本,serverless-microservice-data-api/bash/apigateway-lambda-dynamodb/invoke-lambda.sh

#!/bin/sh
. ./common-variables.sh
rm outputfile.tmp
status_code=$(aws lambda invoke --invocation-type RequestResponse \
    --function-name ${template}-sam --region ${region} \
    --payload file://../../sample_data/request-api-gateway-get-valid.json \
    outputfile.tmp --profile ${profile})
echo "$status_code"
if echo "$status_code" | grep -q "200";
then
    cat outputfile.tmp
    if grep -q error outputfile.tmp;
    then
        echo "\nerror in response"
        exit 1
    else
        echo "\npass"
        exit 0
    fi
else
    echo "\nerror status not 200"
    exit 1
fi 

我们调用 Lambda,但也使用grep命令检查outputfile.tmp文件中的响应。如果检测到错误,则返回退出代码1,否则返回0。这允许您在其他工具或 CI/CD 步骤中链接逻辑。

测试 API 网关是否已正确部署

我们还希望在部署后能够测试无服务器微服务 API 是否正常工作。我使用 Python 和 bash 混合使用,以使其更容易。

首先使用名为serverless-microservice-data-api/bash/apigateway-lambda-dynamodb/get_apigateway_endpoint.py的 Python 脚本查询 AWS API Gateway 以获取完整的端点,并在成功时返回代码0

import argparse
import logging

import boto3
logging.getLogger('botocore').setLevel(logging.CRITICAL)

logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(levelname)s %(name)-15s: %(lineno)d %(message)s',
                    level=logging.INFO) logger.setLevel(logging.INFO) 

def get_apigateway_names(endpoint_name):
    client = boto3.client(service_name='apigateway', 
                          region_name='eu-west-1')
    apis = client.get_rest_apis()
    for api in apis['items']:
        if api['name'] == endpoint_name:
            api_id = api['id']
            region = 'eu-west-1'
            stage = 'Prod'
            resource = 'visits/324'
            #return F"https://{api_id}.execute-api.
             {region}.amazonaws.com/{stage}/{resource}"
            return "https://%s.execute-api.%s.amazonaws.com/%s/%s" 
                % (api_id, region, stage, resource)
    return None

def main():
    endpoint_name = "lambda-dynamo-xray"

    parser = argparse.ArgumentParser()
    parser.add_argument("-e", "--endpointname", type=str, 
        required=False, help="Path to the endpoint_name")
    args = parser.parse_args()

    if (args.endpointname is not None): endpoint_name = 
        args.endpointname

    apigateway_endpoint = get_apigateway_names(endpoint_name)
    if apigateway_endpoint is not None:
        print(apigateway_endpoint)
        return 0
    else:
        return 1

if __name__ == '__main__':
    main()

然后我们使用一个 shell 脚本来调用 Python 脚本。Python 脚本返回 API 端点,该端点在 curl 中与样本GET请求一起使用。然后我们查看是否获得有效的状态码。

这是serverless-microservice-data-api/bash/apigateway-lambda-dynamodb/curl-api-gateway.sh的完整脚本:

. ./common-variables.sh
endpoint="$(python get_apigateway_endpoint.py -e ${template})"
echo ${endpoint}
status_code=$(curl -i -H \"Accept: application/json\" -H \"Content-Type: application/json\" -X GET ${endpoint})
echo "$status_code"
if echo "$status_code" | grep -q "HTTP/1.1 200 OK";
then
    echo "pass"
    exit 0
else
    exit 1
fi 

以这种方式设置这些脚本使我们能够轻松地自动化这些集成测试。

函数即服务FaaS)仍然是一个相对较新的领域。关于应该使用哪种类型的集成测试仍然有很多讨论。有一种观点是,我们应该在不同的 AWS 账户中进行全套测试,特别是那些会写入或更新数据存储的测试,比如POSTPUT请求。

如果您想这样做,我已经包括了--profileaws_account_id。此外,使用 API Gateway,您可以使用一系列已经存在的围绕 HTTP 端点的测试套件,但是测试 Lambdas 与其他 AWS 服务的集成,比如在 S3 中创建触发 Lambda 的对象,需要更多的工作和思考。在我看来,无服务器集成测试仍然不够成熟,但我已经展示了如何通过使用 AWS CLI 直接调用 Lambda 函数以及使用 JSON 事件源负载调用 API Gateway 端点来实现它们。

接下来,我们将看看 SAM CLI 如何用于本地测试。

AWS 无服务器应用程序模型 CLI

在本节中,我们将通过完全可用的示例演示 SAM Local 的不同功能。对于本地测试,您可以像我展示的那样使用 Python 和 bash,也可以使用 SAM CLI (github.com/awslabs/aws-sam-cli),在撰写本文时仍处于测试阶段。它使用 Docker,并且基于开源docker-lambda (github.com/lambci/docker-lambda) Docker 镜像。如果您使用的是 Windows 10 Home,我建议您升级到 Pro 或 Enterprise,因为在 Home 版上更难使 Docker 工作。还有一些硬件要求,比如虚拟化,需要注意。我们需要执行以下步骤:

  1. 安装 AWS CLI (docs.aws.amazon.com/cli/latest/userguide/installing.html)。

  2. 安装 Docker CE (docs.docker.com/install/)。

  3. 安装 AWS SAM CLI (docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)。

  4. 对于 Linux,您可以运行以下命令:

$sudo pip install --user aws-sam-cli 
  1. 对于 Windows,您可以使用 MSI 安装 AWS SAM CLI。

  2. 创建一个新的 SAM Python 3.6 项目,sam-app,并docker pull镜像(这应该会自动发生,但我需要执行pull才能使其工作):

$ sam init --runtime python3.6
$ docker pull lambci/lambda-base:build
$ docker pull lambci/lambda:python3.6 
  1. 调用以下函数:
$ cd sam-app
$ sam local invoke "HelloWorldFunction" -e event.json --region eu-west-1 

您将获得以下内容:

Duration: 8 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 19 MB
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"} 

这可以用于添加自动化测试。

  1. 启动本地 Lambda 端点:
$ sam local start-lambda --region eu-west-1
# in new shell window
$ aws lambda invoke --function-name "HelloWorldFunction" \
 --endpoint-url "http://127.0.0.1:3001" --no-verify-ssl out.txt 

这将启动一个模拟 AWS Lambda 的 Docker 容器,并在本地启动一个 HTTP 服务器,您可以使用它来自动化测试 AWS CLI 或 Boto3 中的 Lambda 函数。

  1. 启动 API 并使用以下进行测试:
$ sam local start-api --region eu-west-1
# in new shell window
$ curl -i -H \"Accept: application/json\" -H \"Content-Type: application/json\" -X GET http://127.0.0.1:3000/hello 

这将在本地启动一个带有 HTTP 服务器的 Docker 容器,您可以使用它来自动化测试与curl、Postman 或您的 Web 浏览器一起使用的 API。

  1. 生成示例事件的一种方法是从 Lambda 中打印出事件,并从 CloudWatch 日志中复制它(这是我的首选)。另一种方法是使用sam local,它可以生成一些示例事件。例如,您可以运行以下命令:
$ sam local generate-event apigateway aws-proxy 

就我个人而言,我并没有广泛使用 SAM CLI,因为它非常新,需要安装 Docker,并且仍处于测试阶段。但它看起来很有前途,作为测试无服务器堆栈的另一个工具,它很有用,因为它可以在 Docker 容器中模拟 Lambda 并公开端点,我期望未来会添加更多功能。

也许不太有用的是,它还将一些现有命令的无服务器打包和部署命令包装为 CloudFormation 命令的别名。我认为这样做是为了将它们都放在一个地方。

  1. 以下是 SAM CLI packagedeploy命令的示例:
$ sam package \
 --template-file $template.yaml \
 --output-template-file ../../package/$template-output.yaml \
 --s3-bucket
$bucket $ sam deploy \
 --template-file ../../package/$template-output.yaml \
 --stack-name $template \
 --capabilities CAPABILITY_IAM

使用 SAM 的 CloudFormation 进行packagedeploy命令:

$ aws cloudformation package \
 --template-file $template.yaml \
 --output-template-file ../../package/$template-output.yaml \
 --s3-bucket $bucket \
 --s3-prefix $prefix
$ aws cloudformation deploy \
 --template-file ../../package/$template-output.yaml \
 --stack-name $template \
 --capabilities CAPABILITY_IAM

加载和规模化的端到端测试

接下来,我们将看一下 Locust,这是一个用于性能和负载测试的 Python 工具。然后我们将讨论减少 API 延迟和改善 API 响应时间的策略,使用 Locust 将向我们展示性能改进。

对您的无服务器微服务进行负载测试

首先,您需要运行一个带有./build-package-deploy-lambda-dynamo-data-api.sh的无服务器微服务堆栈,并使用python3 dynamo_insert_items_from_file.py Python 脚本将数据加载到 DynamoDB 表中。

然后安装 Locust,如果它尚未与requirements.txt中的其他软件包一起安装:

$ sudo pip3 install locustio 

Locust (docs.locust.io)是一个易于使用的负载测试工具,具有 Web 指标和监控界面。它允许您使用 Python 代码定义用户行为,并可用于在多台机器上模拟数百万用户。

要使用 Locust,您首先需要创建一个 Locust Python 文件,在其中定义 Locust 任务。HttpLocust类添加了一个客户端属性,用于发出 HTTP 请求。TaskSet类定义了 Locust 用户将执行的一组任务。@task装饰器声明了TaskSet的任务:

import random
from locust import HttpLocust, TaskSet, task

paths = ["/Prod/visits/324?startDate=20171014",
         "/Prod/visits/324",
         "/Prod/visits/320"]

class SimpleLocustTest(TaskSet):

    @task
    def get_something(self):
        index = random.randint(0, len(paths) - 1)
        self.client.get(paths[index])

class LocustTests(HttpLocust):
    task_set = SimpleLocustTest

要测试GET方法与不同的资源和参数,我们从路径列表中随机选择三个不同的路径,其中一个 ID 在 DynamoDB 中不存在。主要思想是,如果我们已经将相应的行从文件加载到 DynamoDB 中,我们可以轻松地扩展到模拟数百万个不同的查询。Locust 支持更复杂的行为,包括处理响应、模拟用户登录、排序和事件挂钩,但这个脚本是一个很好的开始。

要运行 Locust,我们需要获取 API Gateway ID,看起来像abcdefgh12,以创建用于负载测试的完整主机名。在这里,我编写了一个名为serverless-microservice-data-api/bash/apigateway-lambda-dynamodbget_apigateway_id.py的 Python 脚本,可以根据 API 名称执行此操作:

import argparse
import logging

import boto3
logging.getLogger('botocore').setLevel(logging.CRITICAL)

logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(levelname)s %(name)-15s: %(lineno)d %(message)s',
                    level=logging.INFO)
logger.setLevel(logging.INFO)

def get_apigateway_id(endpoint_name):
    client = boto3.client(service_name='apigateway', 
             region_name='eu-west-1')
    apis = client.get_rest_apis()
    for api in apis['items']:
        if api['name'] == endpoint_name:
            return api['id']
    return None

def main():
    endpoint_name = "lambda-dynamo-xray"

    parser = argparse.ArgumentParser()
    parser.add_argument("-e", "--endpointname", type=str, 
                        required=False, help="Path to the endpoint_id")
    args = parser.parse_args()

    if (args.endpointname is not None): endpoint_name = args.endpointname

    apigateway_id = get_apigateway_id(endpoint_name)
    if apigateway_id is not None:
        print(apigateway_id)
        return 0
    else:
        return 1

if __name__ == '__main__':
    main() 

运行以下命令启动 Locust:

$ . ./common-variables.sh
$ apiid="$(python3 get_apigateway_id.py -e ${template})"
$ locust -f ../../test/locust_test_api.py --host=https://${apiid}.execute-api.${region}.amazonaws.com 

或者,我还有这个locust运行命令,可以作为一个 shell 脚本运行,在test文件夹serverless-microservice-data-api/bash/apigateway-lambda-dynamodb/run_locus.sh下:

#!/bin/sh
. ./common-variables.sh
apiid="$(python3 get_apigateway_id.py -e ${template})"
locust -f ../../test/locust_test_api.py --host=https://${apiid}.execute-api.${region}.amazonaws.com 

您现在应该在终端中看到 Locust 启动并执行以下步骤:

  1. 在 Web 浏览器中导航到http://localhost:8089/,以访问 Locust Web 监控和测试界面。

  2. 在开始新的 Locust 群中,输入以下内容:

  • 模拟的用户数量10

  • 5用于 Hatch 速率(每秒生成的用户)

  1. 在统计选项卡上让工具运行几分钟。

在统计选项卡中,您将得到类似以下的内容:

在图表选项卡上,您应该会得到类似以下的内容:

在响应时间(毫秒)图表中,橙色线代表 95^(th)百分位数,绿色代表中位数响应时间。

以下是有关前面图表的一些观察:

  • 最大请求时间为 2,172 毫秒,约为 2.1 秒,这非常慢——这与所谓的冷启动有关,这是首次启动 Lambda 的较慢方式。

  • 大约一分钟后,失败的次数也会增加——这是因为 DynamoDB 在开始限制读取请求之前允许一些突发读取。如果您登录到 AWS 管理控制台并查看 DynamoDB 表指标,您将看到正在发生这种情况:

减少 API 延迟的策略

有许多减少延迟的策略。我们将看两种,这两种都在 SAM 模板中设置:

  • 增加 Lambda RAM 大小:目前设置为最小的 128 MB

  • 增加 DynamoDB 读取容量:目前设置为最小值 1 个单位

我真的很喜欢 DynamoDB 的一点是,您可以更改每个表的容量,并且可以独立更改写入容量和读取容量。这对于我来说在读取密集型用例中非常有趣和划算,我可以将读取容量设置得比写入容量更高。甚至还有选项可以根据读/写利用率自动调整表的容量,或者完全基于需求,按照每次读/写请求付费。

我们将从 1 增加 DynamoDB 表的读取容量到 500 个读取单位(保持写入容量为 1 个单位)。费用原本是每月$0.66,但现在将增加到每月$55.24。

编辑lambda-dynamo-data-api.yaml SAM YAML 模板文件,并将ReadCapacityUnits1增加到500

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: >-
  This Lambda is invoked by API Gateway and queries DynamoDB.
Parameters:
    AccountId:
        Type: String

Resources:
  lambdadynamodataapi:
    Type: AWS::Serverless::Function
    Properties:
      Handler: lambda_return_dynamo_records.lambda_handler
      Runtime: python3.6
      CodeUri: ../../package/lambda-dynamo-data-api.zip
      FunctionName: lambda-dynamo-data-api-sam
      Description: >-
        This Lambda is invoked by API Gateway and queries DynamoDB.
      MemorySize: 128
      Timeout: 3
      Role: !Sub 'arn:aws:iam::${AccountId}:role/
                  lambda-dynamo-data-api'
      Environment:
        Variables:
          environment: dev
      Events:
        CatchAll:
          Type: Api
          Properties:
            Path: /visits/{resourceId}
           Method: GET
  DynamoDBTable:
    Type: AWS::DynamoDB::Table 
    Properties:
      TableName: user-visits-sam
      SSESpecification:
        SSEEnabled: True
      AttributeDefinitions:
        - AttributeName: EventId
          AttributeType: S
        - AttributeName: EventDay
          AttributeType: N
      KeySchema:
        - AttributeName: EventId
          KeyType: HASH
        - AttributeName: EventDay
          KeyType: RANGE
      ProvisionedThroughput:
        ReadCapacityUnits: 500
        WriteCapacityUnits: 1

运行./build-package-deploy-lambda-dynamo-data-api.sh来部署带有 DynamoDB 表更改的无服务器堆栈。

现在以 5 的孵化速率再次使用 10 个用户运行 Locust:

在图表选项卡上,您应该会得到类似以下的结果:

以下是关于前述图表的一些观察:

  • 没有故障

  • 平均响应时间为 64 毫秒,这非常好

我们得到这些结果是因为增加了 DynamoDB 表的读取容量,也就是说,请求不再被限制。

现在增加 Lambda 函数中可用的 RAM:

  1. 通过将 MemorySize: 128 更改为 MemorySize: 1536 来修改lambda-dynamo-data-api.yaml SAM YAML 文件。

  2. 运行./build-package-deploy-lambda-dynamo-data-api.sh来部署带有 Lambda RAM 更改的无服务器堆栈。

以下是我们对前述更改所做的一些观察:

  • 没有故障

  • 平均响应时间为 60 毫秒,这稍微好一些,特别是考虑到这是 API Gateway 到 Lambda 到 DynamoDB 再返回的一个往返。

使用 10 的孵化速率和 100 个用户,我们得到以下结果:

  • 没有故障

  • 平均响应时间为 66 毫秒,负载测试开始时最大为 1,057 毫秒

使用 50 的孵化速率和 250 个用户,我们得到以下结果:

  • 没有故障

  • 平均响应时间为 81 毫秒,负载测试开始时最大为 1,153 毫秒。

您也可以测试更高数量的并发用户,比如 1,000 个,即使响应时间会因其他瓶颈而大大增加,但系统仍然可以正常工作。如果您想进一步扩展规模,我建议您考虑不同的架构。仅仅通过在配置文件中更改几个参数,就能如此轻松地扩展无服务器微服务,仍然令人印象深刻!

这让您对如何减少 API 的延迟有了很好的了解。

清理

使用./delete-stack.sh运行以下 shell 脚本来删除无服务器堆栈:

#!/usr/bin/env bash
. ./common-variables.sh
aws cloudformation delete-stack --stack-name $template --region $region --profile $profile 

摘要

在本章中,我们探讨了许多类型的测试,包括使用模拟进行单元测试,使用 Lambda 和 API Gateway 进行集成测试,本地调试 Lambda,提供本地端点以及负载测试。这是我们将在本书的其余部分继续构建的内容。

在下一章中,我们将探讨无服务器的分布式数据管理模式和架构,您可以在组织中应用这些模式。

第五章:保护您的微服务

在本章中,我们将简要概述 AWS 中的安全性,以确保您的无服务器微服务是安全的。在创建我们的第一个微服务之前,我们首先需要了解 AWS 安全模型。我们将讨论一些重要的术语和整体 AWS 安全模型。然后,我们将讨论 IAM,用于访问任何 AWS 资源。最后,我们将讨论如何保护您的无服务器微服务。

在本章中,我们将涵盖以下主题:

  • AWS 安全概述

  • AWS 身份和访问管理(IAM)概述

  • 保护您的无服务器微服务

AWS 安全概述

在本节中,我们将概述 AWS 中的安全性。我们将看看为什么安全很重要,提供一些安全性的例子,讨论重要的安全术语类型,并谈论 AWS 共享责任模型。

为什么安全很重要?

以下几点讨论了安全的重要性:

  • 法律和标准的合规性:例如,欧盟通用数据保护条例GDPR)和美国颁布的健康保险可移植性和责任法案HIPAA)负责监管所有个人数据保护和隐私的法律。

  • 数据完整性:不安全的系统可能会被剥夺数据或篡改数据,这意味着您不能再信任数据。

  • 个人可识别信息:隐私是当今的主要关注点。您应该当然地保护您的用户数据和资产。

  • 数据可用性:例如,如果审计员要求您提供特定数据,您需要能够检索该数据。如果您的数据中心附近发生自然灾害,那些数据需要是可用和安全的。

让我们看看以下清单:

在左侧,我们有各种配置不正确、缺少更新或未加密通信手段的系统。这实际上可能导致中间部分,例如系统被黑客入侵、勒索软件要求或对您的系统进行渗透。例如,可能会发动分布式拒绝服务攻击,这将使您的电子商务网站无法访问。

在右侧,您可以看到一些影响。可能会有诉讼成本、数据丢失或数据泄露、对您的组织的财务成本,以及声誉成本。

AWS 中的安全术语类型

AWS 中的许多安全性实际上是配置和正确架构的重要性。因此,了解一些这些安全术语是很重要的。

  • 传输安全:将其视为 HTTPS SSL。如果您考虑一个网络浏览器,您会在浏览器中看到一个挂锁,表示通信是安全的,例如当您访问任何在线银行系统时。

  • 静态安全:这是加密在数据库或文件系统中的数据。只有拥有密钥的用户才能访问数据。

  • 身份验证:这指的是确认用户或系统是否是其应该是的过程。

  • 授权:一旦您经过身份验证,系统会检查正确的授权。这是为了检查权限和访问控制是否已经就位,以便您访问特定的 AWS 资源。

AWS 身份和访问管理(IAM)概述

在本节中,我们将简要讨论 AWS IAM,特别是用于无服务器计算。IAM 是一个中心位置,您可以在那里管理用户和安全凭据,如密码、访问密钥和权限策略,以控制对 AWS 服务和资源的访问。我们将讨论最相关的 IAM 资源:策略、角色、组和用户。

IAM 策略是定义受影响操作的资源和条件的 JSON 文档。以下是一个 JSON 文档的示例,它将授予对 DynamoDB 表的读取访问权限,仅当请求来自特定 IP 范围时,表名为 Books

还有一个可视化编辑器,允许您创建这些组,或者您可以通过编辑 JSON 文档来手动创建。

IAM 用户

IAM 用户是与 AWS 交互的人员或服务。他们通过密码或多因素身份验证(对于新用户)访问管理控制台,或者他们可能具有访问密钥,以便使用命令行界面或 SDK 进行编程访问。如下图所示,您可以将策略附加到用户,以授予他们对特定 IP 范围内 DynamoDB 的读取访问权限:

IAM 组

IAM 组用于更好地模拟组织中的安全术语。您可以将它们视为活动目录组。例如,在您的组织中,您可能会有管理员、开发人员和测试人员。

要创建一个组,您可以使用 AWS 管理控制台、SDK 或 CLI,在 IAM 中添加组,然后附加策略。创建组后,您可以将其附加到用户,或者您可以创建一个新的组。

IAM 角色

IAM 角色类似于用户,它们可以附加策略,但可以由需要访问的任何人附加到受信任的实体。通过这种方式,您可以委派对用户、应用程序或服务的访问权限,而无需给他们新的 AWS 密钥,因为他们可以通过这个受信任的实体使用临时安全令牌。例如,您可以授予第三方对 S3 存储桶的读取访问权限,而无需在您的 AWS 环境中共享任何密钥,纯粹使用角色:

保护您的无服务器微服务

在本节中,我们将讨论构建第一个微服务所需的安全性。具体来说,我们将看一下围绕 Lambda 函数、API 网关和 DynamoDB 的安全性,然后我们将讨论在检测到可疑事件时可以使用的监控和警报方式。

Lambda 安全

在 lambda 安全中,有两种类型的 IAM 角色:

  • 调用 lambda:这意味着具有实际调用和运行 lambda 函数的权限。例如,这可以来自 API 网关或另一个服务。

  • 授予 lambda 函数对特定 AWS 资源的读写访问权限:例如,您可以允许 Lambda 函数从 DynamoDB 表中读取。

此外,密钥管理服务KMS)是 AWS 管理的密钥服务,允许您对数据进行加密和解密,例如在数据库或 NoSQL 数据存储中的数据。亚马逊虚拟私有云是另一个选项,Lambda 默认在安全的 VPC 中运行。但是,如果您需要访问资源,例如弹性碰撞集群或 RDS,这些资源位于私有 VPC 中,您可能希望在自己的私有 AWS VPC 中运行它。以下是使用 AWS Lambda 使用 AWS KMS 和 AWS VPC 的工作流表示:

对于 API 网关安全性,有三种方式可以控制谁可以调用您的 API 方法。这被称为请求授权,如下图所示:

以下是控制谁可以调用您的 API 的不同方法:

  • IAM 角色和策略:这提供对 API 网关的访问。API 网关将使用这些角色和策略来验证请求者的签名。

  • 亚马逊 Cognito 用户池:这控制谁可以访问 API。在这种情况下,用户必须登录才能访问 API。

  • API Gateway 自定义授权者:这是一个请求,比如一个持有者令牌或 lambda 函数,用于验证并检查客户端是否被授权调用 API。

如果您从 API 自己的域之外的域接收请求,您必须启用跨域资源共享。此外,API Gateway 支持 SSL 证书和证书颁发机构。API Gateway 可能需要通过 IAM 角色授权调用或调用 AWS 内的特定资源,比如 Kinesis 流或调用 Lambda 函数。

DynamoDB 安全

您可以使用 IAM 用户进行身份验证,也可以使用特定的 IAM 角色。一旦它们经过身份验证,授权就受到控制,并且 IAM 策略被分配给特定用户或角色。我建议的是,在为 DynamoDB 创建这些策略时,尽可能地限制它们,这意味着避免对所有表和 DynamoDB 进行读取和写入访问。最好为特定表使用特定名称。

监控和警报

监控系统中的任何可疑活动并检测任何性能问题非常重要。API Gateway、DynamoDB 和 Lambda 函数都支持 CloudTrail、CloudWatch 和 X-Ray 进行监控和警报。它们的讨论如下:

  • CloudTrail 允许您监控所有 API 和任何用户或系统对资源的访问。

  • CloudWatch 允许您收集和跟踪指标,监视日志文件,设置特定警报,并自动对 AWS 资源的更改做出反应。

  • X-Ray 是一项新服务,可以跟踪请求并生成服务地图。

这些免费系统的组合为您提供了对无服务器系统的非常好的洞察力。

摘要

阅读完本章后,您应该对 AWS 中的安全有更深入的了解,以及为什么对您的组织来说这是重要的。毕竟,没有人希望成为负责数据泄露的人。我们讨论了 IAM,您现在知道策略是确保对 AWS 资源受限访问的关键文档。我们还研究了一些保护您的无服务器微服务的安全概念;具体来说,我们了解了 lambda、API Gateway 和 DynamoDB。

标签:指南,服务,Python,AWS,DynamoDB,API,dynamo,服务器,Lambda
From: https://www.cnblogs.com/apachecn/p/18202364

相关文章

  • python操作redis数据库
    官方文档https://redis.io/docs/latest/develop/connect/clients/python/仓库https://github.com/redis/redis-py安装库pipinstallredis普通连接r=redis.Redis(host='10.0.0.5',port=6379,decode_responses=True)decode_responses表示响应的结果是解码后的......
  • Python 将PowerPoint (PPT/PPTX) 转为HTML
    PPT是传递信息、进行汇报和推广产品的重要工具。然而,有时我们需要将这些精心设计的PPT演示文稿发布到网络上,以便于更广泛的访问和分享。本文将介绍如何使用Python将PowerPoint文档转换为网页友好的HTML格式。包含两个简单示例:Python将PowerPoint文档转为HTML格式Python将指定......
  • 服务器被攻击后,我学到的知识...
    目录又被攻击了?事实还原怎样避免被攻击又被攻击了?前段时间每天早上都被阿里云的报警短信和邮件叫醒,给看一下记录。事实还原我当时以为是有几篇被其他博主转发了文章,导致被知名度(狗头)上升,被人盯上了。每天早上6、7点手机就开始报警。上一篇文章发出去后,也收到很多小伙伴的支......
  • Ansible2-安全自动化指南-全-
    Ansible2安全自动化指南(全)原文:zh.annas-archive.org/md5/CFD4FC07D470F8B8541AAD40C25E807E译者:飞龙协议:CCBY-NC-SA4.0前言IT正在经历一次巨大的范式转变。从以正常运行时间作为IT成功的衡量标准的时代,我们正在转向不可变基础设施的理念,根据需求,我们可以自动地随时启......
  • python requests get请求 如何获取所有请求
    在Python中,使用requests库发送HTTPGET请求非常简单。如果你想获取所有的请求,通常意味着你想记录或跟踪这些请求。这可以通过使用requests的Session对象和自定义的HTTPAdapter来实现。以下是一个如何实现这一功能的示例代码:importrequestsfromrequests.adaptersimportHTTP......
  • 食物识别系统Python+深度学习人工智能+TensorFlow+卷积神经网络算法模型
    一、介绍食物识别系统。该项目通过构建包含11种常见食物类别(包括'Bread','Dairyproduct','Dessert','Egg','Friedfood','Meat','Noodles-Pasta','Rice','Seafood','Soup','Vegeta......
  • Django框架指南
    Django框架指南入门安装Django系统安装python后,使用pip安装Djangopipinstalldjango创建一个Django项目在命令行中,使用以下命令创建一个新的Django项目:django-adminstartprojectmyproject这将创建一个名为myproject的文件夹,其中包含了一个Django项目的基本结构。创建......
  • [998] Python unpacking operators (* and **)
    ref:Pythonunpackingoperators(*and**)(RECOMMENDED)ref:PythonFunctionsref:PythonUnpackDictionary:AComprehensiveGuideHerearesomecrucialthingsfortake-awayofthe unpackingoperators:Asingleasterisk * unpacksitemsfromlists,tupl......
  • python去除图片白边黑边
    主要用于去除图片的白边和黑边,比如在截图表情包的时候,通过小米的传送门保存图片的时候,图片往往会有黑边和白边,此时使用此脚本二次处理importosfromPILimportImage,ImageChopsdeftrim_white_border(image):bg=Image.new(image.mode,image.size,image.getpixel((0......
  • conda使用指南
    Python与Conda:简单、便捷的包管理工具Python作为一门流行的编程语言,在数据分析、机器学习和深度学习等领域得到了广泛应用。然而,Python的库众多,版本繁多,所以在库的安装和版本管理上可能存在困难。为解决这一问题,我们可以借助Conda这一方便易用的包管理工具。安装Anaconda首先......