持续集成(二)持续集成在web端和app端的探索实践

持续集成的前提条件:

  1. 一天多次提交
  2. 创建全面的自动化测试套件
    • 单元测试和集成测试套件由开发人员准备
    • 系统测试套件由测试人员准备
  3. 保持较短的构建和测试过程
    测试一阶段
    • 本机提交前的预编译和单元测试和单元集成测试过程(DEV本机环境五分钟之内)
    • 持续集成服务器提交前的预编译和单元测试和单元集成测试过程(DEV本机环境五分钟之内)
    测试二阶段
    • 在持续集成服务器上运行SIT测试,UAT测试和性能测试
    • 通过高性能的多进程机器分布式执行(将时间控制在半小时之内)
    • 将简单的冒烟测试也加在提交阶段
  4. 管理开发工作区
    • 在本地开发环境中应确保所使用的自动化过程和与持续集成环境中的一致,与测试环境中的也一样。
    • 这个的先决条件是细心的配置管理,包括测试数据,数据库脚本,构建脚本和部署脚本
    • 确保自动化测试可以在开发机上运行

开发人员的最佳实践

  1. 构建失败之后不要提交新代码
  2. 提交前在本地运行所有提交测试,活着让持续集成服务器完成此事
  3. 等提交测试通过后再继续工作
  4. 回家之前,构建必须处于成功状态
  5. 时刻准备着回滚到前一个版本
  6. 在回滚之前要规定一个修复时间,如果规定时间(例如:10分钟)不能修复就回滚到上个版本
  7. 不要将失败的测试注释掉

部署流水线

持续集成软件

持续集成软件包括两个部分

  • 一个一直运行的进程,每隔一段时间就会执行一个简单的工作流程
  • 提供展现这个流程运行结果的视图, 通知你构建和测试成功与否,让你可以找到测试报告,拿到生成的安装文件(例如APP就是提供二维码)
  • 除了上述几本功能外,还可以将构建状态发送到外部设备中,或者在一个屏幕上显示构建的状态以及提交者的头像。 
    建立在以上基础上的进一步的优化扩展是在构建过程中对源代码进行一些分析工作,包括分析测试覆盖率,重复代码,是否符合编码标准,圈复杂度,以及其他一些健康指标

Docker和持续集成

通过部署自己私有的Docker镜像registry,并建立自己的自动化构建,将Docker集成到持续集成工作流中。 
除了持续集成和持续部署之外的自动化测试部分,只需要从Jenkins建立一个hook来启动自动化脚本并生成测试报告。 
关于本节需要做到

  • 管道化部署介绍
  • 持续部署的技术思路
  • 效果展示
  • 配置Git和Jenkins联动
  • 配置Jenkins自动更新代码
  • 效果图文详解
  • FAQ

管道化视图

通过Jenkins系统的Job触发机制,我们可以方便的创建各种类型的集成Job用例。但缺乏统一标准的Job用例使用方法,会导致项目Job用例使用的混乱,难于管理维护。这也让开发团队无法充分利用好集成系统的优势,当然这也不是我们期望的结果。所以,敏捷实践方法提出了一个可以持续交付的概念 DeploymentPipeline(管道部署)。通过Docker技术,我们可以很方便的理解并实施这个方法。 
Jenkins的管道部署把部署的流程形象化成为一个长长的管道,每间隔一小段会有一个节点,也就是Job,完成这个Job工作后才可以进入下一个环节。形式如下: 

开发同学通过git push上传代码,经Git和Jenkins配合,自动完成程序部署、发布,全程无需运维人员参与。 
这是一种真正的容器级的实现,这个带来的好处,不仅仅是效率的提升,更是一种变革: 
难者不会,会者不难。通过简单的4个配置,即可优雅地实现持续部署。

1. 持续部署的技术思路

在本例中,假设我们JAVA项目的名称为hello。简要的技术思路如下。 

本案例中假设代码托管在git.oschina.com上,Jenkins和Docker Registry(类似于yum源)各运行在一个Docker容器中。JAVA项目自己也单独运行在一个叫hello的容器中。 
本文采取的持续部署方案,是从私有的Docker Reistry拉取代码。有些变通的方案,把代码放在宿主机上,让容器通过卷组映射来读取。这种方法不建议的原因是,将代码拆分出容器,这违背了Docker的集装箱原则: 
这也导致装卸复杂度增加。从货运工人角度考虑,整体才是最经济的。这样,也才能实现真正意义的容器级迁移。 
或者说,容器时代,抛弃过去文件分发的思想,才是正途。本文最后的问答环节对此有更多阐述。 
容器即进程。我们采用上述方案做Docker持续部署的原因和意义,也在于此。容器的生命周期,应该远远短于虚拟机,容器出现问题,应该是立即杀掉,而不是试图恢复。

2. 效果展示

本文最后实现的效果,究竟有多惊艳呢?且看如下的演示。

2.1 程序代码更新前的效果

我们以时间戳来简洁、显式的表述程序更新情况。 

2.2 提交程序代码更新

本例中,我们把首页的时间戳从201506181750,修改为201506191410(见如下)。 

2.3 上传新代码到Git

顺序执行如下操作,输入正确的git账号密码。 

然后呢? 
然后什么都不用做了。端杯茶(如果不喜欢咖啡的话),静静地等待自动部署的发生, 旁观一系列被自动触发的过程,机器人似的运转起来(请容稍候再加以描述)。 
为什么需要3~5分钟?只是因为本案例中的JAVA项目,需要从国外download Maven程序包,以供Jenkins调用和编译JAVA。正式应用环境中,可以把Maven源放在国内或机房。如果仅仅需要对PHP项目做持续部署,那就更快捷了。

2.4 查看代码更新后的效果

在静静地等待几分钟后,新的代码确实已经自动部署完毕。 

那么,这一切怎么实现的呢?要按照如下几步,便可快速实现。

3. 配置Git和Jenkins联动

这个过程也是难者不会,会者不难。主要分为如下三步。

3.1 Jenkins配置Git源

Jenkins中新建项目java-app,并配置从Git拉取程序代码。具体如下: 

3.2 Jenkins配置远程构建

Jenkins中配置token,以供git远程调用时使用。 

3.3 Git开启钩子

怎么让Git在接收到用户更新的代码后,把消息和任务传递给Jenkins呢?这借助于Git的hook功能,配置起来也非常简单,如下。 

4. 配置Jenkins自动更新代码

Jekins在接收到Git传递过来的消息后,再触发一个远程构建(到目标服务器),按照预定义的任务列表,执行一系列的工作,重建容器等。详见如下: 

我们把其中最关键的Shell脚本内容摘抄出来。 

5. 效果图文详解

在2.3这个章节中,我们当时的操作如下,这个目的是向Git提交更新代码。 

当时并没有细说后续发生的事情,既然上面已经说清楚了原理,那我们就可以接下来说说实际发生的事情啦。

5.1 上传代码到Git

这里貌似整个过程已经完成并顺利退出。其实,后台的工作才刚刚开始。 

这时会触发Git服务器向相应的Jenkins服务器发出一个操作请求,此工作太过迅速,也没啥好说的,我们接下来看Jenkins都干啥子了。

5.2 Jenkins进行的精彩互动
  1. Jenkins会自动冒出来一个构建任务。 

  2. 我们点进来,看看具体操作日志。是的,正在接受来自Git的任务。 

  3. 下载Maven相关的软件包(就是这个过程慢)。 

  4. 下载完成后,就开始利用maven BUILD 新的hello项目包。

  5. 然后重建Maven容器,构建新的Image并Push到Docker私有库中。 

  6. 最后,重新把Docker容器run起来。这样,又新生了。 

6. FAQ

问题1:采用这么相对复杂的办法(而不是把更新代码放在宿主机然后卷组映射),是因为项目基于JAVA么;是否PHP项目就可以采用更新代码放在宿主机然后卷组映射这种方式? 
回答1:将代码拆分出容器,违背了集装箱原则。导致装卸复杂度增加。从货运工人角度考虑,整体才是最经济的。一切版本化。抛弃过去的文件分发。这是正途。至于文件大小,大的war包也就50M或100M,在现有网络下不成问题,性能问题最好优化。另外建议关注docker 2 docker,p2p传输。

问题2:如果整体代码超过500m或者1g以上,整体集装箱是否就不太好了?如果容器与代码分离,镜像就100m左右(2层,base+服务),然后代码的话,是放到共享存储里,每个代码有更新,比如svn的代码,可以直接在共享存储里进行svn update就可以控制版本 
回答2:如果你的代码500M,说明就需要精简了。

使用Jenkins搭建iOS/Android持续集成打包平台

1. 背景描述

根据项目需求,现要在团队内部搭建一个统一的打包平台,实现对iOS和Android项目的打包。而且为了方便团队内部的测试包分发,希望在打包完成后能生成一个二维码,体验用户(产品、运营、测试等人员)通过手机扫描二维码后就能直接安装测试包。 
该需求具有一定的普遍性,基本上所有开发APP的团队都可能会用到,因此我将整个需求实现的过程整理后形成此文,并且真正地做到了零基础上手,到手即飞、开箱即用,希望能对大家有所帮助。 
首先,先给大家展示下平台建设完成后的整体效果: 
该平台主要实现的功能有3点:

  • 定期对GitHub仓库进行检测,若有更新则自动执行构建打包;
  • 构建成功后根据ipa/apk生成二维码,并可在历史构建列表中展示各个版本的二维码,通过手机扫描二维码可直接安装对应版本;
  • 在构建结果页面中展示当次构建的成果物(Artifact,如.ipa、.app、.apk、info.plist等文件),供有需要的用户进行下载。 
    接下来,本文就开始对平台建设的完整实现过程进行详细介绍。
2. 安装Jenkins

Jenkins依赖于Java运行环境,因此需要首先安装Java。 
安装Jenkins的方式有多种,可以运行对应系统类型的安装包,可以通过docker获取镜像,也可以直接运行war包。 
我个人倾向于直接运行war包的形式,只需下载jenkins.war后,运行如下命令即可启动Jenkins。 
$ nohup java -jar jenkins_located_path/jenkins.war --httpPort=88 & 
如果不指定httpPort,Jenkins的默认端口为8080。

3. Jenkins插件

Jenkins有非常多的插件,可以实现各种功能的扩展。 
针对搭建的iOS/Android持续集成打包平台,我使用到了如下几个插件。

  • GIT plugin
  • SSH Credentials Plugin
  • Git Changelog Plugin: 获取仓库提交的commit log
  • build-name-setter:用于修改Build名称
  • description setter plugin:用于在修改Build描述信息,在描述信息中增加显示QRCode(二维码)
  • Post-Build Script Plug-in:在编译完成后通过执行脚本实现一些额外功能
  • Xcode integration: iOS专用(可选)
  • Gradle plugin: Android专用(可选) 
    安装方式也比较简单,直接在Jenkins的插件管理页面搜索上述插件,点击安装即可。
4. 创建项目(Job)

在Jenkins中,构建项目以Job的形式存在,因此需要针对每个项目创建一个Job。有时候,一个项目中可能有多个分支同时在进行开发,为了分别进行构建,也可以针对每个分支创建一个Job。 
创建Job的方式有多种,本次只需要创建Freestyle project类型的即可。 
Main page -> New Item -> Freestyle project 
对于一个持续集成打包平台,每次打包都由4步组成:触发构建、拉取代码、执行构建、构建后处理。对应的,在每个Job中也对应了这几项的配置。

5. 配置Git代码仓库

要对项目进行构建,配置项目的代码仓库是必不可少的。由于当前我们的项目托管在GitHub私有仓库中,因此在此需要对Git进行配置。 
在【Source Code Management】配置栏目下,如果之前GIT plugin安装成功,则会出现Git选项。 
配置Git代码仓库时,有三项是必须配置的:仓库URL地址(Repository URL)、仓库权限校验方式(Credentials),以及当前Job需要构建的代码分支(Branches to build)。 
在配置Repository URL时,选择HTTPS URL或SSH URL均可。不过需要注意的是,Credentials要和Repository URL对应,也就是说:

  • 如果Repository URL是HTTPS URL形式的,那么Credentials就要采用GitHub用户名密码的校验方式;而且,如果在GitHub中开启了2FA(two-factor authentication),那么还需要在GitHub中创建一个Personal access token,输入密码时将这个Personal access token作为密码进行输入。
  • 如果Repository URL是SSH URL形式的,那么就需要先在Jenkins所在的服务器上创建一个SSH秘钥对,并将公钥添加到GitHub的SSH keys中,然后在填写Credentials时,选择SSH Username with private key的校验方式,填入GitHub Username、SSH私钥、以及创建SSH秘钥对时设置的Passphrase。 
    如果对Git权限校验的概念还比较模糊,可以参考《深入浅出Git权限校验》。 
    在配置Branches to build时,可以采用多种形式,包括分支名称(branchName)、tagName、commitId等。其中分支名称的形式用的最多,例如,若是构建master分支,则填写refs/heads/master,若是构建develop分支,则填写refs/heads/develop。 
    除了以上关于Git的必填配置项,有时根据项目的实际情况,可能还需要对Jenkins的默认配置项进行修改。 
    比较常见的一种情况就是对clone的配置进行修改。 
    在Jenkins的默认配置中,clone代码时会拉取所有历史版本的代码,而且默认的超时时限只有10分钟。这就造成在某些项目中,由于代码量本身就比较大,历史版本也比较多,再加上网络环境不是特别好,Jenkins根本没法在10分钟之内拉取完所有代码,超时后任务就会被自动终止了(错误状态码143)。 
    这种问题的解决方式也很简单,无非就是两种思路,要么少拉取点代码(不获取历史版本),要么提高超时时限。对应的配置在Advanced clone behaviours中:
  • Shallow clone:勾选后不获取历史版本;
  • Timeout (in minutes) for clone and fetch operation:配置后覆盖默认的超时时限。
6. 配置构建触发器

代码仓库配置好了,意味着Jenkins具有了访问GitHub代码仓库的权限,可以成功地拉取代码。 
那Jenkins什么时候执行构建呢? 
这就需要配置构建触发策略,即构建触发器,配置项位于【Build Triggers】栏目。 
触发器支持多种类型,常用的有:

  • 定期进行构建(Build periodically)
  • 根据提交进行构建(Build when a change is pushed to GitHub)
  • 定期检测代码更新,如有更新则进行构建(Poll SCM) 
    构建触发器的选择为复合选项,若选择多种类型,则任一类型满足构建条件时就会执行构建工作。如果所有类型都不选择,则该Jenkins Job不执行自动构建,但可通过手动点击【Build Now】触发构建。 
    关于定时器(Schedule)的格式,简述如下: 
    MINUTE HOUR DOM MONTH DOW
  • MINUTE: Minutes within the hour (0-59)
  • HOUR: The hour of the day (0-23)
  • DOM: The day of the month (1-31)
  • MONTH: The month (1-12)
  • DOW: The day of the week (0-7) where 0 and 7 are Sunday. 
    通常情况下需要指定多个值,这时可以采用如下operator(优先级从上到下):
  • 适配所有有效的值,若不指定某一项,则以*占位;
  • M-N适配值域范围,例如7-9代表7/8/9均满足;
  • M-N/X或*/X:以X作为间隔;
  • A,B,C:枚举多个值。 
    另外,为了避免多个任务在同一时刻同时触发构建,在指定时间段时可以配合使用H字符。添加H字符后,Jenkins会在指定时间段内随机选择一个时间点作为起始时刻,然后加上设定的时间间隔,计算得到后续的时间点。直到下一个周期时,Jenkins又会重新随机选择一个时间点作为起始时刻,依次类推。 
    为了便于理解,列举几个示例:
  • H/15 * * * *:代表每隔15分钟,并且开始时间不确定,这个小时可能是:07,:22,:37,:52,下一个小时就可能是:03,:18,:33,:48;
  • H(0-29)/10 * * * *:代表前半小时内每隔10分钟,并且开始时间不确定,这个小时可能是:04,:14,:24,下一个小时就可能是:09,:19,:29;
  • H 23 * * 1-5:工作日每晚23:00至23:59之间的某一时刻;
7. 配置构建方式

触发策略配置好之后,Jenkins就会按照设定的策略自动执行构建。但如何执行构建操作,这还需要我们通过配置构建方式来进行设定。 
常用的构建方式是根据构建对象的具体类型,安装对应的插件,然后采用相应的构建方式。例如,若是构建Android应用,安装Gradle plugin之后,就可以选择Invoke Gradle script,然后采用Gradle进行构建;若是构建iOS应用,安装Xcode integration插件之后,就可以选择Xcode,然后选择Xcode进行构建。 
该种方式的优势是操作简单,UI可视化,在场景不复杂的情况下可以快速满足需求。不过缺点就是依赖于插件已有的功能,如果场景较复杂时可能单个插件还无法满足需求,需要再安装其它插件。而且,有些插件可能还存在一些问题,例如对某些操作系统版本或XCode版本兼容不佳,出现问题时我们就会比较被动。 
我个人更倾向于另外一种方式,就是自己编写打包脚本,在脚本中自定义实现所有的构建功能,然后在Execute Shell中执行。这种方式的灵活度更高,各种场景的构建需求都能满足,出现问题后也能自行快速修复。 
另外,对于iOS应用的构建,还有一个需要额外关注的点,就是开发者证书的配置。 
如果是采用Xcode integration插件进行构建,配置会比较复杂,需要在Jenkins中导入开发证书,并填写多个配置项。不过,如果是采用打包脚本进行构建的话,情况就会简单许多。只要在Jenkins所运行的计算机中安装好开发者证书,打包命令在Shell中能正常工作,那么在Jenkins中执行打包脚本也不会有什么问题。

8. 构建后处理

完成构建后,生成的编译成果物(ipa/apk)会位于指定的目录中。但是,如果要直接在手机中安装ipa/apk文件还比较麻烦,不仅在分发测试包时需要将好几十兆的安装包进行传送,体验用户在安装时也还需要通过数据线将手机与计算机进行连接,然后再使用PP助手或豌豆荚等工具进行安装。 
当前比较优雅的一种方式是借助蒲公英(pgyer)或http://fir.im等平台,将ipa/apk文件上传至平台后由平台生成二维码,然后只需要对二维码链接进行分发,体验用户通过手机扫描二维码后即可实现快速安装,效率得到了极大的提升。

9. 上传安装包文件,生成二维码

不管是蒲公英还是http://fir.im,都有对应的Jenkins插件,安装插件后可以在Post-build中实现对安装包的上传。 
除了使用Jenkins插件,http://fir.im还支持命令上传的方式,蒲公英还支持HTTP Post接口上传的方式。 
我个人推荐采用命令或接口上传的方法,并在构建脚本中进行调用。灵活是一方面,更大的好处是如果上传失败后还能进行重试,这在网络环境不是很稳定的情况下极其必要。 
Jenkins成功完成安装包上传后,pgyer/http://fir.im平台会生成一个二维码图片,并在响应中将图片的URL链接地址进行返回。

10. 展示二维码图片

二维码图片的URL链接有了,那要怎样才能将二维码图片展示在Jenkins项目的历史构建列表中呢? 
这里需要用到另外一个插件,description setter plugin。安装该插件后,在【Post-build Actions】栏目中会多出description setter功能,可以实现构建完成后设置当次build的描述信息。这个描述信息不仅会显示在build页面中,同时也会显示在历史构建列表中。 
有了这个前提,要将二维码图片展示在历史构建列表中貌似就可以实现了,能直观想到的方式就是采用HTML的img标签,将写入到build描述信息中。 
这个方法的思路是正确的,不过这么做以后并不会实现我们预期的效果。 
这是因为Jenkins出于安全的考虑,所有描述信息的Markup Formatter默认都是采用Plain text模式,在这种模式下是不会对build描述信息中的HTML编码进行解析的。 
要改变也很容易,Manage Jenkins -> Configure Global Security,将Markup Formatter的设置更改为Safe HTML即可。 
更改配置后,我们就可以在build描述信息中采用HTML的img标签插入图片了。 
另外还需要补充一个点。如果是使用蒲公英(pyger)平台,会发现每次上传安装包后返回的二维码图片是一个短链接,神奇的是这个短连接居然是固定的(对同一个账号而言)。这个短连接总是指向最近生成的二维码图片,但是对于二维码图片的唯一URL地址,平台并没有在响应中进行返回。在这种情况下,我们每次构建完成后保存二维码图片的URL链接就没有意义了。 
应对的做法是,每次上传完安装包后,通过返回的二维码图片短链接将二维码图片下载并保存到本地,然后在build描述信息中引用该图片的Jenkins地址即可。

11. 收集编译成果物(Artifacts)

每次完成构建后,编译生成的文件较多,但是并不是所有的文件都是我们需要的。 
通常情况下,我们可能只需要其中的部分文件,例如.ipa/.app/.plist/.apk等,这时我们可以将这部分文件单独收集起来,并在构建页面中展示出来,以便在需要时进行下载。 
要实现这样一个功能,需要在【Post-build Actions】栏目中新增Archive the artifacts,然后在Files to archive中通过正则表达式指定成果物文件的路径。 
设置完毕后,每次构建完成后,Jenkins会在Console Output中采用设定的正则表达式进行搜索匹配,如果能成功匹配到文件,则会将文件收集起来。

原文地址:https://www.cnblogs.com/mu-shi-shi/p/7551609.html