基于服务网格的微服务故障治理

2021-09-14 02:33李铭轩
信息通信技术 2021年4期
关键词:调用实例容器

赵 鑫 李铭轩

1 北京邮电大学 北京 100876

2 中国联通研究院 北京 100048

引言

在传统代码架构中,随着新功能的增加,代码库会越变越大。尽管代码被分成各个模块,但随着时间推移,这些界限将变得模糊。代码之间功能类似的模块将越来越多,维护将变得越来越困难[1]。相对于传统软件,微服务是一种新型架构方式。它提倡将单一应用程序划分成一组小的服务,每个服务独立完成一个很小的功能。服务之间相互协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务和服务之间采用轻量级的通信机制(通常是基于HTTP的Restful API)相互沟通[2]。但微服务架构下软件稳定性不够强,故障率相对较高,且故障原因复杂,微服务的故障治理一直是一个很大的挑战。

容器是一种虚拟化技术,相比于VM,具有更好的性能,更轻量和更好的扩展性[3]。每个容器上运行一个进程,容器内打包这个进程所需要的所有依赖环境,拥有良好的移植性。容器之间彼此独立运行,负责单一的功能。容器间也可以相互配合,相互调用,完成更大的功能。容器技术的特点使它与微服务架构完美适配。这里也使用容器技术实现微服务架构。

服务网格是用于处理服务与服务之间通信的专用基础设施层。其主要思路是为每个微服务实例(往往以容器形式)都设置反向代理组件,即Sidecar。所有进出微服务的流量会被Sidecar劫持,通过该组件进行处理和转发[4]。服务网格在不侵入业务代码的情况下可以完成对微服务集群的监控,为微服务故障治理提供了新的解决思路。本文基于服务网格技术,对于微服务软件进行管理,深入探讨微服务的故障特点,提出一套完善的治理方案。

1 微服务的故障问题分析

微服务带来灵活性的同时,也使得架构变得复杂,原本一个单体软件被分解成上百个微服务,微服务之间调用关系复杂[5],服务之间协作也容易受到网络层影响,这对故障的治理提出了很高的要求。微服务故障治理主要体现在以下几个方面。

1.1 故障定位困难

一个大型的应用程序被拆分成几十甚至上百个微服务,分布在多台服务器上。各个微服务之间相互协作,一层层进行调用,调用拓扑异常复杂。故障可能是某个微服务实例内部发生故障(计算错误、返回数据错误),可能是多个实例之间交互错误(如实例运行顺序不对),也有可能是环境因素(如网络延时,使得请求无法及时响应;配置错误,JVM配置和容器配置不一致)。

故障还可以分成功能性故障和非功能性故障,功能性故障会导致运行结果的错误,非功能性故障会使服务性能、稳定性下降[6]。相比于功能性故障,非功能性故障更为隐秘,更不容易被发现。总之,在微服务架构下,故障的种类繁多且原因复杂,定位也十分困难,尤其是在几百台服务器组成的大型集群中。

1.2 服务集群稳定性低

在单一架构的软件中,各个功能的协作在设计和开发之初就被设计好,大多数问题能够在最初开发时被规避。当软件被拆分成一个个小的服务时,服务之间、服务组件之间层层依赖,一个服务组件出现故障或者因为网络层发生故障无法通信,会影响到所有与它协作的服务组件,导致这些组件积累大量请求,最终不可用,形成雪崩式的崩溃。

例如,上游发来一次请求,为了完成这个任务,组件中形成了一系列的调用。这些调用将服务组件连接在一起,称之为调用链[7]。假设每次调用服务出故障概率为Fone,整条调用链上出现故障的概率Fall,则Fall=1-(1-Fone)n,其中n为调用链长度。

如图1所示,当调用链长度增加时,即使单个服务出现故障的概率极小,本次请求调用失败的概率也会变得很大。

图1 整体故障概率与调用链长度关系

考虑到每个类型的服务有m个备份均衡流量,如果某一个服务组件故障,请求会转移到备份服务组件,每种类型的服务不可用的概率是:Ftype=Fonem,整条调用链Fall=1-(1-Ftype)n=1-(1-Fonem)n,图2是每种服务分别只有1个、5个、10个备份实例的情况,当每种服务有多个备份时(m变大)能够有效降低请求失败的概率。但是在微服务架构中,上层服务需要通过网络层调用下层服务。网络层是脆弱的,很容易因为一些不可抗因素发生网络连接失败,或者服务没有及时响应,使得上游一系列服务积压了大量的请求,占用大量资源,Fone数值激增,导致整个集群雪崩式地崩溃。因此在生产环境下,需要及时发现故障位置,防止整个集群发生崩溃。

图2 服务有备份时整体故障概率与调用链长度关系

1.3 微服务测试困难

测试对于提前发现错误,验证软件功能有着极其重要的意义,但是在微服务架构下,软件被拆分成多个服务,需要为每个服务搭建测试环境,对每个服务功能进行验证,过程繁琐,消耗人力巨大。其次,微服务故障多在实例之间交互中产生,而且交互结果容易受到网络层状态(延迟、带宽)影响。微服务本身和实例之间没有固定的对应关系,服务实例在整个集群中动态地创建和销毁。故障具有动态性,难以重现,测试环境下很难提前发现故障。

2 服务网格的简介与选型

2.1 服务网格简介

容器技术有很强的可移植性,一次打包随处部署,相比于VM更加轻量级,启动和销毁十分容易[8],最重要的是容器之间能相互协作,共同完成更加复杂的任务。这些特点与微服务完美适配,目前工业界普遍以容器的形式部署微服务。如图3所示,服务网格是在每个容器上增加一个反向代理Sidecar,所有进出容器的流量完全经过Sidecar,被Sidecar监控和控制。Sidecar构成了服务网格的Data Plane。比较新的服务网格在Data Plane的基础之上增加Control Plane。Control Plane直接与Sidecar通信,将用户策略转发至Sidecar,实现对流量的更精准地控制。

图3 服务网格示意图

2.2 服务网格优缺点及选型

服务网格在微服务实例上挂载代理,它与Spring Cloud、Dubbo这类侵入式框架不同,它与业务代码耦合很小,业务代码的技术选型、迭代升级都不会受到框架的制约[9]。服务网格有强大的监控功能,能够提供四个黄金指标的监控(延迟、流量、错误、饱和),同时提供完善的日志功能,对于故障定位、原因分析有很大帮助。

但长期以来服务网格的性能被人诟病,消耗过多的系统资源的同时,对进出流量也造成了比较大的延时。而且伴随服务网格强大功能的是较高的复杂性,要熟练运用服务网格需要投入一定的学习时间。

目前服务网格产品有很多,文章挑选了比较主流的几款,分别是Istio、linkerd 2.0、Consul,AWS App Mesh和ASM。综合比对他们的架构设计、支持的功能、安全性和操作复杂度四个方面的信息,如表1所示。

表1 服务网格软件综合对比

在各种服务网格产品中,Istio的功能最为强大,最为灵活。它提供流量管理、扩展性、安全和可观察性四大方面功能,几乎涵盖微服务监控所有需求。Istio从1.6版本开始支持虚拟机节点,不再完全依赖Kubernetes平台,能更好地适应异质架构的大型集群管理。同时Istio是一个开源项目,社区相比于其他的服务网格产品更加活跃,使用Istio作为微服务故障治理解决方案,更有代表意义。

3 故障检测模型

3.1 故障检测流程

整个故障预测流程如图4所示。首先由手动点击或者使用postman模拟发送http请求,得到正常情况下和故障注入情况下两种数据,包括可视化监控图、服务调用日志,各个容器运行指标如表2所示。

表2 容器采集指标

图4 故障预测流程

最后将可视化监控图、日志和容器运行时参数、定位故障,输入故障预测模型,确定故障原因。

3.2 故障注入

使用Istio提供的HTTP abort功能,该功能可以拦截并丢弃所有到达某个容器的流量,使得该容器对其他容器处于不可达状态。同时故障注入分为服务级别和容器级别故障注入。一种微服务往往有多个容器备份,服务级别故障注入,让该服务所对应的所有容器全部处于故障状态,用于服务级别的故障定位验证;容器级别故障注入,让该服务的某个容器处于故障状态,用于容器级别的故障定位验证。

3.3 故障服务定位

在微服务中,为了完成一个任务,微服务之间往往形成一个很长的调用链,这个调用链上任意一个服务失败都会导致本次请求失败。当故障出现时,表现的是请求结果无回应或者返回错误结果。但具体错误是出现哪个微服务上,需要消耗大量时间排查。这里提出调用链交叉累计法(Invoke Chain Intersection Accumulation Method,ICIAM)。在图5中,绿色方块代表正常服务,红色方块代表故障服务,灰色方块代表因上游服务故障而导致下游故障的服务。

图5 ICIAM示意图

正常情况下,有三个请求,当某个服务出现故障时(红色),会影响到两条调用链。对每条出现故障的调用链上所有服务实例分数加1。分数最高的服务,往往是故障源头。

3.4 故障容器定位

每种服务可能存在多个容器备份,需要确定具体故障是否出现在容器[10]。其次,调用链交叉部分服务可能不止一个,故障服务不一定会导致后续的服务不可用,使用ICIAM定位的故障源范围较大,需要进一步缩小。Istio集成Prometheus,可以对每个容器数据进行监控,收集容器CPU、内存方面的数据。容器正常运行和故障时的各项metric特性会有很大差异,可以通过分析这些参数(如表2所示),得到故障容器位置。

4 实例研究

4.1 实验环境描述

使用4台8GB、4VCPU、操作系统为ubuntu18.04x64 的虚拟机,搭建kubernetes-1.17.5 集群。在Kubernetes集群基础上,搭建Istio 1.6.8,对Kubernetes上运行的所有微服务实例进行管理。

4.2 实验用例应用搭建

使用TrainTicket[11]软件作为本次实验测试用例,检验故障治理流程能否定位故障。这是由复旦大学实验室基于微服务架构开发的应用。它是由41个微服务组成,使用了Java、Node.js、Python、Go、Mongo DB和MySQL多种语言和技术。虽然复杂程度不及工业界软件,但是服务间技术独立,服务互相配合,使用多种语言开发,几乎体现了微服务架构下软件所有特点,使用这款应用进行实验具有一定的代表性。

4.3 实验过程

使用Postman模拟ts-preserve-service:preserve;ts-travel-plan-service:getByMinStation;ts-travel-service:queryInfo;Ts-preserv-otherservice:preserve;Ts-travel-plan-service:getByCheapest多种操作,这些请求都会调用ts-seatservice,用来获得交叉调用。每次请求有50ms延时,一共发送1 200次。使用Postman ts-travel-planservice:getByCheapest和ts-preserve-service:preserve两种请求各300次,测试当服务在实际生产情况下,调用链种类不够丰富的情况下能否定位故障。

使用Istio中集成的Jaeger,每次有请求发生时,将微服务之间调用关系,每个微服务所消耗时间以日志形式记录。将服务的调用路径、消耗时间等数据以json格式导出,同时Istio也支持微服务信息可视化,方便故障定位。

收集两类数据集,一类是所有服务正常情况下的数据集,另外一类是故障注入下的数据集。使用Istio故障注入功能,调用ts-seat-service发生abort故障。请求到此类服务的HTTP请求,全部得到500的HTTP状态码。

4.4 验证结果

4.4.1 ICIAM验证错误服务环节

我们得到的正确数据集合一共是1 200次请求,错误数据集也是1 200次请求。每种数据集中包含以下请求,ts-preserve-service:preserve;Ts-travel-planservice:getByMinStation;Ts-travel-service:queryInfo;Ts-preserv-other-service:preserve;Ts-travel-planservice:getByCheapest。

无故障情况下每种请求一次请求所引起的总共调用次数,以及调用了ts-seat-service的次数如表3、表4所示。

表3 无故障时各类服务调用次数

表4 ts-seat-service故障时服务调用次数

总的调用次数少了,因为当请求到达ts-seatservice故障位置时,得到错误响应,后续服务全部终止。ts-seat-service被调用的次数为1,因为到这里请求发生错误,调用终止。

如图6所示,同时发起5种请求,使用ICCA算法得分最高的前5名服务如表5所示。

图6 5种请求故障调用链示意图

表5 5种请求时ICCA算法得分最高前5名服务

由于ts-seat-service是故障源头,所有故障链的终点都会汇聚到这个服务,增加ts-seat-service得分,最终故障源头的得分最高。当我们减少请求种类如图7所示,只发起ts-travel-plan-service:getByCheapest和tspreserve-service:preserve两种请求,使用ICIAM得分最高的前5名服务如表6所示。

表6 2种请求时ICCA算法得分最高前5名服务

图7 2种请求故障调用链示意图

可以看到,当故障服务上游服务一一对应时,可能出现故障服务得分和唯一上游服务相同的情况,但是这种情况比较少见,很少出现一种服务只是被一种服务调用。其次,即使出现这种情况,故障服务的得分也是故障调用链上最高之一,也能大概确定故障服务位置。

综合比较,使用ICIAM,服务被越多种服务调用,发生故障时,越容易被定位出来。总体来说,ICIAM可以通过服务网格提供的调用路径,定位到故障服务位置。

4.4.2 定位故障容器位置

一个服务往往有多个备份容器分摊流量,需要定位到具体哪个实例出现问题,采集容器CPU、内存等方面指标,从这些指标可以得到具体故障位置。

如表7、表8所示,使用Istio进行故障注入,sidecar将所有请求拦截,返回http状态码500,此刻容器的CPU负载明显低于正常情况,内存也有少量降低,但是影响很小,分析原因是容器在启动时Request Memory设置在250MB,容器处于空闲状态或处于较低负载水平时,内存保持在250MB左右,当容器处理大量求量之时,会额外申请内存。通过容器各项指标参数,可以发现故障容器异常,从而判别故障。

表7 无故障情况下容器CPU、内存情况

表8 故障注入情况下容器CPU,内存情况

5 结果与未来工作

使用服务网格实现了对微服务全方位的监控,包括服务调用路径、在每个节点延时、容器运行各项指标。完善的监控体系对于故障定位有着很大意义。

使用ICIAM方法能有效确定故障服务种类,而且在服务请求种类越多的情况下效果越明显。因为服务请求种类越多,故障服务越有几率被多种请求调用,得分更高。如果服务请求种类少,而且故障服务上游仅有少量服务甚至一条调用路径,定位的故障范围可能会扩大,但基本能够确定故障位置。通过容器各项运行指标,定位具体故障容器。

然而,微服务故障治理不应该仅仅定位故障位置,还应该找到故障原因。这篇文章研究故障注入类型比较单一,只是使用istio对ts-seat-service在网络连接上进行故障注入,真实生产环境下故障种类十分复杂,有内存负载过大、CPU负载过大、网络环境故障、程序本身错误、配置错误等多种原因,需要进一步细分。这也是未来需要研究的方向。

猜你喜欢
调用实例容器
难以置信的事情
系统虚拟化环境下客户机系统调用信息捕获与分析①
液体对容器底及容器对桌面的压力和压强
取米
完形填空Ⅱ
完形填空Ⅰ
利用RFC技术实现SAP系统接口通信
C++语言中函数参数传递方式剖析