专栏首页互联网运维杂谈一次早期自动化构建的搭建过程

一次早期自动化构建的搭建过程

这是老王07年进入腾讯接手的第一个项目---自动化构建AutoBuild(06年就已经在上线运行),当年还不知道有Hudson,以及后来更名的Jenkins。做完这个项目之后再去反思传统的软件工程就有了很多新的感悟,只有从根源上提出解决方案,并配合最佳的实践才能解决软件交付的质量问题。

也因为腾讯web端项目基本上都是使用的CGI,所以花了不少时间写整套的自动化编译脚本(全部是perl写的),当然里面对研发提出了编译文件Makefile的规范(后面有原理)。

在后续的版本演进中,把自动化测试、代码安全扫描、持续发布都加入了整体过程,真正的做到全过程持续交付,直到今天和客户去讲持续交付整体过程的时候,很多实践和感悟都是来源于此。

1. 编译环境的搭建

1.1. 整体框图

注:ARS是腾讯的Auto Release System,早期我也是作为运维主要需求方参与这个平台的构建和推广,直到所有的业务页面端发布都接入了这一平台。持续部署的过程也就真正成为现实。

说明:

1编译机器支持每天自动编译,以及手工触发编译两种构建方式.

1.2. SVN Server端的配置

每加入一个工程,需要找PM给自动化编译用户isd_webadmin授权只读访问该工程的权限。具体的申请方法:告诉对方工程和用户即可。

1.3. 公共代码配置

/data/auto_build/ // 主目录

|-- bin // 公共程序目录

| |-- svntools.pl // 拉取svn服务器中代码文件,获取最后更新信息的工具,编译

| |-- confgen.pl // 根据qzone项目源码根目录中的makfile文件,生成构建脚本构造文件(make.conf)的工具

| |-- mkgen.pl // 根据构建脚本构造文件(make.conf)生成全构建脚本build.sh的工具,编译环境的关键脚本

| |-- postmessage.pl // 发送邮件的工具(详见后边说明)

| |-- readlog.pl // 读取编译过程中的日志文件,生成编译结果报告buildres.xml的工具,关键脚本

| `-- writemail.pl // 根据编译结果报告build.sh生成通知邮件的工具

|-- config // 项目工程配置目录

| |-- abs_profile.conf // 所有工程的配置

`-- mbox // 存放每天所有工程编译结果的汇总邮件

1.4. 编译机的配置

编译机的这边需要的软件有:

l Apache (版本没有特殊要求) + PHP模块的支持

l PHP 4.4.2 (或者高版本) + DOM + ICONV

l PERL 5.8 (或者高版本) + DOM + ENCODE + HASHINI

建议使用官方源码包编译安装,编译顺序和选项请参考

编译环境的目录结构如下,我们以/data/项目_build做为编译环境的主目录,实际部署中可以修改脚本中的变量设置改变这个目录,只要相应的分区空间足够大,可以支持整个项目的全编译即可.

/data/qzone_build/ // 主目录

|-- admin // 编译管理目录(详见后边说明)

| |-- bin // 编译控制脚本的存放目录

| |-- cgi-bin // qzone.build.isd.com 的cgi程序目录

| |-- htdocs //qzone.build.isd.com 的页面文件目录

|-- source // 项目代码,编译结果的实际存放位置

| |-- qzone_20060829 // 这些以日期作为后缀的子目录,存放着相应日期参加日构建的源码和编译结果

| `-- qzone_20060831 // 可以根据需要保留近几天的历史结果,太早的通过简单脚本删除或者打包备份

`-- temp // 存放一些临时文件或者动态目录

|-- mbox // 发送编译结果通知邮件的信箱(详见后边说明)

`-- src -> /data/qzone_build/source/qzone_20060831 // 一个到当前代码存放目录的软连接, svntools默认会向这里

// 更新最新的代码 (详见后边说明).

admin目录下的文件有:

/data/qzone_build/admin/

|-- bin

| |-- autobuild.sh // 自动编译的总控制脚本,可以直接写到crontab中定时自动运行

| |--buildall.sh // 完整的一次编译过程的控制脚本, 不包含更新代码,发送邮件的过程

| |-- buildres.xsl // buildres.xml需要的xsl风格表单(详见后边说明)

| |-- svntools -> svntools.pl // 为方便使用做的一个软连接

| |-- svntools.conf // svntools.pl的配制文件,它应该始终和svntools.pl放在同一个目录下

| |-- svntools.pl // 拉取svn服务器中代码文件,获取最后更新信息的工具,编译

| | // 环境的关键控制脚本(详见后边说明)

| |-- confgen.pl // 根据qzone项目源码根目录中的makfile文件,生成构建脚本构造文件(make.conf)的工具

| |-- crlf // 将单个文件中每行的结尾标志字符\n(UNIX习惯),替换为\r \n(WINDOWS习惯)的工具

| |-- crlf.c // crlf工具的源代码

| |-- make.conf.def // 构建脚本构造文件(make.conf)的缺省设置文件,供confgengen.pl参考使用

| |-- message.conf // postmessages.pl的配制文件,它应该始终和postmessages.pl放在同一个目录下

| |-- mkgen.pl // 根据构建脚本构造文件(make.conf)生成全构建脚本build.sh的工具,编译环境的关键脚本

| |-- postmessage.pl // 发送邮件的工具(详见后边说明)

| |-- readlog.pl // 读取编译过程中的日志文件,生成编译结果报告buildres.xml的工具,关键脚本

| |-- setroot.sh // 设置文件更新/编译目录连接的工具,关键脚本(详见后边说明)

| |-- u2wlog.sh // 批量转换文件换行字符 \nà \r \n的工具 (内部循环调用crlf程序)

| `-- writemail.pl // 根据编译结果报告build.sh生成通知邮件的工具

|-- cgi-bin

`-- htdocs

|-- buildres.xsl ->/data/qzone_build/admin/bin/buildres.xsl //xsl表单的软连接,将原始文件放在bin中是为方便编辑

|-- index.php // build.qzone.isd.com的入口页面

|-- mainlist.php // 生成目录列表页面的工具脚本

|--slist.php // 文件列表页面

`--test.php // php测试页面 (非必需)

需要的环境变量:

PATH_QZONE_PRJ="/usr/local/qzone_v3.0"

export PATH_QZONE_PRJ

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/c4a/bin:/usr/lib

PATH_SVNTOOLS="/data/qzone_build/admin/bin"

export PATH=$PATH:$PATH_SVNTOOLS

PATH_QZONE_PRJ是qzone编译时必需的环境变量, PATH_SVNTOOLS加入路径中是为了使用方便(非必要).

整个编译环境中各个工具都可以普通用户执行,比如user_*,但应该保证相应的源码目录有足够的读写权限.

1.5. 编译过程

上边是编译的总体流程图,图中黄色圆形标注是编译过程的顺序,依次说明如下(各个控制脚本都放在admin/bin下):

1. 通过setroot.sh在source目录下选取或建立一个日构建的代码存放目录,比如qzone_20060831,并且将它软连接到/usr/local/qzone_v3.0(编译代码的目录),/data/qzone_build/temp/src(svntools.pl更新代码的目录). (注:将代码保存路径,编译目录,更新目录分开是为了支持历史编译结果备份和灵活更新)

2. 使用svntools.pl拉取远程svn服务器上的项目代码,更新到本地的/data/qzone_build/temp/src目录. svntools.pl更新文件的时候会将文件的最后更新信息(时间,更新人,CC事件)写到它所在的的目录下的ccversion.xml中

3. 自动编译系统一般是使用编译配置文件make.conf (位于源代码根目录)生成全编译脚本,但是如果项目代码根目录中有整体makefile文件,则可以使用confgen.pl读取这个makefile生成编译配制文件make.conf

4. 使用mkgen.pl读取源代码根目录下边的编译配置文件make.conf (这个文件中定义了要参加编译的子目录,以及这些目录间的依赖关系,即编译顺序,它可以通过makefile生成,也可以手工编写),在源代码根目录下生成全编译脚本build.sh

5. 上一步生成的build.sh是个可执行的bash脚本,直接运行就可以编译整个qzone代码,编译过程中的提示信息会被写入每个参加编译的子目录下,保存为build_***.log名字的文件,其中***代表build.sh的时间戳(详见后边说明)

6. 编译结束后,使用readlog.pl读取参加编译的各个子目录中的log文件,生成一份报告文件buildres.xml,记录编译结果(出错/异常/警告信息等)

7. 编译机同时兼做编译结果的发布WEB站点,允许大家查看参加编译的代码目录和各个log文件,因为UNIX环境下文本文件的换行符号\n与windows环境下的\r\n不一致,所以这些log在windows下查看时格式比较乱,使用u2wlog.sh可以对各个log文件做UNIXàWINDOWS的换行符替换

8. writemail.pl可以读取编译结果报告文件builres.xml中的关键信息,生成一封发给qzone开发组的通知邮件build_***.mail(其中***是build.sh的时间戳),邮件会放在约定好的发件箱/data/qzone_build/temp/mbox中

9. 最后调用postmessage.pl,它会将发件箱中的所有邮件(*.mail)发送出去(成功发送后将删除原始邮件).

注: admin/bin下边的autobuild.sh中按照上边的顺序串联了整个编译过程, 可以当作总的控制脚本,直接写到crontab中每天定时运行,实现自动编译; 另一个工具buildall.sh是一次完整的编译过程的控制脚本, 不包含更新代码,发送邮件的过程

1.6. build.qzone.isd.com

这是由几个简单的php页面构成的编译结果发布站点,需要在Apache的httpd.conf中作如下配置:

<VirtualHost *>

DocumentRoot /data/qzone_build/admin/htdocs

ServerName build.qzone.isd.com

DirectoryIndex index.php

ScriptAlias /cgi-bin/ "/data/qzone_build/admin/cgi-bin/"

Alias /src/ "/data/qzone_build/source/"

AddType text/plain .cpp .c .h .sh .js .vbs .css .conf .ini .log

ErrorLog logs/build.qzone.isdcom-error_log

</VirtualHost>

全部的页面都放在/data/qzone_build/admin/htdocs下:

/data/qzone_build/admin/htdocs/

|-- buildres.xsl ->/data/qzone_build/admin/bin/buildres.xsl // xsl表单的软连接,将原始文件放在bin中是为方便编辑

|-- index.php //build.qzone.isd.com的入口页面

|-- mainlist.php //生成目录列表页面的工具脚本

|-- slist.php // 文件列表页面

`-- test.php // php测试页面 (非必需)

发布页面的入口如下:

这个页面的信息主要来自/data/qzone_build/source下的每个目录下的buildres.xml,

通过第一列各个日期的连接可以进入查看相应的编译目录,通过查看详细信息,可以了解具体的编译情况:

上图页面的信息主要来自各个目录的ccversion.xml文件,ccversion.xml文件本身不会出现在列表中.

上图页面的信息来自相应的buildres.xml

注:由于源码目录下文件可能会被手工增删或改动,有时有些信息文件无法正确读取,这时php会发出警告,这些都是可以忽略的,可以通过/usr/local/lib/php.ini改变php的告警级别(仅显示错误):

error_reporting = E_ERROR

2. 关键工具的使用说明

2.1. svntools.pl的用法

用途: 拉取svn工程源代码的工具脚本.

配置文件: svntools.conf, 需要和svntools.pl放在同一个目录下, 其中设置远程SVN服务器的连接信息(IP,USER,PASSWD等).

使用方法: svntools [[-i|-u] local_directory|local_file]|[-e]|[-h]

-i local_directory|local_file 显示目录或文件在远程和本地的版本信息;

-u local_directory|local_file 更新本地目录或文件;

-e 查看或编辑配制文件;

-h 显示帮助信息.

注意: 命令行中的目录和文件参数支持相对和绝对路径;路径字符串中不支持通配符(*)和正则表达式.

其中srcroot定义了本地的源代码更新位置和远端svn地址, -i, -u选项后边的文件路径都应该在srcroot以内.

使用[svntools.pl -u 目录名]更新时不会删除服务器上不存在而在本地存在的目录内部文件;但使用[svntools.pl -u 文件名]时会做删除. 如果文件以及父目录都在服务器上不存在,只会删除文件而不会删除父目录.

cctool.pl –i 文件名可以比较本地文件和服务器上相应文件的CC信息,输出内容分两行显示,分别以L和S开头,L (local)代表本地文件信息,S (server)代表服务器端文件信息

2.2. setroot.sh的用法

用途: 设置更新和(或)编译目录的软连接

使用方法: setroot.sh[-b][-u][-d date|-p directory [-f]][-h]

-b set build source root

-u set update source root

-ddate use /data/qzone_build/source/qzone_$dateas base dir (fmt: YYYYMMDD)

-pdir use specified directory as basedir

-f use with -d or -p, force createdirectory if not exists

-h show help information

说明: 如果没有(用-d或者-p)指定具体目录,则会使用当天的日期当作-d的参数,即缺省采用–d + 当天日期

-f选项会在 –d或者-p指定的目录不存在时创建目录

-b会将指定的目录软连接到/usr/local/qzone_v3.0; -u会将指定的目录软连接到/data/qzone_build/temp/src

2.3. confgen.pl的用法

用途: 根据makefile生成make.conf

使用方法:confgen.pl [-i original_makefile|-o outputfile_confile] [-h hint_file]

-i original_makefile, original makefile tobuild conf file;

-o outputfile_confile, name of output makeconfig file;

-f hint_file, a file in which includedefault conf values;

-h, print help messages.

说明: 缺省的输入文件是/usr/local/qzone_v3.0/makefile, 但可以通过-i选项指定输入文件;缺省的输出文件是/usr/local/qzone_v3.0/make.conf, 但可以通过-o选项指定输出文件; -f 用来指定缺省配置所在的文件,如果不指定的话,则会尝试使用confgen.pl同一目录下的make.conf.def,其中可以指定编译目录的依赖关系,以及包括,排除列表的缺省值,这些值会合并到最终生成的make.conf文件中(参见mkgen.pl的用法说明)

2.4. mkgen.pl的用法

用途: 根据make.conf生成全构建脚本build.sh

使用方法: mkgen.pl [-i include_list|-e exclude_list][-h] [-f hint_file] source_directory

-i include_list, ';' splited list forinclude sub_dirs;

-e exclude_list, ';' splited list forexclude sub_dirs;

-f hint_file, a file in which listinginclude/exclude sub_dirs;

-h, print help messages.

说明: 不一定要使用make.conf文件,(但是如果在没有用-f 明确指定配制文件名,而/usr/local/qzone_v3.0/make.conf存在,则会尝试使用它), 可以在命令行中通过 –i,-e指定参加构建的或者不参加构建的子目录(如果-i,-e指定了同一个目录,则以-e为准)

make.conf一个很重要的用途是用来指明编译中的依赖关系,帮助确定编译顺序,下边是一个例子:

[order]

platform/src/comm=16

comm=8

platform=4

main=2

[include]

portal/src/fcg

blog/src/blog

music/src/discuss/fcg

main/src/mall

izone/src/favor

其中order一节定义了不同目录的优先级,数值越大越先编译,(也就是说,越多的其他的代码依赖于它).

优先级的设置使用目录关键字=级别的形式定义,当目录中包含关键字时(同一个关键字多次出现仅计算一次),会将相应的级别加到该目录的总级别,最终的编译脚本build.sh会按照各个目录的级别由高到低依次编译(级别相同的目录意味着他们之间的编译顺序不影响结果,因此只按照目录字母顺序做简单排序), 不含关键字的目录级别为0, 比如:

platform/src/comm 级别 16

platform/src/c5a/c5atool 级别 4

main/src/comm 级别 10

blog/src/comm 级别 8

izone/src/favor 级别 0

music/src/comm 级别 8

建议使用2的幂做为级别的数字,以便拉开差距,防止低级别的目录级别相加后超过更优先的级别.

mkgen.pl每次生成的build.sh都有一个不同的时间戳(选用当前系统时间),这个时间戳相当于build.sh的版本,不同版本的build.sh生成的log文件名不同,这些log的名称格式是: build_***.log其中***就是它的时间戳

2.5. writemail.pl,postmessage.pl的用法

用途: 这两个工具脚本联合起来完成邮件通知编译结果的功能.它们应该放在同一个目录下,并且还会在同一目录下寻找message.conf文件,其中包含发送邮件配制信息,例如:

[server]

ip=192.168.?.?

user=qzonebuilder@tencent.com

port=25

[local]

msgbox=/data/qzone_build/temp/mbox

其中msgbox指定了发件箱的位置,writemail.pl缺省会将新建的邮件放进这里.

l writemail的使用方法: writemail.pl [-i build resultfile] [-h] [-o output mail file]

-i build result file's path & name

-o output mail file's path & name

-h print help messages

说明: 如果没有用-i 指定编译结果文件,则尝试使用/usr/local/qzone_v3.0/buildres.xml,如果没有用-o指定生成的邮件的存放位置,则会根据build.sh的时间戳生成一封名为build_$timestamp.mail的文件放进message.conf中定义的msgbox中.

l postmessage.pl的使用方法: postmessage.pl [-fmessage_box|message_file ]|[-a]|[-e]|[-h]

-f message_box|message_file 指定邮箱路径或者邮件文件名,邮件文件必须以.mail做为名称后缀

-a 自动发送缺省配置的邮箱内的所有邮件和消息;

-e 查看或编辑配制文件;

-h 显示帮助信息.

3. crontab使用说明

3.1. crontab脚本

工程编译crontab脚本:

工程清理crontab脚本(保留一天的工程):

每天的译结果汇总邮件:

至此整个过程结束!

本文分享自微信公众号 - 互联网运维杂谈(waynewang_ops)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 爬取搞笑视频

    最近小编经常刷知乎上的一个问题“你见过哪些是「以为是个王者,结果是个青铜」的视频或图片?”。从这个问题我们就已经可以看出来里面的幽默成分了,点进去看果然是笑到停...

    HuangWeiAI
  • 3.2.8 、Google Tag Manager实战指南——虚拟页面跟踪单页应用

    单页应用就是指浏览器访问站点的时候,使用期间不会重新加载页面,给用户的看到的是所有URL就是一样的,有体验原生APP的感觉,这是一种从Web服务器加载的富客户端...

    GA小站
  • PHP-fpm 远程代码执行漏洞(CVE-2019-11043)分析

    国外安全研究员 Andrew Danau在解决一道 CTF 题目时发现,向目标服务器 URL 发送 %0a 符号时,服务返回异常,疑似存在漏洞。

    Seebug漏洞平台
  • Go 语言网络编程系列(三)—— HTTP 编程篇:客户端如何发起请求

    通过前面介绍的 net.Dial 或 net.DialTimeout 函数来访问基于 HTTP 协议的网络服务是完全没有问题的,因为 HTTP 协议是基于 TC...

    学院君
  • 如何利用并发性加速你的 python程序(上)

    工程师 Jim Anderson 分享了他的经验,他写了一篇关于「通过并发性加快 python 程序的速度」的文章。Jim 有多年的编程经验,并且使用过各种编程...

    AI科技评论
  • cas-client单点登录客户端拦截请求和忽略/排除不需要拦截的请求URL的问题

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    eguid
  • SpringBoot整合mybatis一直失败差不到数据,解决方案

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    多凡
  • 【翻译】看我如何利用PHP的0day黑掉Pornhub并获得2W美刀奖励

    在分析了Pornhub使用的平台之后,我们在其网站上检测到了unserialize函数的使用,其中的很多功能点(例如上传图片的地方等等)都受到了影响,例如下面两...

    ChaMd5安全团队
  • 3.2.9 、Google Tag Manager实战指南——广告参数传递

    通常需要区分不同渠道来源的时候一般会使用UTM标记,其实还有另一种方式可以传递广告参数,这个需要使用GTM,同时配合自定义维度来使用。

    GA小站
  • 爬虫篇 | Python学习之Scrapy-Redis实战京东图书

    scrapy-Redis就是结合了分布式数据库redis,重写了scrapy一些比较关键的代码,将scrapy变成一个可以在多个主机上同时运行的分布式爬虫。

    叫我龙总

扫码关注云+社区

领取腾讯云代金券