混沌工程的核心思想是通过不断地失败来避免失败,以主动制造故障的方法来宏观地验证业务的容灾和恢复能力。本文讨论了携程在实践混沌工程以来的想法和方案,希望能带来一些参考和启发。
我们对故障何时会发生一无所知,而故障也无可避免地一定会发生。
这几年,携程业务和技术架构在不断地快速演进,给服务可用性提出了很大的挑战:系统的宕机成本越来越高,用户对故障的容忍度越来越低。在这样的背景下,我们该如何保持稳定的用户体验,如何降低重大故障带来的各种有形和无形的损失,如何提高团队的整体应急响应能力。这些都是摆在我们面前亟待解决的问题。
以往的很多技术改造多由故障驱动,即在故障发生后进行COE(Correction Of Error)分析,找出根因进行技术、流程等方面的优化升级。但是,这种事后的措施并不能弥补已经产生的业务影响。所以,更需要一种主动攻防、主动演练的方法将故障带来的痛苦提前,提早识别系统中存在的脆弱点,使风险在可控的范围内尽早暴露并得到及时干预,减少真实故障带来的损失。同时,在这种持续验证系统容灾能力的过程中也可以锻炼工程师们的技术水平、作战能力,增强抵御风险的信心。所以,我们从去年开始通过混沌工程来应对服务高可用性提出的技术和组织层面的挑战。
携程目前有上万个应用服务,每周有数千次的生产环境发布。微服务架构提高了开发效率和灵活性,同时也引入了更多的复杂性和不确定性。任何一个服务出现异常,都有可能产生全局影响,在这样的架构下,想要完全消除系统故障已经成为不可能。
我们认为, 一个好的系统并不是指没有故障,而是在故障发生的时候能准确地出错重试、能有效地限流降级熔断。一个好的系统需要具备足够的弹性和韧性, 而如何通过主动地实验来证明生产环境下各系统在面对失效条件时,依然具备韧性和快速恢复能力就是混沌工程实践的方向。
我们将这个方向结合混沌工程落地的5个原则(可参考《混沌工程:Netflix系统稳定性之道》),规划了混沌工程在携程落地的Roadmap。
在混沌工程的实践初期,我们开始建设故障演练平台。此时,平台的目标是需具备常见故障的注入能力。我们分析了在真实环境下发生过的、形形色色的故障事件,对这些故障的触发原因进行降维,沉淀为故障场景。
这些场景可以抽象为五大类:访问入口类、应用类、数据类、系统和网络等基础设施类。这些抽象的故障场景即为需要实现的故障注入能力。
上图中列举了在真实环境中较频繁出现的故障事件:网络层的丢包、延时增加、网络中断等;系统层的实例宕机、CPU高、内存高等;应用层的响应延迟、OOM等。我们可以利用Linux下的TC命令来模拟各种网络层的抖动,通过ASM、JAVAAssist等JAVA字节码技术来模拟应用层的故障,Kubernetes平台上也有很多原生的方法来支持3-7层的故障模拟。另外,业界的很多公司都先后开源了一些混沌工程项目,例如Netflix的Chaos Monkey、阿里的ChaosBlade、PingCap的ChaosMesh等。
基于这些故障场景,我们在生产环境进行了一系列的演练,例如核心工具的Set化演练、非核心服务的降级演练等。下面用两个实验来说明演练过程:
实验一:核心应用对点评服务的弱依赖验证 目的 :验证弱依赖的下游应用故障时,度假产品详情应用是否会因为没有配置正确的熔断或降级策略而被拖垮,产生线程池打爆、full GC等服务不可用的现象。 稳态 :产品详情应用QPS 1000,RT 300ms,点评信息显示完整 故障场景 :点评服务出现响应延迟 结果假设 :产品详情应用熔断对点评服务的访问,产品详情页可正常显示其他信息,如下图。 实验方法 :对点评服务注入客户端定向响应延迟,即只有指定的上游产品详情页感知到下游延迟,其他上游应用不受演练影响。
实验二:核心工具的Set化演练 目的 :验证核心工具Set化能力,即当一个数据中心发生宕机故障时,监控工具、灾备工具、发布工具等核心工具在另外一个数据中心能正常提供服务。 故障场景 :IDC-2内的核心工具全部宕机 结果假设 :IDC-1内的监控工具、灾备工具、发布工具可提供完整服务 实验方法 :飞行演练,不提前通知故障注入目标。实验时,对IDC-X内的核心工具及其依赖服务执行实例关闭。 观测方法 :核心工具健康看板,如下图。
目前,我们已经在生产环境进行了数百场演练,故障注入的对象有数万个。在演练的实践过程中,我们摸索出落地混沌工程的两个关键因素:
首先是 混沌工程的接受度 。混沌工程并不仅仅是解决技术问题,同时也是一种面向失效设计文化的建立过程。过去,程序员们每天在产品经理的狂追猛打下需要完成各种功能性需求,而很多非功能性需求会被妥协。当真实故障发生的时候,往往束手无策。所以只交付功能是不够的,构建功能的同时必须考虑高可用性、可扩展性、安全性等非功能要素。
要达成这个目标,单靠流程制度去约束是不够的,必须有一种外部力量去推动功能性需求和非功能性需求的平衡。而混沌工程就是这样一个外部的力量,当我们不断地在运行实验时,程序员们不得不思考如何让自己开发的服务在各种混沌实验场景中活下去。程序员们需要理解的是——不论是否实施混沌工程,系统的隐患或Bug 都客观存在。实验是使故障及早暴露在可控制的爆炸半径内,避免真实发生时影响生产业务。
其次是 故障演练平台的成熟度 。在一个无时无刻不在变化的生产环境中,想要长期维持较高的可用性,必须常态化演练。而故障演练平台的易用性和成熟度是保证演练长期运转的基础。
实验操作需要简单直观,在系统架构感知、异常检测、监控报警、故障处理方面也需要更为准确和及时,从而降低混沌实验的风险和操作复杂度。随着混沌实验的深入,我们对故障演练平台做了升级,从初期的“覆盖尽可能多的故障场景”演变为“面向混沌实验的全过程”,最终演变为目前的CMonkey, 如图。
Cmonkey支持用户通过WebUI的方式自助创建和运行实验,也支持以OpenAPI的方式集成进CI/CD的持续交付的pipeline中,便于进行自动化演练和随机演练。混沌实验逻辑层中对实验模型进行了抽象,方便用户理解和编排实验场景,还实现了权限控制、各种条件组合的并发注入、一键紧急熔断、Webhook回调,演练覆盖度管理等功能。
底层的故障注入层丰富了混沌实验场景,例如JVMExecutor是通过字节码增强技术对运行在JVM上的应用进行故障注入的执行器,可模拟应用调用延迟、数据库访问错误、指定方法抛出异常等SaaS层故障,Saltstack Executor可以对目标主机执行CPU满载、网络延迟、限流等故障注入。
随着混沌工程的推进,我们意识到依靠人力去完成每个混沌实验必然面临高昂的人力成本和发展的局限性。在测试环境和生产环境中,如何进行设定场景的自动化演练成为近期的工作目标。
想要达成这个目标,需要开展的工作有:
1)基于当前的架构感知能力,完善应用间的强弱依赖梳理。强弱依赖的不合理是在历次演练中被频繁暴露的问题之一。通过APM埋点和人工标注得到的应用-应用、应用-数据层的强弱依赖关系需要以演练的方式被验证。
2)强化监控告警的准确性。海量而缺乏业务关联性和机器解释性的监控数据、过于敏感和过于不敏感的告警都会导致自动化演练过程中止损条件的迟钝或失效。目前,携程的业务监控平台和系统监控平台已覆盖大多数监控场景,同时也会按照一定的规则(如同环比法、LSTM预测算法、固定阈值等)进行异常检测。而通过AIOPS来提高异常检测的准确性和实时性是需要继续探索的方向。
3)修炼智能故障诊断。通过故障演练为智能化的故障诊断提供特征学习和模型训练,而智能化故障同时又可以为故障演练提供精准的止损输入和问题定位,降低自动化演练的复杂度,这是一个互相促进、相辅相成的过程。
在落地混沌工程的过程中必然会遇到很多的挑战和不理解。如果想要获得支持,尤其是自上而下的推动,必须要坚持原则,具备可靠的策略来管理混沌实验的破坏半径,确保这些实验的确能改善薄弱环节,并且具备扎实的业务价值。所以,混沌工程的核心不在于如何制造故障,而在于 如何通过有效的控制手段使问题暴露出来 。
混沌工程是一种方法论,本身并不是一种技术或一个工具。但是,做好混沌工程必须有合适的平台和工具,也需要很多周边系统的支持,例如全面的监控,智能的告警,链路跟踪、架构感知,快速的故障定位。
最后,要做好混沌工程 必须不畏惧失败 ,能悲观地想象各种风险和隐患,并谨慎、乐观地探索和验证,用不断地失败来避免更大的失败。
作者介绍:
Ctrip SRE,负责携程网站系统可靠性保障,探索和落地高可用体系的运维架构,如多活容灾、全链路压测、混沌工程、AIOPS等。
领取专属 10元无门槛券
私享最新 技术干货