企业级DevOps技术与工具实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.1 对失败友好的架构与环境

我们在讨论“Spotify团队如何面对失败”时就提到过对失败友好的环境,这既是目标也是基础。在进行敏捷实践的时候,经常会提倡快速失败,但是不是更应认真思考它的前提和基础呢?对失败友好的环境就是提倡快速失败的前提和基础,作为推行 DevOps 企业文化的支撑之一,对失败友好的架构与环境就像汽车安全带一样重要,它是推行高度信任及免责的企业文化的重要支撑,因为它能在很多方面将损失降至最小。

3.1.1 对失败友好的架构与环境的特点

对失败友好的架构与环境在应用出现故障或者超出设计容纳的限度时,依然能够保证弹性并对服务进行优雅地降级,这里我们通过借鉴Netflix在这方面的做法来了解对失败友好的架构与环境的特点。

2011年4月21日,整个Amazon的AWS云服务系统发生故障,导致很多运行在其上的客户(诸如Reddit和Quora)的服务中断,但是Netflix却基本没有受到影响。AWS并没有对Netflix进行特殊对待,Netflix能够继续为客户提供服务的原因在于,其在2009年对自身的架构进行了优化。

2008年,Netflix的在线视频服务还是一个巨大的、运行在数据中心上的J2EE应用。而在2009年,为了保证该服务在Amazon AWS上具有更好的容错性,Netflix对其进行了重构,类似Amazon服务的影响也被考虑在内,具体的举措有3条。

● 举措1:采用松耦合的架构,某个组件出现问题不会使得整个系统无法运行。

● 举措2:在不同的场合提供不同的服务,如表3-1所示。

表3-1 Netflix服务场合说明

● 举措3:为了保证发生故障的时候服务能够自动恢复,最好的方式就是进行测试。而相较于其他测试内容,模拟生产环境服务器故障往往是非常困难的。为了进行改进,可以使用工具来模拟 AWS 出现的故障,比如可以使用工具随机地停止生产环境的服务器,这样做的目的是使开发团队能够习惯云端故障的发生,并能保证在故障发生时,服务不需要任何手工干预即可自动恢复。

弹性容错的系统、故障发生时的降级服务等都是对失败友好的架构与环境所具有的特点。在这样的系统中,进行持续试验的开发团队自然更有自信。

3.1.2 对失败友好的架构与环境的设计原则

通过Netflix的实践,我们可以发现对失败友好的架构与环境的特点及在对其进行实现过程中需要注意的一些事项,从而得到一些启发。而构建和设计这样的环境,则是一个长期的过程,需要考虑很多要素,如图3-2所示。

图3-2 设计对失败友好的架构与环境时所需的要素

1.设计中考虑非功能性需求

在开发和设计中,那些非功能性的需求同样非常重要。在一定程度上,后续的很多问题都是由于对这些非功能性需求的设计不够重视而导致的。例如,架构、性能、稳定性、可侦测性、配置、安全要素等。

在开发中如果考虑到一系列的非功能性需求,则能很容易地对服务进行部署,更快地侦测到问题的发生,更重要的是能保证服务的可用性和可靠性。这些非功能性需求有:

● 弹性服务能力;

● 问题和故障发生时的服务可用性保证;

● 在应用和环境中有足够的监控;

● 能够精确跟踪依赖的能力;

● 前向和后向的版本兼容性;

● 能够检索各个服务的日志信息的能力;

● 在多个服务中跟踪用户请求的能力。

正是因为 Netflix 在系统的设计上保证了弹性服务能力,考虑到了意外情况下的服务降级,才做到了在问题出现的时候从容不迫。

2.工具上使用诸如猿猴军团(Simian Army)进行辅助

Netflix 使用了猿猴军团工具模拟 AWS 出现的故障,这使得开发团队习惯了云端故障的发生,并将这种故障发生的应对策略融入架构,从而营造出了对失败友好的架构与环境,保证了在故障发生时,服务不需要任何手工干预即可自动恢复。让我们来看一下猿猴军团工具都能模拟哪些故障,如图3-3所示。

图3-3 猿猴军团工具

● 混沌大猩猩:模拟AWS可用区的故障。

● 混沌金刚:模拟整个AWS区域(例如北美或欧洲)的故障。

● 一致性猴子:查找并关闭不符合最佳实践的AWS实例。例如,不属于自动扩展组的实例或没有列出相关工程师电子邮件地址的目录。

● 医生猴子:对每个实例进行健康检查,一旦发现不健康且未被及时修复的实例,就主动将其关闭。

● 看门猴子:确保云环境没有混乱和浪费;搜索未使用的资源并显示它们。

● 延迟猴子:在其 RESTful 客户端/服务器的通信层中引入人为延迟或停机时间,以模拟服务降级的情况,并确保相关服务能适当地做出响应。

● 安全猴子:一致性猴子的延伸,它查找并终止具有安全违规或安全漏洞的实例。例如,未正确配置的AWS安全组。

Netflix 引入了猿猴军团工具,使系统对于失败更加友好,当不同级别的问题发生时,都能对服务进行优雅地降级。在这个过程中能够模拟实际故障的猿猴军团工具功不可没,当然还有其他类似的工具。另外通过红蓝对战的方式,或者使用后文(3.3.2 节)会介绍的游戏日(Game Days)都可以辅助发现并修复现行系统中的潜在问题,从而使得环境对失败更加友好。

3.开发中通过自动化和标准化来提高效率并保证输出

要尽可能地引入自动化操作来提升效率,避免手工作业导致的服务不稳定。当运行和维护的工作不能够完全自动化时,目标就变成了使工作尽可能地可重复和标准化。尽可能清楚地定义手工作业以减少交付时间和错误,借助诸如Rundeck这样的工具进行自动化操作,同时使用JIRA或者ServiceNow等工具管理问题和事件,可以达到更好的效果。

当我们清楚需要做什么样的工作,由谁去执行,以及需要什么样的步骤后,我们就可以通过在开发中创建定义清晰的运维用户故事,更好地进行规划以保证输出是稳定可重复的。而只有当这些输出稳定可重复时,整体环境的可靠性才能得到进一步的保障。

3.1.3 当失败遇见复杂系统

复杂系统往往具有高度耦合的功能组件,难以简单解释的各种系统行为,以及与无数外接系统千丝万缕的关联,导致很难对其可能面临的失败进行精确的预测。而一旦发生故障(失败),其影响往往非常大。比如,核电站就是这样复杂的系统,当复杂系统发生故障(失败)时,保证对失败友好显然不是一件容易的事情。核电站故障事件及其影响如表3-2所示。

表3-2 核电站故障事件及其影响

而在科技领域,工作依赖由多个超级复杂的系统,同样也可能遭遇极高的风险。例如,2015年纽约证券交易所的系统曾经暂停交易218分钟,如表3-3所示。

表3-3 2015年纽约证券交易所交易系统故障

对于纽约证券交易所的交易系统停止的218分钟,损失巨大。其实,类似的情况并不罕见,巨型系统的复杂性已经远远超出了我们的想象,而且一旦发生问题,将会是灾难性的。

像核反应堆这样复杂的系统,其可能出现的故障难道不能被准确预测吗?Charles Perrow博士通过对三里岛危机进行研究发现,准确预测在各种情况下核反应堆可能的反应,以及何时发生故障近乎不可能。当某一个部分的问题正在发生的时候,很难将其和其他部分分开,快速的变化使得结果变得异常复杂,而预测也近乎不可能。Sidney Dekker博士通过研究发现,复杂系统的另外一个特性,两次同样的执行并不一定导致相同的结果。这显然是几乎令人绝望的特性和结论。对于复杂系统的安全,需要引入监控来予以保障。

3.1.4 保障复杂系统的安全

在复杂系统中,问题(失败,下同)的发生是不可避免的,同时又是无法准确预测的。在这种情况下,我们所能做的是创建一种安全的机制,使问题能够快速被检测到,从而在出现灾难性后果之前开始着手应对。这样的机制使得我们对失败不再畏惧,同时有可能改变文化的土壤。

而在制造业领域,严重问题刚发生时,很多现象都会被检测到。更早发现,更早应对,成本更低,速度更快,代价更低,这是我们从制造业经验中学到的。我们也会将其应用到DevOps的实践当中,发现和反馈问题的回路非常重要,在发现和反馈的实践中,更重要的是将每次问题发生都视作一次学习的机会,而不是责备和惩罚。当然,这是需要前提的,我们需要确保我们的系统运作是安全的,这些问题的发生不会导致灾难性的后果。

在制造业中,缺少有效的反馈经常会导致出现质量和安全问题。在制造业的价值链中,快速、高频度、高质量的信息流动随处可见,每个工序都需要被测量和监控,任何故障或可能引起偏离的问题都会很快地被发现和纠正,正是这些奠定了创建高质量安全系统的基础。

而在软件开发领域,这种在制造业中习以为常的反馈并不易得。例如,在传统的“瀑布型软件开发”中,往往在设计和编码工作基本完成之后,才能从测试阶段得到关于质量的反馈,甚至有一部分只有在发布之后才能得到反馈,而这一切都已经太晚,并且往往已经产生了非常严重的影响。我们所期待的是,在软件开发领域也能创建出制造业中的机制为我们保驾护航。

当然,仅仅通过反馈回路检测到预期外的问题发生还远远不够,我们还需要解决这些问题。让我们再次将目光投向制造业,当问题发生的时候,团队负责人会立即被通知到并开始着手去解决问题。若这个问题在一定的时间内(如55秒内)没有得到解决,整条生产线会停下来去协同解决,直到问题得到解决。这就是2.4.5小节所提到的安灯拉绳的实践活动。

虽然看起来很简单,但是往往在最初的阶段需要决心和勇气。《凤凰项目》一书中所提到的部署冻结,其实就是学习制造业的做法,不让问题继续扩散,在问题的初期进行应对效果更好。发现核反应堆已经开始熔化再去应对明显为时已晚。所以只有在整个流动的过程中尽可能早地发现小的问题,并立即解决,我们才有可能创建一种安全的机制,保证在灾难性的后果发生之前有足够时间去应对。