快速发现和定位问题的能力是快速恢复系统的基石,只有先做到快速发现和定位问题,才能谈如何解决问题,尽量减少用户损失。那么如何在复杂的大规模场景中,做到真正的先于用户发现和定位问题呢?我会将我们在管理大型 Kubernetes 集群过程中快速发现和定位问题的一些经验和实践带给大家——我们是如何通过自研通用链路探测+定向巡检工具 KubeProbe 应对遇到的大规模集群的稳定性挑战的。
链路探测: 模拟广义用户行为,探测链路和系统是否异常
定向检测: 检查集群异常指标,发现未来存在或可能存在的风险点
系统增强: 发现问题提速增效,根因分析
发现问题之后: 后置检查和自愈,Chat-Ops
01 业务背景和挑战
Cloud Native
阿里云云原生应用平台的容器服务团队,拥有 ACK 、ASI 等产品,管理了大规模的 Kubernetes 集群,不仅向外部公有云用户提供 Kubernetes 服务,还承担了阿里巴巴集团上云,阿里巴巴应用全面容器化的工作。
目前,整个阿里巴巴的业务都跑在 Kubernetes 集群上并实现了云原生和容器化,例如:天猫/淘宝/高德/考拉/饿了么等等。容器服务作为阿里云的管控底座,各种云服务也运行在这些集群之上,例如视频云/dataworks /MSE 微服务引擎/MQ 消息队列等等。我们需要对这些基础设施的稳定性负责。
现在,云原生的架构越来越流行,越来越多的产品和应用开始选择云原生架构,这里有一张图,大致示意了现代的云原生应用架构,应用生于云上,长于云上,各级提供分层的服务,这种分层的服务能够让业务和应用专注于业务层,屏蔽平台和基础设施层的复杂概念。
从稳定性的角度来讲,这种应用的架构分层,上层应用的稳定性就会开始依赖底层基础设施的支持;另外,大一统的基座既为大规模的资源调度优化和在离线混部提供场景,也对基础设施团队维护大规模集群的稳定性问题提出极大的挑战。
这里有两张形象的图示可以展现出云原生应用架构下的业务应用和平台层基础设施的关系,Kubernetes 集群是非常复杂的,一个单集群的链路组件就有数十个甚至上百个之多,何况是大规模的多 集群管理呢? 但运行在上层 的业务同学 并不会感知到 复杂,因为我们已经把复杂包掉了,留给用户的是一个简单的统一接口 。 就像 淘宝这 样的应用其实是非常复杂的,但在 用户看 来只 是一个简单的提交订单而已,按键背后蕴含着 极其复杂的内容。 为什么做到这样? 因为我们把复杂留给了自己,把简单交给了用 户。
很多时候,好的应用开发者不一定是基础设施专家,云原生让业务专注业务,基础设施专注基础设施。同时,业务很多时候也只能关心业务自身的稳定性,业务大多数时候没有能力关心,或者是不希望投入大量的人力关心基础设施和平台层的稳定性,所以,关于平台层和基础设施的稳定性问题上,我们需要把复杂留给自己,把简单留给用户,为用户提供稳定的平台层服务。同时,更加关心全局稳定性和全局的可用性,而不是单点可用性。
容器服务是阿里巴巴集团业务以及阿里云管控/云服务的底座,上面跑着各种各样的业务,如电商业务/中间件/二方业务/搜索/阿里云云服务等等。此外还有数百个自研和开源的组件,每年数十万次的组件变更/数千个集群/数十万台节点,甚至大的集群单集群节点规模已过万。业务架构更是纷繁复杂,有单租集群、多租集群、vc 集群、联邦集群等等,同时还有各种在离线混布、统一调度、大促活动。在运行时也存在多种形态,如 runC,runD 等等。
因此组件的繁杂、变更频繁、用户场景各异、集群规模庞大、业务架构复杂……都给业务带来了挑战:
挑战一:如何降低系统风险。 场景复杂,业务形态各异,任何一个不起眼细节的遗漏或环节的处置不慎都有可能导致伤害的扩大化;
挑战二:如何对用户集群的稳定性负责。 如何先于用户发现和定位问题成为容器服务生产稳定性建设的重中之重,也是全局高可用体系的基石。
系统是如此的复杂,任何一个不起眼的细节遗漏或处理不慎都有可能导致非预期的伤害,我们要怎样才能降低系统风险呢?另外我们又是如何对形态各异的用户集群运行时全局稳定性负责的呢?如何才能先于用户发现和定位这些集群中已经存在或即将发生的问题,是保障集群的稳定性建设的重中之重,也是 Kubernetes 全局高可用体系的基石。
02 思考和方案
Cloud Native
基于这些挑战,我们做了一些思考和预设。下图是一个极度简化的用户发布扩容链路,虽说极度简化,但实际我们仍可以看出,链路还是比较复杂的。
为了保障这次用户的扩容/发布链路畅通,我们首先带来几个预设:
预设 1: 链路复杂组件众多,各组件分别升级迭代,数据监控无法无死角覆盖全部场景;
预设 2: 即使链路中各组件/节点监控数据正常,也不能保证集群整体链路 100% 可用,只有经过实际业务全链路探测才能确定实际可用的结论;
预设 3: 反证法在证明集群不可用场景一定优于举证法,即使 100% 监控数据正常,但只要发布失败则证明链路不通。
另外,在单集群之外,我们还要关注多集群的管理,下面是一些多集群管控中的不稳定性因素示例,可以看到,多集群场景下,稳定性管控的复杂度会被放大,我们继续带来几个预设:
预设 4: 在大规模集群场景下数据一致性的问题会愈加显现,并且可能引发严重故障,成为一个显著的不稳定因素;
预设 5: 集群内的监控告警链路存在自依赖风险,如果集群故障,则监控告警也有可能同时故障。
接下来是我们基于以上预设的一些解决方案。
探索和解决方案
1. 链路探测
链路探测即模拟广义上的用户行为,探测链路是否畅通,流程是否无异常。
想要做到先于用户发现系统问题,我们自己首先要成为系统用户,并且是使用最多、了解最深、无时无刻不在使用和感知系统状态的用户。
所谓链路探测,就是模拟广义上的用户行为,去对集群组件链路中的各种等待探测的对象去做探测。此处要特别说明的是,这里的用户并不仅仅指的是狭义上使用系统的同学,而是更广义的用户,或者可以理解和引申成为依赖下游。
另外,在实现全链路探测的同时,拆解电路,实现全电路中的短路探测也是非常必要的,也是对全链路探测的一个补充。
2. 定向巡检
定向巡检是指检查和分析大规模集群的异常指标,找到已有或将来可能存在的风险点,就像检修管道一样。
例如有若干个集群,它分为很多集群组,不同集群组之间的 etcd 冷/热备是否配置齐备,风控限流配置是否正常,webhook 版本是否正常,混部参数是否一致,包括它的证书有效期是不是快要到期了等等。不同的集群组之间可能有所差别,但同类型集群之间是有一个转衡的,因此我们可以定向做一些巡检。
接下来是关于链路探测的一些常见场景:
就像一个游戏策划,如果他连自己制作的游戏都不玩,他可能发现游戏机制的问题,把这个游戏越做越好吗?我们要做到先于用户发现系统问题,那我们自己首先就要先成为系统的用户,并且一定是使用最多的,了解最深的,无时无刻不在使用和感知系统状态的用户。
另外,所谓链路探测,就是让自己成为自己系统的用户,模拟广义上的“用户”行为去对集群/组件/链路里的各种等待探测的对象去做探测。
一定要注意,这里的“用户”并不仅仅指的是狭义上使用系统的同学,而是更广义的用户,或者可以理解引申为依赖下游。
例如业务同学要发布业务,就必然要经过 git 系统,再到发布系统,再到我们底层的基础设施平台,也就是我们的 ASI,这就是一次全链路探测流程。在这里业务同学就是用户,探测对象可以是全链路。
但如果我们把 etcd 看作一个系统服务,那么 APIServer 就是它广义上的用户,我们模拟 APIServer 请求 etcd 这条链路的探测也就有了意义。
另外像 MSE 操作 zookeeper,外部用户通过阿里云控制台创建 ACK 集群,PaaS 平台操作联邦集群,甚至视频云业务方发起一次转码任务,都是一样的道理。
还有一点要关注的就是,虽然全链路探测看起来很美,但很多时候,全链路探测同时还很长,可能等到失败的时候问题已经很大了。所以,在实现全链路探测的同时,拆解链路,实现全链路中的短链路探测也是非常必要的,也是对全链路探测的一个补充。
上图是定向巡检的场景,相比链路探测关注于链路可用性,定向巡检的核心还是在大规模的集群场景下,数据一致性是非常困难的问题,数据不一致,将导致一些隐患,可能会在未来引发某些不确定性的故障。
所谓定向巡检就是对整个集群或链路中的各项数据、指标做已知原因的检查,找出不一致或数据偏离的点,判断是否可能引发风险,从而做到防患于未然,治未病。
比如我们这个里边有同一种类型的集群组,A 集群发现它的证书有效期不到三年,而其他集群的证书有效期都有三年;B 集群的 webhook 版本可能是 v2,而其他集群的 webhook 版本是 v3;C 集群的风控限流配置并没有配一个驱逐 Pod 的限流,但其他集群都配配置了驱逐 Pod 的限流,这肯定是不符合预期的;再比如 D 集群的 etcd 的冷/热备没有配置或者是运行不正常,我们也可以先把它检查出来。
03 系统实现
Cloud Native
基于上面许许多多的背景预设以及方案,我们设计并实现了一套巡检/探测平台,我们取名为 KubeProbe (并未开源,和现在社区上有类似命名的项目没有任何联系)。
我们早期也曾考虑使用社区项目 Kuberhealthy,并为 Kuberhealthy 做过一些代码贡献,修复过一些严重的代码 Bug,最终因为功能上不太适用于我们的场景,我们选择了自研自建。
上图是一套中心架构,我们会有一套中心管控系统。用户的用例会通过统一仓库的镜像的方式接入,使用我们通用的 sdk 库,自定义巡检和探测逻辑。我们会在中心管控系统上配置好集群和用例的关系配置,如某用例应该执行在哪些集群组上,并做好各种运行时配置。我们支持了周期触发/手动触发/事件触发(如发布)的用例触发方式。用例触发后会在集群内创建一个执行巡检/探测逻辑的 Pod,这个 Pod 里会执行各种用户自定义的业务巡检/探测逻辑,并在成功和失败后通过直接回调/消息队列的方式通知中心端。中心端会负责告警和用例资源清理的工作。
我举一个例子,比如 Kubelet 在我们的组件运维平台上做分批发布,每批次都会触发一次相关集群的链路探测用例作为后置检查,一旦我们发现某次发布的后置检查失败,我们会阻断掉用户的当前发布,防止伤害扩大,同时第一时间告警以及通知相关同事进入排查,是否组件新版本不符合预期。
同时,我们也支持第三方的事件回调,可以更快的集成进三方系统中。
另外,我们对于某些需要 7*24 小时不间断的高频次短周期探测用例,我们还实现了另外一套常驻分布式架构,这套架构使用一个集群内的 ProbeOperator 监听 Probe Config CRD 变化,在探测 pod 中周而复始的执行探测逻辑。这套架构,完美复用了 KubeProbe 中心端提供的告警/根因分析/发布阻断等等附加功能,同时使用了标准 Operator 的云原生架构设计,常驻体系带来了极大的探测频率提升(因为去掉了创建巡检 pod 和清理数据的开销)基本可以做到对集群的 7*24 小时无缝覆盖,同时便于对外集成。
另外还有一个必须要提的非常重要的点,即平台只是提供了一个平台层的能力支持,真正这个东西要起作用,还是要看在这个平台上构建的用例是否丰富,能不能方便的让更多人进来写各种巡检和探测用例。就像测试平台很重要,但测试用例比测试平台更重要这个道理一样。一些通用的 workload 探测,组件探测,固然能发现很多管控链路上的问题,但是更多的问题,甚至业务层的问题暴露,实际上依赖于基础设施和业务层同学的共同努力。
从我们的实践上来说,测试同学和业务同学贡献了很多相关的检查用例,比如测试同学贡献的 ACK & ASK 的创建删除全链路探测巡检,金丝雀业务全链路扩容用例,比如本地生活同学的 PaaS 平台应用检查等等,也得到了很多稳定性上的结果和收益。目前我们维护的巡检/探测用例有数十个,明年有机会破百,巡检/探测次数近 3000 万次,明年可能会过亿。目前可以提前发现 99%以上的集群管控问题和隐患,效果是非常好的。
04 发现问题之后:根因分析和事件处理
Cloud Native
接下来我们聊聊发现问题之后的事情,这里有一个类似于问诊对话的例子,患者发现 “哎呀我不舒服了! ” 这就是发现问题。医生参考各种化验单,同时做了信息聚合分析推断,告诉患者“你已经 24 小时没睡觉了,你睡不着是因为你很焦虑,你焦虑的根因是因为后天就要期末考试了。”这便是定位问题根因,然后针对根因去解决这个问题,他告诉患者“不要担心,我刚收到的消息,小学生已经不需要期末考试了。”这个过程一定要快!
来自探测链路的告警内容往往是混沌的,和数据监控告警是有所差异的。就像上文提到的,链路探测告警的告警很可能就是一句患者的我不舒服了,需要你作为医生去判断,为什么他不舒服了呢?根因是什么。而数据监控很多时候本身就代表了原因,比如 Etcd OOM,用已有的 oncall 经验可能得不到最好的效果。
另外快速定位问题和根因分析,是一个树状的搜索,经验加工判断的过程,也就是如何从一个混沌的表象推断出根因,核心是逻辑。
这和健康体检是不同的,健康体检是列出检查项 1,2,3,4,5......然后告诉你一堆数值。很多时候,即使存在体检中心,我们仍然也需要医院的专业医生来为您解读和判断病情,不是吗?
同时,根因分析/问题自愈的关键在于专家经验的下沉,也就是把专家经验下沉到系统中去,专家经验的下沉带来的最大收益是可复用可输出。你可以想一下,如果我们把一个最专业的医生的能力放进系统里,他是不是更方便的为每一个人分析病情呢?
这便是 KubeProbe 发现问题之后的全流程,我们首先会经过一个我们自建的中心化根因分析系统,在这里我们会聚合分析所有和本次失败相关的信息,包括事件/日志/变更/告警/组件升级等等,我们将这些信息进行聚合分析,并对事件做关联处理,最终通过一个树状的分析系统初步定位出某次探测失败的原因,比如说 APIServer 超时或者 etcd 断连等等。
此外我再补充一点,文本联想也是一个很好的根因分析方式,我们可以通过机器学习训练文本识别的方式来联想出和这种失败 case 最关联的根因,这种 AIOps 的工作我们只是略微涉及,还在持续的探索中,我们的数据量非常大,我认为这一定是未来的方向之一。
KubeProbe 根因分析和后置处理全流程
上图的左下方是某次我们失败的告警,它经过根因分析系统之后发现首先最核心,最关联,最大的原因可能是 APIserver 的连接断开并且当前已经恢复,所以可能只是偶发的网络抖动,我们暂时不用特别关注,但此时可以看到置信度为 90%。
另外还有一些可能的原因都会关联起来。比如某个组件,这次探测它是由某一个组件发布出发的,它的发布人是 XXX,我们可以观察这个发布对 API server 会产生某些影响,是否多次 list watch 不符合预期,然后把 API server list watch 出问题了,置信度有 50%。
当我们得到一个初步的原因之后,我们会进入二次确认系统做二次的原因确认,比如我们判断原因可能是 APIServer 超时/etcd 断联/节点超时等,我们就会自动重新拉取一下 APIServer 接口,看一下此时是否仍然超时,是否恢复,如果恢复了,我们就普通告警,并且告诉用户,现在没事了,但是你得关注。如果没恢复,那这就很严重了,属于最高优先级,直接电话告警。
就是这个思路,如果有系统无法定位的问题,并且持续无法定位,我们也会触发高级别告警,并且会增加相关的根因分析识别树逻辑。
过多的告警等于没有告警,我是最讨厌告警海的。从经验上讲,当我们构建了一套这样的根因分析+二次确认+后置检查系统之后,我们的 Oncall 成本下降了 90% 以上,并且还能够持续下降,终态可以说是无人值守,大家也可以试试类似的工作,可以说是投入小,见效大。自从这些系统建设起来以后,我们可以自豪的说,我们用很小的精力 Oncall 了每一个告警条目(对,是每一条告警,是数千个集群,数千万次探测巡检的每一条告警)并且不会有任何遗漏了。
最后是一些给 Oncall 人员的小甜品,Chat-ops。
基于 NLP 语义识别的 Chat-ops 系统
我们利用钉钉提供的 NLP 机器人,构建了一套比较完善的 Chat-ops 系统,这样之后我们的 Oncall 人员就可以很方便的在告警群里通过聊天的方式操作 KubeProbe 相关功能了,比如:重跑失败探测,查询集群状态,拉取诊断信息,查询探测日志,集群告警静默。
上图是我们操作 Chat-ops 系统的过程。这个过程非常方便。
比如晚上我已经再被窝里了,这时候它给我了一个告警,比如某个集群之前出现了某次失败但当前已经恢复了,需要我关注一下。
既然我关注了,我便希望某一个常用例再跑一次(它可能周期比较长,例如一个钟头),由于短链路的用例可能随时都在跑,此时我便告诉机器人再跑一次,机器人就会识别我的语义,将集群再跑一次。跑完之后,我再通过查询状态看一下这个集群当前的状态怎么样了,这样是非常方便的,有时候你晚上上班了,或者是在路上,或者是在被窝里,都也可以很舒服的去 on-call 一个系统了。
05 Demo 示例
Cloud Native
1、发布
2、探测列表
3、探测 Pod 开始运行
4、探测结果
5、根因分析&告警
6、Chat-ops
原文地址:https://mp.weixin.qq.com/s?__biz=MzUzNzYxNjAzMg==&mid=2247522591&idx=1&sn=4c5d02880d2b0337d78ecbb40b95cf67&utm_source=tuicool&utm_medium=referral