讲师 | 林栗

编辑 | 黄晓轩

讲师简介


林栗
Micro Focus DevOps工程师
擅长组织级持续集成架构设计与实施,专注于软件配置管理、DevOps 领域十余年,对 CMMI、Agile 与 DevOps 有切肤之体会与感人之经验,依然乐此不疲的自我迭代中。

前言


我今天跟大家分享的话题是:利用 Jenkins Pipline 来编排 DevOps 工具链,把我们的产品部署到任何地方。主要内容分成三块:

第一个我会简单介绍一下我们公司的敏捷和 DevOps 转型;

第二个简单介绍一下 DevOps 工具链;

第三个重点是以 Jenkins Pipline 为核心,怎样实施一个以微服务架构为基础的,以 Kubernetes 为部署方法的持续集成和持续部署的话题。

敏捷和 DevOps 转型


开始之前我先简要介绍一下我们是谁,我们是干什么的。Micro Focus大家会比较陌生,但惠普大家都听说过。不久前2017年9月1日,原先惠普的软件业务跟来自于英国的 Micro Focus 公司进行了合并,于是江湖上就诞生了一家新的公司。

两家公司的历史都非常悠久,最早可以上溯到1939年,在慢慢历史长河里面,如果用三个字概括一下公司的话就是“买买买”,一路上买了很多的软件。

最终形成了混合 IT、DevOps、安全风险以及预测分析这四大领域非常丰富的产品线。

总而言之一句话,我们公司是做企业级软件的,我们客户主要是一些欧美的大公司、政府、银行。介绍这些是想让大家了解一下我们是在做什么,以及为什么有这么一些 DevOps 的选择原因。

做为软件行业的巨头之一,我们整个公司以 Scaled Agile Framework 这种框架来作为公司运作的框架。

在这样大的框架下我们正在经历从传统软件行业向 DevOps 的转型。我自己在这里面也非常深切的体会到了中间巨大的变革。

我们有什么样的变化:

  • 战略层面。我们做了一个产品集的战略,说白了就是把业务上和领域上比较相关的产品打包成全家桶卖给客户。这件事情带来的变化是我们有很多跨团队、跨组织的流程和协作的变革。
    
  • 践行敏捷的方法。尤其是持续集成、持续部署、持续 Demo 作为我们最核心的实践。

  • 技术领域。我们把产品容器化,在架构上,做了微服务化的设计与调整。因为我们是做企业软件的,所以我们对软件质量、安全以及法律法规上面有比较高的要求。我们把安全是内嵌到 CI/CD 的流程里面做。

说这么多那我们项目是什么样子呢?我选一个最典型的指标来跟大家说一下。那就是我们 release 周期,两三年之前我们是一年 release 一次。这在今天,大家可能觉得都不可想象。

我们也在迅速的发生变化,在进步。我们从一年到六个月到三个月,我们目前的目标是两个礼拜 release 一次,我们是很大型的软件。大家可能会说这有什么难的,可是看一下我们项目的情况,我选取了三种比较典型的项目特征。

项目 A,这个项目有20多年,接近30年了。那时候没有 Jenkins,C++ 很多功能也没有诞生,我们还要维护它。最大的挑战是工具链的升级,以及怎么降低维护它的人力成本。

项目 B,比较新一点,最开始是以微服务作为架构设计诞生的。但有一个很挑战的地方,它的 Jenkins job 达到600多个。支撑这个项目的 DevOps 工程师人力是三分之一个。这个事情说明的是:微服务给我们的持续集成以及持续部署带来了更大的挑战和困难。

项目 C,做的事情就是把 A、B 以及其它的项目产出的东西,我们把它容器化,把它装在 Kubernetes 里面,部署到各种公有云和私有云上面。这个项目挑战难度是有很多跨团队的协作,以及产品集成上面的困难。 举例

我们以这个项目 C为一个例子,打个比方,我们干的事情是什么呢?好比说我们在用乐高积木搭一艘军舰,要一块一块的快速组装起来。我们舰队的成员大概有180多个工程师,有21个时光团队。我们的弹药库是6个大的产品,从逻辑业务上切分成了20多个 service ,每个 service 向下还可以切出很多组件和技术角度的 service 。我们 Git 仓库有120多个,发布给客户的东西有140多个Docker image ,在 Kubernetes 里面部署起来有145个 pod 。

我们认为这么高度集成复杂的产品,自动化的测试系统是我们的生命线,而 CI/CD 系统就是我们这艘军舰的动力系统,通过 CI/CD 的流程,我们拉动项目运转周期,协调各团队不同角色之间的协作。

DevOps 工具链


我们为项目选择了什么工具。刚刚也讲到,我们公司自己本身就有非常丰富完整的从 DevOps 到 IT 运维,到项目管理到敏捷的工具都是有的。工具这个东西没有最好的,只有最适合的。我跟大家分享一下,实际用到了哪些工具。

大家看这张图上,Jenkins 像一个老管家一样,他在这里的地位就是起到了对工具编排引擎的作用,可以理解是一个框架。这里有开源的也有商用的,也有我们公司自己做的,选这些工具有一些方法和思路。

我先分享一下方法:

首先,建立一个我们想要达到的目标,以及工具所能提供功能的矩阵图,因为满足需求永远是第一步。

第二,我们要考虑工具跟其它工具集成的功能、能力以及难度。

第三,扩展性和灵活性。就是说它是否提供丰富的 API ,它插件的数量、质量,它提供的 SDK 等等都是需要考虑的。

第四,用户的接受程度。这个对使用面很广的工具是很重要的。

第五,我们的学习曲线,实施的难度和维护成本都要考虑。

第六,商用软件的话还要考虑技术支持。如果是开源的软件,要考虑社区的活跃度,以及热点趋势还在不在,

第七,应用的范围。我们的工具到底是用在项目内部的,还是我部门的,还是 BU 的,还是整个公司的。不同范围对软件的一些高可用性、性能、灾难恢复以及很多企业功能要求是不一样的。

第八,价格因素。

第九,考虑所有因素以后最重要的是要做 POC ,要做工具之间 demo 的对比,以及在一些小范围的项目做试点。因为只有经过项目运作检验以后,才能够证明这个工具是适合我们的,其实我们在做的时候,有时候确实会推翻,会觉得发现有一些 issue 是没有办法解决的。

回到工具链图看一下,最左边的这列是项目管理以及协作的工具,前面乐高军舰的图上,写出了非常精确的人力投入的点,以及计划的点,怎么体现出来的?就是 Agile Manager 软件,这个系统非常好用。它完全是基于 Scrum 模型的项目管理,里面有丰富的数据管理,很漂亮的 Dashboard 等等。

在基础设施上选用了 Docker 和 Kubernetes ,在云平台选用了私有云 Vmware 和公有云 AWS ,并实现了持续部署。

这边源代码管理工具,这里有两个风格,一个是 GitHub,另外一个是谷歌出品的 Gerrit 工具。

这两种工具是完全不一样的理念,谷歌的 Gerrit 在安卓生态圈特别流行,他的理念更注重控制,更中心化,更严格的审核机制。GitHub 是经典的 social coding的理念和产品设计。

我们做了 POC 以后认为 GitHub 是更适合我们想法的,全公司里面我们选择工具有很高自由度,没有强制要求。

可是整个公司 GitHub 应该是第一流行的,大家都选择了它,我们也是把这个工具从公司层面做了全球部署,它的性能、界面和功能都是非常好的,应该是一统江湖的工具了,其它的大家可能各自有各自的想法。

打包和部署 Maven 和 Ansible 是比较通用的工具。Packer 和 Terraform 优点是可以对付各种不同云平台的基础措施,包括 AWS和 Google Cloud Platform, 微软的 Azure 和私有的 OpenStack 也是同样支持的。Cloud Formation 是专门针对 AWS 基础设施提供的工具。

其它还有很多的工具,我们在选择它的时候要符合自己的需求,要按照一定的套路来进行这样的抉择。

利用Jenkins Pipeline 实现CI/CD


有了上面的这些工具我们具体怎么做持续集成和持续部署的呢?讲到CI/CD的话题,我想引用一句托尔斯泰的名言:幸福的家庭都是相同的,不幸的家庭却各有各的不幸。

CI/CD 这个事情,模型和框架都是一样的,各家公司有各家公司的困难和挑战,有不同的条件限制,最终实现的效果也是不一样的。

下面我想分享一些 idea,就是我们在实施 CI/CD 时的一些想法。我认为做这些事情为我们真正带来好处的点分享给大家,希望有遇到跟我们类似情况的时候可以有所启发。

首先讲 CI/CD 有一个先入的条件,就是软件的架构设计。我们是以微服务作为架构设计的。这个架构很大程度上就已经决定了你的流程该是什么样子,以及具体实施应该有什么样的要求。

我们一些新的项目一开始用了微服务,对一些老的项目,我们也花了非常大的经历去做服务的定义和切割。

具体来讲反映到 DevOps 能看到的角度就是把以前的单体代码仓库切割成若干个独立的,每一个仓库里面的内容和服务都可以独立的编译、部署、测试,这是我们的第一步。

基于微服务的架构,我们 CI/CD 的想法就是形成多层级 CI/CD 的系统。每一层的内部是一个小周期,在这个小周期里面是一个标准的构建、测试、部署完整的闭环。

当他跑完这个闭环以后,会向上一个级别 promote 自己的一个版本,就进入一个新的大周期新一轮 CI/CD 标准的循环。例如:假设一个工程师提交了一行代码,那么就会至少经过组件级别、 Service 级别以及 Suite 集成级别,至少有三层 CI/CD 的循环,最终会进入 Release 的队列。

多层级的 CI/CD ,这个也不算很新奇,很多都是这么做的,我想说有下面三点需要特别注意。首先是 Service 之间集成的验证。

理想很丰满,现实很骨感。我们做微服务总是希望服务之间是解耦的,不依赖的,但我们业务上高度集成的产品,现实有时候确实是互相依赖、互相伤害的。

就好比说,我自己的小周期是 ok 的,可 A 和 B 放在一起就不好了,也许旁边还有一个无辜的 C 说我也不好了,怎么办?我们的思路就是说要做通过基于 API 的契约测试。我希望对方该有什么样的行为,我去写 case 测是不是符合我的预期。

第二,我们有 Promote 的机制,必须是一种灵活和自动化的 Promote 和 Rollback 机制。我们做集成的时候总会挑某一个服务 Stable 版本,以及其他的官方认证过的 Stable 版本,放在一起做集成测试,如果 ok ,那就是新的Stable,如果不可以的话,就 Rollback 回去。这套机制也是借助了 GitHub 的Pull request 方式来完成的。

第三,很重要的一点,我们有这么多 Service 而且还分层级,我们 Version mapping 关系的维护是很重要的。每一层的组件,Service 以及整个产品集必须有声明自己版本的机制。Stable 是什么版本,最新是什么版本,RC、GN分别是什么版本,我下面用到哪些组件是什么版本,向上我又被谁用到了我的什么版本,这些都会有很明确的 mapping 关系,这些也是随着我们代码和配置是编译在一起的。并且我们是以一种自动化脚本的方式,能够自动更新他们的,这点很重要。

下面一个话题就是 Operation as a service 。我们把自动化测试和监控等等公共的能力作为一项服务,提供给每个不同团队。

打个比方就以自动化测试的框架和 case 来说,它实际上是运行在 K8s 里面的,是作为 K8s 里面的 Service 跟我们产品运行在一起的。这么做是因为安全上的一些考虑,Service 之间相互调用的端口和 API 是不允许曝露到Kubernetes 外面网络的,全部都要走 K8s 里面的网络。

我们要测这些东西,那自动化的框架就必须要在 K8s 里面。这么做我们发现带来一个好处,我们不仅仅是有官方每天执行的测试和报告,任何一个人,他只要去部署我们的产品,他的环境里面都会缺省给他起这样一个 Service 以及相关的 Pod,里面装的就是测试系统,就会帮他跑。

更进一步这些测试的代码,以及我们部署的文件(例如:K8s 的 yaml 文件等)都是放在 GitHub 上面。任何一个团队,除了用我提供的这个标准,还可以把仓库复制过去,针对特殊的需要做一些修改,就很容易的做出针对自己的测试服务框架。

好处是每个小团队不用花力气维护这些系统,我只关心我的业务,去写 case 就好了。他做出来以后,按照他自己特殊的标志,在 K8s 里面运行起来。并且一些共性的东西还可以贡献给中心的仓库。

与此类似,我们的监控也是这样做的。我们监控是用了 Prometheus 和 Grafana ,这本身就是天生的为 K8s 和 Docker 做的,它也是作为一个 Pod 跟产品环境部署在一起的。

这不是我们官方有一个总的监控系统,我们任何人都可以做自己的小系统,这就很方便了。这个意思就是说共享了基础的能力。

接下来 Pipeline as code,这个用到了 Jenkins 2.0 以后的一个核心功能。用代码、脚本定义我们的流水线。

这个事情跟以前传统方式配置 Jenkins 是有一个革命性的变化,在我看来,我们每一个仓库,在它根目录下面都会有一个Jenkins file。这个文件就定义了该仓库流水线的行为,并且大家可以看到这个版本化的管理。在这件事情我认为不仅仅是流水线是代码定义,更进一步我们做的是通过代码定义了我们的流程和规则。

举例:我们用 Kubernetes 部署东西,要编写很多的 yaml 文件。它本身是不需要编译的,但是它的格式很敏感,无法在构建环节发现错误。在部署时,如果语法有问题就会失败,我们想尽早的发现错误,那我就会对它做语法检查,另外还有语义检查。

我们有很多的 Service 运行在共同的环境里面,我们内部协商了很多端口,协商了很多 lable 和配置,大家要有这套规则,这些规则不是通过开会、邮件、写文档就可以约束得住的。

我们就把这些规则全部翻译成了代码,在我们 CI/CD 环境里面跑,任何一个工程师要改 Kubernetes 中的任何一个配置,当他提交修改时发送到 GitHub, CI/CD 首先做的第一件事情就是对这些 yaml 文件语法跟语义做检查,如果通不过根本不可能让你部署。

Pipeline as code 与 Operation as a service 解决的就是怎么样在微服务的组织下共享一些能力。

Jenkins Pipeline 至少有三种方式可以达到代码的复用:

  • 第一,本地文件级
    
  • 第二,从远端的某个 URL 执行公共的定义的内容
    
  • 第三,把一些公共的函数或者类抽象出来做全局的类,然后调用。
    

这是为了把我们部署的行为和环境,不仅仅要降低维护成本,主要还是做标准化的统一。

统一我们的 build chain。既然我们是微服务的团队,每个服务之间采用的技术栈,差异是非常大的。尤其是像 go 语言这种,编译打包的方式各不相同。我们每个项目都是用 Jenkins file 方式来做流水线的。

我们希望 Jenkins 内部行为,接口的对象尽量是统一。我们就采用 Maven 抹平每个 Service 之间技术栈的不同。Maven 插件非常丰富,可以对 C++、Java、npm 都可以完成打包,甚至包括 docker image 也是用 Maven 做的。

持续集成的时候根据你各种不同的需要,不同的参数打进去,利用模板引擎形成最终的 yaml 文件。这都是用 Maven 做出来的。最终形成的效果就是说简化了 Jenkins file 内部的逻辑编程和配置。

接下来是 pre-flight build 和 private CI/CD 的概念。第一个是在任何变更合入分支之前,跑一套 CI/CD 保证质量的。第二个是我们 CI/CD 不仅仅只有官方系统可以跑,任何一个工程师可以对自己私人的代码修改完成持续集成的。

接下来看看怎么样跟 GitHub 集成。我们官方的 git仓库原则上是不允许存在有特性分支的。

我们是秉持着主线开发的模式,任何一个工程师工作的时候,首先复制出这么一套仓库到他私人仓库的名下,做完了修改,Pull Request 到官方仓库。这时候 GitHub 的 webhook 会告诉 Jenkins 这个事件,Jenkins 就会根据 Jenkins file 定义内容运行 job,并回写消息给 GitHub。当这个 Pull Request 被批准合入以后,同时也会再被执行 CI/CD 的流程。

具体的效果我们是用的 MultiBranch Pipeline 。这个插件有一个好处值得拥有,它可以扫描整个代码仓库,只要发现有 Jenkins file,可以自动帮你创建出每一个分支 job。

当你 Pull Request 提交过来后会自动扫描帮你创建出 Jenkins 的 Pull Request 的 job,如果关闭会在 job 上打一个横线,这个 job 就会自动的销毁了。这个是节省了很多配置 Jenkins job 上面人力的成本,全都可以自动的做出来。

前面主要集中于持续集成,接下来我们看一下部署。我们部署的情况也是比较复杂的,公有云要支持 AWS,微软的 Azure,私有的要支持 OpenStack 和Vmware。部署在实验室或者客户环境里面都要进行部署,部署的用途有给 Dev,有给 QA,有给 Staging 环境,有给 Demo 环境。

具体到产品安装的方式就更五花八门和复杂了,有不同大小、不同模块,我们是变形金刚,A+B、C+D 等等的组合。总的来说我们要做这个部署,大致粗略的划分要做这几件事情:

首先,基于公有云,要创建出一套基础设施,AWS 上的 EC2,网络,存储等等,在私有云上面相映的虚拟机要创建出来。

第二,要把 K8s 安装起来。

最后,在 K8s 中起我们的 Pod 和 Service 等等。

我们要怎么做这个事情呢?我们的思路就是针对每一步高度抽象出独立的工具,这个工具的能力可以完成很复杂的配置。

第一层是用 Terraform(支持各种其他公有云)、Clud Formation、Ansible(主要针对 VMware)。

做了第一层基础设施以后,有时客户已经有自己的IT基础设施,那我们有一个 Pre-check。这个工具会帮客户检查是否符合安装 Kubernetes 的条件,如果不符合要展示出来。

第二层我们有一个工具 Container Delivery Foundation,这件工具专门负责安装 K8s,包括多 master,多 node,各种网络和存储配置,都是用这个工具来完成的。

第三层就是产品安装环节。我们用 Go 语言封装了 Ansible 做大量的复杂逻辑,最终达到的效果是通过输入各种不同参数和配置,就算一个小白,任何一个人拿到他都可以把有140多个 Pod 的产品在 K8s 里面跑起来。

有了这些独立强大的安装部署工具以后,我们怎么样来把他编排起来的呢?主要是用 Jenkins Pipline 的方式。这里有很多共性的步骤可以复用的,图的上半部分是我们在实验室内部模拟了一次产品升级。

下半部分是一次在 AWS 上面的安装部署。大家可以看到有一个 stage:check service status,它总共出现了3次,背后的代码和工具都是一样的,是高度的抽象复用。不仅仅是为了节省成本,主要是为了标准化。要保证大家在所有情况下做的事情都是一样的。

接下来给大家介绍一款比较好玩的工具 —— Slack,是即时聊天的工具,可以做工作协作。它的意义是形成一个虚拟的作战室,相同的话题可以在一起有一个专门的频道进行聊天。

像一些部署的消息,我们测试的报告,我们都会发到这个系统里面。更重要的是他是 ChatOps 界面、接口的工具。你想让这个工具做一些事情,对话框里面打一些关键字,Slack 收到消息以后,就会去跟你想操作的系统讲话。非常好,在手机上也可以操作。

我在 DevOps 领域想挑一个我认为最有价值的实践来讲,我认为是 infra as code。

这个事情不仅仅是解放了我们自己,把我们的基础设施进行了代码的标准化定义,更重要的是做 DevOps 我希望是 everything as code,包括我们的流水线、流程、规则等所有的都翻译成代码,为什么?这才是我们做到自动化轻松运营的基石和前提条件。