MOSN 子项目 Layotto:开启服务网格+应用运行时新篇章
作者简介: 马振军,花名古今,在基础架构领域耕耘多年,对 Service Mesh 有深度实践经验,目前在蚂蚁集团中间件团队负责 MOSN、Layotto 等项目的开发工作。 Layotto 官方 GitHub 地址:https://github.com/mosn/layotto
点击链接即可观看现场视频:https://www.bilibili.com/video/BV1hq4y1L7FY/
Service Mesh 在微服务领域已经非常流行,越来越多的公司开始在内部落地,蚂蚁从 Service Mesh 刚出现的时候开始,就一直在这个方向上大力投入, 到目前为止,内部的 Mesh 方案已经覆盖数千个应用、数十万容器并且经过了多次大促考验,Service Mesh 带来的业务解耦,平滑升级等优势大大提高了中间件的迭代效率。
在大规模落地以后,我们又遇到了新的问题,本文主要对 Service Mesh 在蚂蚁内部落地情况进行回顾总结,并分享对 Service Mesh 落地后遇到的新问题的解决方案。
一、Service Mesh 回顾与总结
A、Service Mesh 的初衷
在微服务架构下,基础架构团队一般会为应用提供一个封装了各种服务治理能力的 SDK,这种做法虽然保障了应用的正常运行,但缺点也非常明显,每次基础架构团队迭代一个新功能都需要业务方参与升级才能使用,尤其是 bugfix 版本,往往需要强推业务方升级,这里面的痛苦程度每一个基础架构团队成员都深有体会。
伴随着升级的困难,随之而来的就是应用使用的 SDK 版本差别非常大,生产环境同时跑着各种版本的 SDK,这种现象又会让新功能的迭代必须考虑各种兼容,就好像带 着枷锁前进一般,这样随着不断迭代,会让代码维护非常困难,有些祖传逻辑更是一不小心就会掉坑里。
同时这种“重”SDK 的开发模式,导致异构语言的治理能力非常薄弱,如果想为各种编程语言都提供一个功能完整且能持续迭代的 SDK 其中的成本可想而知。
18 年的时候,Service Mesh 在国内持续火爆,这种架构理念旨在把服务治理能力跟业务解耦,让两者通过进程级别的通信方式进行交互。在这种架构模式下,服务治理能力从应用中剥离,运行在独立的进程中,迭代升级跟业务进程无关,这就可以让各种服务治理能力快速迭代,并且由于升级成本低,因此每个版本都可以全部升级,解决了历史包袱问题,同时 SDK 变“轻”直接降低了异构语言的治理门槛,再也不用为需要给各个语言开发相同服务治理能力的 SDK 头疼了。
B、Service Mesh 落地现状
蚂蚁很快意识到了 Service Mesh 的价值,全力投入到这个方向,用 Go 语言开发了 MOSN 这样可以对标 envoy 的优秀数据面,全权负责服务路由,负载均衡,熔断限流等能力的建设,大大加快了公司内部落地 Service Mesh 的进度。
现在 MOSN 在蚂蚁内部已经覆盖了数千个应用、数十万容器,新创建的应用默认接入 MOSN,形成闭环。而且在大家最关心的资源占用、性能损耗方面 MOSN 也交 出了一份让人满意的答卷:
-
RT 小于 0.2ms
-
CPU 占用增加 0%~2%
-
内存消耗增长小于 15M
由于 Service Mesh 降低了异构语言的服务治理门槛,NodeJS、C++等异构技术栈也在持续接入到 MOSN 中。
在看到 RPC 能力 Mesh 化带来的巨大收益之后,蚂蚁内部还把 MQ,Cache,Config 等中间件能力都进行了 Mesh 化改造,下沉到 MOSN,提高了中间件产品整体的迭代效率。
C、新的挑战
- 应用跟基础设施强绑定
一个现代分布式应用,往往会同时依赖 RPC、Cache、MQ、Config 等各种分布式能力来完成业务逻辑的处理。
当初看到 RPC 下沉的红利以后,其他各种能力也都快速下沉。初期,大家都会以自己最熟悉的方式来开发,这就导致没有统一的规划管理,如上图所示,应用依赖了各种基础设施的 SDK,而每种 SDK 又以自己特有的方式跟 MOSN 进行交互,使用的往往都是由原生基础设施提供的私有协议,这直接导致了复杂的中间件能力虽然下沉,但应用本质上还是被绑定到了基础设施,比如想把缓存从 Redis 迁移到 Memcache 的话,仍旧需要业务方升级 SDK,这种问题在应用上云的大趋势下表现的更为突出,试想一下,如果一个应用要部署在云上,由于该应用依赖了各种基础设施,势必要先把整个基础设施搬到云上才能让应用顺利部署,这其 中的成本可想而知。 因此如何让应用跟基础设施解绑,使其具备可移植能力,能够无感知跨平台部署是我们面临的第一个问题。
- 异构语言接入成本高
事实证明 Service Mesh 确实降低了异构语言的接入门槛,但在越来越多的基础能力下沉到 MOSN 以后,我们逐渐意识到为了让应用跟 MOSN 交互,各种 SDK 里都需要对通信协议,序列化协议进行开发,如果再加上需要对各种异构语言都提供相同的功能,那维护难度就会成倍上涨,
Service Mesh 让重 SDK 成为了历史,但对于现在各种编程语言百花齐放、各种应用又强依赖基础设施的场景来说,我们发现现有的 SDK 还不够薄,异构语言接入的门槛还不够低,如何进一步降低异构语言的接入门槛是我们面临的第二个问题。
二、Multi Runtime 理论概述
A、什么是 Runtime?
20 年初的时候,Bilgin lbryam 发表了一篇名为 Multi-Runtime Microservices Architecture 的文章,里面对微服务架构下一阶段的形态进行了讨论。
如上图所示,作者把分布式服务的需求进行了抽象,总共分为了四大类:
-
生命周期(Lifecycle) 主要指应用的编译、打包、部署等事情,在云原生的大趋势下基本被 docker、kubernetes 承包。
-
网络(Networking) 可靠的网络是微服务之间进行通信的基本保障,Service Mesh 正是在这方面做了尝试,目前 MOSN、envoy 等流行的数据面的稳定性、实用性都已经得到了充分验证。
-
状态(State) 分布式系统需要的服务编排,工作流,分布式单例,调度,幂等性,有状态的错误恢复,缓存等操作都可以统一归为底层的状态管理。
-
绑定(Binding) 在分布式系统中,不仅需要跟其他系统通信,还需要集成各种外部系统,因此对于协议转换,多种交互模型、错误恢复流程等功能也都有强依赖。
明确了需求以后,借鉴了 Service Mesh 的思路,作者对分布式服务的架构演进进行了如下总结:
第一阶段就是把各种基础设施能力从应用中剥离解耦,通通变成独立 sidecar 模型伴随着应用一起运行。
第二阶段是把各种 sidecar 提供的能力统一抽象成若干个 Runtime,这样应用从面向基础组件开发就演变成了面向各种分布式能力开发,彻底屏蔽掉了底层实现细节,而且由于是面向能力,除了调用提供各种能力的 API 之外,应用再也不需要依赖各种各样基础设施提供的 SDK 了。
作者的思路跟我们希望解决的问题一致,我们决定使用 Runtime 的理 念来解决 Service Mesh 发展到现在所遇到的新问题。
B、Service Mesh vs Runtime
为了让大家对 Runtime 有一个更加清晰的认识,上图针对 Service Mesh 跟 Runtime 两种理念的定位、交互方式、通信协议以及能力丰富度进行了总结,可以看到相比 Service Mesh 而言,Runtime 提供了语义明确、能力丰富的 API,可以让应用跟它的交互变得更加简单直接。
三、MOSN 子项目 Layotto
A、dapr 调研
dapr 是社区中一款知名的 Runtime 实现产品,活跃度也比较高,因此我们首先调研了 dapr 的情况,发现 dapr 具有如下优势:
-
提供了多种分布 式能力,API 定义清晰,基本能满足一般的使用场景。
-
针对各种能力都提供了不同的实现组件,基本涵盖了常用的中间件产品,用户可以根据需要自由选择。
当考虑如何在公司内部落地 dapr 时,我们提出了两种方案,如上图所示:
- 替换:废弃掉现在的 MOSN,用 dapr 进行替换,这种方案存在两个问题:
a. dapr 虽然提供了很多分布式能力,但目前并不具备 Service Mesh 包含的丰富的服务治理能力。
b. MOSN 在公司内部已经大规模落地,并且经过了多次大促考验,直接用 dapr 来替换 MOSN 稳定性有待验证。
- 共存:新增一个 dapr 容器,跟 MOSN 以两个 sidecar 的模式进行部署。这种方案同样存在两个问题:
a. 引入一个新的 sidecar,我们就需要考虑它配套的升级、监控、注入等等事情,运维成本飙升。
b. 多维护一个容器意味着多了一层挂掉的风险,这会降低现在的系统可用性。
同样的,如果你目前正在使用 envoy 作为数据面,也会面临上述问题。 因此我们希望把 Runtime 跟 Service Mesh 两者结合起来,通过一个完整的 sidecar 进行部署,在保证稳定性、运维成本不变的前提下,最大程度复用现有的各种 Mesh 能力。此外我们还希望这部分 Runtime 能力除了跟 MOSN 结合起来之外,未来也可以跟 envoy 结合起来,解决更多场景中的问题,Layotto 就是在这样的背景下诞生。
B、Layotto 架构
如上图所示,Layotto 是构建在 MOSN 之上,在下层对接了各种基础设施,向上层应用提供了统一的,具有各种各样分布式能力的标准 API。对于接入 Layotto 的应用来说,开发者不再需要关心底层各种组件的实现差异,只需要关注应用需要什么样的能力,然后调用对应能力的 API 即可,这样可以彻底跟底层基础设施解绑。
对应用来说,交互分为两块,一个是作为 gRPC Client 调用 Layotto 的标准 API,一个是作为 gRPC Server 来实现 Layotto 的回调,得利于gRPC 优秀的跨语言支持能力,应用不再需要关心通信、序列化等细节问题,进一步降低了异构技术栈的使用门槛。
除了面向应用,Layotto 也向运维平台提供了统一的接口,这些接口可以把应用跟 sidecar 的运行状态反馈给运维平台,方便 SRE 同学及时了解应用的运行状态并针对不同状态做出不同的举措,该功能考虑到跟 k8s 等已有的平台集成,因此我们提供了 HTTP 协议的访问方式。
除了 Layotto 本身设计以外,项目还涉及两块标准化建设,首先想要制定一套语义明确,适用场景广泛的 API 并不是一件容易的事情,为此我们跟阿里、 dapr 社区进行了合作,希望能够推进 Runtime API 标准化的建设,其次对于 dapr 社区已经实现的各种能力的 Components 来说,我们的原则是优先复用、其次开发,尽量不把精力浪费在已有的组件上面,重复造轮子。
最后 Layotto 目前虽然是构建在 MOSN 之上,未来我们希望 Layotto 可以跑在 envoy 上,这样只要应用接入了 Service Mesh,无论数据面使用的是 MOSN 还是 envoy,都可以在上面增加 Runtime能力。
C、Layotto 的移植性
如上图所示,一旦完成 Runtime API 的标准化建设,接入 Layotto 的应用天然具备了可移植性,应用不需要任何改造就可以在私有云以及各种公有云上部署,并且由于使用的是标准 API,应用也可以无需任何改造就在 Layotto 跟 dapr 之间自由切换。
D、名字含义
从上面的架构图可以看出,Layotto 项目本身是希望屏蔽基础设施的实现细节,向上层应用统一提供各种分布式能力,这种做法就好像是在应用跟基础设施之间加了一层抽象,因此我们借鉴了 OSI 对网络定义七层模型的思路,希望 Layotto 可以作为第八层对应用提供服务,otto 是意大利语中8的意思,Layer otto 就是第八层的意思,简化了一下变成了 Layotto,同时项目代号 L8,也是第八层的意思,这个代号也是设计我们项目 LOGO 时灵感的来源。
介绍完项目的整体情况,下面对其中四个主要功能的实现细节进行说明。
E、配置原语
首先是分布式系统中经常使用的配置功能,应用一般使用配置中心来做开关或者动态调整应用的运行状态。Layotto 中配置模块的实现包括两部分,一个是对如何定义配置这种能力的 API 的思考,一个是具体的实现,下面逐个来看。
想要定义一个能满足大部分实际生产诉求的配置 API 并不是一件容易的事,dapr 目前也缺失这个能力,因此我们跟阿里以及 dapr 社区一起合作,为如何定义一版合理的配置 API 进行了激烈讨论。
目前讨论结果还没有最终确定,因此 Layotto 是基于我们提给社区的第一版草案进行实现,下面对我们的草案进行简要说明。
我们先定义了一般配置所需的基本元素:
-
appId:表示配置属于哪个应用
-
key:配置的 key
-
content:配置的值
-
group:配置所属的分组,如果一个 appId 下面的配置过多,我们可以给这些配置进行分组归类,便于维护。
此外我们追加了两种高级特性,用来适配更加复杂的配置使用场景:
-
label,用于给配置打标签,比如该配置属于哪个环境,在进行配置查询的时候,我们会使用 label + key 来查询配置。
-
tags,用户给配置追加的一些附加信息,如描述信息、创建者信息,最后修改时间等等,方便配置的管理,审计等。
对于上述定义的配置 API 的具体实现,目前支持查询、订阅、删除、创建、修改五种操作,其中订阅配置变更后的推送使用的是 gRPC 的 stream 特性,而底层实现这些配置能力的组件,我们选择了国内流行的 apollo,后面也会根据需求增加其他实现。
F、Pub/Sub 原语
对于 Pub/Sub 能力的支持,我们调研了 dapr 现在的实现,发现基本上已经可以满足我们的需求,因此我们直接复用了 dapr 的 API 以及 components,只是在 Layotto 里面做了适配,这为我们节省了大量的重复劳动,我们希望跟 dapr 社区保持一种合作共建的思路,而不是重复造轮子。
其中 Pub 功能是 App 调用 Layotto 提供的 PublishEvent 接口,而 Sub 功能则是应用通过 gRPC Server 的形式实现了 ListTopicSubscriptions 跟 OnTopicEvent 两个接口,一个用来告诉 Layotto 应用需要订阅哪些 topic,一个用于接收 topic 变化时 Layotto 的回调事件。
dapr 对于 Pub/Sub 的定义基本满足我们的需求,但在某些场景下仍有不足,dapr 采用了 CloudEvent 标准,因此 Pub 接口没有返回值,这无法满足我们生产场景中要求 Pub 消息以后服务端返回对应的 messageID 的需求,这一点我们已经把需求提交给了 dapr 社区,还在等待反馈,考虑到社区异步协作的机制,我们可能会先社区一步增加返回结果,然后再跟社区探讨一种更好的兼容方案。