Py学习  »  docker

基于 Docker、Kubernetes 实现高效可靠的规模化 CI\/CD 流水线的搭建

GitChat技术杂谈 • 6 年前 • 712 次点击  


本文来自作者 邸富杰 在 GitChat 上分享,阅读原文」查看交流实录

文末高能

编辑 | 库克

高效可靠的 CI/CD 流水线是一个IT组织实现软件服务快速交付的基础,现如今大量企业采用 jenkins 集群来搭建其交付流水线。然而,如何管理大量 Jenkins Slave 的差异化?

如何简单快速实现 Jenkins 能力的横向扩展?如何实现流水线的高可用?如何有效利用闲置的 Jenkins Slave 资源?

上述这些问题一直困拢着集群管理员,近两年随着虚拟化技术突飞猛进的发展,Docker, Kubernetes 等现代化工具彻底颠覆了交付团队的交付流程,同时也为 CI/CD 流程水线的搭建与管理提供了全新思路。

Setup

目前流行的CI工具很多,鉴于本文讨论CI/CD流水线在企业中的应用,考虑到企业不会有意愿将源代码的访问权开放给第三方,像 Travis CI 等这些基于 SaaS 的 CI 工具自然被 pass,由于 Jenkins 在CI 领域的主导性地位,所以本文的CI工具只涉及 Jenkins。

另外,本文默认您对Jenkins, Docker, Kubernetes 等工具有一些基础的了解。

持续交付流水线简介

简单介绍一下持续交付流水线,如下图所示,简单来说流水线工作流程是这样的,当代码库有代码变更的时候持续集成服务器(Jenkins)会监听到代码变更并自动触发第一个阶段 — 持续集成阶段,这个阶段做的事情是自动构建,单元测试,静态代码分析以及生成报告。

如果所有步骤都如预期成功通过的话会自动进入下一个阶段 — 自动化测试阶段,在跑自动化测试套件之前,首先要把成功通过第一阶段的产出物(artifact)。

如果选择 java 做为开发语言的话就是生成的 war 包,ruby 的话就是 gem 文件,部署到测试服务器上,部署完成之后触发自动化测试套件,包括验收测试,容量测试,性能测试等会自动执行。

如果所有测试用例都顺利通过(有些公司还需要做一些手工测试如探索性测试等)的话,那么这个版本就会被标记成一个可发布的侯选版本。一旦业务需要,就会通过一键部署的方式将相应的侯选版本发布到生产环境上去。

如果你想更多的了解持续交付相关的知识请阅读 David Farley and Jez Humble 的名著《Continues Delivery》。

实践与痛点

上述这个过程实现起来需要这样做,首先需要把上图中各个阶段的工作脚本化,说具体一点就是需要写一个构建脚本来完成编译,单元测试,静态代码分析,生成报告等步骤,从而完成持续集成阶段的工作。

然后是自动化测试脚本,这个脚本可以触发自动化测试套件并生成相关报告,最后需要写一个部署脚本,用于将持续集成阶段的产出物部署到测试环境上(当然最后的发布阶段也会重用这个脚本)。

这里需要注意的是要确保部署都是可重复的,重复部署同一个产出物N次的效果与只部署一次的效果相同,也就是大家常说的幂等。

接下来就轮到 Jenkins 出场了,首先我们需要配置一下 Jenkins 来监听代码库的变更,这就意味着只要有代码迁入就会触发相对应的流水线。

然后我们用 Jenkins job 或 pipeline 将这几个阶段的脚本串联起来(下图是以job为例),这样一个简单的 CI/CD 流水线就搭建完成了。

如上图所示,每一个 Job 负责运行某一阶段的脚本,可以简单类比为 Job1 运行持续集成阶段的脚本将源代码从代码库中迁出,编译,单元测试,静态代码分析以及生成报告。

Job2 首先将持续集成阶段的产出物部署到测试环境并运行自动化测试套件,生成报告并标记产出物,Job3 用于按需发布。

每个 Job 所运行的脚本依赖的语言或运行环境会有所不同,可以通过label方式选择到相应的Slave上运行。

但在企业级大规模的应用 CI/CD 流水线时,由于企业中多团队多产品的存在,不同的团队会根据产品自身的特点来选择不同的技术实现方式,这就意味着产品实现语言会有很多种。

比如 java, C#, ruby, python, nodejs … 那么相对应的 CI/CD 流水线就需要提供所有语言的编译环境并安装相关的依赖包,当然为了减少依赖,避免冲突以及更好的管理这些编译环境聪明的管理员会选择让每一个slave只能运行特定编程语言的 Job, 如下图所示:

上图这个 Jenkins 集群 Maser 只用来调度和收集 log,所有的 Job 都会由 Master 根据不同的 label 导流到对应的 Slave 上运行。

这种集群的实现方式是我们在VM时代不得已的选择,虽然能解决大部分问题,但也带来了很多困扰。

  • 单点依赖
    Jenkins Master 成为单点,一旦 Jenkins Master down 机,那将是灾难性的,整个 CI/CD 流水线都将处于不可用的状态。

  • 不易维护

    大量差异化的Jenkins Slave管理起来难度很大,由于差异化的存在,维护升级几乎都需要手动完成,人力成本投入很高。

  • 举个例子,我们发现流水线对 Java7 这个 Slave 发出的请求量比较大,经常出现排队现像。为了缓解这种情况,管理员需要增加一个可以编译Java7应用的Slave,那么管理员需要怎么做呢?

    首先需要准备一台VM,然后安装 java7 以及所有依赖的软件包,最后配置 Slave 相关信息label等并将新安装好的 Slave 注册到 Master,基本上都需要人为干预,扩展起来非常不方便。

  • 资源浪费

    每一台 Jenkins Slave Server 都是一台实实在在运行的 VM,当 Slave Sever 空闲时也不能将它所占用的资源释放,因为随时可能需要这个 Slave完成相关的 Job。

解决痛点

下面我们来看一下虚拟化技术带来了的福音,下图是一个基于 Kubernetes, Docker 搭建起来的 Jenkins 集群,为了避免混淆我略去了 Kubernetes 集群中的 Master node。

我们看到 Jenkins Mater 以 Docker container 的形式运行在 Kubernetes 一个 Node 上并将所有 Jenkins 相关数据存储到一个 volume 中,Jenkins Slave 也以 Docker container 的形式运行在各个Node中,之所以用虚线来表现 Slave 是因为 Slave 不是一直存在的,它会被动态的按需创建并自动删除。

简单介绍一下这种动态创建注册 Slave 的方式,它的工作流程是,当 Jenkins Master 收到一个 build 的请求时,会用按照 label 的要求动态的创建一个运行在 Docker container 中的 Jenkins Slave 并注册到 Master 上。

然后运行相应的 Job,当 Job 运行完成后这个 Slave 会被注销,所在的 Docker container 也会被自动删除。

这种基于 Docker,Kubernetes 搭建的 CI/CD 流水线给 Jenkins 集群带来了诸多益处:

  • 高可用

    Jenkins Master 被部署在 Kubernetes 集群上,一旦 container 运行异常意外退出,那么 Kubernetes 会自动用相同的 Docker image 帮我们从新起动一个新的 Jenkins。

    并将 volume attach 给新创建的 Docker container,从而保证不会丢失任何数据,实现了 Jenkins 集群的高可用性。

  • 自动伸缩

    由于每一次运行 Job 时,Jenkins Master 都会动态创建一个 Jenkins slave,Job 完成之后 Slave 会被注销所在的 Docker container 也会被自动删除,所占用的资源就会被自动释放。

    也就是说当同时请求的 Job 数量越多,生成的 Slave container 就会越多,占用的资源也就越多,反之亦然,而且这种动态伸缩是完全不需要人为干预的。

  • 完全隔离

    由于每一次运行 Job 都是在一个全新的 Jenkins slave 中运行,避免了同时运行的 Job 与 Job 之间发生冲突的可能性。

  • 容易维护

    对比之前每一个 Jenkins Slave 是一台固定 VM 的做法,以这种方式搭建的集群维护的不再是固定的 VM 而是创建动态 Slave 所需要的 Docker image。

    我们可以很容易通过 Docker File 来 build 适用于我们自已的 Docker image 并将它们存储在私有的 Docker registry 中,非常易于维护。

  • 容易扩展

    当我们发现 Jenkins 的 Queue 中存在大量等待执行的 Job 是因为 kubernetes 集群的资源不足时,能够很容易的初始化一个 kubernetes node 并将它添加到集群中来,实现横向扩展非常的方便。

简单实现

下面我们来完成一个简单的实现:

  1. 首先你需要安装一个 Kubernetes cluster,请参考https://kubernetes.io/docs/setup/
    Kubernetes 安装完成之后,首先需要用下面两条命令和文件部署一个 Jenkins 到 Kubernetes 集群:

    tip: 标红的部分很重要,开放 8080 端口是用来访问 Jenkins web portal 用的;而动态创建的 Jenkins slave 会默认通过 50000(可修改)端口与 master 建立连接。

    检查 jenkins 安装运行情况:

  2. 查看 Jenkins log,用管理员密码登录 Jenkins 并安装 Kubernetes plugin。

  3. 配置 Kubernetes cloud

    Manage Jenkins/Configure System/ Add a new cloud/ Kubernetes:



    Add pod template/Kubernetes Pod Template:


    点击 Save 之后大功告成。


牛刀小试

下面我们来测试一下这个动态注册 Slave的Jenkins 集群是否工作正常,首先登录 Jenkins 创建一个简单的 free style job,指定这个Job只能在Label为“jnlp”的 agent 上运行。

点击 build now,你会发现奇迹发生了,原来没有注册任何 Slave 的 Jenkins 动态的创建一个 Slave 并注册到 Master 上,然后运行相应的 Job,当 Job 运行结束后这个 Slave 被自动清除了。

PS:这只是一个简单的实现,在企业的实践中,我们需要不同的build环境,需要我们基于 jenkinsci/jnlp-slave 这个 Image 构建我们自己的 Jenkins slave Image 并保存到私有的 Registry 中,相对应的 Kubernetes 需要从私有 Registry 拉取 Image。

参考文献:

  • https://kubernetes.io/

  • https://www.docker.com/

  • https://github.com/jenkinsci/kubernetes-plugin

  • https://kumorilabs.com/blog/k8s-6-integrating-jenkins-kubernetes/

近期热文

连公式都没看懂?!学渣谨碰这个「神经网络」

当我说要做大数据工程师时他们都笑我,直到三个月后……

当年校招时,我就死在了这个问题上...

如何成为一个 IT 界的女装大佬?

技术学到多厉害,才能顺利进入BAT?

福利

阅读原文」了解更多知识


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/LSBtChPsda
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/3907
 
712 次点击