本文作者:@ warrenchen
GitHub 地址:https://github.com/warren830
DevLake 是一个DevOps数据收集和整合工具,通过 Grafana
为开发团队呈现出不同阶段的数据,让团队能够以数据为驱动改进开发流程。
1. DevLake 架构概述
- 左边是可集成的 DevOps 数据插件,目前已有的插件包括 Github,Gitlab,JIRA,Jenkins,Tapd,Feishu 以及思码逸主打的代码分析引擎
- 中间是主体框架,通过主体框架运行插件中的子任务,完成数据的收集,扩展,并转换到领域层,用户可以通过 config-ui 或者 api 调用的形式来触发任务
- RMDBS 目前支持 Mysql 和 PostgreSQL,后期还会继续支持更多的数据库
- Grafana 可以通过sql语句生成团队需要的各种数据
接下来我们就详细聊一聊系统是怎么跑起来的。
2. 系统启动
在我们的 golang 程序启动之前,首先会自动调用各个 package 的 init() 方法,我们主要看看services 包的载入,下面的代码有详细注释:
go
func init() {
var err error
// 获取配置信息
cfg = config.GetConfig()
// 获取到数据库
db, err = runner.NewGormDb(cfg, logger.Global.Nested("db"))
// 配置时区
location := cron.WithLocation(time.UTC)
// 创建定时任务管理器
cronManager = cron.New(location)
if err != nil {
panic(err)
}
// 初始化数据迁移
migration.Init(db)
// 注册框架的数据迁移脚本
migrationscripts.RegisterAll()
// 载入插件,从cfg.GetString("PLUGIN_DIR")获取到的文件夹中载入所有.so文件,在LoadPlugins方法中,具体来讲,通过调用runner.LoadPlugins将pluginName:PluginMeta键值对存入到core.plugins中
err = runner.LoadPlugins(
cfg.GetString("PLUGIN_DIR"),
cfg,
logger.Global.Nested("plugin"),
db,
)
if err != nil {
panic(err)
}
// 执行数据迁移脚本,完成数据库框架层各个表的初始化
err = migration.Execute(context.Background())
if err != nil {
panic(err)
}
// call service init
pipelineServiceInit()
}
3. DevLake的任务执行原理
Pipeline的运行流程
在讲解Pipeline流程之前,我们需要先解释一下Blueprint。
Blueprint是一个定时任务,包含了需要执行的子任务以及执行计划。Blueprint 的每一次执行记录是一条Historical Run(也称为 Pipeline),代表 DevLake 一次触发,通过一个或多个插件,完成了一个或多个数据收集转换的任务。
一个pipeline包含一个二维数组tasks,主要是为了保证一系列任务按预设顺序执行。如果下图中的 Stage3 的插件需要依赖其他插件准备数据(例如 refdiff 的运行需要依赖 gitextractor 和 github,数据源与插件的更多信息请看文档),那么 Stage 3 开始执行时,需要保证其依赖项已在 Stage1 和 Stage2 执行完成:
4. Task的运行流程
在stage1,stage2,stage3中的各插件任务都是并行执行:
接下来就是顺序执行插件中的子任务:
- RunTask 之前的工作都是在准备 RunTask 方法需要的参数,比如 logger,db,context 等等。
- RunTask 方法中主要是对数据库中的tasks进行状态更新,同时,准备运行插件任务的 options(把从 config-ui 传过来的 json 转成 map 传到 RunPluginTask 中)
- RunPluginTask 首先通过 core.GetPlugin(pluginName) 获取到对应 PluginMeta,然后通过 PluginMeta 获取到 PluginTask,再执行 RunPluginSubTasks
5. 每一个插件子任务的运行流程(涉及到的 interface 及 func 会在下一节详细阐述)
- 通过调用SubTaskMetas()获取到所有插件所有的可用子任务subtaskMeta
- 通过options["tasks"]以及subtaskMeta组建需要执行的子任务集合subtaskMetas
- 计算总共多少个子任务
- 通过helper.NewDefaultTaskContext构建taskCtx
- 调用pluginTask.PrepareTaskData构建taskData,
接下来迭代subtaskMetas里面的所有子任务
- 通过taskCtx.SubTaskContext(subtaskMeta.Name)获取到子任务的subtaskCtx
- 执行subtaskMeta.EntryPoint(subtaskCtx)
6. DevLake中的重要接口
- PluginMeta: 包含了插件最基本的两个方法,所有插件都需要实现,系统启动的时候存在core.plugins中,在执行插件任务的时候通过core.GetPlugin获取
go
type PluginMeta interface {
Description() string
//PkgPath information will be lost when compiled as plugin(.so), this func will return that info
RootPkgPath() string
}
- PluginTask: 通过PluginMeta获取,插件实现这个方法之后,Framework就能直接运行子任务,而不是扔给插件自己去执行,最大的好处就是插件的子任务实现更加简单,在插件运行当中,我们也可以更容易的去干涉(比如增加日志等等)
go
type PluginTask interface {
// return all available subtasks, framework will run them for you in order
SubTaskMetas() []SubTaskMeta
// based on task context and user input options, return data that shared among all subtasks
PrepareTaskData(taskCtx TaskContext, options map[string]interface{}) (interface{}, error)
}
- 每个插件还有一个taskData,里面包含了配置选项,apiClient以及一些插件其它属性(比如github有Repo信息)
- SubTaskMeta: 一个子任务的元数据,每个子任务都会定义一个SubTaskMeta
go
var CollectMeetingTopUserItemMeta = core.SubTaskMeta{
Name: "collectMeetingTopUserItem",
EntryPoint: CollectMeetingTopUserItem,
EnabledByDefault: true,
Description: "Collect top user meeting data from Feishu api",
}
- ExecContext: 定义了执行(子)任务需要的所有资源
- SubTaskContext: 定义了执行子任务所需要的资源(包含了ExecContext)
- TaskContext: 定义了执行插件任务所需要的资源(包含了ExecContext)。与SubTaskContext的区别在于SubTaskContext中的TaskContext()方法可以返回TaskContext,而TaskContext中的方法SubTaskContext(subtask string)方法可以返回SubTaskContext,子任务隶属于插件任务,所以把这两个Context进行了区分
- SubTaskEntryPoint: 所有的插件子任务都需要实现这个函数,这样才能由框架层统一协调安排
后续
这篇文章介绍了 DevLake 的架构以及运行流程,还有三个核心 api_collector、api_extractor 和 data_convertor 将会在下一篇文章进行剖析。
关于我们:
DevLake
开源产品 DevLake 是一款开源的研发效能数据平台,提供自动化、一站式的数据集成、分析以及可视化能力,能够将散落在不同研发阶段和不同 DevOps 工具中的效能数据汇集起来,转化为有效洞见,从而挖掘关键瓶颈与提效机会。
了解更多最新动态