嘉宾介绍

黄文俊

有容云资深系统架构师

主要负责容器云平台产品架构及设计. 8年工作经验, 有着企业级存储, 云计算解决方案相关理解. 关注于微服务设计思考, 开发流程优化, docker及kubernetes技术在实际环境中的应用。

主题

本次分享主要是介绍我公司如何使用Jenkins Pipeline, Container 和 Kubernetes Deployment的能力, 通过增加使用文本模版引擎, 扩展Kubernetes Config能力, 完成公司产品开发CI工作流的建立。主要内容包括:

1、 Jenkins 和 kubernetes

2、应用容器化和应用微服务化设计思考

3、容器环境下的编译和单元测试

4、对Kubernetes服务模版和服务配置的扩展

5、服务部署和升级

6、自动化测试

摘要

Jenkins作为最为流行的持续集成工具,在结合使用容器技术,Kubernetes 集群的基础上,该如何发挥出新的能力, 在应用微服务化的基础上,提供更好的CI方式,值得我们每一个开发人员去持续不断的摸索。

Jenkins 和 Kubernetes

Jenkins作为最流行的持续集成工具,有着丰富的用户群、强大的扩展能力、丰富的插件,是开发人员最为常见的CI工具。在Jenkins 加强其Pipeline功能后,更是可以通过丰富的step库,实现各种复杂的流程。同时随着Docker的流行,Jenkins内也增加了对Docker的支持,可以实现容器内流程的执行。

而Kubernetes随着版本迭代的速度越来越快,在容器圈内的热度也越来越高,同时每次版本发布,所新增的功能也不断增加。做为当前主流的容器管理平台,其强大的能力无需在此多做介绍。

应用容器化和应用微服务化设计思考

容器化不意味着微服务化,传统单体应用也可以容器化,但是很难享受到容器化后带来的好处。微服务化也不是一定要容器化,应用拆解为微服务后,一样可以不利用容器而是通过传统的运维来完成系统构建和部署。当微服务化和容器化相结合之后,就能充分利用各方优势,带来了弹性伸缩,简化部署,易于扩展,技术兼容等优点。

我们在针对应用进行微服务化拆分的过程中,主要先考虑到的是功能点、控制对象、开发组的人员配置安排,产品路线图规划等。例如,针对现有开发组人员人数、分配和各自的技能熟练程度,就可以考虑到服务模块数量的控制,安排好服务模块开发小组;针对功能点和中远期产品规划,就可以将特定功能归纳到一个服务模块中,并在版本开发迭代的过程中,通过扩展这个服务模块的能力,来完成产品功能的开发,或者暂时将部分功能整合在一个模块中,随着功能增加或迭代开发,再进行进一步的模块拆分或拆解。

对于模块开发的要求,由于使用了容器技术,对于开发语言或特定框架的选型,可以交给具体的模块开发人员。在团队内,我们不做强制要求,但是做建议要求,避免出现过多的技术栈,导致后期的维护困难。在我们团队内,就只集中在两种后端开发语言的使用,相应的框架或主要的开发库,也都有相应而且明确的选择。对于模块的API接口,使用REST,并且至少按照成熟度模型LEVEL2提供API。

容器环境下的编译和单元测试

我们整个CI工作流的驱动,都是由Jenkins完成,并且使用了Jenkins Pipeline。第一,Pipeline可以更好的组合job内的Stage,重复利用模块间相同的部分,并且随着开发复杂度的增加来逐步增加扩展stage,实现更多所需的功能;第二,将Pipeline Groovy脚本来源设置为源代码内,可以根据源代码功能点来控制流程,同时也完成了对脚本的版本管理。

由于有容器这么个工具,我们各个模块的编译环境,单元测试环境,也都放到了容器中。各个模块均可以安装自身模块的运行特性或环境要求,准备自身的编译环境、单元测试环境、运行环境,因此,代码库内会分别留存相应的Dockerfile,通过不同的Dockerfile完成不同环境镜像的准备。

同时,jenkins现在也可以通过Docker Pipeline插件,支持在容器内运行Step,因此我们利用其功能完成的实际的编译和测试流程是这样的:

1. 使用编译环境的Dockerfile构建编译环境镜像。

2. 使用编译环境镜像启动容器并在容器内完成编译,完成编译的中间产物也暂存在Workspace中。

3. 使用测试环境的Dockerfile构件单元测试环境镜像。

4. 使用单元测试环境镜像启动容器并在容器内运行单元测试,单元测试脚本来源于代码库,同时也使用到编译时生成的中间产物。

5. 使用发布Dockerfile构建实际发布镜像并上传镜像库。

其中由于编译环境和单元测试环境不是经常变更,也可以抽出编译环境镜像准备和单元测试环境镜像准备两个步骤放到独立的CI job中去,需要时手工触发即可。

服务部署和升级

对于CI流程,在完成编译和打包后,需要做的就是服务启动和测试了。我们利用的是Kubernetes Deployment和Service,在每次CI流程完成编译和打包后,通过拿到Build号,作为镜像的Tag,完成镜像的上传归档;同时利用Tag,修改Kubernetes中已经创建的Deployment,利用Deployment的Rolling Update,完成升级。

对kubernetes服务模版和服务配置的扩展 

我们在实际使用Kubernetes Deployment 升级的方式进行服务部署的过程中,发现其中还是存在很多不方便的地方。例如:Kubernetes内的同名问题,Kubernetes Deployment升级时的镜像tag变更问题,等等各处需要随着CI流程可能存在变更的地方。例如在有相同名字的Deployment存在的情况下,后来的Deployment会无法创建,这导致如果想以启动新的Deployment的方式来测试某个版本,需要修改名称,对于与Deployment相关的Service也一样,在启动新的命名后的Deployment,也需要启动与其对应的Service用于暴露服务。对于Deployment升级所需的镜像Tag修改,需要每次随着CI生成了新的镜像Tag而做变更,因而每次需要修改相应Yaml文件内的镜像Tag,修改为实际CI流程中生成的值,然后再使用升级功能完成服务升级。

针对这些问题,我们使用了一套文本模板引擎,部署或升级用的Yaml文件本身写成为模板,可能有变化或者需要根据CI流程变化的位置,使用模板标识占位,而具体的模板数据内容,则或者通过Jenkins的CI流程获取,或者使用特定的配置文件读取,或者从具体的输入参数来获取;同时,模板数据内容,也会在实际部署时,做为Kubernetes的Configmap设置到系统中,因此数据内容也可以通过Kubernetes使用Configmap的方式来用到环境变量或启动命令中。通过文本模板引擎,将模板和数据合并后,生成的Yaml文件,再作为后续Kubernetes操作所使用的内容。

通过利用这种方式,我们把需要部署的内容分离成了模板和配置;模板一般在服务架构,使用的镜像名,启动方式或配置参数没有大的变化的情况下保持不变,而通过不同配置的灵活使用,完成服务升级或拉起新部署,完成不同数据存储使用的指向,完成对各模块内部配置的修改。通过利用这种方式,我们的可修改的内容,从Configmap本身只能覆盖到的环境变量或启动命令这块,扩展到了启动名称,Label,镜像等Yaml文件内的各个可填值处,以此来解决同名,镜像修改,Label增加或变更等各种使用Kubernetes时碰到的问题。

自动化测试

在通过Jenkins拉动完成编译打包和服务升级部署后,就可以拉动自动化测试了。测试框架我们选择了使用Robotframework。测试脚本通过Kubernetes Service获取到服务的具体暴露端口,然后再根据测试脚本依次执行针对API的测试。测试脚本的来源,部分是从各模块代码库中,由各模块开发人员提交的针对自身模块的api测试,部分是由测试人员完成撰写提交的针对跨模块的测试。针对自动化测试这块,我们的完成度并不是很高,仅仅是搭建起了基本的运行框架,能够与整个流程对接上。

版本发布

由于开发的产品本身就是由若干镜像构成,因此产品发布,可以归结为镜像的发布。在测试通过后,可以简单的利用镜像复制能力,将测试通过的相关镜像的版本,通过镜像库间的复制,由开发测试所用的内部镜像库,复制到外部发布镜像库,就可以完成版本发布,同时可以通过复制时的Tag控制,发布为指定的版本号。

总结

如上介绍,说明了我们在自身开发过程中建立CI流程的做法。对于整个流程的建立,我们并没有太多需要特殊化处理的地方;对于各项工具的使用,也没有太多突出之处;我们仅仅根据自身需求,建立了和开发过程适配的ci流。在此介绍我们的ci流程的建立,也是希望抛砖引玉,能从更多处获得交流和向大家学习的机会。

Q&A

Q1:嘉宾分享的内容还是概括了些,有没有具体例子?配图说明的直观内容?

A:基于K8s的CICD产品即将发布,会提供对应的Demo演示平台,请及时关注,谢谢!

Q2:关于Jenkins和Docker集成的几个插件可以分享一下吗?

A:Docker Pipeline、Docker Plugin、Docker-build-step 这几个插件。

Q3:请问有容云的镜像复制大体思路是什么?目前我们是测试、预发布、发布共享一个仓库。通过辅助模块标签实现的!

A:镜像复制功能,简单来说实现了不同项目间的镜像克隆,根据我们镜像仓库的设计,对不同阶段(开发,测试,可上线)的镜像以不同项目分类,基于镜像复制功能即可快速实现不同阶段的产品发布,也就是镜像发布,此功能可下载AppHouse版本进行试用。

Q4:贵公司同步的吗?你们的配置文件是通过配置中?你们的配置文件是通过配置中心管理还是镜像间的环境变量实现的?

A:不是实时同步的,而是通过了我们公司镜像库产品的镜像复制能力实现的。目前开发流程中的产品运行配置是通过自身增强的配置文件能力实现的,配置会用来修改应用部署的Yaml文件,也会生成为Configmap。

Q5:有没有一个简单的Sample?可以上手跟着练习一下。

A:基于K8s的CICD产品即将发布,会提供对应的Demo演示平台,请及时关注,谢谢!

Q6:此次内容的Step by step 示例,能提供一个Demo吗?

A:基于K8s的CICD产品即将发布,会提供对应的Demo演示平台,请及时关注,谢谢!

Q7:我的项目的版本二只是修改了几个页面,也是和第一次发布一样的流程吗?

A:是的,流程化就是代表着可重复和可自动化,而无需去关系具体的修改内容。

Q8:你们部署的K8s版本是哪个?K8s的调度服务只能觉得容器起在哪个节点上吧,Rc是2的话,构建任务运行在哪个容器中这个怎么调度,为什么我的都运行在一个容器里面?

A :我们目前使用的是Kubernetes1.5版本。本身构建任务不是跑在Kubernetes上的,而只是应用自身跑在Kubernetes上。

Q9:你们是类似于Openshift那种平台式的吗?还是需要开发人员自己部署?

A :本身讲的是我们自身产品开发的流程,相关的产品会是类似平台型产品,需要自行部署,而不是公有云产品。

Q10:两个框架是开源的吗?

A :是的,都是开源的。

Q11:K8s的Yaml的部署文件的模板怎么去替换占位符?旧版本的容器怎么处理?

A :使用了自身开发的一套文本模板引擎,其实类似Web框架中的模板引擎,完成模板和配置的合并,通过使用配置中的key-value,替换掉模板中key的占位符。另外由于是使用的K8s Deployment的Rolling Update,升级完成后旧版本的容器/Pod会由Kubernetes自行删除。

Q12:开发测试、部署等各个阶段的镜像,方便共享一下吗?

A :这些镜像都是我们自身针对我们各应用模块所准备的,对其他人并没有什么用处,编译环境用的镜像不少就是官方镜像,例如Golang:1.7, Python:2.7。

Q13:传统单体应用如何容器化改造,可否分阶段实施?

A : 可以的,容器化改造或微服务化改造有很多实施方法,例如逐渐重构拆解,或新增模块进行微服务化和容器化,或开发新的模块替代原有应用的功能点,等等。各团队均可以选择合适自身的流程来进行改造。

Q14:数据库可以容器化吗?

A :可通过将数据卷挂入容器的方式将数据库容器化,但是现在实际项目中还很少见。

Q15:我们目前也是使用jenkins动态挂载容器来运行安卓编译构建作业,但现在每次使用镜像生成容器后因为要更新代码,导致整体编译时间比以前使用物理机方式要延长10多分钟,我们的镜像每天自动制作和下载,但因为代码更新太快及并发太多,编译时需要独占物理机,所以机器占用也很多,大家有什么建议解决这个问题,谢谢!

A :容器化编译就是将编译环境封装起来了,这种情况下一台物理机同步跑多个Job也行,可以看看怎么提高对物理机的利用率。

Q16:请问项目中存在多个镜像,是如何做关联发布的?

A :我们是使用了Jenkins的Mutil Pipeline,另外由于我们的模块的微服务拆解,对应同步发布镜像的需求并不高。

Q17:有这样一个场景,两个服务有依赖关系,服务A依赖于服务B,如何保证服务A和B的启动顺序的?

A :良好的设计是使得A服务启动后自行完成对B服务的检测发现和调用,而不是强依赖其启动顺序。

Q18:Kubernetes的服务的模板和配置,这个模板怎么来的,是用户自己编排?还是自己事先准备好的?配置数据是怎么存储的?

A : 因为当前模板和配置只用来启动我们自身开发的应用,因此这个模板是我们自己为我们的应用准备的。配置数据以文件的形式存储,但同时在使用文本引擎做模板和配置合并时,也可以接受参数作为配置。

Q19:什么是CI和CD?

A : CI更多是偏向应用编译、代码检查、单元测试等动作,CD是偏向于应用部署、运行流程,我们的开发过程在编译打包完成后,实际也会将应用跑起来用于测试,也可以算是针对测试的CD。

Q20:使用容器打包Jenkins流程的主要收益是什么?

A : 由于不同程序对于编译环境的依赖各有不同,原有使用Jenkins方法是在Jenkins Node上完成环境准备,现在可以利用容器完成环境准备,对于Jenkins Node的依赖可以进一步降低。同时环境变更也可以由开发人员自行控制。

Q21:多编译环境是用的不同镜像么?如何处理pipeline处理编译环境的问题?

A : 是的。由于我们本身产品开发各个模块有各个模块的开发语言和框架,因此各模块都要维护自身的编译环境镜像。使用Pipeline在进行编译时,是通过使用镜像运行容器然后在容器内编译的方式来使用编译环境的。

Q22:产品发布的产出为镜像,如果有很多镜像,最终会在镜像上打上统一的Tag么代码上的Tag什么时机打呢?

A : 我们是在产品发布时,进行镜像复制时统一打发布Tag的,这时候从开发测试库会复制到发布库,利用了我们公司自身镜像库产品的镜像复制能力。

Q23:请问Jenkins 也是部署在Docker里面的吗?如果Jenkins在Docker里面怎么样在Docker里面使用Docker执行CI?

A : 是的,我们也在摸索将Jenkins本身放到容器中运行。在这种情况下,Jenkins容器内使用Root权限,挂载Docker.Sock和Docker数据目录到容器中就可以了。

Q24:使用Pipeline先构建编译环境镜像,再编译,是否会导致整个流程需要很长时间?是否有优化方案?

A :编译镜像由于不会经常变动,因此这个镜像的构建通常使用Cache就能直接完成。另外我们也把编译环境镜像打包这个步骤抽出来单独作为Job执行了,这样在实际编译流程中就无需再进行编译环境构建。

Q25:直接使用Jenkins的Docker插件和直接调用脚本,自己写Docker命令,如何在灵活程度和方便程度上做权衡?

A :看个人或开发团队的自行权衡吧,只是开发团队内最好统一,便于进行相应维护。

Q26:Jenkins和Kubernetes的用户是怎么管理的?我的期望是用户只能看到自己得资源,别的用户是没有权限的。

A : 我们本身只是使用这两种工具,在开发环境中不对外,所有不存在用户管理的问题。在我们公司正在开发的CICD产品中,对这块有我们自身理解基础上的设计。

Q27:Jenkins的持续集成是怎么实现的?比如不同的源码仓库的提交触发,如Github gitlab,版本号怎么控制的?

A : Jenkins的CI流程触发可以有很多种,代码提交触发、定时触发、手动触发。版本号的控制也可以有很多方案,比如使用Job的编号、使用Git的Commit号、使用时间戳等等。

Q28:Jerkins 调用K8s API 用了什么插件么?

A :没有直接使用Jenkins调用Kubernetes API。

Q29:Jenkins的Pipeline支持流水线执行,一般从头开始执行,可以从流水线中任意一步执行吗?

A : 因为我们团队的Jenkinsfile都是直接放置在代码库内的,因此每次执行都是从头完成流水线的执行。

Q30:容器化后发布也要通过Jenkins,感觉Docker的发布没有Jenkins方便,除了容器化的可移植,还有什么原因值得推进项目容器化?

A : 应用容器化,其实更多的是看重应用在容器管理平台上运行起来后所获得的能力,例如在Kubernetes上运行后的水平扩展,服务发现,滚动升级,等等。

Q31:K8s Update需要制定新的镜像才能做滚服更新(升级),如果只是更新了Configmap,有办法做滚服更新吗?

A : 我们的CI流程完成后,各模块的镜像Tag会发生变化,我们利用具体生成的Tag生成配置,然后部署的Yaml文件写为模板,镜像的具体Tag会根据每次CI流程生成的配置不同而组合为不同的Yaml文件,然后使用组合后的Yaml,即Tag已经变更为最新版本的Yaml文件进行应用的滚动升级。

Q32:Pipeline采用在镜像里构建的方案,是怎么实现的?用Jenkins现成的插件 or 用Groovy脚本实现?

A :使用了Jenkins的Docker插件,同时使用方式是将相应命令写在Groovy脚本里。

例如:stage('Build'){        docker.image('golang:1.7').inside {             sh './script/build.sh'                  }    }

干货大放送

请大家扫描以下二维码关注本公众号并回复【进群】,有容云小助手会第一时间拉您进入【有容云Docker技术交流群】,干货大放送正在敬候您的光临,期待大家就技术的更多细节和疑问与群里的大牛们进行咨询探讨。

往期回顾:

【干货-容器系列】补脑专用,容器生态圈脑图大放送

【干货-容器系列】Kubernetes调度核心解密:从Google Borg说起

感谢各界朋友支持,更多精彩内容敬请期待!

【有容云案例系列】基于Jenkins和Kubernetes的CI工作流的更多相关文章

  1. 基于 Jenkins+Docker+Git 的CI流程初探

    在如今的互联网时代,随着软件开发复杂度的不断提高,软件开发和发布管理也越来越重要.目前已经形成一套标准的流程,最重要的组成部分就是持续集成(Continuous Integration,CI)及持续部 ...

  2. 「持续集成实践系列 」Jenkins 2.x 构建CI自动化流水线常见技巧

    在上一篇文章中,我们介绍了Jenkins 2.x实现流水线的两种语法,以及在实际工作中该如何选择脚本式语法或声明式语法.原文可查阅:「持续集成实践系列」Jenkins 2.x 搭建CI需要掌握的硬核要 ...

  3. 「持续集成实践系列」Jenkins 2.x 搭建CI需要掌握的硬核要点

    1. 前言 随着互联网软件行业快速发展,为了抢占市场先机,企业不得不持续提高软件的交付效率.特别是现在国内越来越多企业已经在逐步引入DevOps研发模式的变迁,在这些背景催促之下,对于企业研发团队所需 ...

  4. 基于Jenkins的持续集成CI

    CI(continuous integration)持续集成 一次构建:可能包含编译,测试,审查和部署,以及其他一些事情,一次构建就是将源代码放在一起,并验证软件是否可以作为一个一致的单元运行的过程. ...

  5. 有容云-【原理】Docker存储驱动之AUFS

    编者按:今天聊一聊Docker的Image(镜像)与Container(容器)的存储以及存储驱动之AUFS.   Docker存储驱动简介 Docker内置多种存储驱动,每种存储驱动都是基于Linux ...

  6. 视频私有云实战:基于Docker构建点播私有云平台

    私有云是为一个客户单独使用而构建的,因而提供对数据.安全性和服务质量的最有效控制.前置条件是客户拥有基础设施,并可以使用基础设施在其上部署应用程序.其核心属性是专有的资源.本篇文章将会结合网易云信的实 ...

  7. 灵雀云Kube-OVN:基于OVN的开源Kubernetes网络实践

    近日,灵雀云发布了基于OVN的Kubernetes网络组件Kube-OVN,并正式将其在Github上开源.Kube-OVN提供了大量目前Kubernetes不具备的网络功能,并在原有基础上进行增强. ...

  8. 基于Jenkins的环境搭建

    基于 Jenkins 快速搭建持续集成环境 持续集成是一种软件开发实践,对于提高软件开发效率并保障软件开发质量提供了理论基础.Jenkins 是一个开源软件项目,旨在提供一个开放易用的软件平台,使持续 ...

  9. 基于jenkins的go语言项目自动化发布遇到的坑

    之前我们研究dep,就是为了有一天可以实现go语言项目在我们系统里的CI. 之前联物科技的项目主要是使用java作为后端开发语言,基于jenkins的自动发布,使用了pipeline编写脚本,从拉取代 ...

随机推荐

  1. 长春理工大学第十四届程序设计竞赛(重现赛)L

    L.Homework Stream 题目链接:https://ac.nowcoder.com/acm/contest/912/L 题目 作为大珩班尖子生,小r每天有很多作业要完成,例如工图.工图和工图 ...

  2. leadcode的Hot100系列--104. 二叉树的最大深度

    依然使用递归思想. 思路: 1.树的深度 = max (左子树深度,右子树深度)+ 1 . ------> 这里的加1是表示自己节点深度为1. 2.如果当前节点为null,则说明它的左右子树深度 ...

  3. 微服务-springboot+websocket在线聊天室

    一.引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...

  4. 并发编程-concurrent指南-阻塞双端队列-链阻塞双端队列LinkedBlockingDeque

    LinkedBlockingDeque是双向链表实现的阻塞队列.该阻塞队列同时支持FIFO和FILO两种操作方式,即可以从队列的头和尾同时操作(插入/删除): 在不能够插入元素时,它将阻塞住试图插入元 ...

  5. java中session和application的用法

    Session的用法 首先创建2个jsp文件t1.jsp  t2.jsp 在t1.jsp <% //设置session的键与值 session.setAttribute("abc&qu ...

  6. 利用Python模拟GitHub登录

    最近学习了Fiddler抓包工具的简单使用,通过抓包,我们可以抓取到HTTP请求,并对其进行分析.现在我准备尝试着结合Python来模拟GitHub登录. Fiddler抓包分析 首先,我们想要模拟一 ...

  7. Linux命令学习-history命令

    Linux中,history命令的作用是显示历史记录和执行过的命令. 查看历史所有命令执行记录 history 查看最近的13条历史执行命令 history 13 执行历史记录中,序号为123的命令 ...

  8. java 带静态域的导出类创建时都发生了什么?

    先按从基类到导出类的顺序初始化静态域(之前已经初始化过的静态域不再初始化) 再按从基类到导出类的顺序初始化类,即基类普通字段+基类构造器主体+导出类字段+导出类主体... package test; ...

  9. ES6中用&&跟||来简化if{}else{}的写法

    目录 ES6中用&&跟||来简化if{}else{}的写法 1. if else的写法 2. ES6中 && ||的用法 3 ES6实例 4 开发环境 ES6中用&am ...

  10. springboot之mybatisplus,mp的简单理解

    这是一张简单的service的继承图.可以看到我们的执行类,即XxxServiceImpl的继承关系. 从上到下,ServiceImpl和BaseMapper是一个依赖关系,ServiceImpl和I ...