上次跟大家分享了 Service Mesh 双十一后的探索和思考(上)。在过去的一年多时间里,蚂蚁在 Service Mesh 上建设了大量能力,而这些基础设施能力的快速演进正是得益于 Service Mesh 将业务和基础设施的解耦。
1. 引言
在 Service Mesh 落地之后,我们曾设想过 Service Mesh 再向前探索可能会遇到的种种困难,包括资源利用率、性能损耗等等,但是未曾想到过去一年中最大的挑战是研发效能。
2. 研发效能的挑战
当 Service Mesh 的优势逐渐显现,越来越多的能力希望下沉到 MOSN 中。而我们也逐渐发现 MOSN 在实际迭代过程中遇到的一些困难,其中比较突出的两个难题:
变更量大:大量的功能希望进入 MOSN,因此每一个 MOSN 的版本发布都是一次代码量巨大的变更。这对质量保障和技术风险的挑战都是很大的。
集中式发布:以前由业务应用升级基础组件 SDK 的方式,由各个业务应用主动发起升级的发布,发布时主要由该业务应用自己监控其系统稳定性。这种升级方式从覆盖的空间和时间上来说都是分散的,便于灰度和发现问题。而 MOSN 的升级方式则是集中式的。即使按照万分之一容器数量进行灰度,这个数字都是巨大的。这对质量保障和技术风险的挑战也是很大的。
面对这些难题,每一个 MOSN 的版本在上线之前需要很长时间的测试,上线过程中需要很长时间的灰度。但是当最终发布上线后,如果出现一个问题,则又会导致整个版本的回滚,长时间的测试和灰度付之一炬,又需从头再来。在面对稳定性这个压倒一切的要求时,退让的就是 MOSN 的研发效能。即各个功能从开始开发到最终全集群覆盖的速度。这是在牺牲 Service Mesh 的核心价值啊!这是我们所不能接受的。因此只有在质量保障和技术风险上突破掉这些挑战,才能让 Service Mesh 发挥出它真正的价值。
- 质量保证
在 MOSN 研发流程中,质量保障作为研发效能中的关键一环,重点需要解决的难题:
多模块共建质量:云原生背景下,除去 Service Mesh 自身外,很多业务方的诉求也一并下沉至 MOSN,多个业务方的共建质量复杂度几何倍的提升,如何保障共建方也持续高质量的输出。
版本稳定性:MOSN 的每一次发布,变更内容多、变更范围广,如何确保每个版本的稳定性,以及线上多个版本共存情况下,版本之间的兼容性。
效能提升:在保障质量稳定性的前提下,如何提升版本测试的效能。
测试策略上,我们主要通过云原生多模块质量数据建模和版本稳定性两个维度解决上述问题。
- 云原生多模块质量数据建模
对于 MOSN 里的每个模块的功能,除了基础的单元测试和集成测试保障手段外,我们期望通过录制线上的真实流量,经过数据清洗和建模后,获取 MOSN 中不同模块的真实业务场景,作为 MOSN 中多模块测试场景的输入;另外,MOSN 支持 MOCK 模式,对这些线上录制的业务场景,在线下做自动化的回放验证。
多模块数据采样
这里采集的数据共包含:流量数据、业务特征数据和内存配置数据。
MOSN 使用 Golang 语言编写,无法像 JAVA 一样,通过 JVM 插桩的方式实现流量的录制和回放。
对于 Golang 语言的录制回放工具,开源也有优秀的工具,如 tcpcopy 和 goreplay 等,这些工具主要是从程序外部,对生产的正式流量进行录制,而这对于 MOSN 并不适用。
如能力建设部分所述,蚂蚁内部通信目标 100% 覆盖链路加密,使用上述工具录制的流量为加密后的流量数据,线下回放时,会因为证书问题导致回放失败。因此,我们需要在 MOSN 内,建立一套流量录制、业务特征数据上报、内存配置数据上报的能力,我们称之为 Test Mesh 的能力。
流量录制:录制的是 TLS 解密后的二进制数据。
数据上报:提供统一的数据上报接口,供 MOSN 中不同的业务模块上报各自的流量特征数据,通过和录制的流量做关联,达到流量清洗和线上流量建模的目的。
数据采样:对于持久化的数据(包括二进制数据、流量特征数据、内存镜像和静态配置),通过配置中心控制采样开关和采样频率,在开关开启的情况下,根据采样频率开启采样窗口,每个采样窗口内同一个业务类型采集一笔流量。
熔断保护:为了不影响主流程,流量录制和数据上报均设计为同步受理异步处理的模式,且支持根据 CPU, MEM 的水位自动熔断的能力,水位的阈值支持动态调整。
建模清洗
数据同步:线上录制的数据持久化到磁盘文件中,通过数据服务平台将数据同步至离线 ODPS 表中。
数据清洗:算法模块包装了数据清洗的原子能力(如:正则匹配、去重等),用户通过不同模块上报的流量特征数据,指定清洗规则,由算法平台定时做数据清洗,并将清洗后的数据同步至存储平台,构成不同业务模块的业务场景基线。
模型场景回放
有了业务场景和二进制流量,我们构建了回放系统,并在 MOSN 内部支持了 MOCK 模式,支持线下回放验证。
MOCK 模式:线下回放时,MOSN 基于线上采样的内存配置加载起来后,其与外围的配置需要全部 MOCK 掉,确保内存不会被线下的 Registry、动态配置中心 等更新掉,保障回放结果的正确性。
流量回放:回放系统提供回放任务触发、任务编排、MOSN 以 MOCK 模式拉起、回放执行、结果展示等功能。
- 版本稳定性 持续集成
我们在原有持续集成 pipeline 流水线的基础上,提出了更高的要求:每个代码 PR 合并后即达到可发布的状态。
提交的每个 PR 均会触发这样一条流水线的运行,每个 PR 独立构建测试环境,并在整个 pipeline 流程中集成了研发、测试活动,从而确保 PR 合并后即可达到发布状态。
这里仅简单介绍了 pipeline 的流程,pipeline 中的每个 job 还在持续建设中。
功能预演
原先在 MOSN 的研发流程中,MOSN 版本交付后,逐步灰度升级生产的业务应用,这中间缺失了一环 MOSN 自身生产升级的灰度验证能力。
为此,我们搭建了 MOSN 自身的预演应用,这些应用模拟线上业务应用的真实使用场景,且按照线上真实业务应用部署。MOSN 每个版本交付后,先对这些应用做升级验证,确认无问题后再做业务应用的升级,从而实现 MOSN 自身的灰度发布和版本升级的质量保障。
对于这里说的“真实使用场景”,其数据也是来源于 Test Mesh 录制到的流量特征数据,这些流量特征数据经过清洗建模后,构建了业务场景基线,预演应用参照业务场景基线达到对线上“真实使用场景”的全面覆盖。
- 技术风险
通常来说,减小升级频率就会将稳定性的风险减小。毕竟代码变更是导致故障的一大来源。但是这对 MOSN 来说是不可接受的,例如一年我们只做一次发布升级,前面提到了 MOSN 的代码变更量是较大的,可想而知一年堆积下来的代码变更量是多么巨大。加上集中式的发布方式,这就好像是把一颗“代码变更炸弹”在短时间内扔在了不小数量的容器上。对 MOSN 自身的这一大量代码变更来说,要保证完全没有问题有不小挑战,容易导致我们无法成功完成这次全集群升级。所以这不仅对稳定性是挑战,对 MOSN 的研发效能也是挑战。如何去解决这个问题呢,这里我们解读两个对这方面的探索。一个是采取更低侵入高频的发布方式,我们反而利用更加高频的发布模式来将较大量的变更风险分散开来;另一个是对健康度的度量,这能让我们对每个 MOSN 节点的健康情况都准确掌握,做到更加自动化的发布和相应的技术风险决策。
- 低侵入高频发布
MOSN 是以 sidecar 形态与业务容器共同部署在 Pod 内。由于 MOSN 依赖与应用的业务进程交互获取应用的服务信息,与服务配置中心交互并建立连接。这些信息在 MOSN 的发布过程中都需要重建。因此 MOSN 的发布过程也要求应用容器同时做重启,以保证应用的服务信息能完整的在 MOSN 重新被初始化。但由于应用的启动速度通常较慢,这个方式也严重的降低了 MOSN 的发布效率。
在最初考虑这个问题时,我们做了一些热升级的尝试,但热升级引入了多个 MOSN 容器的交互,大幅增加了运维复杂性,因此我们又探索了新的低侵入发布模式,我们称之为温升级——仅关闭一切流量,不通知应用业务进程,接近业务无感方式的发布。
温升级整体的流程如下。
流量管理与服务元数据的继承
我们在原有持续集成 pipeline 流水线的基础上,提出了更高的要求:每个代码 PR 合并后即达到可发布的状态。
MOSN 接管了业务的所有进出流量,并直接与流量相关的中间件交互,因此 MOSN 具备从源头关停流量的能力。当发布开始后,MOSN 先从各中间件注销自身,确保不再承接流量,之后 MOSN 容器退出销毁,kubelet 使用新的 image 重建新的 MOSN 容器并启动 MOSN。
由于此时无法从应用业务进程获取服务的元数据信息(业务进程不知道 MOSN 进行了更新),新启动的 MOSN 只能从之前的 MOSN 继承这部分信息:MOSN 会准实时的将所有的动态配置信息(含服务元数据)dump 到一个共享的挂载卷,当新的 MOSN 容器启动时,也会挂载同一个卷,并在 MOSN 进程启动时加载这些配置,从而最大程度继承原来 MOSN 的状态。
由于依赖于旧版本动态配置信息的加载,MOSN 也做了相应的向下兼容,确保升级过程中的元数据都能正确加载。
连接重建
完成状态继承后,MOSN 将自身所在 Pod 重新发布到服务配置中心等中间件,并开启与远程建立多个主动或被动的连接。对于后端的服务连接来说,MOSN 的升级过程就是一次普通的连接中断,通常的服务框架都能自动处理连接中断并重试建立新的连接。
高频发布
当低侵入的发布实现之后,更高频的发布也变得可能。我们基于温升级的能力,建设了从研发迭代直通到生产的 nighly build 交付的 CI/CD 流程,让新研发的功能代码在风险可控的前提下能够以最快的速度接触到生产流量,缩短了反馈回环,极大加快了新能力的落地。
- 健康度度量
当新版本的 MOSN 发布后,如何快速知道 MOSN 运行得是否健康。除了传统的监控方式外,我们让 MOSN 将自己的健康度主动透出,并配套监控平台,发布平台和巡检系统做到第一时间发现问题,自动暂停发布和回滚。
静态健康度
MOSN 定义了标准的组件框架,MOSN 中的每个组件都需要实现健康状态的相关接口将自己的健康状态细致地透出。例如:注册中心客户端会透出自己和服务端的连接状态、心跳状态,透出自己每一个订阅和发布结果等;配置中心客户端会透出自己和服务端的连接状态,透出自己对每一个配置的拉取结果等。这些组件的状态和结果会作为发布后流量是否开启的前置检查项,如果有组件不健康则不会开启流量。巡检平台也会在查看 MOSN 运行时的健康状态,如果是不健康的状态则会阻断继续发布。
动态健康度
静态健康度侧重在以组件的视角去观察各个组件自身运行情况的健康情况,动态健康度则是侧重在以全站流量的视角去观察各个 MOSN 节点的健康情况。以 RPC 为例,每一笔调用其实都是 应用_A -> MOSN_A -> MOSN_B -> 应用_B 的模型,这个模型中可以从 6 个地方观察到这笔调用的健康状态:应用_A 的 Egress 视角,MOSN_A 的 Ingress 和 Egress 视角,MOSN_B 的 Ingress 和 Egress 视角,应用_B 的 Ingress 视角。每一个视角都可以做调用情况的统计,基于这些调用统计,最终可以聚合计算出每个 MOSN 节点的健康情况。
如图 应用_B 的 Egress 成功率和 MOSN_B 的 Ingress 成功率下跌,由此可判断 MOSN_B 节点为不健康状态,而 MOSN_B 的 Egress 成功率未变,所以还能进一步判断是 MOSN_B 的 Ingress 模块可能出现了问题。如果此时 成功率_7 也出现下跌,而 成功率_8 和 应用_A -> MOSN_A -> MOSN_C 链路上的成功率都未发生改变,则可判断是 MOSN_B 的 Egress 模块出现了问题。
实际上,我们的动态健康度的度量维度会更复杂一些,例如还会区分出该笔流量是在 MOSN 节点内部发生异常还是在发起调用之后发生异常,还会有 MOSN 的版本等信息,由此可以更加精确的定位到出现异常的 MOSN 节点,并追溯到它的发布单及迭代等信息,帮助最终的自动化决策
- 问题诊断
在质量保障和技术风险的强大保障下,每一次 MOSN 的升级已经达到了一个高水准的稳定性。但是 MOSN 升级的覆盖范围之大,场景之复杂,有的问题甚至可能在几个月之后才会暴露出来,如何再去发现这些漏网之鱼的 corner case 呢。其中有一些问题较难定位,这些问题的特点是:发生随机,过程时间短,难以抓到现场。一些偶发的 CPU 使用飚升、OOM 的问题对稳定性是大风险,我们需要有办法把这些问题尽快定位出来,不能让其一直潜伏到业务的关键时间点再爆发。
我们选取程序所占用的 CPU、RSS 和 goroutine 数来作为进程的运行时指标。分为两种情况:
- 如果这些指标在较短时间内发生较大波动,那么认为可能发生了瞬时抖动
- 如果这些指标到达非预期的阈值,那么认为可能发生了资源泄露或瞬时抖动
对上述三个指标,每经过 5s 采集一次,保存在相应的环形数组中。每次采集新一轮指标时,均与之前十个周期的数值平均(可以认为是一种形式的 moving average)进行 diff,并判断当前的值是否已到达预期外的阈值。
- 若 CPU 规则被触发,自动启动 Go 的 CPU Profile,并将文件保存在日志目录中
- 若 RSS 规则被触发,自动将 Go 的 heap Profile 保存在日志目录中
- 若 Goroutine 规则被触发,自动将 Go 的 goroutine Profile 保存左日志目录中
这些功能上线之后,我们只要根据监控系统推下来的报警 host 找到相应的目录位置,并把 profile 下载到本地慢慢分析就可以了。
诊断功能上线之后帮我们定位出了多个之前难以发现的漏洞/代码问题,我们将该功能沉淀为一个开源 lib:holmes,目前已开源在 MOSN 组织下。
3. 总结及未来
在过去一年中,得益于 Service Mesh 的落地,我们的基础设施得到前所未有的演进速度,并在性能,效能,稳定性和可用率等各方面帮助业务得到提升。
在向前探索的过程中,我们越发看清 Service Mesh 真正价值的体现不在于它建设了多少能力,而是体现在它自身将这些基础设施能力大规模应用于业务的周期和稳定性。
由此我们发现 Service Mesh 目前最大的挑战其实是它自身的研发效能,只有一个高效和稳定的 Service Mesh 才能持续不断地将基础设施能力赋予业务并得以演进。因此我们在质量保障,技术风险和问题诊断上也在不断突破创新,最终使得 Service Mesh 的核心价值真正发挥了出来。
蚂蚁的 Service Mesh 一直走在这个领域的最前沿,接下来我们又会向什么方向探索呢。我们会持续将更多的能力下沉至 MOSN ,接管所有的进出流量,让任意技术栈的应用只需要接入 MOSN 就能拥有完整的基础设施能力;结合蚂蚁丰富的实践经验与社区的力量制定出一套 API 作为应用和控制面等同 Service Mesh 交互的标准,一起推进 Service Mesh 的发展;不断结合业务挖掘出有价值的场景,通过 Service Mesh 的优势为业务带来帮助;我们也会通过技术创新让 MOSN 具备被集成的能力(https://github.com/mosn/mosn/issues/1559),使得 MOSN 和更多优秀高性能网关生态打通,进一步发挥 MOSN 沉淀的能力, 从而释放 1 1 > 2 的技术红利;另外会持续投入开源,MOSN 的核心能力一直是开源共建的方式,不断吸取社区良好的输入,并与蚂蚁内部大规模场景下的实践相碰撞,最终又将这些优秀的能力贡献于社区。