Roman Atachiants · Tharaka Wijebandara · Abeesh Thomas
原文: https://engineering.grab.com/chaos-engineering
译:时序
背景
对每个用户来说,Grab是一个可以叫车,叫外卖或付款的一个APP。对工程师来说,Grab是一个有许多服务并通过RPC交互的分布式系统,有时也可以叫做微服务架构。在数千台服务器上运行的数百个服务每天都有工程师在上面进行变更。每次复杂的配置,事情可能都会变糟。 幸运的是,很多Grab App的内部服务不像用户叫车那样的动作这么重要。例如,收藏夹可以帮用户记住之前的位置,但如果它们不工作,用户仍然可以得到较合理的用户体验。
服务部分可用并不是没有风险。工程师需要对于RPC调用非核心服务时需要有有备用计划。如果应急策略没有很好地执行,非核心服务的问题也可能导致停机。
所以我们如何保证Grab的用户可以使用核心功能,例如叫车,而此时非核心服务正在出问题?答案是混沌工程。
在Grab,我们通过在整体业务流的内部服务或组件上引入故障来实践混沌工程。但失败的服务不是实验的关注点。我们感兴趣的是测试依赖这个失败服务的服务。
照理来说,上游服务应该有弹性并且整体业务流应该可以继续工作。比如,叫车流程就算在司机地址服务上出现故障时仍应该可以工作。我们测试重试和降级是否配置正确,是否熔断器被正确的设置。
为了将混沌引入我们的系统,我们使用了我们的实验平台(ExP)和Grab-Kit.
混沌实验平台Exp将故障注入到处理流量服务的中间件(gRPC或HTTP服务器)。如果系统的行为与期望一致,你将对非核心服务故障时服务会平稳降级产生信心。
混沌实验平台ExP在Grab的基础设施中模拟不同的混沌类型,如延迟和内存泄漏。这保证了每个组件在系统的依赖不响应或响应很高时仍能返回一些东西。它能保证我们对于实例级失败有弹性,因为微服务级别的中断对于可用性也是一个威胁。
配置混沌实验
为了构建我们的混沌工程系统,我们认为需要在两个主要领域引入混沌:
- 基础设置:随机关闭基础设施的实例和其他部分
- 应用: 在较粗粒度引入运行时故障(如endpoint/request级别)
你可以稍后启用有意的或随机的混沌实验:
随机的
- 比较适合‘一次性’基础设施(如EC2实例)
- 测试冗余的基础设施对最终用户的影响
- 当影响面已经十分确定
实验
- 精确度量影响
- 使用实验参数控制
- 对最终用户有限的影响
- 适用于对于影响不十分确定的复杂故障(如延迟)
最后,你可以将故障模式按以下分类:
- 资源:CPU,内存,IO,磁盘
- 网络:黑洞,延迟,丢包,DNS
- 状态:关机,时间,杀进程
这些模型都可以在基础设施或应用级别使用或模拟:
对于Grab,进行应用级别的混沌实验并仔细度量影响面很重要。我们决定使用一个已有的实验平台来对围绕系统的应用级别混沌实验进行编排,即紫色部分,通过对下层像Grab-Kit这样的中间件进行注入来实现。
为什么使用实验平台?
现在有一些混沌工程工具。但是,使用它们经常需要较高级的基础设施和运维技巧,有能力设计和执行实验,以受控的方式有资源手工编排失败场景。混沌工程不是简单的在生产环境搞破坏。
将混沌工程理解成受控的实验。我们的ExP SDK提供弹性和异步追踪。这样,我们可以将潜在的业务属性度量对应到混沌失败上。比如,在订车服务上进行10秒延迟的混沌故障,我们可以知道多少辆车被影响了进而知道损失了多少钱。
使用ExP作为混沌工程的工具意味着我们可以基于应用或环境精确定制,让它可以像监控和部署管道一样与其他环境紧密集成。
在安全上也可以获得收益。使用ExP,所有的连接都在我们的内部网络中,给我们攻击表面区域的能力。所有东西都可以掌控在手中,对外部世界没有依赖。这也潜在的使监控和控制流量变容易了。
混沌故障可以点对点,编程式的,或定期执行。你可以让它们在特定日期的特定时间窗口来执行。你可以设定故障的最大数量并定制它们(比如泄漏的内存MB数量,等待的秒)。
ExP的核心价值是让工程师可以启动,控制和观察系统在各种失败条件下的行为。ExP提供全面的故障原子集,用来设计实验并观察问题在复杂分布式系统发生时的表现。而且,将混沌测试集成到ExP,我们对于部署流水线或网络基础设施不需要任何改动。因此这种组合可以很容易的在各种基础设施和部署范式上使用。
我们如何打造Chaos SDK和UI
要开发混沌工程SDK,我们使用我们已有ExP SDK的属性 - single-digit , 不需要网络调用。你可以看这里对于ExP SDK的实现。现在我们要做两件事:
- 一个在ExP SDK之上的很小的混沌SDK。我们将这个直接集成在我们的已有中间件,如Grab-Kit和DB层。
- 一个专门的用来创建混沌实验的基于web的UI
归功于我们与Grab-Kit的集成,Grab工程师不需要直接使用混沌SDK。当Grab-Kit处理进入的请求时,它先使用ExP SDK进行检查。如果请求“应该失败”,它将产生适合的失败类型。然后它被转发到特定endpoint的处理器。
我们现在支持以下失败类型:
- Error - 让请求产生error
- CPU Load - 在CPU上加大load
- 内存泄漏 - 产生一些永远不能释放的内存
- 延迟 - 在一小段随机时间内停止请求的处理
- 磁盘空间 - 在机器上填入一些临时文件
- Goroutine泄漏 - 创建并泄漏goroutines
- Panic -
- 限流 - 在请求上设置一个频率限制并在超过限制时拒绝请求
举个例子,如果一个叫车请求到了我们的叫车服务,我们调用GetVariable(“chaosFailure”)来决定请求是否应该成功。请求里包含所有需要用来做决定的信息(如请求ID,实例的IP地址等)。关于实验SDK的实现细节,看这篇博客。
为了在我们的工程师中推广混沌工程我们围绕它建立了很好的开发者体验。在Grab不同的工程团队会有很多不同的技术和领域。所以一些人可能没有对应的知识和机能来进行合适的混沌实验。但使用我们简化过的用户界面,他们不需要担心底层实现。
并且,运行混沌实验的工程师是与像产品分析师和产品经理不同的实验平台用户。所以我们使用一种简单和定制化UI配置新的混沌实验来提供一种不同的创建实验的体验。
在混沌工程平台,一个实验有以下四步:
- 定义系统正常情况下的理想状态。
- 创建一个控制组的配置和一个对比组的配置。控制组的变量使用已有值来赋值。对比组的变量使用新值来赋值。
- 引入真实世界的故障,例如增加CPU负载。
- 找到区分系统正确和失败状态标志性不同。
要创建一个混沌实验,标明你想要实验破坏的服务。你可以在以后通过提供环境,可用区或实例列表来更细化这个选择范围。
下一步,指定一组会被破坏的服务影响的服务列表。你在试验期间需要仔细监控这些服务。尽管我们持续跟踪表示系统健康的整体度量指标,它仍能帮助你在稍后分析实验的影响。
然后,我们提供UI来指定目标组和对比组的策略,失败类型,每个对比组的配置。最后一步,提供时间周期并创建实验。你已经在你的系统中加入了混沌故障并可以监控它对系统的影响了。
结论
在运行混沌实验后,一般会有两种可能输出。你已经确认了在引入的故障中系统保持了足够的弹性,或你发现了需要修复的问题。如果混沌实验最初被运行在预发环境那么两种都是不错的结果。在第一种场景,你对系统的行为产生了信心。在另一个场景,你在导致停机故障前发现了一个问题。
混沌工程是让你工作更简单的工具。通过主动测试和验证你系统的故障模式你减轻了你的运维负担,增加了你的弹性,在晚上也能睡个好觉。