标签 持续集成 下的文章

使用BuildBot搭建持续集成环境

部门的持续集成一直做的不太好,我们开发部这边甚至一直没能做起来,这其中有各种原因:工具、意识、执行力、沟通等等。将持续集成引入到我们的开发过程中也一直是我的一个目标。去年末启动的一个项目让我感到时机变得成熟了。

新项目的代码是完全重写的,这样的机会甚是难得。因为大多数情况下大家都是在维护现有系统:做些添添补补、修正Bug以及优化之类的事情。项目初期,我特别向大家强调了要严格遵守统一代码风格并将astyle代码格式化工具介绍给大家,手把手地教大家如何利用类似LCUT这样的单元测试框架编写单元测试,讲解什么是Mock测试。前些时间我又将代码风格检查
脚本加入到工程的构建过程中,并将代码风格检查作为最终构建目标的关键依赖,强制大家编写出统一风格的代码。

情况就是这样的情况,的确我们现在只做到了这些。不过有了这些基础,我就更有信心去做持续集成了。

今年年初部门统一部署了产品的多平台移植的开发任务,作为新项目,我们的成果物被要求天生就应具备适合在多个平台上运行的能力。这次产品平台移植仿佛一针催化剂加快了我在项目中实施持续集成的脚步。我希望搭建出这样一套系统:每当开发人员提交代码后,持续集成框架都能发现这些代码变动,并在多个不同平台的主机上分别Checkout出最新代码,Build,Check代码风格并运行单元测试集,最终将结果通知所有人。

我需要找到满足我这一需求的工具。记得若干年前我第一次研究持续集成时曾经研究过一款名为CruiseControl.rb的工具,不过很遗憾的是这款工具似乎早已不更新了。在Thoughtworks的官方站点上,其最后一次发布是在2年前了。另外CruiseControl.rb一个比较大的缺憾就是性能差些。另外如果想满足我在多个不同平台主机上同时运行构建以及测试的要求,似乎需要部署多套CruiseControl.rb。

在寻找工具的路上,我发现了BuildBot。这是一款由Python实现的开源持续集成工具。与CruiseControl.rb相比,其性能更好,其Master+Slaves的结构更易于扩展,并且可以很好地满足多平台版本构建的需求,大名鼎鼎的Google Chrome浏览器项目用的也是这款工具。另外我个人对Python的更加青睐也让我决定使用这款工具。

BuildBot的文档比较丰富和全面,这也使其安装过程比较简单。BuildBot由两部分组成,一部分是Master,用于监视代码库变动,控制各个Slave节点进行构建操作,并收集反馈结果以各种方式(Mail、Html等)展示给用户;另外一部分就是Slave了。每个Slave节点都承担着构建过程的具体工作:他们接收Master发过来的指令,并按指令一步一步完成构建工作,并将结果反馈给Master。

一个BuildBot持续集成环境就是由一个Master与一些Slaves组成的。其安装过程大致如下:
1、在装有Python(最好是Python 2.6.x版本)的Master主机上安装Buildbot master:下载BuildBot安装包(我用的是最新的BuildBot-0.8.3p1)。解包后,执行python setup.py build和python setup.py install安装BuildBot master包。注意install默认是需要root权限的。
2、安装BuildBot依赖包:下载最新的Twisted包(我用的是11.0.0)与zope.interface包(我用的是3.6.1),安装方法与BuildBot一致。
3、在装有Python(最好是Python 2.6.x版本)的各个Slave主机上安装BuildBot slave:下载Buildslave安装包、最新的Twisted包与zope.interface包,安装方法与BuildBot Master一致。
4、以上安装完成后,在Master host上执行buildbot,在各Slave host上运行buildslave,检查一下是否成功安装了。

安装Ok,我们就可以建立Master实例以及诸多Slave的实例了。先说说Master。在Master主机上某路径下,创建foo_ci_master目录,进入foo_ci_master目录下,执行:"buildbot create-master ./"。执行后,在当前目录下会有master.cfg.sample文件。这是一个样板文件,我们将其改名为master.cfg后,打开master.cfg,开始进行master的配置。

Master的master.cfg是这套持续集成系统的核心。我们用一个简单的例子来说明这个配置。假设我们的持续集成环境由三台主机组成:Master Host(假设其ip为10.0.0.1)以及两台Slave Host,其中一台Slave Host上运行着RHEL 5.5,而另外一台Slave Host上则运行着PC Solaris 10。我们希望当有代码被提交到代码库中后,Master可及时发现这一变化,并且指挥两台Slave Host检出最新代码并且都能Build成功。

下面是master.cfg中的一些关键配置及说明(省略了一些默认配置):

#
# master.cfg
#

c['slaves'] = [BuildSlave("x86-solaris-bot", "x86-solaris-bot-passwd"),
               BuildSlave("redhat-bot", "redhat-bot-passwd")]

这里告诉Master我们有两个Slave node,分别是x86-solaris-bot和redhat-bot,而这两个Slave登录Master的密码分别为x86-solaris-bot-passwd和redhat-bot-passwd。

我们使用subversion作为我们源码版本管理工具,所以我们采用SVNPoller来监测源码库的变化:

from buildbot.changes.svnpoller import SVNPoller
c['change_source'] = SVNPoller("svn://10.10.0.1:8888",
                                svnuser='YOUR_SVN_USER',
                                svnpasswd='YOUR_SVN_PASSWD',
                                pollinterval=30,
                                split_file=foo_split_file)

def foo_split_file(path):
        pieces = path.split('/')
        if pieces[0] == ‘foo’ and pieces[1] == 'trunk':
            return ('foo/trunk', '/'.join(pieces[2:]))
        else:
            return None

在SVNPoller的参数中split_file是比较难理解的一个。它是为下面的Scheduler提供服务的。split_file会将变更的源码文件的完整路径信息进行拆分,并返回一个(branch, relative_pathname)的元组。而Scheduler将尝试匹配元组中的branch以决定此次变更是否是自己所关心的。看下面配置代码:

c['schedulers'].append(Scheduler(name="foo-ci-plan",
                                 branch='foo/trunk',
                                 treeStableTimer=5,
                                 builderNames=["foo-redhat-builder", "foo-x86-solaris-builder"]))

显然这个Scheduler关心"foo/trunk"这个branch。一旦某源码文件归属于该分支(如svn://10.10.0.1:8888/foo/trunk/main/main.c),则该Scheduler会启动构建过程。其构建过程将通过两个builder完成,它们分别是foo-redhat-builder和foo-x86-solaris-builder。这样一来,我们就可以适当定义foo_split_file并设置多个Scheduler,以满足我们对不同branch的不同构建需要。

builder都是关联到某个builder factory的,而下面则是factory的配置:

foo_builder_factory = factory.BuildFactory()
foo_builder_factory.addStep(SVN(mode='update',
               baseURL='svn://10.10.0.1:8888/',
               defaultBranch='foo/trunk'))
foo_builder_factory.addStep(Compile(command=["make"]))

这个factory生产出来的builder会执行两个step:首先执行svn update,将svn://10.10.0.1:8888/foo/trunk更新到本地;然后执行make命令。

下面是builder的设置:

b1 = {'name': "foo-redhat-builder",
      'slavename': "redhat-bot",
      'builddir': "foo-redhat",
      'factory': foo_builder_factory,
      }

b2 = {'name': "foo-x86-solaris-builder",
      'slavename': "x86-solaris-bot",
      'builddir': "foo-x86-solaris",
      'factory': foo_builder_factory,
      }

c['builders'] = [b1, b2]

builder的设置看起来没那么难,一目了然。

BuildBot Slave的创建和配置就更加简单了。首先到那台运行着solaris系统的Slave host上,在适当目录下创建foo_ci_slave目录,进入该目录后,执行“buildslave create-slave –umask=022 ./ 10.0.0.1:9989 x86-solaris-bot x86-solaris-bot-passwd”命令,一个Slave就创建完了,实际上也配置完了,无需额外配置了。其配置文件就是foo_ci_slave下面的buildbot.tac文件。Rhel上的slave也是如此创建的。

启动Master。在Master host的foo_ci_master下面,执行buildbot start ./即可启动buildbot master,其当前日志会被输出到twistd.log中;如果要停止buildbot master,依旧是在该目录下,但执行buildbot stop ./。

启动slave。在Slave Host的foo_ci_slave下面,执行buildslave start ./即可启动buildbot slave,其当前日志会被输出到twistd.log中;如果要停止buildbot slave,依旧是在该目录下,但执行buildslave stop ./。

当Master和各个Slave都成功启动后,我们就可以来试试执行一次Build过程:修改foo/trunk下的某源码文件并提交。Master将会侦听到变更,便会启动两个Slave Host上的build过程。此次构建的结果在哪里可以看到呢?试试访问http://10.0.0.1:8010,页面上红色代表构建失败,绿色代表构建成功。

Buildbot还支持将构建结果通过Mail通知的机制,不过由于公司用的是ssl方式,我试验了许久都没能将mail发出来。不知道是不是Twisted的Mail包的实现有问题还是其他什么原因,后续会继续查证。

CruiseControl.rb初体验

我所在的项目一直以C语言作为主要开发语言,与做Java以及其他新兴语言的人不同,组内的同事似乎对新鲜的东西不是那么感兴趣,也没有主动去研究新鲜事物的意愿和意识。我深为此闹心,看到外面世界中那么多美妙的工具,再也不能坐以待毙了。我一直都是有很多想法的,但是迫于自身精力有限,自己无法全身投入,以前都是交予别人去做的,但是收到的效果都不是很好。认识到这点后,我决定自己动手,丰衣足食。

从心底一直对公司的CMMI流程有所抵触,眼看着外面世界中的Agile Development等轻量级开发过程日益壮大,但自己每天还不得不按照各种繁复的流程去做,真有一种"身在曹营心在汉"的感觉。但是在一个CMMI流程"森严"的公司,又如何才能让大家接受Agile的思想呢?我想让大家看到新实践的与以往不同的成效,大家自然也就能够接受了。在Infoq上,有人也给出了建议:“切莫开口提大名词(例如SCRUM或者XP)。建议以CMMI x级的"自我改进"做旗帜,找到组织中存在浪费的环节,引入最佳实践来消除浪费,没有必要把敏捷挂在嘴边 ”。而持续集成也是文章中建议的首选实践,与我的想法不谋而合。

持续集成,以前不是没有做过,在部门的一个项目组中曾经尝试过,自己编写脚本,完成定时更新代码、构建的任务,后因项目紧张,似乎没有收到良好效果。我也曾经在项目组内推进过做Java的同事进行持续集成的实践,并安排一个同事搭建了CruiseControl的服务器,但后来似乎不了了之,也怪我没有在后期给予充分的重视和压力。有过这些教训后,我时常也在想?推进一个持续集成的实践就这么难么?这回我亲自来做,从C开发人员入手。

持续集成是需要良好的工具支持的,业内最知名的持续集成工具莫过于CruiseControl了,CruiseControl在Java开发领域占据着No.1的地位,而且其设计思想也影响着后续的持续集成工具的开发。但是一想到Java,我第一感觉就是复杂,CruiseControl会不会也像配置一个Web服务器一样那样复杂呢?另外CruiseControl使用Maven + Ant + cvs/subversion的组合,我曾经研究过Ant, Maven,大量的xml配置让我感到很头疼,另外是否与C/C++良好配合也是疑问。这样一来,我就没有继续走CruiseControl这条路,我尝试寻找一种配置简单能快速上手,核心功能也不逊于CruiseControl的工具,Thoughtworks的CruiseControl.rb走入我的视野。

一直关注Thoughtworks,因为以前部门的两位大牛级别的同事都在那任职,另外Thoughtworks推出的产品也的确让我很感兴趣,以前就曾研究过Mingle,只不过Mingle是收费的,而且目前性能还有待提高。CruiseControl.rb(以下称为CC.rb)是Thoughtworks的一款开源作品,其主页上的一小段话很是对我的胃口:"continuous integration isn't rocket science. we keep it simple."。简单,正是我所期望的。

目前CC.rb的最新版本是1.3.0,下载后就是一个压缩包:cruisecontrolrb-1.3.0.tgz。解压后,在你的当前目录下会出现一个cruisecontrolrb-1.3.0的目录。CC.rb的依赖非常之少,你只需要在你的集成服务器上安装上ruby和svn客户端即可,注意:ruby和svn的可执行程序的路径需要添加到你的环境变量的PATH中,否则CC.rb将无法找到ruby和svn。目前CC.rb只是支持Svn,其他版本管理工具尚未得到支持,毕竟CC.rb还年轻,开发时间较少,情有可原。

现在CC.rb已经安装完毕了,没错!CC.rb是用ruby实现的,ruby是脚本类语言,无需编译。你现在就可以进入cruisecontrolrb-1.3.0目录下,执行"cruise –version"(在Unix主机上,你需要先将cruise文件chmod一次,否则cruise将无可执行权限),你会看到:
CruiseControl.rb, version 1.3.0
Copyright (C) 2007 ThoughtWorks

现在你可以添加和配置你的project了。这里要先说一下,CC.rb默认将用户的project数据放在$(HOME)/.cruise/下面,如果你是在Windows平台上,project数据会被默认放在C:\Documents and Settings\USER_NAME\.cruise下面。添加一个proj很简单,你只要提供足够的信息即可:进入到cruisecontrolrb-1.3.0目录下,执行命令:
cruise add PROJ_NAME –url SVN_URL –username USER_NAME –password PASS_WORD,如果你的svn库有权限管理,你需要提供user和passwd。举例: cruise add test_proj –url svn://192.168.0.2:3999/trunk/test_proj –username tony –password tony

这样你就在$(HOME)/.cruise/projects的下面建立了一个test_proj的工程,如果你提供的信息是正确的,CC.rb会在初始建立工程的时候,将你的svn库中的代码checkout出来一份最新的,放在$(HOME)/.cruise/projects/test_proj/work下面。

完成上一步后,$(HOME)/.cruise目录下面有几个配置文件是我们需要重点关注的:
$(HOME)/.cruise
 - config/site_config.rb
 - projects/test_proj/cruise_config.rb

$(HOME)/.cruise/config下的site_config.rb文件是一个全局性的配置文件,无论你在一份CC.rb下建立几个proj,这些proj都会共享该配置,该配置每次修改后都需要重启CC.rb才能生效;该文件中有两个最主要的配置:
1) 邮件服务器配置
如果你想将每次build的结果通过mail的形式发送给相关干系人的话,你就需要配置你的mail server相关信息。
ActionMailer::Base.smtp_settings = {
   :address =>        "xx.xx.xx.xx",
   :port =>           25,   #一般服务器默认smtp端口都是25
   :domain =>         "YOUR_DOMAIN.com",
   :authentication => :plain,
   :user_name =>      "YOUR_USER_NAME",
   :password =>       "YOUR_PASSWD"
 }
2) Dashboard URL
Configuration.dashboard_url = 'http://ip : port'; Dashboard URL将被加入到发给干系人的mail中,这样相关干系人收到mail后可以直接点击该url登录到CC.rb的Dashboard查看相关内容。CC.rb的配置文件也同样适用ruby语言编写的,ruby语言语法较为简单,相信大家都看得懂。

$(HOME)/.cruise/projects/test_proj下的cruise_config.rb文件则是一个Project-specific的local配置文件,它是动态更新的,你的更新在下一次build时是即时生效的。该配置文件中的内容都很重要和实用:
1) project.email_notifier.emails = ['email1@your.site'] 该配置用于添加干系人邮件,这样每次build后,CC.rb都会读取该配置,发送mail给该email list的人;
2) project.email_notifier.from = 'email2@your.site',该配置将指定notify mail中的发件人,你可以因项目的不同而配置不同的mail。
3)  project.rake_task = 'custom',似乎该配置只有当你使用rake这个工具时才需要配置,如果你使用诸如ant,make等工具的话,该配置还需保留原先的被注释状态;
4) project.build_command = 'build_my_app.sh',该配置给予你自定义build脚本的机会,也正是这样,你才可以将CC.rb与Ant, Make等工具集成在一起使用。如:project.build_command = 'make'。注意CC.rb执行build_command的working dir是$(HOME)/.cruise/projects/test_proj/work下面,如果你的build_my_app.sh没有放在work下面,而是在$(HOME)/.cruise/projects/test_proj下面的话,你这块就要配置成:project.build_command = '../build_my_app.sh'; project.build_command与 project.rake_task不能一起使用。
5) project.scheduler.polling_interval = 5.minutes ,CC.rb定期去检测svn是否有new revision,这个配置就是用来指定检测周期的,如果你不配置,那默认是30s。

如果你以上都配置完毕了,你现在就可以启动CC.rb了。启动方法:进入到cruisecontrolrb-1.3.0目录下,执行"cruise start -p port"。CC.rb会启动自带一个轻量级web server – WEBrick ,对于我这个对web server不熟悉的人还是很方便的,我只需要告诉CC.rb端口即可。CC.rb启动后,你可以在浏览器输入http://ip:port,CC.rb的简洁的页面就会显示出来,你会在页面上看到你刚才建立的工程test_proj,点击右上方的"build now"按钮,你就开始了一次build的过程。无论成功还是失败,你都应该收到一封mail,关于此次build的详情可以点击mail中的链接。如果失败了,你可以在页面上的"build log"中看到此次build的详细日志,以帮助你分析失败原因。

CC.rb是如何判断build过程成功还是失败了呢?我们以project.build_command = 'xx'的配置为例,其实CC.rb是通过判断xx这个builder的返回值来决定build过程是否成功的。当xx这个builder返回0,说明build成功;否则build失败;我们不妨测试一下:在$(HOME)/.cruise/projects/test_proj/work下面写一个小程序:
/* test.c */
int main() {
 exit(0);
}
gcc -o test test.c

配置project.build_command = 'test',点击"build now",收到mail,提示build success;如果你将exit(0)改成exit(1),那么一封failed的mail就会发到你的邮箱里。

C/C++没有很好的单元测试工具,一般C/C++单元测试工具都会将单元测试用例编译链接成为一个可执行文件,然后执行,以判断是否通过。到这里你是否想到了如何将C/C++的单元测试与CC.rb集成到一起的方法呢?对,我们通过脚本也好,或者干脆自己编写一个单元测试框架,通过程序返回值告知CC.rb新提交的代码是否通过了单元测试的考验。

以上是第一次使用体验CC.rb时记录下的内容,其实CC.rb就是这么简单!

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 AI原生开发工作流实战 从 0 开始构建 Agent Harness Go语言精进之路1 Go语言精进之路2 Go语言第一课 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com
这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats