Drone CI 源代码走读
开篇
使用开源工具的好处是,可以通过阅读源代码,深入了解工具的运行原理,可以更好的使用和掌握工具,遇到问题能更好的定位。阅读源代码,往往比阅读文档更加高效,尤其在遇到复杂的问题,代码才是最好的文档。
drone是用golang开发的,golang本身入门简单,方便掌握,golang本身更强调工程实用性,golang写出的代码相对更直接,好理解。
Drone代码量不是很多,结构很清晰,如果对Drone基本流程比较熟悉的话,很容易能把源代码顺下来。
drone 代码扩展性很好,作为开发人员可以很容易的定制自己的功能,如扩展yaml pipleline的语法,扩展权限管理系统等,从而更高效率的使用drone CI。我后边会写一系列的文章来由浅入深的介绍drone源码。
本篇为基础篇将从主体核心逻辑来讲解,从代码角度过一下drone的触发和build流程,不会深入太多细节。
建议对drone本身有一定使用经验,并且阅读过我前两篇文章,再来阅读这篇文章。
正文
为了便于理解,我会结合drone 的web UI 的操作,分布讲解UI中的操作背后触发的代码逻辑,给读者更好的直观感受,更好的理解代码。本文只会涉及核心的代码逻辑流程,不会过多深入代码细节。
名词解释:
scm 源代码管理工具 如github gitlab
同步repos列表:
以gitlab为例,首先通过gilab的oauth2 authorization flow授权进入主页,点击syncer按钮, 实现repo list的同步,这个过程实际是调用后端service/syncer/syncer.go 文件Sync 方法实现本地数据库repos 数据和scm remote的数据的同步,通过RepositoryService获取scm列表信息,然后将数据更新到repos数据库表中。
激活repo
在同步完的Repositories列表中,选择想要的repo,点击active按钮,进入repo的详细页面,点击Activate repository实现repo的激活。实际上通过API执行了handler/api/repos/enable.go中HandleEnable方法,该方法主要是:
- 更新repos数据库表对应的repo的数据状态
- 调用scm api将drone的webhook api注册到scm中,当scm接收到对应事件(用户commit并push代码)会给注册进来的所有webhook API发送消息,调用注册的web API方法。
// 调用api的方式激活scm对应repo的webhook功能
err = hooks.Create(r.Context(), user, repo)
实际是调用 RepositoryService 中DeleteHook和CreateHook
Drone中所有和scm交互的代码都在go-scm这个包中,go-scm为多种scm提供了统一的操作接口, 目前支持 gogs bitbucket gitea github gitlab 等scm系统
https://github.com/drone/go-scm
buid执行流程:
当用户commit并push代码到scm中,scm就会触发第二步注册的drone API,该API实际上是执行handler/web/hook.go HandleHook方法,HandleHook方法是所有drone build流程的起点。HandleHook功能主要是,根据scm触发的webhook类型不同(如PushHook TagHook)构建出对应的core.Hook core.Repository数据结构,再用这个core.Repository 对象找到对应的数据库对象
repo, err := repos.FindName(r.Context(), remote.Namespace, remote.Name)
。然后将这两者当做参数传递给tiggerer.
builds, err := triggerer.Trigger(ctx, repo, hook)
Trigger(trigger/trigger.go)方法利用ConfigService找到drone config文件也就是drone.yaml
raw, err := t.config.Find(ctx, req)
,然后对done.yaml文件进行解析,对pipeline类型的Resource进行处理,根据skip条件过滤掉相关的pipeline,利用pipeline数据构造stage对象,构造build对象,然后将build对象和对应生成的stage对象,以及他们之间的依赖关系,分别存入数据库。然后stages列表循环调用schedule方法,来schedule stage。
err = t.sched.Schedule(ctx, stage)
schedule依据三种runtime环境 docker k8s nomad,目前有三种实现,这里介绍常用的两种。
-
基于docker的默认运行方式
启动多个runner进程(默认值是2,可以配置),每个进程不停的从server(db )中轮询、获取待执行的build然后执行
operator/runner/runner.go
func (r *Runner) Start(ctx context.Context, n int) error {
var g errgroup.Group
for i := 0; i < n; i++ {
g.Go(func() error {
return r.start(ctx)
})
}
return g.Wait()
}
-
基于k8s运行时的方式
以scheduler\queue\kube为例,程序在Trigger 中执行 Schedule 方法,实际就是调用k8s API,构建一个k8s job,用drone-controller作为镜像,将stage id通过环境变量传过去,然后启动container,在这个drone-controller节点执行整个stage的build流程。drone-controller (cmd/drone-controller)主要功能是调用runner.Runner(operator/runner.go)控制执行stage build流程。
build实际执行主要流程
Run(operator/runner.go )是核心运行函数,Runner对象是核心对象,负责获取,解析和执行实际的build,并且实时更新build状态到数据库。
Drone yaml解析、运行基本流程
将drone yaml 转成drone-runtime使用的数据结构spec(drone-runtime包 engine/spec.go),再转换为go-docker api 使用的数据结构(container.Config container.HostConfig network.NetworkingConfig container.DeviceMapping),调用 docker go api 利用解析后的参数运行相关step。
drone build 实际执行核心代码在包drone-runtime包中,
Engine
Engine定义用于pipeline step的运行时引擎,是drone pipeline 定义的step的实际执行者。对应有docker 和k8s两种运行时。
Docker运行时环境(engine/docker)
对于docker api进行封装,实现container的构建的过程(共享临时volumes创建,网络环境的创建,container的文件拷贝,container创建并执行,container wait ,container销毁,日志获取等功能)
Runtime
Runtime 负责执行整个drone config pipline,解析配置文件,如果各个step之间没有依赖关系,就按照顺序执行的方式执行,否则根据config 定义的 depdon关系创建一个,有向非循环图,按照并行拓扑顺序运行对应step。理解有向非循环图,可以更好的理解step并行执行如何定义和实现,怎们通过不同的depdon来实行step并行,这种有向非循环图方式从学术上更专业了,但是比之前通过配置group的方式要难理解一点,但是如果从理伦方面了解了相关概念其实还是很好掌握的。