当前位置:   article > 正文

Python 无服务器微服务构建指南(一)_如何搭建python的微服务

如何搭建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"
    },
    {
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

{
  "firstName": "John",
  "lastName": "Smith",
  "age": 27,
  "address": {
  • 1
  • 2
  • 3
  • 4
  • 5

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

$ cd /mnt/c/
  • 1

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单中的单词或对话框中的单词会在文本中以这种方式出现。这里有一个例子:“在 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/
  • 1

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

更新 Ubuntu,安装 Git 和 Python 3

Git 将在本书的后面使用:

$ sudo apt-get update
$ sudo apt-get -y upgrade
$ apt-get install git-core
  • 1
  • 2
  • 3

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
  • 1
  • 2

检查 Python 版本:

$ python --version
  • 1

你应该使用 Python 版本 3.6+。

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

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

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

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

安装和设置 AWS CLI

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

$ sudo pip install awscli --upgrade
  • 1

您之前创建了一个名为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>
  • 1
  • 2
  • 3
  • 4
  • 5

有关设置 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"
    }
  ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

前面的代码是与 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"
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这是一个 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"                 
                 ]
             }
         ]
     }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

更新<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": "*"
         }
      ]
     }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

这个策略的主要目的是允许 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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

与在 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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

在这里,我们使用 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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

就像我之前做的那样,我创建了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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  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": "*"
 }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果出现错误,请检查详细信息的日志输出,可能与 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"
   }
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果您没有收到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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 在新的浏览器选项卡中粘贴调用 URL:https://{restapi_id}.execute-api.{region}.amazonaws.com/Prod/visits/324?startDate=20171002。因为我们添加了startDate=20171002参数,您应该在浏览器选项卡中看到以下代码:
      [{"EventCount": 5, "EventDay": 20171002, "EventId": "324"}]
  • 1

这是使用 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 集成有很好的理解。当我们使用配置、脚本和代码自动化大部分创建和配置时,这种坚实的基础非常有用。在接下来的章节中,我们将添加更多功能,自动化测试和部署流水线,并实施微服务模式。

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

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

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号