第3章 云原生部署的演变
我们在前一章中讨论了在采用云原生基础设施之前需要什么。在部署之前,需要有API驱动的基础设施供应(IaaS)。
在本章中,我们将探讨云原生基础设施拓扑的概念,并在云中实现它们。我们将学习可以帮助运维人员控制其基础设施的常用工具和模式。
部署基础设施的第一步应该是能够将其表述出来。传统上,可以在白板上处理,或者如果幸运的话,可以在公司wiki上存储的文档中处理。今天,一切都变得更加程序化,基础设施表述通常以便于应用程序解释的方式记录。无论如何表述,全面的表述基础设施的需求是不变的。
正如人们所期望的那样,精巧的云基础设施可以从简单的设计到非常复杂的设计。无论复杂性如何,必须对基础设施的表现给予高度的重视,以确保设计的可重复性。能够清晰地传递想法更为重要。因此,明确、准确和易于理解的基础设施级资源表述势在必行。
我们也将从制作精良的表述中获得很多好处:
- 随着时间的推移,基础设施设计可以共享和版本化。
- 基础设施设计可以被fork和修改以适应特殊情况。
- 表述隐含的是文档。
随着本章向前推进,我们将看到基础设施表述是如何成为基础设施部署的第一步。我们将以不同的方式探索表述基础设施的能力和潜在缺陷。
表述基础设施
首先,我们需要理解表述基础设施的两个角色:作者和观众。
作者将定义基础设施,通常是人类运维人员或管理员。观众将负责解释基础设施表述。有时候,这是一个运维人员执行手动步骤,但希望它是一个可以自动分析和创建基础设施的部署工具。作者在准确表达基础设施方面表现得越好,我们就可以在听众解释表达的能力中获得更多的信心。
创作基础设施表述时主要关心的是要让观众了解它。如果目标受众是人,则表述可能以技术图或抽象代码的形式出现。如果目标受众是一个程序,那么表示可能需要更详细的信息和具体的实施步骤。
尽管有观众,作者应该让观众更容易使用。随着复杂性的增加以及人与程序共同使用基础设施,这将变得非常困难。
表示法需要易于理解,以便能够对其进行准确分析。易于阅读但分析不准确的表述否定了整个工作。观众应该总是努力去解释他们的表述,而不是做出假设。
为了使表达成功,解释需要可预测。如果作者忽略了一个重要的细节,那么最好的观众就会很快失败。具有可预测性将在应用变更时减少错误的发生,并有助于在作者和受众之间建立信任。
基础设施即图
我们用到了白板,开始绘制一张基础设施图。通常情况下,这个过程始于在角落上代表互联网的云形状,以及一些指向方框的箭头。每个框代表系统中的一个组件,箭头表示它们之间的交互。图3-1是基础设施图的一个例子。
图3-1. 简单的基础设施图
这是一个非常有效的头脑风暴和将想法传达给其他人的方法。它允许对复杂的基础设施设计进行快速而强大的表示。
图片适用于人类,大量人群和CEO。这些图也适用,因为它们使用常用语来表示关系。例如,此框可能会将数据发送到那个框,但不会将数据发送到其他框。
不幸的是,图表对于计算机来说几乎是不可能理解的。在计算机视觉迎头赶上之前,基础设施图仍然是一个代表,可以用眼球来解释,而不是代码。
从图中进行部署
在例3-1中,我们看一个来自bash_history文件的熟悉的代码片段。它代表一个基础设施运营商,作为描述基础服务器与网络、存储和转租服务运行的图表的受众。
运维人员已经手动部署了一台新的虚拟机,并通过SSH连接到了该机器并开始配置它。在这种情况下,人类充当图解释者,然后在基础设施环境中采取行动。
大多数基础设施工程师在他们的职业生涯中都这样做了,而且这些步骤对于某些系统管理员来说应该是非常熟悉的。
例3-1. bash_history
sudo emacs /etc/networking/interfaces
sudo ifdown eth0
sudo ifup eth0
sudo fdisk -l
sudo emacs /etc/fstab
sudo mount -a
sudo systemctl enable kubelet
基础设施即脚本
如果您是一个系统管理员,您工作的一部分是在复杂系统中进行更改;确保这些更改是正确的也是您的责任。需要将这些变化传播到广阔的系统中是非常现实的。不幸的是,人为错误也是如此。管理员为这项工作编写便利脚本并不奇怪。
脚本可以帮助减少重复任务中人为错误的数量,但自动化是一把双刃剑。这并不意味着准确性或成功。
对于SRE,自动化可以让你力量倍增,单它不是万能药。当然,加倍的力量并不会自然地改变应用力的准确性:不经意地进行自动化可能会产生很多的问题。
——Niall Murphy、John Looney和Kacirek,自动化在谷歌的演变
编写脚本是自动执行步骤以产生所需结果的好方法。该脚本可以执行各种任务,例如安装HTTP服务器,配置并运行它。但是,脚本中的步骤在调用时很少考虑到它们的结果或系统的状态。
在这种情况下,脚本是编码数据,表示创建所需基础设施应该发生的情况。另一位运维人员或管理员可以评估您的脚本,并希望了解脚本正在做什么。换句话说,他们会解释你的基础设施表示。了解所需的基础设施需要了解步骤如何影响系统。
脚本的运行时会按照它们定义的顺序执行这些步骤,但运行时不知道它正在生成什么。脚本是代码,脚本的执行结果希望是所需的基础设施。
这适用于普遍的场景,但这种方法存在一些缺陷。最明显的缺陷是运行相同的脚本可能获得两个不同的结果。
如果脚本第一次运行的环境与第二次运行的环境大不相同?从科学的角度来说,这将类似于程序中的缺陷,并会使实验数据无效。
使用脚本来表示基础设施的另一个缺陷是缺少声明状态。脚本的运行时不理解结束状态,因为它只提供了执行步骤。人类需要从步骤中解释理想的结果,以了解如何进行改变。
我们看到过很多人类难以理解的代码。随着配置脚本复杂性的增长,我们解释脚本的能力就会减弱。此外,您的基础设施页需要随时间而变化,脚本将不可避免地需要更改。
如果不将步骤抽象为声明性状态,为了给每个可能的初始状态创建过程,脚本将不断增长。这包括抽象出操作系统(例如apt和DNF)之间的步骤和差异,以及验证可以安全地跳过哪些步骤。
基础设施即代码带来了一些工具,这些工具提供了一些抽象,以帮助减轻使用脚本管理基础设施的负担。
从脚本部署
创建基础设施的下一个发展是开始采用先前手动管理基础设施的流程,并通过将工作封装在脚本中来简化它。想象一下,我们有一个名为createVm.sh的bash脚本,它将在我们的本地工作站中创建一台虚拟机。
该脚本需要两个参数。第一个是分配给虚拟机上的网络接口的静态IP地址。第二个是以千兆字节为单位的大小,用于创建卷并将其挂载到虚拟机。
示例3-2将基础设施的基本表示形式显示为脚本。该脚本将提供新的基础设施,并在新创建的基础设施上运行任意配置脚本。该脚本可能演变为高度可定制的,并且可能是(危险地)自动化的,只需点击一下按钮即可运行。
例3-2. 基础设施即脚本
#!/bin/bash
# Create a VM with a NIC on 10.0.0.17 and a 100gb volume
createVm.sh 10.0.0.17 100
# Transfer the bootstrapping script
scp ~/vm_provision.sh user@10.0.0.17:vm_provision.sh -v
# Run the bootstrap script
ssh user@10.0.0.17 sh ~/vm_provision.sh
基础设施即代码
配置管理曾经是代表基础设施的主要角色。我们可以将配置管理视为抽象脚本,自动考虑初始状态以执行正确的过程。最重要的是,配置管理允许作者声明节点的期望状态,而不是实现它所需的每一步。
配置管理是基础设施即代码的第一步,但相关工具很少超出单个服务器的范围。配置管理工具在定义特定资源和他们的状态方面做得非常出色,但由于基础设施需要资源之间的协调,所以出现了复杂性。
例如,服务的DNS条目在提供服务之前不可用。在主机可用之前不应该提供该服务。如果不能在独立节点之间协调多个资源,则配置管理提供的抽象化是不足的。有些工具增加了协调资源之间配置的能力,但协调通常是程序性的,责任落到了人们的协调资源和理解所需状态上。
您的基础设施不包含没有通信的独立实体。代表基础设施的工具需要考虑到这一点。因此,需要另一种表示来管理低级别抽象(例如操作系统)以及供应和协调。
2014年7月,有个开源工具在代码发布的时候采用了更高级别的基础设施抽象概念。这个名为Terraform的工具非常成功。它在配置管理完善并且公有云的采用呈上升趋势的时间节点发布。用户看到了新环境中工具的局限性,Terraform很好的满足了他们的需求。
在2011年时,我们最初将基础设施视代码。我们注意到我们正在编写工具来解决许多项目的基础设施问题,并希望将流程标准化。
——Hashicorp首席执行官兼Terraform创始人Mitchell Hashimoto
Terraform使用专门的领域特定语言(DSL)表示基础设施,它在人类可理解的图像和机器可分析的代码之间做了良好的折衷。Terraform最成功的部分是抽象的基础设施视图,资源协调以及应用时利用现有工具的能力。Terraform与云API进行通信以配置基础设施,并可在必要时使用配置管理来配置节点。
这是该行业的根本性转变,因为我们看到一次性配置脚本正在消失。越来越多的运营商开始在新的DSL中开发基础设施表示。过去在基础设施上手动操作的工程师现在正在开发代码。
新的DSL解决了将基础设施表示为脚本的问题,并成为表示基础设施的标准。工程师发现他们正在开发更好的基础设施代码,并允许Terraform对其进行解释。与配置管理代码一样,工程师们开始将他们的基础设施表述存储在版本控制系统中,并将基础设施与软件等同看待。
通过表述基础设施的标准化方式,我们摆脱了学习各种专有云API的痛苦。尽管并非所有云资源都可以用单一表示抽象出来,但大多数用户可以接受其代码中的云锁定。拥有人类可读并且机器可解析的基础设施表示,而不仅仅是独立的资源声明,这一点永远得改变了行业。
从代码部署
在面临将基础设施部署为脚本的挑战之后,我们已经创建了一个程序来解析输入并针对我们的基础设施采取行动。
例3-3显示了从Terraform开源库中获取的Terraform配置。注意代码中有变量,需要在运行时解析。
基础设施的声明性表示很重要,因为它没有定义创建基础设施的各个步骤。这使我们能够分离需要调配的部分和调配的部分。这就是使这种基础设施代表成为新范例的原因;这也是向软件基础设施演进的第一步。
以这种方式来表示基础设施对于工程师来说是一种常见的强大做法。用户可以使用Terraform来应用基础设施。
例3-3. example.tf
# Create our DNSimple record
resource "dnsimple_record" "web" {
domain = "${var.dnsimple_domain}"
name = "terraform"
value = "${hostname}"
type = "CNAME"
ttl = 3600
}
基础设施即软件
基础设施即代码是朝着正确方向发展的强大举措。但是代码是基础设施的静态表示,并且有其局限性。您可以自动执行部署代码更改的过程,但除非部署工具持续运行,否则仍会出现配置漂移。传统上,部署工具只能在一个方向上工作:它只能创建新对象,并且不能轻易删除或修改现有对象。
为了掌握基础设施,我们的部署工具需要根据基础设施的初始表示进行工作,并对数据进行变更以创建更灵活的系统。当我们开始将基础设施表示视为一个可持续执行所需状态的可版本化数据体时,下一步就是将基础设施视为软件。
Terraform从配置管理中吸取教训并改进了这一概念,以更好地配置基础设施和协调资源。应用程序需要一个抽象层来更有效地利用资源。正如我们在第1章中所解释的那样,应用程序不能直接在IaaS上运行,而需要在可以管理资源和运行应用程序的平台上运行。
IaaS将原始组件作为临时API端点呈现,平台呈现更容易被应用程序使用的资源的API。其中一些资源可能提供IaaS组件(例如,负载均衡器或磁盘卷),但其中许多资源将由平台管理(例如,计算资源)。
平台揭示了一个新的基础设施层,并不断强化所需的状态。平台的组件也是应用程序本身,可以使用相同的期望状态声明进行管理。
API机制允许用户获得将基础设施标准化为代码的好处,并增加了随着时间的推移版本化和更改表示的能力。API允许通过标准实践(如API版本控制)消费资源的新方式。API的使用者可以将其应用程序构建到特定的版本,并相信在使用新的API版本之前,它们的使用不会中断。其中有些做法是以前基础设施即代码工具所缺少的重要功能。
通过持续强化表示的软件,我们现在可以保证我们系统的当前状态。通过提供正确的抽象,平台层变得更加易于使用。
您可能正在绘制基础设施演变与软件演进之间的相似之处。堆栈中的这两层以非常相似的方式进化。
软件正在吞噬世界。
——Marc Andreessen
封装基础设施并将其视为版本化的API将会非常强大。这极大地提高了负责解释表示的软件项目的速度。由平台提供的抽象是跟上快速增长的云所必需的。这种新模式是当今的模式,并且已经被证明可以扩展到难以估量的基础设施和应用程序。
从软件部署
基础设施即代码和基础设施与软件之间的根本区别在于,软件能够改变数据存储,从而改变基础设施的表示。这是由软件来管理基础设施,代表是运营商和软件之间的交换。
在例3-4中,我们看看使用YAML表示的基础设施。我们可以信任该软件来解释这种表示,并为呈现YAML的结果。
就像与我们开发基础设施代码时一样,我们从基础设施的表示开始。但在这个例子中,软件会持续运行,并确保表示会随时间的推移。从某种意义上说,这仍然是只读的,但是软件可以扩展这个定义来添加自己的元信息,比如标记和资源创建时间。
例3-4. infrastructure.yaml
location: "New York 1"
name: example
dns:
fqdn: infra.example.com
network:
cidr: 172.0.0.0/12
serverPools:
- bootstrapScript: /home/user/bootstrap.sh
diskSize: 40gb
firewalls:
- rules:
- ingressFromPort: 443
ingressProtocol: tcp
ingressSource: 0.0.0.0/0
ingressToPort: 443
maxCount: 1
minCount: 1
image: centos-amd64-7
subnets:
- cidr: 172.0.100.0/24
部署工具
我们现在了解部署基础设施的两个角色:
作者
定义基础设施的组件
观众
部署工具解释表示并采取行动
我们可以通过很多途径来表述基础设施,采取行动的组成部分是对最初表示的逻辑反映。准确地表示适当的基础设施层并尽可能消除该层的复杂性非常重要。通过简单、有针对性的发布,我们将能够更加准确地应用所需的更改。
《站点可靠性工程》(O’Reilly,2016)总结说:“简单版本通常比复杂版本更好。衡量和理解单一变更的影响,而不是同时发布的一批变更要容易得多。“
随着我们对基础设施的表示随着时间的推移而变化,以便从底层组件中抽象出来,我们的部署工具已经发生变化,以匹配新的抽象目标。
我们正在将基础设施视为软件边界,并且可以注意到基础设施部署工具新时代的早期迹象。互联网上的开源项目正在出现,声称能够随着时间的推移管理基础设施。工程师的工作是了解项目管理的基础设施层以及它如何影响其现有工具和其他基础设施层。
云原生基础设施方向的第一步是采用配置脚本并安排它们持续运行。有些工程师会故意设计这些脚本,以便随着时间的推移安排好。我们开始看到精心设计的全局锁定机制、高级调度策略和分布式调度方法。
这基本上是配置管理承诺的,尽管在更具资源特定的抽象中。感谢云计算,管理基础设施的自动化脚本的日子已经过去了。
自动化已死。
——Honeycomb首席执行官Charity Majors
我们正在想象一个我们开始以完全不同的方式看待基础设施工具的世界。如果您的基础设施旨在运行在云上,那么IaaS不是您应该解决的问题。使用云提供的API,并构建可直接由应用程序使用的新基础设施层。
我们在基础设施发展方面处于特殊地位,我们从第一天开始就将基础设施部署工具设计为优雅的应用程序。
良好的部署工具是可以快速从基础设施的人性化表示到可工作基础设施的工具。更好的部署工具是撤销任何与初始表示不一致的变更的工具。最好的部署工具可以完成所有这些工作,而无需人工参与。
在我们构建这些应用程序时,我们不能忘记从处理复杂系统至关重要的专业工具和软件实践中学到的重要经验。
我们将看到的部署工具的一些关键方面是幂等性和处理失败。
幂等性
软件应该是幂等的,这意味着持续输入相同的输入,必须并始终获得相同的输出。
在技术上,这个想法被超文本传输协议(HTTP)通过像PUT和DELETE这样的幂等方法而著名。这个想法飞非常强大,并且在软件中宣传幂等性的保证可以塑造出更佳复杂的软件应用程序。
我们从早期的配置管理工具中学到的经验之一就是幂等性。我们需要记住这个功能为基础设施工程师提供的价值,并且继续将这种模式构建到我们的工具中。
能够自动创建、更新或删除基础设施,保证无论您运行任务的频率如何,始终都会输出相同的结果,这非常令人兴奋。它允许运维人员开始自动化任务和杂事。过去对于运维人员来说,过去相当大量的工作现在可以像在网页中点击按钮一样简单。
幂等保证也有助于运营商在其基础设施上执行质量科学。运营商可以在许多物理位置开始复制基础设施,并知道别人重复他们的程序会得到同样的结果。
我们开始注意到围绕这种自动执行任意任务以实现可重复性的思想构建的整个框架和工具链。
就像软件一样,基础设施也是如此。运营商开始使用这些表示和部署工具自动管理整个管理基础设施的流水线。现在,运维人员的工作变成了开发自动执行这些任务的工具,而不再是自己执行任务。
处理失败
任何软件工程师都可以告诉你在代码中处理故障和边缘案例的重要性。作为基础设施管理员我们自然而然就要考虑这些问题。
如果部署作业在执行过程中失败,更重要的是在这种情况下会发生什么,会发生什么情况?
在考虑失败的情况下设计我们的部署工具是朝着正确方向迈出的又一步。失败时发送消息或在监控系统中注册警报。我们保存了自动化任务的详细日志。在失败的情况下,我们甚至将逻辑连接在一起。
我们沉迷于失败。我们在失败的情况下开始采取行动,并在事件发生时采取行动。
但围绕单个组件可能出现故障的想法来构建系统与构建组件以使其更容易出故障完全不同。根据故障重试组件或调整其方法是将系统的弹性进一步深入到软件中。这允许更稳定的系统并减少系统本身所需的整体支持。
面向故障而设计组件,而不是系统。
最终一致性
以设计失败的组件为名,我们需要学习一个描述处理失败的常用方法的术语。
最终的一致性意味着企图随着时间的推移调和一个系统。较大的系统和较小的组件都可以遵循这种随时间推移重试失败过程的理念。
最终一致的系统的好处之一是运维人员可以确信它最终会达到预期的状态。这些系统的一个担忧是,有时他们可能花费不恰当的时间来达到所需的状态。
知道什么时候选择一个稳定但缓慢的系统与一个不可靠但快速的系统是管理员必须做出的技术决策。在这个决定中要注意的重要关系是系统交换速度的可靠性。这并不容易,但如果有疑问,请始终选择可靠的系统。
原子性
与最终一致的系统相反的是原子系统,这是一项保证交易,决定了整个工作的成功。如果作业无法完成,则会恢复所做的更改并完全失败。
想象一下需要创建10个虚拟机的工作。工作到达第七台虚拟机,出现问题。根据最终的一致性方法,我们只会反复尝试这项工作,希望最终获得10个虚拟机。
了解我们只能创建7个虚拟机的原因非常重要。想象一下,云计算允许我们创建多少个虚拟机是有限制的。最终一致性模型将继续尝试创建另外三台机器,并且不可避免地会失败每次。
如果这项工作是设计成原子的,那么它将在第七台机器上达到极限,并意识到这是一场灾难性的失败。这项工作将负责删除部分系统。
因此,运维人员可以放心,他们或者完全按照预期建立系统,或者根本不会创建任何东西。这是一个很有意义的想法,因为为了能让系统正常工作,基础设施中的许多组件都依赖于系统中的其他部分。
我们可以引入信心来换取不便。也就是说,管理员会相信他们的系统状态永远不会改变,除非可以应用完美的改变。为了交换这个完美的系统,运维人员可能会面临很大的不便,因为系统可能需要很多工作才能保持平稳运行。
选择一个原子系统是安全的,但可能不是我们想要的。工程师需要知道他们想要什么系统,以及何时选择原子性与最终一致性。
结论
部署基础设施的模式很简单,并且在云可用之前一直保持不变。我们代表基础设施,然后使用一些设备,将基础设施变为现实。
基础设施层与软件应用层具有惊人的类似历史。云原生基础设施也不例外。我们开始发现自己在重复历史,并以新的方式学习古老的教训。
如果我们已经知道其软件对手的未来,那么对于预测基础设施行业未来的能力还有什么要说的?
云原生基础设施是基础设施演变的一种自然而可能预期的结果。能够以可靠和可重复的方式部署、表示和管理它是必要的。随着时间的推移,我们能够部署我们的部署工具,并转移我们的工作方式,这对于将我们的基础设施保持在一个能够支持其应用层的空间中至关重要。