持续集成框架buildbot初探

最近在写一个新程序,由于需要依赖平台相关的特性,因此每次提交前都需要手动在各种平台测试,令人十分苦恼。

好在,最近在实验室的一台主机上装了ESXi,虚拟出几个节点别提有多方便,所以CI也可以搞起了。

目前(一直以来)比较流行的CI有Jenkins,那就先从Buildbot开始吧。

很遗憾,无法使用Docker进行部署,因为需要用到x86平台以及FreeBSD。先来亮个图,虽然这个web端其实并没有什么用。。。
buildbot.png

0x00 基本概念


Buildbot使用的是典型的Master-Slave架构,按照节点的角色分类,可以分为

  • Buildbot-master:负责处理仓库的变动,通知以及向Buildbot-worker发送命令。
  • Buildbot-worker:负责具体的代码拉取,构建和测试等操作。

几个更细粒度的概念:

  • Scheduler:接收代码变动的通知,向Builder队列发送build请求,Buildbot具备不同类型的Scheduler,例如针对特定分支的SingleBranchScheduler,手动触发的ForceScheduler等。
  • Builder:负责向Worker分发任务,可以关联多个Worker,但每次build只能在一个Worker上执行。
  • Worker:对应Buildbot-worker,可以对应多个Builder。

Builder和Worker之间是多对多的关系:

  • 多个Worker对于单个Builder而言,相当于是一个Worker Pool,这样就能够避免在几个Worker offline后,导致该Builder无法执行构建操作的情况。
  • 单个Worker能够对应多个Builder,是因为每个Worker其实可以包含多个tag信息,而每个tag都可能对应一个Builder。

关于Master-Slave架构的单点故障问题,Buildbot也有具体的解决方案,但本文作为初探显然不会将其作为关注点。

0x01 部署


我在ESXi上开了5个不同的节点,分别是Debian9-i386、Debian9-amd64、FreeBSD11-i386、FreeBSD11-amd64。其中Debian9-amd64有两个节点,分别作为Buildbot-master和Buildbot-worker。

所有包都可以通过pip下载,建议使用Python2.7 Python3,本文适用于buildbot-2.6.0。

Buildbot-master部署

Buildbot-master需要先安装如下依赖:

pip3 install --user service_identity x509
pip3 install --user six treq
pip3 install --user buildbot-waterfall-view buildbot-console-view buildbot-grid-view # 用于web端中的显示
pip3 install --user buildbot-www buildbot

启动Buildbot-master:

# 创建Buildbot-master
buildbot create-master master # master 为目录名
# 启动Buildbot-master
buildbot start master

对于Buildbot-master,需要做比较多的配置,这在下一节进行阐述。

Buildbot-worker部署

对于Buildbot-worker,安装如下包即可:

pip3 install --user buildbot-worker
# 创建Buildbot-worker
buildbot-worker create-worker worker MASTER-HOST NAME PASSWD # worker为目录名
# 启动Buildbot-worker
buildbot-worker start worker

NAME和PASSWD均会在配置Buildbot-master时用到,这些信息用于在Buildbot-master中认证Buildbot-worker,所以只有得到Buildbot-master授权的Buildbot-worker才可以得到连接。

由于Buildbot-worker节点执行具体的构建工作,因此还需根据项目安装编译、测试必须的依赖和工具,目前暂时用到的是gtest、cmake、g++、gmake(FreeBSD下的GNU make)等,根据需要进行定制,我是手动去部署的,但是这些工作实在冗余,如果数量巨大则建议使用Ansible进行管理。

0x02 配置


Buildbot-worker配置

Buildbot-worker基本不需要配置,其目录下存储的是配置信息、日志以及在本地构建的仓库。

buildbot.tac存储有创建Worker时的信息,包括name,master地址等,此外还有一些默认的信息,如端口等。

info目录下有2个文件:admin和host,它们会显示在web端的worker信息栏中,至于它们的作用,看名字就知道了。

如果你已经在该worker上构建过项目,则会发现一个名字和相关builder相同的目录,里面就是下载到本地的仓库。

Buildbot-master配置

BuildMaster的配置比较复杂,大致需要完成如下步骤:

  1. Worker信息配置
  2. Builder信息配置
  3. Scheduler信息配置
  4. Build-step配置

打开master.cfg,整个配置文件就是一个map,配置文件对其的命名为c。

配置Worker,只需要找到c['workers'],在内部构造Worker即可,示例如下:

c['workers'] = [
    #worker.Worker("NAME", "PASSWD")
    worker.Worker("Debian-i386", "123456", properties={'os':'debian', 'arch':'i386'}),
    worker.Worker("Debian-amd64", "123456", properties={'os':'debian', 'arch':'amd64'}),
    worker.Worker("FreeBSD-i386", "123456", properties={'os':'freebsd', 'arch':'i386'}),
    worker.Worker("FreeBSD-amd64", "123456", properties={'os':'freebsd', 'arch':'amd64'})
]

这里的NAME和PASSWD,必须与创建Buildbot-worker时填写的一致,properties可以有选择地填写。完成这一步,就意味着整个框架内部已经有几个对应的Worker了,然后我们需要为这些Worker编组,分配给对应的Builder,记住之前所说的,一般一个平台对应一个Builder。

找到c['builders'],在其内部配置Builder的信息:

c['builders'] = [
    util.BuilderConfig(name="run-linux-32", workernames=["Debian-i386"], factory=factory),
    util.BuilderConfig(name="run-linux-64", workernames=["Debian-amd64"], factory=factory),
    util.BuilderConfig(name="run-freebsd-32", workernames=["FreeBSD-i386"], factory=factory),
    util.BuilderConfig(name="run-freebsd-64", workernames=["FreeBSD-amd64"], factory=factory)
]

这里的factory表示具体的构建步骤,具体配置下文阐述,由于这里在所有平台上执行相同的操作,因此使用同一个factory即可。至此,我们就已经将Worker和Builder关联了,剩下的则是关联Scheduler和Builder,以使其能够在恰当时机向Builder发送构建请求。

找到c['schedulers'],进行相关配置,这里只配置2个不同的Scheduler:

  • SingleBranchScheduler - 用于特定分支的构建,也就是说当发现仓库变动时,只有分支匹配才会执行构建。
  • ForceScheduler - 用于在Worker页面上添加一个按钮,使用户可以手动进行构建,这在测试时很有用。
run_all = ["run-linux-32", "run-linux-64", "run-freebsd-32", "run-freebsd-64"]
c['schedulers'] = [
    schedulers.SingleBranchScheduler(name="all", change_filter=util.ChangeFilter(branch='master'),
        treeStableTime=None, builderNames=run_all),
    schedulers.ForceScheduler(name="force", builderNames=run_all, buttonName="Build")
]

treeStableTime表示在仓库变动事件触发后,若在指定时间内没有出现新的变动,则执行构建,否则重置计时器。buttonName则表示显示在Worker页面的按钮的名字。

接下来,我们需要做的就是定制构建过程,也就是Build-step,找到之前的那个factory,它是一个工厂,也就是说在每次构建中,均会创建一个Build实例,我们要做的就是向这个工厂不断添加中间步骤(addStep)。

这里的用例比较简单,详细步骤如下:

  1. git pull
  2. cmake
  3. make
  4. bin/TestMain

因此这里只需要借助内置的ShellCommand类即可,大致如下:

factory = util.BuildFactory()
factory.addStep(steps.Git(repourl="xxxxxxxx", mode="incremental"))
factory.addStep(steps.ShellCommand(command=['cmake', '.']))
factory.addStep(steps.ShellCommand(command=['make']))
factory.addStep(steps.ShellCommand(command=['bin/TestMain']))

注意command数组表示执行的命令及其参数,可别填错了。

buildbot reconfig master

更新配置后,就可以在Worker的页面上执行手动构建了,在测试通过后,我们接下来可以配置变更源,也就是Change-source,Buildbot支持的类型较多,例如Github Hook等,这类Hook有个问题,要求Buildbot-master具备公网IP,显然这边没有这条件,所以我们使用GitPoller,它可以按指定周期去轮询相关仓库,获取变动信息。

找到c['change_source'],修改相信息即可,默认的配置就已经基本配置好了。

c['change_source'] = [
    changes.GitPoller("REPOSITORY_URL", project="PROJECT_NAME", workdir="gitpoller-workdir",
        branch="XX", pollinterval=300)
]

pollinterval表示轮询间隔,300秒应该还是挺合适的。

0xFF 总结


Buildbot最大的优点在于可定制度极高,但也正因为如此,配置起来会略显麻烦。

其实,还有很多内容没有涉及,例如深度定制构建过程、容错等,我们之后再深入讨论。

作为一个喜欢折腾的coder,我还是挺喜欢Buildbot的,只不过web端真的是个很大的遗憾。。。

说两句: