| 导语 腾讯轻舟平台(RAFT,Rapid Application Framework of Tencent)是一个致力于组件治理,为创新型应用提供“端云一体、开箱即用“的组件生态系统。本文先引入组件治理的概念,然后重点描述后台部分的组件治理思路。这里面有很多比较新颖的云化建设的理念,对传统后台的组件化建设和云化升级,希望也提供一些解决思路的参考。
一、“组件治理“的引入
为了让大家更好理解什么是组件治理,先简单回顾一下大家常见的公共组件的研发和推广流程。
大部分情况下,可能首先是因为业务需求,我做了一个系统,后面突然又来了一个类似的需求,于是萌生了把系统提炼成组件的想法。然后,小部分情况下(原因相信大家都知道的~),组件被做出来了,于是放到github上开源推广。
那么问题来了,首先,我的组件是用我自己擅长的开发语言/协议/框架来写的,可能也依赖一堆我们team在用的公共组件或编译/运维/部署平台,这些依赖脱离我自己的team,别人用起来的阻力可能比较大。其次,即使我开放源代码,接口文档/使用文档可能也很详细,还是避免不了,别人用这个组件的时候,需要不断打扰我咨询各种接入使用问题。还有另外一种情况,可能不是我们希望发生的,组件也写完了,github也推广了,最后就是没有人用,因为实在太难用了。。。
于是我们有了组件治理的想法,把组件规范管理起来,并提供一个平台,让组件变得非常容易使用,每个组件都可以先体验再使用,就像你在超市里面先试吃再决定要不要买。就这样腾讯轻舟(RAFT)平台应运而生了。
二、组件治理要做什么
腾讯轻舟平台的使命,可以简单总结为“组件治理”,那么,组件治理具体要做什么事情呢?接下来我们来简单汇总一下。
1. 端云一体
简单说,就是部署客户端组件的时候,同时把对应的后端组件也一起部署了,并且自动配置好客户端组件,使得能够正确寻址到刚部署的后端组件。
这样一来,组件的使用者只需要关心终端的API部分,不需要关心云端的情况,换句话说,云端对开发者来说是一个黑盒,开发过程不需要卷入后台开发同学和运维同学。
2. 一键部署
一个App可能包含多个后台组件,一个后台组件本身也可能包含其他的组件,RAFT平台在应用开发者提交部署的时候,会把所有依赖的后台组件都一起部署,用户不需要挨个去部署。
3. 配置打通
这里有两层含义:
1)一个组件A可能依赖了另外的组件B,这时部署A时需要把B也部署起来,并且需要为B申请一个名字服务(比如腾讯内部广泛使用的L5,腾讯云的CLB,AWS的ELB等),关联B的运行实例(将容器Pod的生命周期跟名字服务关联),并注入到A的运行配置中,这样A才能寻址到新部署的B。
2)组件可能依赖很多第三方服务(PaaS或SaaS,我们统称为XaaS),这些服务可能是不能被不同的应用共享的,每个应用需要申请自己的appid。Raft平台在部署组件的时候,需要申请xaas服务的appid,并注入组件的配置中,使得组件可以访问到给本应用申请的xaas服务。
4. 组件“试用”
组件希望是拿来即用的,不需要复杂的部署、调试流程,为此,我们建设了一套“试用”机制,称为组件预览。每个组件上架的时候,会部署一个默认的实例,应用开发者可以通过示例代码直接体验这个服务。
5. 其他功能
上面只列举了部分组件治理的功能,在RAFT平台中,还有更多致力于“开箱即用”目标的功能,比如“模板”,“向导”等,用户可以很方便地按照一定的应用场景选择类似的应用,并在特定模板的基础上进行开发。
三、 后台治理思路
下面,我们重点说下后台组件的治理思路。
从后台组件的视角来看,组件治理要做的事情主要是一键部署和配置打通,一键部署解决多个组件一键快速部署的问题,配置打通解决组件依赖和xaas服务依赖的问题。
目前已经有很多类似的系统可以实现快速把一个或多个镜像部署起来,比如基于k8s的helm chart、kustomize、rudr等。不过这些系统都有一个共同的特点,他们只负责部署,不负责打通,不会关心组件之间相互调用的情况,而这恰恰就是我们需要重点考虑的问题,否则应用开发者还需要对所有的组件实例进行配置,使得他们能够相互调用,而这是非常不利于组件的推广的,也违背了“开箱即用”的原则。
那么,怎样才能把组件的调用关系打通呢?
为了解决这个问题,我们首先对组件进行了抽象,把组件最核心的东西用模型进行描述。下面我们重点描述这些模型,以及怎样基于这些模型进行链路和资源的打通。
1. 应用模型
·应用
对后台来说,多个组件按一定的逻辑关系组合起来后,就构成一个应用,但对客户端来说,一个应用可能是指安卓QQ,而IOS/MAC QQ可能是另外的两个应用。
为了避免引起混淆,这里的应用统一指后台应用,定义为一份相同的组件集合跟服务集成配置,每个应用由应用开发者指定一个全局唯一的名字,比如QQ(IOS/安卓QQ都属于同一个后台应用)。
客户端看到的应用:
后台看到的应用:
·应用实例
同样的一份应用配置可以被部署多次,为了区分不同的部署,我们引入应用实例的概念,应用的每一个独立的部署,就是一个应用实例。
一个应用实例有一个独立的实例ID,由这几个元素组成:
AppInstanceID = AppName EnvName
其中:
AppName:由应用开发者指定的后台应用名字,需要确保全局唯一,如果不同的前端(比如ios和安卓)使用同一个后端,这个名字必须保持一致。
EnvName:环境描述字符串,比如Test/Production/Preproduction。
2. 组件模型
·组件
客户端一般说的组件是指各种库文件(libs),如果需要跨进程提供服务,则称为服务;但对于后台而言,组件一般指能够提供一定功能的任何形式的服务。
在我们这里,组件主要指托管到组件治理平台上的所有后台服务,这些服务需要以源代码和对应镜像的方式提供,应用如果需要复用这些组件,可以直接拷走源代码,做适合自己业务逻辑的修改,并做独立的部署。
·组件模型
为了解决依赖组件自动部署并相互通讯的问题,我们需要对组件进行建模。首先,我们抽象出组件对几个关键词汇:
·Imports:指组件本身依赖哪些组件,组件部署的时候,这些组件也需要跟着一起部署。组件Import另一个组件,还需要一个属性,帮助寻址到这个组件,这就是名字服务。
·Depends:一个组件所依赖的外部服务或资源(各类XaaS),需要由组件治理平台来帮忙申请和打通的。Depends包含强依赖(strong)和弱依赖(weak),强依赖的服务,如果申请不成功,整个app的部署返回失败,弱依赖如果申请失败,只会标记出来,但不影响整个app的部署。
·Exports:是指这个组件本身为上层依赖组件或客户端应用提供的访问方式,可能是名字服务,也可能是接入平台的一个appid或命令字。Exports的名字服务或接入服务,也是一个xaas服务,需要有Raft平台来申请appid资源。
·Configs:是指部署组件需要的外部参数。这些参数可能是必选的(缺乏的话组件无法初始化),也可能是可选的(缺乏的话组件用默认值初始化)。
我们约定下面两个规则:
1.透传规则:如果一个组件依赖的下层组件也需要输入参数(Configs列表有没有默认值的必选参数),且当前组件无法确定参数的值,则组件必须把他们(没有默认值的必选参数)也全部填入到自己的Configs列表,并在参数名加上组件名前缀(比如a依赖组件b,b有个alias参数,则a需要加上参数b.alias)。这样的目的是为了确保必选参数能够透传给上层,逐层透传给App,最终让应用开发者来确定参数值。
2.覆盖规则:一个App或者大组件,可以对其依赖链中任意的组件设置参数,覆盖下层已经确定的值。
根据输入参数被处理对象的不同,我们把Configs分成三类:
- Envs:组件本身进程需要的参数,这些参数Raft平台会以环境变量的方式注入到容器的运行环境中,组件进程可以读取。
- Parameters:容器平台或各类XaaS平台申请资源时需要的参数,这类参数会被传递给申请PaaS/SaaS的插件处理,插件会翻译成申请资源API需要的参数。
- Features:Raft平台自己需要的参数,这些参数会在raft后台解析和处理。
有了上面的关键词,我们就可以构建一个完整的组件模型,如下图:
3. 链路打通
·Export内容的规范化
为了规范名字服务的使用,我们export的内容统一采用json格式进行编码。
比如:
·Import-Export机制
一个组件Export的内容,就是这个组件可以被访问的方式(我们统称为名字服务),有可能是普通的名字服务,也有可能是某个平台提供的接入方式,比如TGW。
当一个组件import另一个组件的时候,需要通过某种访问方式来访问被import的组件,假设组件a通过l5访问b,在我们的模型中,Raft后台需要执行:
- 申请l5名字服务,得到 l5route: {service:"l5",modid:“mid1”,cmdid:“cid1”}
- 把l5route填到b的exports的l5route.value中
- 把l5route填到a的configs.envs的b.l5route.value中
- 部署b的时候,把l5route跟容器实例进行关联(容器创建时将ip端口注册到名字服务中,容器销毁时从名字服务中剔除)
- 部署a的时候把a.configs.envs注入到a的环境变量或指定的配置文件中(a的进程可以获得b的l5路由)
上面的操作完成后,a和b的访问关系就建立起来了。
4. 依赖管理
为了更好理解部署的过程,我们提出了静态依赖和动态依赖的两个概念。
·静态依赖
Raft平台采用yaml格式的描述文件com.yml来描述一个组件。这个yaml文件描述的是静态的组件信息,组件依赖的下级组件和XaaS服务,都是还没有被部署或申请下来的(有点像C 中的类,实例化后就是对象),这个依赖关系我们称为静态依赖。
根据yaml描述文件,可以看出组件对其他组件的依赖,但无法看出部署的时候,某个组件是否上独立部署一份,还是公共被其他组件使用。
在静态依赖图中,每个组件节点由组件的名称唯一标识。
·动态依赖
根据应用部署的场景,在静态依赖关系的基础上,增加应用的环境描述和对每个组件实例部署情况的描述,就构成动态依赖关系。
根据动态依赖关系图,可以看出组件或应用所依赖的组件实例,如果一个实例被多个组件依赖,也可以清晰看出来。
在动态依赖图中,每个组件节点有一个唯一的组件实例ID,由这些元素组成:
CompInstanceID = AppInstanceID CompName Alias
其中:
·AppInstanceID:标识本应用实例的唯一ID,请参考【应用实例】的描述。
·CompName:组件上架名称,由Raft组件市场确保唯一。
·Alias:应用开发者或大组件开发者为组件指定的别名,如果两个不同组件依赖的一个组件别名一样,表示他们共享同一个实例。默认情况下,系统会给每个组件生成不同别名,确保不同调用链不共享组件实例。
在上图中,Relation Server只有一个别名C4,所以C1/C2/C3共享了同一个实例,而3个redis的别名都不一样,所以Raft平台分别为他们部署不同的实例(redis存储的数据不共享)。
·共享机制
从上面的描述我们可以看出,静态依赖跟动态依赖最主要区别,是用户(APP开发者或者由多个小组件构成的大组件开发者)指定了组件实例间的共享关系。为了让用户更容易理解,我们引入了Share关键字,来描述组件的共享关系。
用户可以在yaml描述文件中通过Share关键字来指定共享关系,比如上图中,可以指定:
5. XaaS服务打通
为了让组件更方便被使用,我们需要把组件用到的第三方XaaS服务尽可能多的加入到Raft平台中来,并在部署组件的时候自动帮用户申请这些XaaS服务的实例(appid、ip或者名字),让组件使用者不需要过多关注组件的内部细节就可以使用组件。
为此,我们做了几个方面的努力:
1.拓广XaaS服务来源。
我们当前正在跟腾讯云TCB(Tencent CloudBase)团队合作,当前正在接入接入腾讯云的MySQL和Redis服务,后续将会接入更多腾讯云资源。
2.开放XaaS接入框架。
为了接入更多的XaaS服务,我们设计了一套开放接入框架,允许XaaS服务提供方自行进行接入,降低接入成本。
大概的思路是把申请XaaS的插件做成一个Raft组件,并通过Raft平台进行部署和打通(刚好利用了Raft平台的这两个核心能力)。这样XaaS服务提供者在我们这里上架XaaS服务,只需要上架相应申请XaaS服务的组件就可以了。
通过这个框架当前已经接入了近10个腾讯内部的SaaS/PaaS服务。
3.集成优秀开源组件。
有很多开源组件,比如Redis/mySQL/ES等,应用很广,我们也会陆续把他们当作Raft组件上架到组件市场(目前已经上架了Redis),给用户提供一个快速试用的服务平台。当然如果业务最终上线,就需要购买腾讯云的服务,或者使用第三方PaaS平台提供的服务。
四、总结
前面介绍了腾讯轻舟(Raft)平台中后台组件的主要治理思路,其中最核心的思路是对组件进行模型抽象,抽象出Imports/Exports/Depends关键词,通过这些关键词描述组件的依赖关系,并利用名字服务进行打通。
Raft平台目前还处于公司内部推广阶段,但已经得到很多组件方的认可,目前已经接入了30 个组件,并且仍然在活跃接入中。
Raft是一个开放和开源的平台,我们也非常欢迎各个团队跟我们合作,创建更多的组件和服务,也提供更方便快捷的接入能力。