从亚马逊云宕机近20小时的事故,我们能学到什么?

source: https://www.reportdoor.com/aws-outage-has-taken-down-a-big-chunk-of-the-internet/

今天,我们以亚马逊去年的一个事故报告(AWS PES#11201)为例子,聊一聊如何从事故报告中学习。

读前须知


从外部的官方报告中学习,我们需要秉持正确的期望

  1. 不要:

    1. 不要认为能学习并理解到事故背后的技术架构全貌。

    2. 不要认为能学习并理解某个技术的基础知识。

  2. 要:

    1. 要认识到官方报告是为了对外做沟通,而不是对内。

    2. 要学习如何快速关注事故报告的重点内容,比如TTx、事故影响、处理过程尤其是如何快速缓和事故的步骤,事故发送的根本原因、针对事故发现的缺陷的改进想法,等等。

    3. 要学习一个好的事故报告应该准确地包含什么内容

    4. 要保持思考。对哪些做的好,哪些不好要有独立的思考;哪些内容和过去自己的经验相符或不相符,并思考原因;哪些内容能够对现在的工作有所启发;等等。 

亚马逊云服务事故报告的简要介绍


AWS会将重大事故的事后总结(Post-Event Summary,PES)发布在官网:

https://aws.amazon.com/premiumsupport/technology/pes/

目前官网上已有的PES并不多,从2011年开始至今才有13篇事故事后总结,去年2020年就仅仅有一篇。

我想这并不是表示AWS比谷歌云和微软Azure更稳定和可靠,而是AWS只会对造成重大影响的事故发布PES。

对于希望从事故报告中学习的人来说,这可能是一件好事——容易找到重大事故的事后总结。

我简单浏览了已有的一些PES,我发现PES大多是诉述风格的“作文”,我们需要耐心阅读才能获取重要的信息,事故影响、缓和方式、事后改善措施等。这一点和谷歌云或微软Azure的事故报告有很大的不同,后者有一个结构化的格式,更容易快速获取关键信息。

不过,PES对事故的描述非常详尽,至少这次介绍的这篇总结要比之前介绍的谷歌云事故报告和微软Azure RCA(Root Case Analysis)更详尽。在这点上看,AWS PES可能是更适合学习的事故报告,尤其当我们了解了事故报告里哪些关键信息值得关注、学习和思考。后者可以参考谷歌和微软的事故报告的结构化格式。

除此之外,我觉得AWS PES有一点做得更加比较贴心。PES所使用的语言会考虑事故所影响的地区的当地语言。例如发生东亚地区的事故,除了英文版本,PES页面还有中文、日文和韩文的版本。

了解亚马逊PES#11201


先看看标题,我们能知道这个事故和Amazon Kinesis服务有关。

Summary of the Amazon Kinesis Event in the Northern Virginia (US-EAST-1) Region

Amazon Kinesis是什么?

看这篇文章之前我并不知道,于是我快速学习了一下。

这里我想先分享学习事故报告过程或者学习其他技术文章遇到不懂的技术,我们怎么快速了解这些它们?

我想大多人都能想到“搜索”。这确实是可靠的方法,但搜索的效率因人而异,而且差距可能很大。我的习惯是除了搜索它的基本介绍,还会特地地搜索它的替代品或竞争品,比如对于Amazon Kinesis,我会再看看“Amazon Kinesis alternatives”或“Amazon Kinesis competitors”的搜索结果。

于是,我对Amazon Kinesis有一个很简单的认知——AWS提供的一个实时的数据流处理服务,类似Azure的Event Hub。当然,这还需要自己对一些常见技术有一定的了解,比如熟悉Kafka的人会更容易理解Kinesis的关键技术模型。

由于这篇PES是一篇连一个加粗高亮都没有的朴素的诉述式长文,我们需要通读至少一遍全文,才能知道哪些是要点。

我快速通读一遍后,快速了解这篇PES的内容。按照内容顺序,大致如下:

第一部分是关于事故的梗概,以及和事故根因相关的设计实现。

第二部分具体描述了事故从发生到缓解的全过程,以及事故的根本原因。

第三部分介绍了Amazon Kinesis服务的短期和中长期的事后改善措施。

最后一部分简单介绍了依赖于Amazon Kinesis的上层AWS服务(Amazon Cognito, Amazon CloudWatch,Customer communication services)的事故缓解过程和优化措施。

接下来,我们聊聊这次事故的要点。

事故影响总结


2020年11月25日,Amazon Kinesis服务发生宕机,期间无法正确响应服务请求,导致依赖Amazon Kinesis的第三方服务和第一方服务受到严重影响。

受到影响的AWS第一方服务包括Amazon Cognito,Amazon CloudWatch,Amzon EventBridge,以及Amzon的客户沟通服务。这里还没包括依赖这些服务的更上层服务,比如AWS Auto Scaling,AWS Lambda,ECS(Elastic Container Service),EKS(Elastic Kunbernetes Service),等等。

根本原因


要了解根本原因,原文开始的时候介绍了导致事故发生的根本原因背后的设计。这部分描述的足够简单清楚,于是不做二次解读了,直接节选原文并高亮一些要点了。

Kinesis has a large number of “back-end” cell-clusters that process streams. These are the workhorses in Kinesis, providing distribution, access, and scalability for stream processing. Streams are spread across the back-end through a sharding mechanism owned by a “front-end” fleet of servers. A back-end cluster owns many shards and provides a consistent scaling unit and fault-isolation. The front-end’s job is small but important. It handles authentication, throttling, and request-routing to the correct stream-shards on the back-end clusters.

Each server in the front-end fleet maintains a cache of information, including membership details and shard ownership for the back-end clusters, called a shard-map. This information is obtained through calls to a microservice vending the membership information, retrieval of configuration information from DynamoDB, and continuous processing of messages from other Kinesis front-end servers. For the latter communication, each front-end server creates operating system threads for each of the other servers in the front-end fleet. Upon any addition of capacity, the servers that are already operating members of the fleet will learn of new servers joining and establish the appropriate threads. It takes up to an hour for any existing front-end fleet member to learn of new participants.

看完后,我们发现Amazon Kinesis服务的设计并不是完整的“云”化——后端集群“云”化了(或者说具备multi-tenant特性的),而前端集群还是同享的

而且,每台前端服务器需要在缓存里维护统一的Shard-Map,维护这个Map还需要每台服务器创建系统线程和其他服务器保持通信。构建和维护这个Shard-Map的成本很大,而且前端集群里机器越多机器成本越大。

从分布式设计模式的角度看,这设计有点反模式了——单机瓶颈可能会限制了集群的容量,甚至造成集群的“单点故障”

正是因为这个设计缺陷,那天一次小规模的前端集群扩容(即增加机器)导致了这次事故。还是直接附上原文:

Rather, the new capacity had caused all of the servers in the fleet to exceed the maximum number of threads allowed by an operating system configuration. As this limit was being exceeded, cache construction was failing to complete and front-end servers were ending up with useless shard-maps that left them unable to route requests to back-end clusters

PS:这里的fleet说的是前端集群,而cache construction说的是缓存的Shard-Map的构建。

由于集群机器增加,前端服务器需要创建新的线程,结果总线程数超过了系统配置的最大值然后Shard-Map不仅构建失败而且还返回一个不可用的实例,导致前端无法分发请求到相应的后端机器,Amazon Kinesis服务便不可用了。

由于扩容把整个集群搞宕机了,看到这里的我直呼“活久见”????????????。

一般来说,增加新的机器导致的事故一般是新的机器由于一些问题没配置好环境,进而分发到新机器的请求都失败了,但是一般来说SLB(Software Load Balancer)能快速检测出问题便缓和了事故(当然,前提是给SLB监听的heartbeat服务能检测环境问题)。

我们接着看看当时Amazon的工程团队是怎么发现和解决这个事故的。

事故发生和缓解过程


先从Kinesis服务的角度说说事故发生和缓解的过程(还附上自己的注释式旁白),之后在补充其他受影响的上层服务的处理过程。

Amazon Kinesis


2:44 AM PST:前端集群开始增加新的机器。

3:47 AM PST:前端集群新机器增加完毕。

5:15 AM PST:首次报警。on-call介入,开始看日志。然而,并无法直接定位问题根因源于扩容,因为日志里有大量与扩容无关的错误信息。虽然不肯定删除新增机器能恢复服务,但是还是决定先这样做了,同时继续研究其他错误,根因分析进展缓慢。

// 谨记:出事故的时候,错误日志多并不一定是好事,错误日志精准才是最重要的,否则只是拖延根因研究进展,尤其当错误日志里有大量“非错误”的正常日志。

7:51 AM PST:根因范围缩小,并确定最有可能的解决方式是重启前端集群。为了加速重启速度,做了一个hot-fix,即给前端服务器增加一个配置,以便重启后重新构建Shard-Map直接根据authoritative metadata store构建,而不需要其他机器的通信信息。

// authoritative metadata store是原文原话。根据之前介绍的Shard-Map构建过程(This information is obtained through calls to a microservice vending the membership information, retrieval of configuration information from DynamoDB, and continuous processing of messages from other Kinesis front-end servers),应该说的是前两部分吧,或者之一。

// 如果大家注意到上面原文的一个小细节(It takes up to an hour for any existing front-end fleet member to learn of new participants),应该会明白为什么构建shard-map的时候不参考其他服务器的信息能更快。

// 这里引出一个问题,为什么需要和其他前端服务器保持通信去维护shard-map呢?我想可能是为了动态地维持服务的高可用性吧,例如在后端出现错误结点的时候能更快速做故障切换。

9:39 AM PST:确认了根因,明确问题不是内存压力导致的,而是系统线程数超过了系统阈值。在这个时候,技术人员决定在缺乏足够测试的时候并不去冒险提高系统配置里的最大线程数,继续保持在删除新增机器之后重启。

// 谨记:在事故处理过程中,能恢复原样就尽量恢复原样,而不是冒险做hot-fix,因为后者可能引入新的问题,而且不进行充分测试我们也很难100%确定hot-fix依赖的功能是否真的可用。所以,我觉得Amazon工程团队当时的决定是非常正确的(这篇PES实在太详尽了,近乎啰嗦,这点细节都说出来了。)

10:07 AM PST:恢复了第一组前端服务器,并继续恢复更多服务器,以每小时几百台的速度。

10:23 PM PST:Kinesis服务恢复正常。

我们可以使用TTx(不清楚或忘了的请看之前的文章),来衡量一下Amzon工程团队这次事故处理的“成绩”。

TTD(Time to Detect)28m~1h31m

取决于压死骆驼的最后一根稻草(导致宕机的最后一台新机器),有可能是第一台,也可能是最后一台。我想,大概率是最后增加的机器,否则这个情况下应该会更早报警。我想大概率不会新机器增加完毕后才统一开始刷新Shard-Map,应该一台新机器加到集群后就开始正常运转了。

TTE(Time to Engage):不知道,但时间应该不长。

毕竟这个报警大概率是直接给Kinesis组打电话的。

TTM(Time to Mitigate):19h39m

这么基础的服务,在US EAST-1宕机了近20个小时,这肯定是非常糟糕的情况了,可能也是为什么会发布PES的原因吧。

当我们之后看到事后改进措施时,我们应该有意识地思考哪些措施是为了改善糟糕的TTM的,那些是为了改善有点问题的TTD。

Amazon Cognito


in the early stages:尝试增加容量来增加对Kinesis调用的缓冲区,之后略有改善。

7:01 AM PST:错误率显著提升(坑不住了),工程团队开始做一个改动来减少对Kinesis的依赖。

10:15 AM PST:改动部署,错误率开始下降。(这个时候Kinesis也开始恢复了)

12:15 PM PST:错误率显著下降了 (也许更多是由于Kinesis的恢复)

2:18 PM PST:Cognito服务恢复正常。

在这里就不说Cognito服务是什么了,这里主要是关注Cognito团队为了缓解这个事故做了什么。

由于Cognito服务有对Kinesis请求的缓冲区,所以这个服务能够容忍一定时间段的Kinesis故障,但是故障时间长了缓冲区也坑不住。

他们开始选择了最简单的增大缓冲区的方式——增加机器。我挺好奇“一开始增加机器缓解了影响”背后的技术架构,因为这个听起来也不怎么“云”????。但原文并没有给我机会了解答案。

后来增加机器坑不住了,就开始做改动减少对Kinesis的依赖。原文没提改了什么,我想一般就是单机增加缓冲区容量,或者增加额外的缓冲区存储。其实,做优化去缓解事故并不是处理Live Site的优先方案,我们可以假设当时这个服务的团队受到的业务压力巨大,只能选择这条最后的路。

Amazon CloudWatch


5:15 AM PST:开始产生影响了,并报警。(基本和Kinesis自己的报警时间一致)

5:47 PM PST:开始从指标上看到恢复的迹象(不得不说,这缓解速度够慢的,这时候等了12小时了)

10:31 PM PST:服务指标恢复正常,报警也恢复了。之后开始回填数据了。

从原文的描述,CloudWatch并没有着急而采取hot-fix的方式缓和事故。也许对于CloudWatch的应用场景,丢失写数据可能相对来说不会产生太大的业务影响吧。不过,事后CloudWatch团队还是说要改善缓冲区的存储。

原文还简单提及一些依赖CloudWatch的上层服务的事故过程,内容太杂,在此就不再一一介绍了。

事后改进


先说说原文里提及的Amazon Kinesis服务相关的改进措施。

在短期内,Amazon说会进行以下的改进

第一,将前端服务移到有更大CPU和内存的服务器上,减少集群服务器总数,进而减少事故再发生的机会。// 非常直观的一个改进想法,即有效减少事故再次发生的概率,也有助于提高TTM。

第二,增加更细粒度的线程消耗相关的报警。// 提高TTD

第三,验证增加系统配置里线程数的限制的方法是否能够有效缓和这次的事故,增加未来遇到同类事故的可靠的缓和方式。// 提高TTM

第四,做一系列改动去提高前端服务冷启动时间,比如将缓存Shard-Map放到专门的前端服务器,给一些重要的上层AWS服务配置单独的前端集群分区。我觉得还提高了前端服务的可靠性,至少对于核心的上层服务。// 提高TTM

在中期,Amazon会加速前端集群的单元化(cellularization),从而更好地对不同的使用者进行故障的隔离。

关于单元化,我的理解是去完备地实现Kinesis服务的多租户特性,毕竟现在Kinesis前端集群还是一个共享模式的,和后端集群的组织方式不一样,而且出现问题可能导致影响所有客户端“单点故障”,对于云平台服务而言不可接受。

原文提到,这项工作一直在进行,但尚未成功,还需要较长的时间完成。这点容易理解,毕竟架构的平滑迁移或升级需要谨慎小心,尤其对于现在使用量巨大的服务。

原文还简单提及其他受影响的服务的改进措施。比如,

Amazon Cognito改善缓冲区,以确保能容忍更多的Kinesis请求错误,避免用户侧错误。

Amazon CloudWatch做了更改,将近3小时的数据存到本地的数据存储中,以应对同类事故再次发生。原文说改动已经在US-EAST-1地区部署,之后会逐步部署到所有地区。

// 这是一种典型的渐进式部署方案,尤其对于平台或基础设施服务的新功能。

简单总结


这篇文章讲得比之前两篇要细琐许多,主要还是为了保持原文的风格。

其实,原文细节更多,我已经略过了一些内容,例如对ECS(Elastic Continer Service)和EKS(Elastic Kunberntes Service)的影响,对AWS客户沟通流程的影响和改进,等等。

最后,简单总结一些(我认为)应该要从这个事故中学习的事情:

第一,架构一致性很重要,而架构的不一致性通常会引入复杂的问题。虽然我们无法确保永远做到前者,但对于后者我们需要特外地留意,考虑好它可能带来的代价,并做好充分的DRI准备。

第二,要重视服务日志的准确性,日志噪音太多不利于事故的快速处理。过去我也被一些不相关的“错误日志”误导过,进而耽误了事故的处理。同理,对于报警亦然。

第三,事故发生后,还是要优先考虑是否由最近的更改导致的。事故都是由变化导致的,比如代码变更,运行环境变更,等等。

第四,处理事故,尽可能不要引入新的代码改动。即使看到故障由服务器系统线程数超过系统配置的最大线程数,也不要轻易去修改配置,尤其当过去没有人尝试过修改系统配置的时候。我个人觉得受影响的上层服务临时做的修改还是挺冒险的。

第五,对于下游依赖可能发生的故障或宕机,要提前考虑好应急方案。虽然不一定对于所有的业务架构都容易且需要做备用方案,但是我们应该花时间考虑清楚下游依赖出错的时候服务该如何处理,以及DRI流程该如何进行。

可能你们还有更多收获。如果你们有什么想分享或评论意见,欢迎留言交流????

PS: 点击“阅读原文”可看这篇PES的原文。

最后,耐心看到这里,觉得这篇文章不错的,记得给个点赞、再看和转发分享????

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值