基于Jenkins的CI过程

一 切要从2013年4月开始说起,当我4月份从委内瑞拉回来之后立即投身到国内一个运营商的大型后端建设项目的尾声中(项目历时3年多,当时已经接近尾 声),这个项目涉及100多台主机,包含数十个集群,除了传统的WEB应用外,还用到了流程引擎、ESB、规则引擎、搜索引擎以及缓存和日志,是当时比较 复杂的体系结构(当然不能跟现在的云平台相比,但在项目开始的年代这还是一个很不错的架构),整个项目当时一两百号人占了局方整整一层楼十几个办公室。

我 到了项目组之后成为了一个小组的小头目,管个四五个人,小组美其名曰“平台组”,干的都是打杂的事情,包括编译、打包、部署,日常监控以及系统优化等工 作,说起来简单,做起来还是很复杂的,当时所有的工作基本上是靠人工的,可想而知,100来台机器的环境一台一台的部署环境,还得靠人工监控,手工检查, 四五个到处救火忙得不可开交,当时我虽然还不知道CI为啥物(压根儿就没这个概念),但也下定决心要改变忙乱的状态,累一点不要紧,但是累得跟狗似的还干 不好那就白辛苦了。

在 2013年的4~8月份,我们主要研究的是自动编译、打包和发布,采用的基本方式是各种脚本,包括windows下的批处理bat、Linux上的 shell甚至Python,虽基本上完成了自动从SVN取代码、自动编译、自动打包以及将应用发布到WebSphere上的这些工作(如下图):

jenkins 直接发布docker 版本 jenkins使用docker_micro services

但也明显存在一些问题:

  1. 自动执行靠的是Windows任务计划,执行过程、执行情况只能通过检查脚本执行时写的日志文件,不直观。
  2. 代码只作了编译,没有做代码走查,对代码质量的提升作用不大。
  3. 发布过程利用IBM提供的wsadmin脚本,只能进行全量的发布,发布过程较长。

9月份之后,项目基本稳定后我也离开了项目现场,但自那之后对这块工作更加着迷,我从项目现场回来之后也组建了一个科室,还是四五个人,当时我查阅了一些资料,尤其是看到了一本书《持续集成,软件质量改进和风险降低之道》,从此学到一个名词:CI(持续集成),一发而不可收拾,逢人就鼓吹CI。我组织科室人员一起研究了CruiseControl、Apache Continuum、QuickBuild、Hudson等业界CI常用工具,最后决定以Hudson为框架来逐步实现CI.。一早采用的是Hudson,后来为便于作二次开发切换到其社区版本Jenkins上。

Jenkins提供了一个管理界面,并且有丰富的第三方插件,支持定时任务,支持从SVN取代码,支持Ant编译和Maven编译(我们产品编程框架逐渐从ANT转向maven模式),支持向tomcat、JBoss、Weblogic和WAS发布应用(Jenkins的WAS插件不支持集群模式,我们仍然沿用了wsadmin脚本),支持用PMD、Checkstyle、findBugs进行代码走查并以图形化方式展现走查结果(包括趋势图和结果详情),支持调用Windows批处理bat、Linux的Shell等。

在采用Jenkins框架的基础上,我们作了一些二次开发,实现了:

  1. 根据任务单增量从SVN取代码(有一些奇葩的项目现场要求挑任务单升级,因此我们修改了jenkins的svn插件以支持这种需求)。
  2. jenkins 直接发布docker 版本 jenkins使用docker_micro services_02

  3. 支持增量编译(采用两个Jenkins的JOB,一个做全量编译,作为首次编译并产生一个jar包给增量编译使用,此全量编译JOB只使用一次;另一个JOB就引用全量JOB产生的jar包,只对变更(或新增)的代码编译产生class等文件,并将它们按部署目录放好以便于作增量发布,同时将这些class文件再打入到全量JOB下的jar包中以备下次增量编译使用)。
  4. 支持增量发布,通过调用lftp脚本实现快速的应用部署(在比较了cwRsync、unison、wget、lftp、ftpsync、csync、Syncrify、DeltaCopy、tar、bacula等工具后,最终lftp胜出,我们采用: lftp -c 'open -e "mirror --allow-chown -x vssver.scc -R --parallel=10 --use-pget-n=10 --log=%LOG_FILE% %LOC_DIR% %REMOTE_DIR%" sftp://%USER%:%PASSWORD%@%IP%'这样的脚本来进行增量发布,将编译后的结果与部署环境上的进行自动比对更新)。
  5. 支持基于Ant和基于Maven的代码走查,编写Ant脚本和Maven脚本以支持PMD、Checkstyle、findBugs进行代码走查(由于jenkins中代码走查插件生成界面时会消耗大量系统资源,对机器性能影响很大,后面我们改成了通过脚本方式生成并将走查结果打成压缩包发邮件给相关人员)。
  6. 支持基于Maven的代码的单元测试(采用TDD编码方式)。
  7. 支持自动化测试(调用ZTP,ZTP是我司自产的一个自动化测试工具,支持自动化脚本录制、回放等工作,其工作原理与robotframework比较类似,ZTP工具支持批处理脚本调用,故可以集成到jenkins中)。

当时,我们还想在Jenkins上集成更多的功能,包括:

  1. 改进websphere-deploy插件,支持界面部署WebSphere应用包(这个计划后面搁浅了,主要是脚本方式已经能支持绝大部分WAS集群的部署了)。
  2. 应用环境迁移,通过将应用环境迁移过程自动化为Jenkins中的任务,实现应用环境迁移过程的自动化和可视化(目的就是想实现研发、测试及生产都是一套环境,测试通过后的环境能直接迁移到生产上去,当时只是做运维的一种本能的想法,但也由此引发了对Docker的关注)。
  3. 业务监控系统相结合,形成流程化的跨多个Jenkins任务的、整体的应用环境部署自动化和可视化,为将来生产环境部署的自动化和可视化作准备(曾经研究过一段时间jenkins的FLOW插件,当时FLOW插件的版本还比较低,功能还很弱,引入第三方的工作流工作量会比较大,而事实上这种流程化的编译部署过程实用性也比较低,这事就慢慢搁浅了)。

目前我所在的产品线所有的项目都已经采用Jenkins进行编译、打包、代码走查及自动部署到测试环境和准生产环境(运营商项目的生产环境发布后面会逐渐由我司自主开发的另一利器“云应用管理平台”来支持,后面还会讲到)。

2基于Docker的应用发布

前面讲了,关于应用环境迁移的想法引发了我对Docker的兴趣,实际上这时已经是2014年的6月份了,于是就跟一些同事自学鼓捣一下,当时Docker 1.0才刚刚发布,当时也就把官网的例子都做了一遍,参考官网作了Hadoop的镜像,自己又作了WebSphere的镜像,搭建了Registry,断断续续的作了一些东西,算不上很深入,而Docker本身也在不断发展,感觉隔几天就有一个新版本发布出来。

Docker大潮来势汹涌,到9月份的时候,我一开始说的那个巨大项目的运营商中有个技术专家提出了要用Docker来作应用发布平台,当时简直是不谋而合,于是有了一个项目,也就可以明正言顺的进行Docker研究了,不过既然已经是一个正式的项目了,那光有几个爱好者是不够的,需要有正规军了,于是请出了公司技术委员会下属的一个研发团队,大约有六七人,也就是“云应用管理平台”的开发团队,一起进行相关的研究。给生产环境用的发布平台跟我们前面讲的用Jenkins作的自动部署还是有些不同的,生产环境上版本的发布一般是有严格限制的,包括版本要求、时间要求(升级时间、故障率和故障解决时间)等,这一点是与现在的互联网企业升级自家的系统是完全不同的。

“云应用管理平台”围绕着Docker进行了大量的开发工作,制作了主机管理、容器管理、集群管理、版本计划管理、版本执行管理等等,其架构如下图:

jenkins 直接发布docker 版本 jenkins使用docker_micro services_03

1)Jenkins打包镜像

  • 自动获取SVN代码版本
  • 使用Dockerfile打包镜像,并自动上传到Docker Registry。
  • jenkins 直接发布docker 版本 jenkins使用docker_Docker_04

2)集群配置

  • 应用基本信息配置
  • 集群应用绑定
  • 环境变量配置
  • 固定端口号配置
  • jenkins 直接发布docker 版本 jenkins使用docker_Jenkins_05


  • jenkins 直接发布docker 版本 jenkins使用docker_Jenkins_06

  • jenkins 直接发布docker 版本 jenkins使用docker_Docker_07


3)制定发布计划

  • 选择镜像版本(Docker Registry API获取镜像列表)
  • 选择主机,并设置启动的容器实例数
  • jenkins 直接发布docker 版本 jenkins使用docker_Docker_08

 

4)执行发布计划

  • 使用Docker Java API连接docker daemon启动容器
  • 记录容器ID
  • jenkins 直接发布docker 版本 jenkins使用docker_IP_09

 

5)容器管理

  • 主机检测(能检测主机上是否安装了docker,如果没有可以自动安装Docker)
  • 容器节点增加、缩减,用户选择的应用镜像版本和实际运行版本一致时执行伸缩
  • 容器状态监测

3基于Dubbo的跨主机的容器连接

一开始我们在测试环境上将所有容器都放在一台主机上,测试过程很顺利,但在移到准生产环境上时,由于要模拟生产环境只能将容器部署到不同的主机上,这时候就发现了一个奇怪的现象,应用之间调不通了,这里要说一下,我们应用程序是由20多个服务组成的、通过Dubbo【阿里提供的一个服务框架】作为服务总线串连起来的,Dubbo提供了一个方便的服务发现机制,各个服务(称为服务提供者)只要向Dubbo注册中心注册过,注册中心就会将服务的地址发送给同样在注册中心注册的服务调用方(称为消费者),之后即使dubbo注册中心挂了也不影响服务的调用。

jenkins 直接发布docker 版本 jenkins使用docker_micro services_10

当服务提供者部署在容器中时,这时候发现其在Dubbo中心注册的是容器的IP地址,而对处于另一个主机上的消费者来说这个IP是不可访问的,我们当时也参考了多种方式,想让消费者能够连接上服务提供者的IP,查阅资料总结起来大概有两种做法:

  1. 设置容器的IP与主机IP在同一网段内,使容器IP可直接访问【会占用大量的IP地址,且IP会限制在同一网段,在生产环境中往往不可能】。
  2. 通过复杂的iptables路由规则,通过多层桥接方式打通网络【此法是可行的,也是我们今后要考虑的,但当时一堆开发人员对网络这块都不是太熟悉】。

考虑到当时公司技术委员会下属另一个研发团队正在做dubbo的研究和改造,于是拉他们进来讨论,结果他们说这个很容易解决,由于主机之间是连通的,而容器在创建时也映射了主机和端口,只需要在服务注册时注册的是映射的主机IP和端口就可以连通了,该研发团队的效率很高,讨论的当天就给出了实现,考虑到局方要求严格管理容器和主机间的映射,我们将主机IP和端口作为环境变量在容器启动时传入【扩展了dubbo protocol配置,增加了两个配置项 publish host、 publishport,对应主机的ip port,并且在注册服务时将主机的ip port写到注册中心】,果然解决了这个问题。

当然这是一种特殊情况下的跨主机容器连接方式,更为普遍的方式目前我们正在讨论当中,基于ovs的连接方式是正在考虑的一个方案。

4困难与展望

目前我们对Docker的使用还比较初步,虽然基本满足了项目的要求,但考虑到将来云平台要求自动扩展、服务发现,这些还有待我们进一步研究。

5Q&A

Q:你好,问一个问题,我们前段时间也把Dubbo框架运行在Docker里面,也是采用你们现在的把宿主机和端口作为环境变量传入的方式实现的,我比较想了解的是后继你们有什么更好的方式实现,我看你提到了基于OVS的方案?

A:有两种解决办法:

  • 一种是将显式传递环境变量做成隐式的自动获取宿主机和端口,从而减少配置工作;
  • 另一种则是通用的Open vSwitch(OVS)方案,这是与Dubbo无关的。

Q:容器中的Dubbo注册问题,扩展Dubbo的protocol配置,增加publishhost和publishport解决了注册问题,能不能说的详细一点?

A:目前我们硬编码了Dubbo的protocol,在里面加了两个字段,这种扩展方式有点野蛮,但Dubbo本身提供的扩展方式目前很难支持传递环境变量方式,我们在考虑将环境变量隐式获取,这样就不用硬编码了。

Q:你们用的还是端口映射吧,那么也会存在很多个端口的问题吧,像IP可以访问一样?

A:在这个项目中作端口映射是运营商的要求,他们要求能通过配置来设置每个容器的端口映射,这与他们现有的运维方式有关,一开始我们考虑的是docker的自动端口映射,当然这种需求将来肯定是趋势,我们的“云应用管理平台”中也有考虑。

Q:为何考虑Dubbo而不是etcd做服务发现,Dubbo的优势是什么?

A:选中Dubbo是很偶然的,公司本身有ESB产品,但相对来说比较重,一般用于多个产品间的调用,而Dubbo我们一般用于产品内部多个模块之间的调用,是一种轻量级的服务总线,Dubbo这种方式不依赖于硬件和操作系统,etcd并不是所有操作系统都能支持的吧,当然我也没有对etcd作深入的研究。

Q:Jenkins的slave是选用了虚拟机还是直接物理机?

A:我们的Jenkins的master和slave都是用的虚拟机。

Q:代码提交上去,如果测试有问题,回滚是肿么处理,也是通过Jenkins?

A:这里要分情况来说,一种是测试发现问题,提单子给开发修改,开发修改完代码提交到scm,然后触发Jenkins下一轮的编译和部署;另一种情况是如果某次部署失败,则会用部署前的备份直接还原。

Q:请问用的Registry V1还是V2 ,分布式存储用的什么,有没有加Nginx代理?

A:目前我们用的是V1。生产环境多是集群环境,需要加Nginx作分发。目前应用中分布式存储用的并不多,一般来说用hdfs来存储一些日志便于后面分析,也有用FastDFS和MongoDB的。

Q:底层云平台用的是私有云?

A:底层平台一开始想用私有云,但运营商已经有了vCenter的环境,因此后来我们改用Ansible来管理各类物理机和虚机,用Docker API来管理容器。

Q:Dubbo实现的服务发现是否具备failover功能,自动检测并迁移失败容器?

A:Dubbo目前不具备迁移容器的功能,其failover是通过负载均衡和心跳连接来控制的,自动检测和容器迁移我们一般会考虑放在监控系统里面来做,如果放在Dubbo里面会加重Dubbo,只所以用Dubbo也是考虑到它的轻便性。

Q:能否谈下对Jenkins+Mesos的看法,这个涉及到docker-in-docker的必要性?

A:Mesos我们才刚刚接触,我了解的不太多,至于docker-in-docker我觉得生产上很难用,因为性能方面损失比较严重,我们做过性能测试,非--net=host方式的容器性能损失接近30%。

Q:能具体介绍下利用Dockerfile打包镜像吗,jar包也是在这一步编译出来的吗,这样发布出去的镜像会既包括代码又包含jar包吧?

A:我们的镜像中是不包含代码的,镜像里面是产品包,编译是在打镜像之前做的。

Q:对不生产环境中不适合以容器运行的组件,Jenkins+Docker是否就没有优势了?

A:开发和测试环境还是很有优势的,当然有些有大量IO操作的服务其实不适合放在容器里面,这主要是性能方面的考虑。

Q:云平台是怎么管理容器的,有没有使用Docker生态系统相关的组件?

A:目前没有用到Swarm\Compose之类的组件,将来要看这块的发展了,也有可能会引入k8s或者Mesos来作管理,这些目前都在考虑当中 。

Q:在怎么判断部署Docker服务不可用,不可用后自动迁移还是如何操作?

A:目前云应用平台只在发布时才对Docker容器进行状态检测,如果检测到失败,会根据指定的容器数目进行重新创建。后续我们会把对容器状态的持续检测统一放到监控系统中。

Q:我是不是可以这么理解,你们的Jenkins是主要用来CI,而实际集群管理则是云应用平台做的?

A:是的,这个是严格分工的,当时作云应用管理平台时,是以测试交付物为起始点的,这里的测试交付物就是CI的产物,容器方式下就是镜像了。

Q:我可以理解Docker是部署在实体机,实体机上都有一个agent的东西负责与管理端通信,主要负责Docker的管理(安装、部署、监控等)吗?

A:我们的Docker目前都是部署在虚拟机上的,操作系统是Redhat 7.1,你所谓的agent其实应该就是Docker daemon吧。

徐新坤:这个我补充一下,作为Jenkins的slave,会向slave里面启动一个agent来执行相关脚本命令的。这个属于Jenkins的功能,可以去体验下。

Q:一个应用多个容器你们怎么负载均衡?

A:前面其实回答过,要加Nginx的。

Q:利用Dockerfile打包镜像并上传到Registry更像是CD环节的事情,那在单元测试、集成测试环境是否有利用到Docker呢,是否使用Jenkins中Docker相关的插件了?

A:当前项目的单元测试、集成测试都用到docker容器的。Jenkins中没有用Docker插件,试过感觉都不太成熟,目前还是Docker命令行最方便。

Q:开始的时候有讲如果没有Docker自动部署会自动部署,这个是如何部署的?

A:这个前面讲过,是通过lftp脚本比对编译环境与待部署的远程目录。

Q:也就是你们在虚拟机里面部署的Docker?

A:是的,当前的项目是在虚拟机里面部署Docker的,但我个人观点从长远看来,其实在物理机上部署Docker会更好,所以现在很多私有云比如OpenStack、CloudStack都能支持直接管理容器,不过目前虚拟机还是不能缺少的,容器的隔离性不如VM。

Q:如果用nat模式 容器如何指定IP啊?

A:不需要指定容器IP,只需要映射端口。

Q:有通过Dubbo做服务路由么?

A:Dubbo本身就有服务路由的功能。