2.5 生产环境基础设施:Amazon Web Services
在上大学的时候,我的法学教授讲过一个故事,这大概是第一个在法国运营的Web托管服务的故事。在上世纪90年代,这个服务由他的一位朋友运营。那时,在新兴的互联网上托管一个Web页面,要操心从网络到系统层面的所有事情。教授的这位朋友没有能力支付数据中心的费用,所以他将硬盘、主板和电缆直接堆在家里地下室的桌子上,通过几个经专门改装的调制解调器连上了互联网。磁盘旋转和擦写产生的噪声就像是怪兽的嚎叫,而且还存在巨大的火灾隐患,但它确实可以工作——托管了网站。
互联网的起源充满了类似这样的故事。互联网的发展突显了我们在构建和运营在线服务方面取得的进步。直到本世纪第一个十年的末尾,从零开始建立完整的基础设施仍是一项复杂且乏味的任务,涉及大量的硬件和布线工作。现在,大多数组织已经将这些复杂的工作外包给了专业公司,从而将精力集中在构建自己的核心产品上。
IaaS提供商将复杂的处理隐藏在幕后,仅仅把简单的接口暴露给运维人员,通过这种方式简化了基础设施的搭建任务。Heroku、Google Cloud、Microsoft Azure、Cloud Foundry、Amazon Web Services及IBM Cloud是这份长提供商名单中的一些代表,这些提供商可以帮你管理基础设施。IaaS用户只需要在逻辑层声明基础设施,并且让提供商将这些声明转换成物理层。一旦声明了基础设施,运维人员将对其完全管理。当你完成初始设置后,发票应用的管理就被外包给了提供商,而你完全不用管理基础设施的组件了。
本节我们将着重介绍AWS,具体来说是它的Elastic Beanstalk(EB)服务。EB是专为托管容器而设计的,它将基础设施的管理工作从运维人员那里抽象了出来。对于本书的目标而言,选择使用EB完全是随意的。它没有什么特色功能,简单到足以满足本章的需要,也足以演示如何在AWS中实现云服务。
在进入技术话题之前,我们需要先讨论三层架构的概念,托管发票应用需要实现这种架构。接下来,我们将介绍发票应用是如何一步步部署到AWS EB中的。
你是Amazon Web Services新手?
从这里开始,我假设读者已了解AWS,并能够在这个平台上执行一些基本的任务。对于不了解AWS的读者来说,可以在Michael Wittig和Andreas Wittig所著的Amazon Web Services in Action(Manning出版社于2015年出版)一书中找到精彩的介绍。这里展示的基础设施都包含在了AWS的免费套餐所覆盖的范围里,所以你可以用自己的账户进行免费试验。
2.5.1 三层架构
图2.5展示的就是Web应用常用的三层架构模式:
•第一层处理来自客户端(Web浏览器或者客户端应用)发来的HTTP请求。可以在这一层实施缓存和负载均衡。
•第二层处理请求并构建响应。通常这里就是应用的核心所在。
•第三层是数据库和其他存储应用数据的后端。
图2.5 AWS的三层架构展示了一个负载均衡层(第一层)、紧随其后的计算节点(第二层)和在后端支持的关系型数据库(第三层)。
图2.5使用了AWS的官方术语和图标。我们将在整本书中沿用这些术语和图标,所以你最好能尽快地熟悉它们的含义。
•ELB——Elastic Load Balancing是一项由AWS管理的服务,它接收来自互联网客户端的流量并将其分配给应用。ELB的主要目标是在不修改应用前端的情况下,允许应用按需增加或减少服务器的数量。ELB还提供了SSL/TLS终端来简化应用中对HTTPS的处理。
•EC2——Elastic Compute Cloud实例就是一个运行着操作系统(OS,Operating System)的虚拟机(VM,Virtual Machine)。EC2的底层基础设施由AWS管理,运维人员能访问的只有VM系统中运行的系统,而不能访问VM之下的虚拟机管理程序或者网络。你将在EC2上运行应用。
•RDS——大多数应用都需要存储数据,因此需要数据库。Relational Database Service(RDS)提供了完全由AWS管理的MySQL、PostgreSQL及Oracle数据库,这让DevOps团队可以把工作重心放在数据本身上,而不是数据库服务器的管理上。在这个示例中,我们使用PostgreSQL来存储发票应用的数据。在线服务往往比图2.5中的例子更加复杂,但它们的架构几乎都是以同样的三层架构为基础。发票应用也是三层架构。在下一节,我将讲解如何在AWS中使用EB服务来创建这个环境。
2.5.2 配置访问AWS
你将使用AWS官方命令行工具来创建AWSEB基础设施,它的设置十分简单。首先,在Web控制台中的IAM(Identity and Access Management)界面找到账户的访问凭证。访问密钥应该存储在本地机器的HOME/.aws/credentials文件中。你可以配置多个账户,每个账户都有自己的访问密钥,但是现在只用在默认账户中设置一个访问密钥就够了,如代码清单2.6所示。
你还要在HOME/.aws/config文件中声明优先使用的区域,将其告知AWS。我们将选择区域US East 1,但你也可以选择更接近目标用户的区域,以减少网络延迟。
AWS提供的标准工具可以自动地在这些文件中寻找配置。安装提供了“aws”命令的awscli,它是最受欢迎的工具之一。它是一个可以通过pip安装的Python包(还能用Homebrew安装,但是仅对macOS有效)。
包管理器
Pip和Homebrew都是包管理器。Pip是标准的Python包管理器,可以在所有操作系统上运行。Homebrew则是只能在macOS上运行的包管理器,它由社区的贡献者们管理。
尽管安装包的名字是awscli,但它提供的命令却叫作aws。它是一个可以控制整个基础设施的强大工具。接下来,你将频繁地使用这个工具,并逐渐掌握各种各样的命令。
创建EB的脚本
在本章其余的内容中,用来创建EB环境的aws命令被放在了一个shell脚本里,你可以在GitHub网站的Securing-DevOps主页上找到(链接2.6)。如果你不喜欢手动输入命令,那么你完全可以使用这些脚本。
2.5.3 虚拟私有云
所有AWS账户都自带一个虚拟私有云(VPC,Virtual Private Cloud),默认分配给每个区域的账户。如图2.6所示,在给定区域的基础设施中,VPC是专属于一个客户的AWS网段。VPC相互之间是隔离的,它们具备我们稍后会用到的网络能力。在物理层,所有客户共享同一个网络设备,但抽象的IaaS将物理层完全隐藏了起来。
图2.6 每一朵内部的云代表一个VPC,它是某个AWS客户所私有的。在默认情况下,VPC之间无法通信,并且为每位客户提供了虚拟隔离层。
你可以使用代码清单2.9中的AWS命令,来获取在us-east-1区域中和账户一同创建的VPC的ID。
命令返回了默认VPC的ID:vpc-2817dc4f。这个ID是唯一的,但当你设置了自己的账户时,会得到不一样的ID。每个AWS账户可以拥有多个用于托管组件的VPC,但使用默认的VPC就已足够满足我们的需要。
2.5.4 创建数据库层
下一步的设置就是创建基础设施中的第三层:数据库,如图2.7所示。这一层包含了一个运行PostgreSQL的RDS实例,这个实例被置于一个安全组之中。你需要先定义一个安全组,再将实例放入其中。
图2.7 发票应用基础设施的第三层是由安全组中的RDS构成。
什么是安全组?
安全组是控制AWS组件之间交互的虚拟域。我们在第4章介绍基础设施安全时将讨论安全组。
使用AWS命令行工具和下面这些参数来创建安全组。现在,安全组不会阻止或者允许任何动作。
接下来,创建数据库并将其放到安全组sg-3edf 7345之中。
代码清单2.11包含了很多内容。AWS创建了一个为运行PostgreSQL 9.5.2而设计的VM。这个VM使用了最少的资源(CPU、内存、网络吞吐量和磁盘空间的配置都较低),这由命令中的两个参数决定,这两个参数分别代表了5 GB的分配存储空间和db.t2.micro的实例类型。最后,AWS在PostgreSQL中创建了名为“invoicer”的数据库,并把它的管理员权限授予了名为“invoicer”的用户,该用户的密码是“0m3thlngr4nd0m”。
创建RDS实例需要一些时间,因为AWS需要在物理基础设施中找到一个合适的地方,还要运行一遍所有的配置步骤。你可以像代码清单2.12那样,使用AWS命令的describe-db-instance参数来监控实例的创建过程。这段脚本每隔10秒就会检查AWS API的返回,当返回的JSON响应中出现了数据库的主机名时,将结束轮询。
使用jq查询JSON
注意这里使用jq工具来解析AWS API返回的JSON响应。jq是一种流行的命令行工具,用来从JSON格式提取数据中的信息,而且不需要任何编程语言基础。你可以从GitHub官网的stedolan主页找到更多关于jq的信息(链接2.7)。在Ubuntu上,使用命令apt-get install jq安装jq。在macOS上,使用命令brew install jq安装jq。
当数据库实例创建成功之后,就有了一个在VPC内部使用的主机名,并且它被封闭在一个安全组里。你现在已经做好了准备,可以创建基础设施的第一层和第二层了。
2.5.5 用EB创建前两层
AWS提供了许多不同的技术来部署应用和管理服务器。在这个例子中,我们使用的可能是自动化程度最高的一种:EB。EB是位于其他AWS基础设施资源之上的一个管理层。它可以用来创建ELB和EC2实例,以及它们的安全组,还可以把应用部署到这些实例中。对这个例子来说,通过CI流水线构建的Docker容器将被部署到EC2实例中,这些实例被置于一个ELB之后,并由EB管理。图2.8展示了这种架构。
图2.8 基础设施的第一层和第二层由AWS EB管理。
EB首先需要一个“应用”,即一个用来组织组件的空结构。使用代码清单2.13中的命令来为发票应用创建一个。
在发票EB应用的内部,创建一个用来运行其Docker容器的环境。这部分配置需要更多的参数,因为你需要指定要用哪一个解决方案堆栈。解决方案堆栈是为某种特殊用例而预先配置好的EC2实例。我们希望用预先配置好的最新版本来运行Docker实例。使用list-available-solution-stacks命令,并使用jq和grep来过滤它的返回结果,这样就可以得到这个预先配置好的版本的名字。
性能怎么样?
你可能注意到了,我们在一个运行于虚拟机管理程序之上的VM里运行Docker容器。这好像不太高效。的确,这种方法的基本性能低于在服务器裸机上直接运行的应用,但是,部署和维护的简化能抵消主要的性能损失(我们可以轻而易举地根据负载增加服务器的数量)。一切都要归结于,对你而言,最重要的是什么:是基本性能,还是部署灵活性。
当你读到这几页时,Docker解决方案堆栈的版本可能已经发生了变化,但你可以使用AWS API来获取最新版的名字。
在创建环境之前,你还需要准备好发票应用的配置。每个应用都需要一些配置参数,通常由应用服务器文件系统中的配置文件提供。然而这些文件的创建和更新需要直接访问服务器,而你需要避免这样的访问。
如果你看过发票应用的源代码,会发现它只需要一些参数来连接PostgreSQL数据库。可以从环境变量中获取这些参数,并不需要管理配置文件。代码清单2.15展示了发票应用如何从环境变量中读取数据库配置。
在启动时,发票应用将读取代码清单2.15中定义的四个环境变量,并使用它们连接到数据库。你需要在EB中配置这些环境变量,这样才能在应用启动时通过Docker将这些参数传递给应用。这是通过以下展示的JSON文件完成的,创建环境的命令会加载这个文件。代码清单2.16的内容保存在一个名为ebs-options.json的文件中。
安全提示
你应该创建一个数据库权限受限的独立用户,而不应该在应用中使用数据库管理员账户。在第4章中,我们将讨论如何利用数据库权限来防止应用受到攻击。
将文件保存在ebs-options.json名下,然后继续创建环境。
EB负责创建环境中的EC2实例和ELB,并一步到位地创建好前两层的基础设施。这一步需要几分钟才能完成,因为需要初始化各种各样的组件。在这一步结束之后,可以使用describe-environments命令来获取用来访问应用的公开终端节点。
安全提示
EB创建的这个ELB只支持HTTP,不支持HTTPS。第5章将会说明如何配置ELB以支持HTTPS的方法,包括应该使用哪种SSL/TLS配置。
你的环境已经准备好了,但EC2实例还没有被允许连接到数据库。在默认情况下,安全组会阻止所有的入站连接,因此你需要开放RDS实例的安全组来允许EC2实例接入,如图2.9所示。
图2.9 RDS实例的安全组必须允许入站连接,以便EC2实例能连接到数据库。
你已经知道了RDS安全组的ID是sg-3edf7345。你需要在其中插入一条规则——允许来自任意IP的接入连接,即0.0.0.0/0。
安全提示
把数据库开放给整个互联网的规则肯定是不够好的。在第4章中,我们将讨论如何使用安全组来管理动态的和细粒度的防火墙规则。
当进行到这一步时,你已经拥有了完整的运行基础设施,但里面没有运行任何内容。接下来就要将之前创建和发布的发票应用的Docker容器部署到EB基础设施之中了。
2.5.6 将容器部署到系统中
发票应用的Docker容器托管在Docker Hub网站上(链接2.8),见图2.1中的步骤(5)。你需要告知EB该容器的地址,这样EB就可以从Docker Hub上拉取该容器并部署到EC2实例中。下面就是这份处理声明的JSON文件。
每个加入EB基础设施的新实例都会读取这份JSON配置,所以你要将这份配置上传到AWS S3,才能确保这些实例都可以取到。将这些定义保存在一个本地文件中,然后使用命令行工具上传该文件。务必将存储桶名字“invoicer-eb”改成你自己的存储桶名字,因为存储桶的名字在所有AWS账户中必须是唯一的。
在EB中,你将引用应用定义的地址来创建名为invoicer-api的应用版本。
最后,让EB使用刚创建的应用版本invoicer-api来更新环境。只用一条命令,就可以自动完成下面的全部工作:通知AWS EB拉取Docker镜像,将其放到EC2实例中,并使用之前配置好的环境来运行。接下来,要部署新版本的应用,你只需要用代码清单2.23中的这一条命令。
环境更新需要几分钟的时间,你可以在Web控制台中观察至更新结束。当环境变绿时更新就成功了。发票应用有一个特殊的终端节点 /__version__,它可以返回当前运行的应用版本。你可以用命令查询版本终端节点,并检查返回的版本是否是期望的版本,你可以通过这种方法来测试部署。
创建并获取发票,确保数据库连接按预期工作。
第一张发票已经成功创建了,这是令人鼓舞的。现在我们来获取它。
安全提示
将一个发票管理API完全开放给互联网,这显然不是一个好主意。在第3章中,我们将讨论如何使用身份验证来保护Web应用。
就是这样:发票应用已经启动,并在AWS EB中运行了起来。我们花了不少时间才做到这一点,但是看看你的成就:只用一条命令,就可以部署发票应用的新版本。不需要管理服务器,也不需要手动配置,从代码测试到把容器部署到生产环境的一切都是自动化的。就像我们在本章开始时计划好的那样,在15分钟之内你就可以让发送给代码仓库的补丁部署到基础设施中。
我们的基础设施还是太简单,它也没有运营生产服务所需的全部安全控制。但那些都是配置。CI/CD背后的逻辑始终如一,我们会逐渐提高基础设施的安全性。我们要继续保持这种能力——在15分钟的时间窗之内,无须手动步骤就能完成应用新版本的部署。
这是DevOps的承诺:完全自动化的环境,让组织在短周期内把创意变成产品。随着运维压力的减轻,组织会将更多的精力集中在它的产品上,包括产品的安全性。