用开源软件打造企业级 DevOps 工作流(三):持续集成

2,640 阅读16分钟

前言

本文为《用开源软件打造企业级 DevOps 工作流》系列的第三篇文章。接着上一篇版本控制系统篇,本篇文章主要讲介绍 DevOps 工作流中另一个核心模块 持续集成(CI),这可以说是 DevOps 中的重中之重,因为这涉及到自动化管理项目的部署过程。有了持续集成,我们就可以从手动部署中解脱出来,把时间用在更重要的事情上,例如代码重构、回归测试、架构设计等等。

在本篇文章中,我们将介绍持续集成的基本概念,为什么用持续集成,以及如何利用开源软件 Jenkins 来实现持续集成。

持续集成简介

本系列文章的第一篇概述中我们提到,持续集成可以类比于工厂中的流水线操作。例如生产一辆汽车,一切从裸车架开始,车架用吊钩送上传送带,经过装配、打磨、测试等一系列复杂而紧凑的统一化流程,最终产出一辆功能齐全、质量卓越的完整汽车。

持续集成类似软件开发中的流水线,只是持续集成中的流水线通常来说是自动化的,也就是说不需要任何人为干预,一个软件程序就可以从源码开始,经过一系列的构建步骤,最终形成成熟的产品。什么时候启动这个流水线呢?一般来说,是代码变更的时候。其实,这里所说的流水线还包括了部署的过程,一般说持续集成通常会提到 持续部署(CD)。严格意义上来说这是两个概念:持续集成(CI)是指不断将代码不断同步到主干,持续部署是指代码经过评审后自动部署到生产环境。为了方便起见,我们将这两个概念合二为一,统称为 CI/CD

例如上图这个持续集成流水线,当持续集成工具检测到源代码有变更的时候,会启动这个流水线:

  1. 首先获取源代码;
  2. 并行构建前后端应用;
  3. 构建 Docker 镜像;
  4. 单元测试;
  5. 完成部署。

经历了这几个步骤之后,一次集成过程就算完成了。整个流程不需要人为干预,唯一需要开发者做的,就是提交一次代码更新代码仓库,持续集成工具会自动将后面的事情(包括部署)一步步帮你做完,全程自动化。

为什么用 CI/CD

其实我们可以不用 CI/CD 来完成从编码到部署的过程。那么,为什么我们要用 CI/CD 呢?首先我们看看下面一个流程图。

这是一个从码代码到最终部署交付的流程例子:我们花数小时码代码(Coding),不到 1 分钟提交代码(Commit),然后构建(Build)这个应用花 1 分钟到 1 小时(根据应用复杂程度),接着花数分钟部署(Deploy)这个应用,最后测试(Test)。除开代码和提交的时间,后面的构建+部署时间最少也得花几分钟,而测试可能会花更久。

我们要知道的是,构建和部署一般来说是重复性的工作,例如运行一次 npm run build:prod 打包前端代码,或者将打包好后的前端静态文件拷贝到目标地址。如果是人来操作这些流程,非常浪费时间和精力。虽然构建部署一次可能花不了多少时间,但考虑到企业中通常是多人协同工作,每个人都会提交代码,而且很可能是多次提交,这样需要执行的次数就是远不止一次或几次了。你简单做一做乘法,就知道会有多少人工时间成本会花费在上面,而人工成本目前来说是越来越贵的。因此,使用持续集成将降低时间成本(Time Expense)

另外,人工来处理构建部署等流程容易出错。人无完人,即使是富有经验的架构师上线部署也可能会出现操作失误导致系统上线失败。在作者的职业生涯中,发现有很多因为人的原因部署失败的情况,例如忘记更改环境变量、数据库连接串配置错误、执行命令输入错误等等。而这些都可以通过自动化的流程来避免,因为机器是很死板的,一旦设置好,就会原封不动的按规定执行,不会出现操作失误(当然排除诸如网络原因等导致的错误)。因此,使用持续集成还可以降低人为错误(Human Error)

上面这个流程图中红色部分(编码和提交)是无法自动化的,需要人工操作;而绿色部分(构建和部署)是可以自动化的,也是需要尽可能全部自动化的;黄色部分(测试)是半人工半自动化的,因为我们可以通过 单元测试(Unit Test) 来自动化很多测试工作,但一些复杂的测试用例(例如页面的排版是否符合设计预期)则需要人工来完成,因此测试部分是半自动化的。这里多提一下单元测试,固然单元测试是非常有助于提升代码的工程质量,但是编写单元测试却相当耗费时间,差不多跟写功能的时间差不多,因此会将整个项目时间加倍,所以需要在时间与质量之间权衡是否需要采用单元测试。

开源工具 Jenkins

要实现 CI/CD,我们需要一些工具。我们推荐的工具是 Jenkins,这也是企业环境中用的比较多的持续集成开源软件。下面我们将详细介绍一下 Jenkins,包括基本介绍、安装、基本使用以及如何实践应用到 DevOps 工作流中。

Jenkins 简介

可能你对 Jenkins 本身并不陌生,这是因为 Jenkins 从 2011 年开始就已经存在,是最早的也是最常用的选择。它出自 Sun 公司的一个工程师的业余项目,不断改进优化后逐渐壮大成为最热门的持续集成工具(听起来是不是跟 JavaScript 的经历差不多?)。

关于 Jenkins 的介绍,我们主要引用其官网上的描述。以下是官方的关于 Jenkins 的一句话描述:

Jenkins是开源CI&CD软件领导者, 提供超过1000个插件来支持构建、部署、自动化, 满足任何项目的需要。

其中,CI 指代持续集成,CD 指代持续交付。

以下是其主要特点:

  • 持续集成和持续交付: 作为一个可扩展的自动化服务器,Jenkins 可以用作简单的 CI 服务器,或者变成任何项目的持续交付中心;
  • 简易安装: Jenkins 是一个基于 Java 的独立程序,可以立即运行,包含 Windows、Mac OS X 和其他类 Unix 操作系统;
  • 配置简单: Jenkins 可以通过其网页界面轻松设置和配置,其中包括即时错误检查和内置帮助;
  • 插件: 通过更新中心中的 1000 多个插件,Jenkins 集成了持续集成和持续交付工具链中几乎所有的工具;
  • 扩展: Jenkins 可以通过其插件架构进行扩展,从而为 Jenkins 可以做的事提供几乎无限的可能性;
  • 分布式: Jenkins 可以轻松地在多台机器上分配工作,帮助更快速地跨多个平台推动构建、测试和部署。

从介绍总结来看,Jenkins 的核心功能是持续集成和持续交付,另外还有便捷性(安装简单)和扩展性(插件、分布式)。

安装 Jenkins

跟之前的文章里安装 GitLab 一样,我们同样是用 Docker 来安装 Jenkins。在确保您已安装 Docker 、并且能顺利运行 docker ps 的前提下,执行以下命令。

docker run 
  -u root \  # 以root用户运行
  --restart always \  # 永远设置为重启状态,开机时也会启动
  -d \  # daemon方式后台运行
  -p 8080:8080 -p 50000:50000 \  # 将web端口映射出来,8080为web界面端口
  --name jenkins \  # 容器名称
	-v jenkins-data:/var/jenkins_home \  # 持久化数据
	-v /var/run/docker.sock:/var/run/docker.sock \  # Docker守护进程默认监听的Unix域套接字
	-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \  # 调整时区为中国时区
	jenkinsci/blueocean  # Jenkins镜像

以上命令已经有注释了,读者可以根据需要更改其中的一些配置,例如映射端口,8080 可以是 9090、80 等等。执行上述命令后,Jenkins 需要一定初始化时间,等待片刻后在浏览器中输入 http://<宿主机ip>:8080,就可以看到 Jenkins 的初始界面(如下图),这是 Jenkins 在加载必要数据和第三方库,大概需要几分钟。

初始化完毕后,浏览器会重新加载,我们会看到如下配置界面。

这个是告知您,需要输入初始密码,而这个密码在 Jenkins 容器里的 /var/jenkins_home/secrets/initialAdminPassword 文件里。输入以下命令来查看初始密码。

docker exec -it jenkins bash  # 进入容器命令行
cat /var/jenkins_home/secrets/initialAdminPassword  # 查看初始密码

将查看到的初始密码输入到上述界面中,点击 Continue 进入下一步。加载片刻后,您将看到如下选择界面。

选择默认的 Install suggested plugins 安装建议的插件。然后 Jenkins 会安装一些常用的插件,例如连接 Git 的 Git plugin 、邮件插件、管道插件等等。安装进度如下图。请耐心等待,这个过程可能花很长的时间!

安装完毕后,输入第一个管理员账户,点击 Save and Continue 进入到下一步,然后点击 Save and Finish,最后选择 Restart,完成重启。

重启完毕后,您就可以注册登陆了,请选择刚才的管理员账户登陆。

使用 Jenkins

你可能最开始使用 Jenkins 的时候会发现它朴素的用户界面一点也不酷炫,总体显得比较老旧,不像是现代软件的风格,不过我们有 Open Blue Ocean 加强版界面,非常扁平化和现代化,不习惯 Jenkins 默认界面的可以切换过去。下图是进入 Jenkins 的主页界面,可以看到左侧有一些操作选项以及构建队列和执行器状态,右侧是自动化项目的列表。

下图是 Open Blue Ocean 的主页界面,功能上跟默认界面差不多,只是排版和风格有所不同。

其实,界面这些都不是特别重要的问题,很快你会被它朴实但强大的核心功能(持续集成)所折服。在使用这些强大的功能之前,我们需要做一些配置。

配置节点

首先看下图,作者有两台物理机,IP 地址分别是 192.168.0.2 和 192.168.0.3 (假设),Jenkins 是用 Docker 起动的容器,在 192.168.0.2 这台宿主机里,而我们想在宿主机(192.168.0.2)上以及另一台物理机(192.168.0.3)上构建应用,因此需要分别需要将这两台机器注册到 Jenkins 里。

这里简单介绍一下 Jenkins 的原理,Jenkins 构建项目是通过 Jenkins Executor 这个 jar 包来实现构建和部署的。Jenkins 服务的本地机器叫做 主机(master) ,如果告诉主机其他机器的 IP 地址和登陆信息,Jenkins 就可以通过 SSH 的方式在该机器上将 Jenkins Executor 拷贝过去并运行,再而与其交互通信,通过 Jenkins Executor 完成远程构建工作。

下面我们介绍如何配置节点。

  1. 在主页中点击 Manage Jenkins (确保您有管理员权限),进入管理页面;
  2. 然后点击 Manage Nodes ,进入到管理节点页面;
  3. 这里您应该能看到 master 节点,这时候你需要添加节点,点击左侧的 New Node
  4. 输入节点名称(Node Name),点击 OK,将看到很多配置信息;
  5. 完成配置(配置信息如下图),点击 Save 保存设置。

然后,您应该就可以看到下图节点列表了,左侧 S 列代表的是节点状态(Status),如果节点在线,会显示为计算机的符号,如果离线,则会显示一个计算机加红叉的符号。你可以点击去看它的日志(Log),如果一直连不上,需要查看日志排查错误。

节点配置好了,您就可以在这些节点上运行构建程序了。如果您有多个机器需要部署,只要一次性手动将这些信息注册进 Jenkins,就可以一劳永逸的在各台机器自动化部署应用程序了。是不是很酷?

创建项目

现在,我们介绍在 Jenkins 中如何创建一个自动化项目。其实过程很简单,只是配置方面稍微复杂一些。

  1. 在主页点击 New Item 创建新项目;
  2. 输入项目名称,然后选择项目类别,强烈建议选择 Multibranch Pipeline (多分支管道),因为这一个类别是最方便配置和最容易集成之前介绍的 GitLab 版本控制系统的类别,然后点击 OK;
  3. 配置各种输入,包括展示名称、项目来源(来自VCS,例如Git/SVN)、构建周期(选择每分钟)等等。

点击 Save 保存项目,就创建好了一个项目。

读者可能会问:等等,构建过程跑哪儿去了?别着急,我们接下来会将如何配置构建过程,也就是用一个配置文件来定义构建过程,而这个配置文件叫 Jenkinsfile。

Jenkinsfile

读者可能会问,Jenkinsfile 是不是与 Dockerfile 有异曲同工之妙?没错!这两种配置文件都属于 Infrastructure as Code(架构即代码) ,仅仅用代码就可以配置出计算机和网络架构,非常易于管理和扩展。下面我们会详细介绍 Jenkinsfile。

Jenkinsfile 是 Jenkins 2 发布的一个实用功能,这个功能让用代码管理构建过程变成可能。它使用 Groovy 语法,很容易配置不同的构建阶段。下面是一个 Jenkinsfile 的例子。

pipeline {
  agent {
      node {
          label 'docker'  // 要执行的节点名称
      }
  }

  stages {
    	// 配置阶段
      stage('Configure') {
          steps {
              echo 'Configuring'
              script {
                // 根据 Git 分值名称选择不同的 docker-compose 文件
                switch (env.GIT_BRANCH) {
                  case 'develop':
                  	// 开发分支的 docker-compose 文件
                    env.DOCKER_COMPOSE_FILENAME = 'docker-compose-dev.yaml'
                    break
                  case 'master':
                  	// 主干的 docker-compose 文件
                    env.DOCKER_COMPOSE_FILENAME = 'docker-compose-prod.yaml'
                    break
                  default:
                    echo "Unknown branch ${env.GIT_BRANCH}"
                    exit 1
                }
              }
          }
      }
    	// 构建阶段
      stage('Build') {
          steps {
              echo 'Building'
            	// 用 Docker 不同分支的构建镜像(用 GIT_BRANCH 环境变量区分)
              sh """
                docker build -t awesome-image:${ENV:GIT_BRANCH} -f Dockerfile-local .
              """
          }
      }
    	// 测试阶段
      stage('Test') {
          steps {
              echo 'Testing'
          }
      }
    	// 部署阶段
      stage('Deploy') {
          steps {
            	// 用 docker-compose 重启镜像服务
              sh """
                docker-compose -f ${ENV:DOCKER_COMPOSE_FILENAME} down | true
                docker-compose -f ${ENV:DOCKER_COMPOSE_FILENAME} up -d
              """
          }
      }
    	// 清理阶段
      stage ('Clean-up')
      {
          steps
          {
            	// 清理不用的 Docker 镜像
              sh '''
                  docker rmi $(docker images -f "dangling=true" -q) || true
              '''
          }
      }
  }
}

这里,我们用了 Docker 来完成构建工作,Docker Compose 来完成部署工作。后面的文章会详细介绍 Docker 在 DevOps 中的应用。

关于 Jenkinsfile 的内容以及 API 还很多,本文不会全部涵盖。需要的朋友可以参考官方文档 jenkins.io/zh/doc/book…

配置好了 Jenkinsfile,将它放在 Git 项目根目录下,当每次有代码提交的时候,Jenkins 就会拉最新代码,然后开始构建过程,如下图。

查看构建进度

用 Pipeline 构建的好处在于可以直观的看到构建的过程,包括各阶段花了多少时间等信息。下图是某个项目构建过程的概览。可以看到,每一次构建都是由 VCS 代码提交触发的。这样的话,只要我们在某个分支上提交了代码,Jenkins 就可以自动帮我们构建、部署了。如果构建过程中有报错,Jenkins 中会有提示信息。

Jenkins 与其他 CI/CD 工具

Jenkins 是一个强大的工具,不过市面上还有其他很多优秀的 CI/CD 工具。如下图,我们可以看到整个 CI/CD 市场的市场份额。

可以看到,除了 Jenkins,还有很多人用 Circle CI、Travis CI、GitLab CI 等工具。其实 Jenkins 不是唯一选择,开发者运维者们还有很多其他的选择,都可以分别尝试。例如,GitLab CI,其实用了 GitLab 管理代码的话,可以用 GitLab CI 来做持续集成。为什么用 Jenkins 而不是 GitLab CI 呢,这其实跟作者的工作经历有关,作者对 GitLab CI 并不是很熟悉,有机会一定会尝试一下。当然,客观来说,Jenkins 灵活性更强,更轻量级,加密性好,而且是跨平台的,但插件质量参差不齐,导致有时容易出问题(所以我们推荐使用官方插件)。因此,对于用 GitLab 做 VCS 且项目通常复杂性不高的用户来说,GitLab CI 是个更好的选择。

为什么 Jenkins 这么出名,是因为它是最早的开源 CI/CD 工具,存在市场比较久而已,读者有权利选择任何合适的工具。

下图是各个 CI/CD 工具的市场竞争矩阵图。

可以看到,Jenkins 的地位其实不是属于领先地位,后来居上的 Circle CI 的市场满意度和市场份额都领先于其他工具,是一个非常有潜力的工具,作者也会找时间尝试一下,当然也欢迎读者去尝试。

总结

本文介绍了持续集成的基本概念,以及使用 CI/CD 的原因,此外还详细介绍了开源 CI/CD 工具 Jenkins,包括 Jenkins 的简单介绍、如何安装、如何配置、创建项目以及如何使用 Jenkinsfile 和查看构建进度。相信仔细阅读了本篇文章的读者,应该会基本了解持续集成的概念和框架,以及在企业中推广使用持续集成的重要性。此外我们还了解了 CI/CD 工具的市场情况,并知道 Jenkins 并不是唯一的选择,市面上还可以选择很多其他优秀的工具,例如后起之秀 Circle CI。我们选合适的 CI/CD 工具是为了满足企业的 DevOps 需求,而不应该局限于工具的名声和自己所掌握的知识,只要做好了前期调研和需求分析,我相信是可以实践好 DevOps 中的持续集成功能的。最后提一点,就是工具并不是全部,如果没有良好定义的流程与技能储备,DevOps 是实践不通的,应用再优秀的 CI/CD 工具也会失去意义。因此,我们需要经常充实自己,加强 DevOps 的知识了解,扩充自己的能力范围,这样才能打造出最佳的 DevOps 工作流。

我们之前介绍了 DevOps 概述、VCS,现在又介绍 CI/CD,接下来我们会讲容器化,会详细介绍 Docker,敬请期待后续文章。