轻松应对上千节点失效,去哪儿网混沌工程自动演练实践

朱仕智 2022-08-02 09:14:57

本文根据朱仕智老师在2022 Gdevops全球敏捷运维峰会-广州站〗现场演讲内容整理而成。(点击上方【dbaplus社群】公众号,回复“220617”可获取完整PPT)



 

分享概要

一、混沌工程价值探讨

二、去哪儿网混沌工程平台

三、大规模自动演练

四、故障注入攻防演练

 

一、混沌工程价值探讨

 

因为混沌工程是一个比较新兴的技术,所以不可避免地会面临一个问题:当我们要落地混沌工程时,需要对其进行价值的论证,再决定我们需要投入的人力,以及期望达到的效果。

 

作为技术行业的从业者,我们经常会了解到各大公司出现的宕机的情况,比如去年韩国电信网络的崩溃,以及Facebook服务器宕机的事件,国内各大厂商尤其是云厂商其实都会出现大规模的宕机故障。大部分人看到这些新闻都是吃瓜心态,但是作为技术行业的从业者,这类问题也许某一天就会发生在我们身上,出现在我们的机房和公司里,并且需要我们去解决。

 

 
1、去哪儿网的系统群情况

 

 

接下来我简单介绍去哪儿网的系统群情况。目前线上运行的活跃的应用有3000+个,提供18000+个dubbo rpc的服务接口,有3500+个http的域名注册,有13000+个mq的主题,公司内部有5种语言的技术栈,以Java和node为主,可见其中关系的复杂程度。只要一个地方出现问题,就有可能会级联地传递到c端,那么用户侧就会感受到问题。因此我们应该如何治理这些系统群?混沌工程则是一个非常好的手段,后面我将会介绍如何用混沌工程解决这些问题。

 

 
2、常见故障原因

 

 

在我从业的八九年期间,我们也遇见过大量的基础设施问题,如微服务的注册中心zookeeper经常出现抖动的问题,消息队列出现问题,甚至机房的网络、断电也会出现问题。

 

 

从故障的原因来看,我们可以将其分成几大类:

 

  • 底层的机房、中间件、机器问题影响规模相对较大,机房挂掉时整个机房的相关应用都会受到影响;

 

  • 上层有应用问题,以及整个拓扑里都会有依赖问题。

 

不同故障的原因千差万别,只有拉长时间线才能够发现其中存在着一定的共性,但是短期内很难通过方法解决一切问题,混沌工程则能够很好地使其从被动转成主动的形式。

 

混沌工程主要解决的问题并不是让软件不失败,而是软件在失败的情况下是否还有一定的韧性,是否还能提供高质量的服务,以及能否主动构建失败并提前将其修复。因此我认为混沌工程的目标有两个:

 

  • 一是通过混沌工程,我们可以建立对系统抵御生产环境中失控条件的能力和信心,这需要我们提前处理问题,并产生对应的改进和预案。

 

  • 二是把不确定的东西变确定,实际上无论是质量还是可靠性、稳定性,最可怕的并不是我们能否做什么,而是我们永远不知道我们做完之后是否还有问题。

 

 
3、混沌工程的收益

 

 

我认为混沌工程可以有以下三方面的收益。

 

1)人

 

人的不同角色,如从用户的角度来看,能够得到稳定的用户体验,不会再由于技术问题导致用户体验的不佳,甚至于无法得到服务;从测试的角度来看,能够提前降低甚至有可能阻止故障的发生;从开发和运维的角度来看,可以提升应急的效率。

 

2)流程

 

把一个乱序的具体问题具体分析的流程转化为体系性治理的流程。

 

3)系统

 

面向正确的设计转向了面向失败的设计,为整个系统提供了一定的韧性容灾能力。对于一些基础能力而言,可观测、可灰度、可回滚、可降级都不再是期望,而是实际能够得到的能力。

 

二、去哪儿网混沌工程平台

 

我们首先提出一个问题:混沌工程的最佳实践到底是什么?

 

在业界里大家经常看到的混沌工程,第一反应就是攻防演练,如线上随机地插拔、随机地注入故障,而我个人并不是很认同。混沌工程是把不确定的问题变成确定的,虽然人的攻防也可以一定程度上解决这个问题,但是人的攻防需要投入的时间非常多,而且攻防产生的知识,能够固化、扩展的幅度也是有限的,除非能够把混沌工程的文化在全公司里从上到下执行得非常彻底。

 

从这个角度来看,随机演练不是我们的最终目标,那我们的目标到底是什么?

 

我认为需要分成多阶段的目标,待会我会按照我们的实践路线进行讲解。需要注意的是,功能支持并不等于实践模型。目前很多公司在做混沌工程相关的系统,或者在提供同样的能力。但我认为其中存在很大的偏差,在于提供了能力并不代表达到了混沌工程的目标。因为从实践模型来看,覆盖面、有效率如何,接下来我会具体讲解我们的实践。

 

 

从应用架构层次来看,基本每个公司偏差不大,最底层是机房可用区,往上依次是中间件、服务器层、应用层、依赖层。层次越往下,出问题的概率相对而言越小。在服务治理里超时熔断甚至限流发生频率非常高,但是在机房层面统一的挂掉或断电的概率则小很多。然而,底层的失效概率小,影响面却非常大。应用架构的层次和对应的失效情况,很大程度影响了我们实践的节奏。

 

混沌工程要达到两个目标,并不是一蹴而就的,这意味着我们要做大量的工作以确保机制的制定和生效。应用架构层次是我们对应阶段划分投入的人力时一个很好的向导。当我们一无所有时,应当优先解决影响面大的问题。如果把服务之间依赖关系做得很好,但机房挂掉或中间件挂掉了,整个服务都不可用时,一切都是笑谈。

 

 

因此,从实践路径出发,我认为可以分成四个节奏:

 

1)先要做到机房、中间件、实体机、虚拟机级别的关机演练,尽可能消除大面的影响。

 

2)应用演练在于应用的可用性治理。

 

3)依赖演练在于拓扑合理性依赖的治理。

 

4)攻防演练解决的是人面对失效时,是否有对应的预案策略,预案策略的执行时效性与恢复时间。

 

从左往右的路径其实是一个影响面逐渐缩小的过程。

 

 
1、关机演练

 

去哪儿网的混沌工程平台建设第一步就是关机演练的支持。能力目标上,我们要求同一机房某业务线所有服务节点挂掉时不受影响,那么我们单次演练可能需要有上千台节点的失效。要达到这样的能力,需要直接利用混沌工程现代化的技术并不多,因为关机演练不是针对应用级别,而是针对机器节点级别,它的关键点在于基础信息的整合和整个流程的打通。

 

1)机房聚合信息查询

 

能够发现应用是否符合标准,即当机房挂掉时,应用是否受到影响。其中有两个方面的影响,一是应用只在单机房部署,二是单机房挂掉的情况下它的容量是否足够,从而对应用提前进行改造。

 

2)自动建立沟通群

 

如果整个执行周期非常久,我们需要提前制定计划,将非常明显的问题进行改造,再进行真正执行的进度周知,以及事后的总结。以上过程都需要大量自动化信息的周知,我们在内部的IM体系上有一套沟通和通知机制的方式。

 

3)真实关机

 

测试机器重启和应用重启的时间是否满足要求,如果只是做一个假的失效,演练的恢复速度快并不意味着真正的故障发生时速度也足够快。

 

4)接入告警,告警事件关联推送

 

5)虚拟机开机后关联服务自动恢复

 

我们的能力目标需要大量的节点失效,因此我们无法人工进行恢复,当机房真正出现问题时我们也不可能依靠人工逐个应用排查和恢复,因此虚拟机开机后关联服务自动恢复的功能非常关键。

 

 

关机演练的实现有以下几点:

 

  • 控制维度:我们提供了机房、应用、机器三种级别的筛选。

 

  • 技术角度:没有涉及到现在的chaosmesh或chaosblade等技术,更多运用一些云集群的原生技术,如openstack的API或saltstack,以及一个我们自研的控制面,包含了我们刚才提到的控制、演练、编排的功能。

 

 

关机演练效果:

 

  • 我们执行了49次机房演练,覆盖的机器去重完之后共有4000+个机器,覆盖了500+个应用,每次演练基本上能发现10+个问题。

 

  • 针对单机器级别的关机演练,我们执行了71次,覆盖了3000+个机器和200+个应用。

 

当我们执行真正的关机演练时,如果监控图出现异常情况,就代表机房有问题会影响C端的业务,那么我们就需要将其提前修复。主动发现问题,则能制定对应的修复计划,提前修复问题。

 

 
2、应用演练

 

应用演练的能力目标是对所有应用都可以选择多策略的故障注入,主要解决应用可用性的问题。关键点在于:

 

1)要在线上环境做单应用的可用性演练

 

因为线上环境与测试环境、演练环境存在区别,包括服务治理的参数和对应的配置,进而服务产生的应对现象也是不同的。如果在测试环境做得很好,线上环境由于配置不同,也很可能导致演练得到的结果是错误的。

 

2)可靠的注入工具

 

因为我们要对非常核心的应用做失效情况的注入,可靠的工具对我们而言是一个生命线,如果注入工具本身不可靠,很可能会级联带来很多问题。

 

3)丰富的演练策略

 

4)生效面要可控

 

不能因为演练导致无差别的攻击,导致线上出问题。

 

 

首先要对注入工具做技术选型。我们云平台里的容器和虚拟机都需要支持,要求的场景也比较丰富,我们对开源的要求也比较高,因为需要进行二次开发,最终从选型上我们选择了比较适合的chaosblade。

 

 

接下来对chaosblade做一个简单的介绍。

 

chaosblade支持的场景非常多,有容器级别、操作系统级别以及语言层面的,比如Java的executor。以上是我们广泛应用的,尤其是通过Java的技术,我们对其进行了大量的改造。

 

 

chaosblade的功能基本上涵盖了我们日常通用的中间件场景,比如在应用服务的层次里,一般的rpc框架、网关、消息中心件对于我们来说覆盖面是比较广的,特别是数据库、缓存等都有一定的支持。

 

 

chaosblade基础功能比较完善,但是在企业场景里有一些缺失,比如HTTP超时能力、fullGC和日志拥堵,甚至官方不支持同一个接口的依赖但是不同的调用点等区分,以及比如全链路的匹配。我们把这些能力实现之后回馈给了官方,因此大家现在再去用最新版本的chaosblade则具备了以上企业场景。

 

 

选完chaosblade之后,我们需要进行演练前的准备,首先选定它的资源和策略,在演练进行的过程中把 agent自动化挂载上去,再通过选好的策略自动注入故障,通过流量copy的方式和人工手动触发的case在C端触发两部分进行验证。目前我们已经实现了全链路的场景匹配,因此能够区分用户的真实流量和测试人员的人工流量。在恢复阶段我们有两种恢复方式,一是对我们来说是预防底线的告警或超时恢复,如果都执行完之后没有触发则采取人工恢复,然后卸载agent,最后总结问题并改进完成复盘。

 

 

以上是新建演练效果图,演练时可以选择对应的应用、机器以及策略,编排好之后就可以定期执行。

 

 
3、依赖演练

 

 

应用可靠性不仅受应用本身影响,还会受到依赖的信息服务影响。大家维护所有应用时都会想到:死道友不死贫道,即所有的同行都可以出现问题但我不能。当我们保持这个心态治理服务时,就需要非常关注依赖关系,被拖死、被波及、被传递时,如何解决依赖关系导致的蝴蝶效应;依赖的超时、熔断、异常、限流是否符合我们的预期;以及强依赖太多,能否降级为弱依赖,以上问题都非常关键。

 

因此,依赖演练的能力目标是对应用层的外部依赖进行多策略演练,并且断言它是强依赖还是弱依赖。依赖演练的关键点在于:

 

1)做好应用元数据采集

 

 

如果没有应用元数据的采集,则无法得知应用所有的外部依赖。我们将所有rpc和服务注册相关的、数据依赖和redis以及一些定时任务等信息采集到一起,将通过日志、注册中心、DB元数据或应用暴露出来的元数据,甚至还有trace信息进行汇总,产生了一个最全的应用元数据。

 

2)可视化应用拓扑结构

 

便于我们观测到该应用被谁调了,我调了谁,对于我们做生效面控制非常有效。

 

3)区分不同场景的同一个依赖

 

即刚才提到的不同调用点的同一个接口的依赖。

 

4)标注强弱依赖关系

 

 

将采集到的信息进行分析聚合和存储,并对信息进行强弱依赖关系标注。以上是强弱依赖关系标注的界面效果图,我们能够区分的类型多且细,对应的目标也非常多。

 

 

依赖演练存在一个逻辑的闭环:当我们的服务治理收集了依赖关系之后,需要让用户标注依赖关系,相当于得知用户期望的依赖,我们再进行强弱依赖演练,并且得到演练结果进行修正,查看是否符合用户预期,从而产生对比差距。如用户期望它是一个弱依赖,实际线上执行它是一个强依赖,那么就能产生我们改进的计划集,对其进行修复。

 

 

依赖演练效果:

 

我们执行了1200+次演练,依赖的接口涉及3000+个,我们在去年五一前做的依赖演练发现了136个问题,且问题类型各不一致,如代码编写将一个弱依赖写成了一个强依赖,以及配置问题、容量问题等。

 

 
4、平台架构

 

 

关机演练、应用演练与依赖演练做完之后,我们平台架构如上图。

 

  • 下面的资源层我们支持三种形态:实体机、虚拟机、容器,不同的操作有不同的手段。

 

  • 演练系统里我们也有较为丰富的功能:演练生成、演练编排,以及对应的执行、标注等。

 

  • 上面是应用管理平台和依赖信息平台,还有一些监控等APM之类的支持。

 

三、大规模自动演练

 

虽然我们已经具备了大规模自动演练对应的能力,但是当我们想要做到非常大覆盖面的演练时,我们需要思考一个问题:常态化演练的成本收益比是否合理?

 

如果只是节假日演练或者一年演练一两次,那么就需要权衡投入。当我们常态化演练时,不可避免地要考虑人工成本,正如我前面提到的演练次数里,都需要人工进行一定的参与,那么就会耗费一些人工成本。当我们常态化之后就要考虑自动化,能做到自动化就能替换人工成本。

 

此外,质量类工作的性价比天然要求更高,因为质量类工作不像创造性工作,防御型工作要求覆盖面全,如果在每个点上的性价比不够高,那么全局损耗就会非常大,这对我们来说是一个非常好的指导。

 

自动化要达到一个目标,就是持续可靠,我们要使其常态化地运行和自动地演练,主要涉及到两个点:

 

  • 一是我们希望它常用常新,减少人工成本,并且把覆盖面提升到最大。

 

其中的关键点在于它自动化执行的流量和断言。人工参与的目的就是断言最后结果和制定具体的计划。

 

  • 二是可用的环境。

 

我们将周期性自动演练的机制分成两类:

 

  • 增量演练,每天把新增的依赖全部演练一遍。

 

  • 全量演练,设定周期重复执行。

 

因为我们所有的代码和架构都在腐化的过程中,中间会有很多变更,比如调用点的代码变更,则有可能导致强弱依赖的关系被转化,因此我们必须周期性地进行验证。

 

 
1、自动演练流程

 

 

我们自动演练的流程如上图。

 

第一步是做控制面,可以获取到应用的信息。

 

第二步是对其进行故障的注入。

 

第三步是触发自动化测试,从而产生两份流量,这两份流量会打向我们的基准环境和对应测试环境,测试环境的代码配置等与基准环境是相同的,然后自动化平台会对两者返回的结果做一个断言,进而得到一个结论:当我注入这个问题时,流量是否依然正常。自动化检测完结果之后就能产生依赖关系的判断。如果注入了故障从而产生了问题,那就意味着它是个强依赖。

 

通过自动演练流程,引入了自动化测试平台之后,我们就能做到不需要人工触发流量和判断。

 

 
2、演练结论

 

从效果数据上看,不符合开发预期的依赖非常多,远超大家的想象,在抽样中达到了73%,而符合预期的只占了27%。主要问题有不合理的强依赖、单应用演练口径过严、自动化测试平台覆盖不足、无法完全线上化等。

 

如果是单应用通过接口自动化平台做断言,可能会产生一个问题,就是结论可能过严。比如A依赖了B再依赖C,对于 B调C,可能是一个强依赖,但它并不会传导到用户端,对用户侧可能是没有影响的,只是一个局部的强依赖,那么就会放大这个问题。

 

因此我们考虑是否可以全链路进行演练,经过试验是可行的。

 

 
3、全链路演练

 

 

从信息来看,一个功能入口对应后面的一个过程,过程里的强弱依赖关系是非常复杂的,可能有一条完整的强依赖,也可能有中间部分的强依赖,或者一些是纯弱依赖,这些关系决定了我们的方案。

 

 

与刚才说的单应用断言不同,做全链路断言需要引入一个全链路压测的演练系统。我们内部有一个全链路压测平台,能够做到用例自动生成、数据隔离,且执行成本非常低,比如执行完一个机票业务线所有核心场景可能只需要半天,相当于我们只需要0.5pd的人力就能覆盖上千个应用。

 

 

从断言来看,我们有一个断言的逻辑:当入口产生调用流量时,我们就会对它进行一个断言,观测我们的核心指标尤其是用户侧的核心指标是否存在问题,如果存在问题,我们则会中断演练,并且记录对应的结果;如果不产生对应的报警,我们还会从另外一个角度看是否对C端的功能有其它影响。我们会对人工主动标注的告警事件和系统自动分析的雷达事件进行统一,进而得到一个结论,就是当全链路注入问题时,对于入口来说是一个强依赖还是一个弱依赖,这是断言的逻辑。

 

 

从流程来看,与刚才的自动演练流程有点相似,只是把接口自动化的测试平台转化为全链路压测平台,但是它的断言与自动化的断言逻辑不同。

 

 

需要注意的是,我们需要对链路上的命中率做一定的优化。

 

命中率是指从入口发下去的流量是否一定会经过我们想测试的依赖,比如我想测e系统打到g系统的调用,入口的流量发出来不一定都能达到e的依赖链路里,它打到了b系统,再打到e系统,再打到了f系统,这就需要一个非常有效的机制提前发现,演练的流量与我们预期的依赖是否达到。这需要依赖到其它大量的信息,如果我们没用精准的方式,而是随机地挑选入口流量,那么链路上的命中率只有40%左右,我们希望能够将命中率提到90%以上。

 

我们针对命中率做了一个精准的策略:利用依赖关系已经产生的实际数据,即线上APM的trace数据反查能够经过链路的请求条件和流量,比如当我们想要找e和f的调用链路时,可以从b系统入口打到e系统再打到f系统,这就是我们想要找的其中一个链路,再逆向给出一个结论,从而得到对应trace的入口信息,再得到入口的请求条件信息,通过请求条件做用例的自动构造,就能得到一部分非常有效的用例,从而把我们的命中率提升到了90%以上。

 

全链路自动演练的效果来看,目前覆盖了去哪儿网的55个核心入口和80%以上的核心应用,仅剩最后的人工成本,只要对汇总的报告进行最终的分析,产生一个节假日混沌工程的报告即可。

 

四、故障注入攻防演练

 

第四个阶段是故障注入攻防演练。攻防演练的主要目的在于提升人面向失效条件时的处理速度,包括预案和问题。

 

  • 首先通过各个系统的开发处理失效条件很难积累经验。

 

因为一个开发真正能遇到线上故障的概率并不高,公司成百上千人的研发团队会对故障进行稀释,因此人为的经验很难积累,那么我们只能提前创造机会,当他真正面临线上问题时才能给出解决方案。

 

  • 二是故障原因种类繁多。

 

  • 三是没有预案。

 

  • 四是恢复和验证比较困难。

 

 

2020年,我们故障处理时间的中位数是54分钟和39分钟,2021年也是51分钟,因此在线上的真实故障里处理的时间还是比较长的。

 

 
1、攻防演练流程

 

 

我们设计了一个攻防演练的流程:

 

流程依赖于大量的基础设施系统,包含故障平台、混沌工程平台、监控告警平台、日志平台、trace平台等。首先做攻击点的规划,汇总历史故障原因,优先把出现次数多的原因规划为对应的攻击点;其次故障随机注入,注入点与时间随机,不同的接口依赖不提前告知开发;然后监控告警触发,防守方排查问题,需要上报对应的结论、耗时等排查结果;最后对其进行一定的计分公式,复盘分析。

 

 

故障注入流程与全链路依赖流程相似,只需要把流量真正注入即可,不需要断言它的强弱依赖。

 

 

从积分例子可以看出,大部分问题1~3分钟即可解决,通过这个方式演练能够有效提升排查和处理问题的速度

 

 
2、攻防演练的关键点

 

1)培养混沌文化

 

混沌工程要做对应的宣导,开发对于攻防演练是非常关心的。

 

2)时间和策略随机

 

3)抹除信息干扰

 

比如不能在异常栈里暴露注入的信息,不能出现chaosblade等对应的关键字,以及一些流量标识也需要抹除。

 

Q&A

 

Q1:如何确定应用级灾备的演练和验证范围?

A1:应用级的灾备需要多可用区,确保每个应用不能只部署在一个可用区,在信息聚合时可以提前发现并改造。改造后真实演练就是分享中提到的机房大规模关机演练,从云平台元信息中能够方便地查询出某业务线所有应用实例,然后进行真实关机演练。观测对应的核心业务指标,如果业务正常,说明灾备、容量的情况满足。对于验证范围,单应用的演练只需要在当前应用接口级别测试即可,方式可以多种,比如接口自动化测试、人工触发用例测试。

 

Q2:越底层的故障影响越大,请问您有没有用混沌工程做过底层的网络故障?

A2:底层网络故障我们主要通过大规模关机演练替代,因为在交换机和机器上的网络通信失败并不是很可怕的事情,在问题点上恢复后影响基本也就自动恢复了。如果实在有必要演练网络问题,可以进行交换机拔线测试。

 

Q3:混沌工程在推进过程中,如何衡量业务层面的需求?

A3:业务层面需要区分不同角色:对于业务负责人来说,混沌工程的结果需要利于业务,比如保证用户体验、保障业务平稳运行;对于技术负责人来说,混沌工程能够提升人员处理线上问题的速度;对于稳定性保障人员,例如QA,混沌工程能够让不确定性转化为高确定性。此外,如果问题中的需求是指混沌工程推进跟业务项目有人力冲突,可以参考分享中的实践路径,机房关机演练、应用演练、依赖演练其实并不太多的人力资源,攻防演练则需要给业务需求项目让步。

 

Q4:关键系统的混沌工程实验思路有何不同?

A4:在我们实践过程中,关键系统并没有特殊对待,一切都要追求高效和高覆盖。对于关键系统也是真实关机、真实注入、真实线上环境。

 


活动预告