美文网首页BNU北师大校友会
深入理解 OpenStack 社区 CI/CD

深入理解 OpenStack 社区 CI/CD

作者: 燕京博士 | 来源:发表于2018-03-22 21:06 被阅读1263次

    在介绍 OpenStack 社区 CI/CD 平台之前,有必要先直观认识下社区日常运营的工作量,这将有助于我们理解为什么社区要建设如此丰富、复杂的基础设施服务。

    社区 CI 规模

    • 500 > Git 项目仓库数
    • 5000 > Jenkins Job 项目任务数
    • 10 > Jenkins Master 节点数
    • 1000 > Jenkins Slave 节点数
    • 24000 > 每天执行的任务数
    • 5000 > 社区开发者人数
    • 1000 > 虚拟机运行数量

    社区 CI 挑战

    • 项目多、Git 仓库多、分支多(master、stable)。
    • 有各种项目相关的 Job 任务。
    • 任务配置麻烦(多种后端、多种云环境、多个部署节点等)。
    • CI Pipeline(流水线)复杂且繁多(如检查、测试、发布和周期任务等)。

    针对所面临的规模和挑战,OpenStack 社区构筑了一套完善的 CI/CD 平台,被统称为OpenStack Infra(OpenStack 基础设施平台)。OpenStack 基础设施平台主要包括:

    • Storyboard——任务跟踪系统,用于管理那些彼此之间紧密联系的系统。在 OpenStack这种内部关系紧密的系统中,一个功能或一个 Bug 都会至少影响两个项目,所以这样的问题需要在项目间统一跟踪。
    • Gerrit——代码评审系统,包括 Git 等子系统。
    • Zuul——运行 Jenkins 任务的组织系统(通过测试系统与代码评审系统结合,反馈提交的代码集成测试状况等),包括触发构建等子系统。
    • Jenkins——持续集成系统,包括 Jenkins 任务执行节点、Gearman 服务等子系统。
    • GitHub——代码仓库管理系统。
    • DevStack——一个从源码库安装 OpenStack 环境的工具。
    • Nodepool——负责管理 OpenStack 云平台上虚拟机的生命周期,构建 VM 资源池以供DevStack 安装使用。
    • ELK(Elasticsearch、Logstash 和 Kibana)——社区日志管理系统。
    • Launchpad——用于跟踪 OpenStack 相关事项的系统,包括 Bugs、Releases、Blueprints等子系统。
    • IRC——社区开发人员在线交流工具。

    其中 Zuul、Jenkins-job-builder、Gearman Plugin、DevStack、Nodepool 等都是由 OpenStack 基础设施服务团队开发维护的。社区 CI/CD 系统运行工作流程如下图所示。

    image

    这里简要梳理下当社区开发人员向代码评审系统提交代码之后,代码会经过哪些环节,系统会执行哪些任务,才会最终被合并到 GitHub 代码仓库系统中。整体流程如下图所示。

    image

    首先是 PEP 8 代码风格测试。因为社区开发者人数众多,所以保证代码风格统一是非常重要的,需要确保大家都使用同样的编码方式和风格等。

    然后是单元测试。仅仅测试被变更的子项目,不考虑跟其他子模块交互的情况。社区会针对不同的平台和软件版本进行测试,包括 Python 2.x 和 3.x,在不同操作系统上运行不同的软件版本。

    最后是集成测试。社区在由 HP、Rackspace 等提供的 IaaS 云虚拟机上,使用 DevStack安装所有的组件,然后在这个单节点(all-in-one 环境)上运行不同的模板。不同的模板对不同的模块进行不同的配置,比如使用不同的数据库、不同的消息队列、不同的存储类型,不过基本上只测试那些常用的如 MySQL、PostgreSQL、RabbitMQ 等,当然社区也在考虑引入 ZeroMQ 的测试。

    集成测试所使用的 VM 一般配置为8GB内存,系统是 Ubuntu ,然后让 DevStack 在该VM 上安装 OpenStack。Nodepool 用来管理 VM,通过缓存来预备这些机器。同时将 DevStack 所需要的依赖软件包等都预先下载到本地,这样测试本身就可以离线运行了。

    测试执行完之后,再销毁这些 VM。实际创建的 VM 数量要比运行成功的测试数量多,因为 Zuul 的随机机制,有时候当测试跑到一半时才发现还需要一些其他东西,于是测试执行不下去了,此时会删除该 VM,启动一个新的。大致的比例是,如果一天跑10000个任务,那么启动的 VM 数量差不多在100000量级,即1:10的比例。

    无疑,社区在测试方面做出了严格的限制,即只有写好了单元测试的代码变更提交才能够被接受。对于社区而言,未经测试的变更就是有问题的和有风险的。因为现在 OpenStack 项目发展很快,不断催生出新的组件,一个小错误就可能会影响整个系统的运行。

    为了解决这些问题,社区使用了 Test Repository 框架,让大多数单元测试在这个框架中可以并行处理,并快速反馈测试结果。社区 QA 团队自行开发的 Zuul 系统,一方面可以并行测试一系列提交的代码,同时又保持它们的测试顺序不变。

    OpenStack Infra资料请参考这里

    持续集成系统(Jenkins)

    OpenStack 社区使用开源的 Jenkins 系统来负责持续集成。在应用中,Jenkins 使用 Zuul和 Gearman 进行管理。其具体的任务配置管理,使用 Jenkins-job-builder 基于 YAML 文件格式来定义 Job 内容,采用命令行创建和管理 Jenkins Job,Job 文件采用 Git 版本控制工具进行管理。OpenStack 的使用方式为:

    • 使用 YAML 文件编写 Job 内容。
    • 使用 Gerrit 管理 Job 定义的创建和变更。
    • 使用 Jenkins-job-builder 定义创建多个 Job。
    • 使用 Puppet 推送 Jenkins-job-builder 进行部署、更新 Jenkins 的 Job。

    Pipeline

    Pipeline 的字面意思就是流水线,是很好用的一个 Jenkins 插件,将很多步骤按顺序排列好,结束一个后执行下一个。在真实的工作环境中有很多 Job,比如先编译,然后执行代码静态检查、单元测试,最后部署服务、重启服务、进行 UI 测试等。我们需要对这些 Job 进行一些设置,将它们的上下游关系配置好。Pipeline 提供了图形界面,可以在界面上形象地看到整个构建任务的执行流程和完成度。示例如下。

    Job模板:

    - job-template:
        name: 'gate-{name}-docs'
        builders:
          - shell: 'git checkout {branch_name}'
    
    

    Jenkins-job-builder项目定义:

    - project:
        name: project-name
        branch_name: new_branch
        jobs:
          - gate-{name}-docs
    
    

    Job分组管理:

    - job-group:
        name: '{name}-tests'
        jobs:
          - '{name}-unit-tests'
          - '{name}-perf-tests'
    
    

    Job定义:

    - job:
        name: foo-test
        project-type: freestyle
        builders:
          - make-test
        publishers:
          - archive
    
    

    可以在 Job 文件中增加模块,比如 Builder、Publisher 等,使用宏(即批量处理,就是将一些命令组织在一起,作为一个单独命令完成某个特定任务)来定义 Jenkins 需要执行的任务。

    - builder:
      name: make-test
      builders:
        - shell: 'make test'
    
    

    如何使用 Jenkins-job-builder

    Jenkins-job-builder(简称 JJB)是用来创建 Jenkins 任务的工具。为了简化配置大量 Jenkins任务的工作量,OpenStack 采用更容易阅读的基于 YAML 或 JSON 格式的文件来编辑任务,然后使用 JJB 将 YAML 或 JSON 格式的配置转化为可以被 Jenkins 理解的 XML 格式的任务配置文件,并更新到 Jenkins 中。

    实际上,Jenkins创建的任务是以 XML 文件的格式保存在 $JENKINS_HOME/jobs/ [job-name]/config.xml 中的,JJB 能够将 YAML 转化为 XML 文件(借助 Jenkins 提供的命令接口),并更新到 Jenkins 中。

    这里,以测试配置、更新任务以及删除任务为例,阐述Jenkins-job-builder方法的使用。

    (1)首先,安装Jenkins-job-builder。

    # pip install jenkins-job-builder
    
    

    (2)测试配置任务。

    当任务配置的 YAML 文件编写完成之后,可以通过“jenkins-jobs test”测试配置格式和内容是否正确。

    # jenkins-jobs test path/to/myjob.yaml  
    
    

    若测试通过,将输出 XML 格式的任务配置;如果想将测试的 XML 格式的任务配置输出到文件,则可以添加“-o”参数。

    # jenkins-jobs test -o output path/to/myjob.yaml  
    
    

    JJB 将在与 myjob.yaml 相同的目录下新建一个名为“output”的文件夹,并在其内创建一个用 YAML 中定义的任务名命名的文件,就是 XML 格式的任务配置文件。

    (3)更新任务。

    测试通过后,可以使用“jenkins-jobs update”命令将 YAML 文件定义的任务更新到 Jenkins中。

    # jenkins-jobs [--conf jenkins_jobs.ini] update path/to/job1.yaml  
    
    

    Update 命令需要指定保存有 Jenkins 服务器信息的配置文件,用“--conf”来显式指定该配置文件,如果不显式指定,则默认读取“/etc/jenkinsjobs/jenkinsjobs.ini”文件。该配置文件保存有 Jenkins 的 URL 和登录信息,内容如下:

    [jenkins]  
    user=jenkins  
    password=password  
    url=http://localhost:8081/jenkins  
    
    

    从而 JJB 可以将任务更新到 ini 配置文件指定的Jenkins系统中。路径也可以是目录,那么就是更新指定目录下所有的YAML、YML和JSON文件,多个路径用冒号隔开。

    (4)删除任务。

    删除任务使用如下命令:

    # jenkins-jobs [--conf jenkins_jobs.ini] delete job1
    
    

    其中 job1 为任务名称。

    OpenStack 如何使用 Jenkins-job-builder

    在 OpenStack 环境中,可以在 project-config:jenkins/jobs/ 目录下找到 YAML 脚本文件。在该目录下有4种配置文件。

    (1)defaults.yaml 文件

    该文件为全局默认配置文件,是所有 Jenkins 任务的全局属性定义,即 name 为“global”的属性,比如默认的描述、任务是并行执行的、任务超时时间为30分钟等。

    (2)macros.yaml文件

    该文件保存了所有的宏定义,包括一些比较通用的 builders 或 publishers,供各任务调用,如下所示。

    - builder:  
          name: git-prep  
          builders:  
          - shell: "/slave_scripts/git-prep.sh"  
    
      - publisher:  
          name: console-log  
          publishers:  
            - scp:  
                site: 'scp-server'  
                files:  
                  - target: 'logs/$JOB_NAME/$BUILD_NUMBER'  
                    copy-console: true  
                    copy-after-failure: true  
    
    

    (3)<project-name>.yaml 文件

    该文件为各个项目的配置脚本,任务和任务模板定义在各个项目的脚本文件中。每个项目都有一个以项目名命名的 <project-name>.yaml 文件,里面的内容基本就是“- job:”和“- job-template:”,还有“- job-groups:”。

    (4)projects.yaml 文件

    该文件包含了所有项目的 Job 配置,即所有项目均集中配置在 projects.yaml 文件中,各个项目以字母顺序排列,各项目的 job 属性来自于任务和任务模板的定义。

    针对 Jenkins-job-builder 的单独介绍,请访问这里

    Jenkins-job-builde 配置实例,请查看这里

    集群任务分发系统(Gearman)

    Gearman 是一个分布式队列系统,负责将合适的任务分发到多台机器上,以便快速分解完成大型任务。Gearman 架构示意图如下图所示。

    image

    Gearman通常由三部分组成,即 Client、Worker 和 Job Server(任务服务器)。由 Worker执行 Client 发来的 Job,再通过 Job Server 返回给 Client。Gearman 提供了 Client、Worker 的 API,利用这些 API 与 Job Server 通信。

    Gearman 接收到来自 Jenkins 的分发任务后,将 Job 发送到具体的工作节点上。Gearman和 Jenkins 系统集成示意图如下图所示。流程如下:

    • 首先,Jenkins Master 节点通过 Gearman Plugin 和 Gearman Server 系统建立交互。
    • 其次,Zuul 系统将构建请求提交到 Gearman Server 系统。
    • 最后,Gearman 系统再将 Job 任务分发到 Jenkins Slave 工作节点上执行。
    image

    任务组织系统(Zuul)

    简单而言,Zuul 是一个面向多项目配置与自动化检测代码测试是否通过的系统。它同时与Gerrit、Jenkins 进行消息通信,使用 layout.yaml 文件进行灵活配置,适合多种项目的自动化操作,可以并行执行一系列变更的测试。

    Zuul 有两个主要组件分别为 scheduler 和 merger。Zuul 保证合并进入源代码库的代码变更都是通过测试的。比如通过监测 Gerrit 的事件以触发相应的 Pipeline 及其对应的 Job,从代码的提交,到社区人员评审的 -1/+1、-2/+2 等事件流触发相应操作。

    如下是社区的 OpenStack Nova 计算项目的一个 Zuul 配置文件。

    projects:
      - name: OpenStack/nova
        check: 
          - gate-nova-pep8
          - gate-nova-docs
          - gate-nova-Python27
          - gate-Tempest-devstack-vm-full
        gate: 
          - gate-nova-pep8
          - gate-nova-docs
          - gate-nova-Python27
          - gate-Tempest-devstack-vm-full
        experimental: 
          - gate-devstack-vm-cells 
        slient: 
          - gate-Tempest-devstack-vm-large-ops
        post: 
          - nova-branch-tarball
          - nova-coverage
          - nova-docs
          - nova-upstream-translation-update
        pre-release:
          - nova-tarball     
        release:
          - nova-tarball  
          - nova-docs
        periodic:
          - nova-propose-translation-update
          - periodic-nova-Python27-stable-folsom
          - periodic-nova-Python27-stable-grizzly
    
    

    这里有一个特性是预测执行,即预测并提前执行可能需要的任务,以便加速整个处理过程。这个特性在 Pipeline 中得到了广泛使用,以提高整体效率。比如 Nodepool 会预先在 OpenStack 云环境中创建好相应的虚拟机;DevStack 会事先将相应的依赖包和其他项目源码下载下来,以便快速安装单节点的 OpenStack 测试环境。

    一个 Check Pipeline 文件内容如下:

    pipelines:
      - name: check
        manager: IndependentPiplelineManager
        precedence: low
        trigger:
            Gerrit:
            - event: patchset-created
        success:
            Gerrit:
            verified: 1
        failure:
            Gerrit:
            verified: -1
    
    

    一个 Gate Pipeline 文件内容如下:

    pipelines:
      - name: name
        manager: DependentPiplelineManager
        precedence: high
        trigger:
          Gerrit:
            - event: comment-added
              approval:
                - approved: 1
        start:
          Gerrit:
            verified: 0         
        success:
          Gerrit:
            verified: 2
            submit: true
        failure:
          Gerrit:
            verified: -2
    
    

    一个 Zuul 任务队列的文件内容如下:

    projects:
      - name: OpenStack/nova
        gate: 
          - gate-nova-Python27
          - gate-Tempest-devstack-vm
      - name: OpenStack/glance
        gate:
          - gate-glance-Python27
          - gate-Tempest-devstack-vm
    
    

    什么是 Gate Job

    在回答该问题之前,我们需要先认识下 OpenStack 代码的提交与合并过程。

    首先,开发人员克隆相关的 OpenStack 项目(如 Keystone、Nova 等),做了代码(也包括 rst 文档)修改后,使用 git 命令提交到代码评审系统中。

    然后,Jenkins 做自动化测试。Jenkins 可能会运行一个或多个工作任务来执行不同类型的测试,以验证这些提交的 Patch(补丁)是否安全、正确。这些工作便被称为“Gate Job”。

    这里 Gate 可以被形象地理解为“看门人”。社区开发人员提交的 Patch 是否得到批准,是根据 Jenkins Gate Job 执行的测试结果来确定的。然后基于测试结果,将 -1/+1 的投票反馈到代码评审系统(Gerrit)中。

    在这里,我们可以找到 OpenStack 每个项目的 Gate Job 配置文件,这些配置文件均采用YAML 文件格式编写。这里是链接地址

    向一个特定项目提交 Patch 后,会执行哪些任务

    如果想知道向社区提交代码后,Jenkins 会执行哪些任务,一个直接的办法是在 Gerrit 系统中查看。还有一个办法则是查看项目的配置文件:project-config/zuul/layout.yaml。通过该链接地址,可以找到 OpenStack 项目的模板文件内容。

    如何添加一个新的 Job

    首先需要选择哪些项目属于这个 Job。在 projects.yaml 文件中可以看到所有项目的 Job。点击这里获得链接地址:。

    比如将一个名称为“abregman-new-job”的 Job 添加到“devstack-jobs”项目下。

    jobs:

      - gate-Tempest-dsvm-{name}{job-suffix}:
          pipeline: gate
          name: abregman-new-job
          node: devstack-centos7
          job-suffix: ''
    
    

    文件中的“node:devstack-centos7”用于告诉 Jenkins 在哪些节点上运行任务。devstack-centos7只是一个节点的标签。所以,当 Job 运行时,它将寻找标签为“devstack-centos7”的节点。

    假设每次提交 Patch 时,都要运行“openstack-dev/devstack”这个 Job,则可以这样编写:

    - name: openstack-dev/devstack
      gate:
        - gate-Tempest-abregman-new-job
    
    

    现在,我们添加了一个 Job(gate-Tempest-abregman-new-job)到“openstack-dev/devstack”项目中,并在 Gate Pipeline Job 下。这样,当上述代码内容提交并合并后,就可以提交 Patch 到 openstack-dev/devstack 项目中,来观察它们的 Job 是如何运行的。一个全局的社区 Zuul Job 运行状态如下图所示。

    image

    代码评审系统(Gerrit)

    社区采用了开源的 Gerrit 系统用于代码评审,以及细粒度的开发者权限控制等。而第三方系统与 Gerrit 的集成主要通过 Webhooks、event-stream、REST API 等方式交互。

    通常,Gerrit评审系统上的代码有如下几种状态。

    • 代码已提交,正等待审核。
    • 代码处于 Jenkins 的自动化测试验证阶段。
    • 代码处于人工审核阶段。
    • 代码处于接受认可阶段。
    • 代码被合并。

    相应地,Gerrit 的触发类型有如下几种。

    • 合并代码同步复制到GitHub仓库。
    • 修改合并。
    • 添加评论(处于评审状态时)。
    • 引用更新(包括分支、标签等)。

    Gerrit 与 Jenkins 集成

    通常将 Gerrit 评论页面中显示的 Jenkins Job 执行结果和耗时时间,作为代码评审的依据。还有一个 Gerrit 的辅助配置工具 Jeepyb,它是一个帮助简化 Gerrit 管理的工具集,可以很简便地管理项目并和 Launchpad、GitHub 等系统进行集成。在 Jeepyb 中采用 YAML 文件定义项目。

    - project: example/Gerrit
      description: Fork of Gerrit used by Example
    - project: OpenStack/project-name
      acl-config: /home/gerrit2/acls/project-name.config
      upstream: git://github.com/awesumsauce/project-name.git
    
    

    Jeepyb 基于 Gerrit 进行分组,将 Administrators、Registered Users、Anonymous Users 用户组划分在每个项目中,针对每个项目都有一个 ACL(访问控制列表)的配置文件。Jeepyb 描述项目和分值的权限采用 git refs 的语法格式。

    [access "refs/heads/*"]
    label-Code-Review = -2..+2 group project-name-core
    label-Workflow = -1..+1 group project-name-core
    
    [access "refs/heads/proposed/*"]
    label-Code-Review = -2..+2 group project-name-milestone
    label-Workflow = -1..+1 group project-name-milestone
    
    [receive]
    requireChangeId = true
    requireContributorAgreement = tree
    
    [submit]
    mergeContent = true
    
    

    Gerrit 与 Launchpad 集成

    在 Gerrit 系统中,修改 etc/gerrit.config 配置文件,如下所示。

    [commentlink "launchpad"]  
      match = "([Bb]ug\\s+#?)(\\d+)"  
      link = https://bugs.launchpad.net/mahara/+bug/$2
    
    

    OpenStack 社区遵循的原则是人人平等,即每个人提交的代码都需要经过相同的验证测试和其他人员的评审,只有通过了,其代码才能被合并到仓库中。在代码评审系统中评分有0、-1/+1、-2/+2这几项,具体含义如下表所示。

    分值 含义
    0 不计分,即该项分值不会被统计到stackalytics.com中
    -1 对于评审人员而言,这个提交是需要修改的
    +1 对于评审人员而言,这个提交是不错的,但是还需要其他人批准
    -2 严重错误,不能合并到项目中
    +2 对于核心评审者而言,这个提交很好,同意合并代码

    通常给出评分为-1、-2的评审人员,需要进行评论,阐明其为什么是错误的,或者说明应该怎样修改。这些评论可以帮助提交者或其他评审人员重新更新和评审。核心评审者能够给予分值为 -2/+2 的投票并批准提交,一旦有两个核心评审者给了+2分值,那么代码就会被合并到 GitHub 项目中。

    Software Factory 介绍和使用

    Software Factory 是一个由 Red Hat 开源的用于在 OpenStack 云平台和其他环境中部署的CI/CD 项目,该项目集成了 Jenkins、Zuul、Gerrit、Nodepool、Redmine 等系统,支持从源代码提交,到编译打包和部署,再到项目管理等任务。

    Software Factory可以安装在 OpenStack 云平台上,也可以安装在 LXC 容器中及虚拟机上等。如果需要使用 Nodepool 在 Jenkins Slave 节点上管理 Job、测试等任务,或者将应用 push 到 Swift 对象存储上,那么需要:

    • 创建一个或多个运行虚拟机的 OpenStack 租户。
    • 创建 Swift Endpoint(端点)来存储和发布应用。

    Red Hat 官方提供了不同版本的 Software Factory 的 qcow2 镜像,用于作为虚拟机来直接使用。如何在 OpenStack 云平台上部署 Software Factory 项目呢?这里使用命令行方式部署,执行如下步骤。

    (1)下载镜像。

    # wget http://46.231.133.241:8080/v1/AUTH_sf/sf-images/softwarefactory-C7\. 0-2.2.2.img.qcow2
    
    

    (2)生成镜像。

    # glance image-create --progress --disk-format qcow2 --container-format bare --name sf-2.2.2 --file softwarefactory-C7.0-2.2.2.img.qcow2
    
    

    (3)使用镜像创建虚拟机。

    # nova boot --image sf-2.2.2 --flavor 4 --nic net-id=b6c3b5d7-73b3-4b6d-8b3b-2a543ce91bee software_factory
    
    

    (4)在 Dashboard 界面上,绑定一个外网 IP 地址给虚拟机。

    (5)在浏览器中,使用该外网 IP 地址访问 Software Factory 系统,并可以使用 GitHub 账号登录 Software Factory,如下图所示。

    image

    可以在系统的导航栏中看见该平台集成了很多子系统,用于负责不同的任务。Software Factory 非常适用于中小型规模的研发测试环境,通过高度集成、简化的自动化部署方式等,在使用方面显得非常简便。无论是 Gerrit、Jenkins 还是 Redmine等,均可以在其上面自行创建项目、分配权限、管理日常开发等。

    知其然,知其所以然。相较于DevOps而言,CI/CD是一个相对具象的概念。在 IT 企业中,CI/CD的应用愈加广泛,成为推动软件研发活动的重要基础设施服务,同时推动 DevOps 模式的实际落地。

    什么是 CI/CD

    在实践 CI/CD 相关内容之前,我们有必要先认识下什么是 CI/CD。

    一般传统或者狭义、普遍的 CI/CD,是指持续集成(Continuous Integration,CI)和持续交付(Continuous Delivery,CD)。而更加广义、全面的理解,是指持续集成(Continuous Integration,CI)、持续测试(Continuous Testing,CT)、持续交付(Continuous Delivery,CD)和持续部署(Continuous Deployment,CD)四个方面。通常,一个软件开发的流水线如下图所示。

    • Design:这一阶段完成软件开发的需求分析和设计。
    • Develop:这一阶段完成软件开发的功能代码,一个最佳实践是采用测试驱动开发(TDD)的方法,测试代码和功能代码的编写同时进行。需要注意的是,在 Develop 阶段也会运行单元测试和其他小型测试。
    • Test:这一阶段完成软件的各项大型或专项测试,比如界面测试、API 测试、性能测试和系统测试等。
    • Release:这一阶段完成软件产品的发布,并交付给用户使用。
    image

    持续集成(Continuous Integration)

    随着敏捷开发的发展,持续集成在软件项目活动中也日益成为主流。顾名思义,持续集成是指每日频繁地(比如一天多次)将代码集成到主干分支中。强调通过集成和测试的速度,快速给出一个集成的结果(是失败还是成功),在代码集成之前,必须先通过自动化测试验证,只要有一个测试用例失败,就不能集成。

    Martin Fowler 说过,“持续集成并不能消除Bug,而是让它们非常容易被发现和改正”。这也正是持续集成的真谛所在。

    敏捷开发的核心是指整个软件开发活动被划分成一系列短的迭代过程,每个迭代完成一定数量的功能,迭代周期应该尽量短。在软件开发需求已经确定的情况下,迭代应该由测试驱动开发(TDD)和集成反馈来驱动。只有这样,才能为质量持续改进奠定一个良好的基础。

    持续集成是和单元测试结合在一起的,也就意味着,持续集成和单元测试需要并行工作。持续集成一般由代码每次 git push/review 触发。先签入代码就先看到构建结果,后签入,则要排在后面。这就要求构建时间不能太长,否则在构建时容易引起混乱,很难知道是谁的代码破坏了集成,导致很难定位问题。

    可以说,持续集成是敏捷开发的重要基础环节,没有持续集成,所谓的敏捷开发便失去了赖以生存的土壤,其实施效果也会大打折扣。持续集成是一种软件开发实践,团队成员频繁集成他们开发的代码,每次集成都会经过自动构建——自动测试的验证,以尽快发现集成错误。使用这种方法可以显著减少集成引起的问题,并加快团队合作开发软件的速度。

    (1)持续集成过程

    持续集成的工作阶段比较明确,主要有三个大的阶段:持续集成准备阶段、持续集成使用阶段和持续集成测试阶段。

    持续集成准备阶段的工作主要包括:

    • 通过代码评审系统(比如 Gerrit),实现代码审查和集成反馈。
    • 通过版本控制系统(比如 Git或 GitLab)建立源码仓库。
    • 通过构建工具运行相关构建和测试(比如 Python 项目的 Tox 和 Pytest)。
    • 通过 CI 系统(比如 Jenkins)建立 Job,将版本控制和构建工具整合,并设置构建触发条件。

    持续集成使用阶段的工作主要包括:

    • 开发人员向代码评审系统(比如 Gerrit)提交代码。
    • 通过 CI 系统监听代码的提交,运行单元测试,反馈集成结果。
    • 通过构建工具对代码进行编译、打包和部署。
    • 通过版本控制工具实现版本控制与管理。

    持续集成测试阶段的工作主要是,CI 系统根据集成结果进行不同操作,如果成功,则将代码合并到主分支;如果失败,则反馈给开发人员修改重新提交。

    从中我们可以看到,持续集成涉及的主要工具类别包括:

    • 版本控制工具——实现源代码管理、版本控制。
    • 构建工具——实现代码的自动化编译、打包等,这是持续集成的核心工具。
    • 测试工具——实现代码的自动化测试,以及大型测试或专项测试。
    • CI 系统——整合版本控制、构建工作和测试工作,实现持续集成。

    (2)持续集成的好处

    主要有以下几点:

    • 易于定位错误。比如当持续集成失败时,说明新提交的代码引起了错误,这样也很容易知道是谁犯了错误,可以找谁来讨论。
    • 提高团队的开发信心。虽然整个系统还不是那么可用,但至少可以看到功能已经在一点点被集成了。
    • 提高对进度的控制和把握。这点非常明显,如果每天都在集成,当然每天都可以看到哪些功能可以使用,哪些功能还没有实现。如果你是开发人员,则不用为在每日 Scrum 晨会时说自己完成了多少开发而烦恼;而如果你是 Scrum Master,那么也不用再烦恼开发人员说完成了代码的40%到底是个什么概念。
    • 有助于代码开发的质量评估。比如从开发质量的评估图表中,找出经常出错的测试和源码等。
    • 与测试工具结合,做到每次提交都进行测试。
    • 快速发现错误。每完成一点开发,就提交评测、代码审查,可以快速发现错误,及时修复,越尽早解决,成本越低。

    持续测试(Continuous Testing)

    持续测试作为软件持续集成中的重要组成部分,为软件项目的成功提供了保证软件质量持续改进的重要手段。在持续集成中,更多的是运行小型的单元测试,因此,关于其他测试将在后续章节中阐述。

    持续测试是指开发人员提交代码后自动运行相关单元测试,给出测试结果的反馈;如果失败,则会在测试结果的详情页面中输出错误提示。

    有了持续测试,我们才能实现真正的测试驱动开发(TDD)。举一个例子,当开发人员向代码评审系统提交了代码后,Jenkins hooks 监听到有代码提交,自动运行单元测试,然后在页面视图上显示测试的结果;如果测试失败,开发人员需要重新修改并再次提交测试,直至成功。

    这也需要开发人员在编写功能代码时,同时编写测试代码,这样几乎能够在问题产生之时就将其发现。不经常运行测试通常就不怎么有效,因为从产生缺陷到发现该缺陷相隔时间很长,但持续地(即每一次代码改变时)运行测试能确保快速地发现症结。

    持续测试应当输出测试报告,报告是将持续集成的运行情况以适当的形式展现给相关人员的基本方式。报告是持续集成的晴雨表,所以它必须直观、易懂,比如开发人员从错误日志中找到代码出错的位置和原因;QA 测试人员发现测试的覆盖率和执行情况;管理人员从持续集成通过率的趋势中了解到项目的进度和质量。

    持续交付(Continuous Delivery)

    持续交付和持续部署是两个非常容易混淆的概念。持续交付指的是频繁地将软件的新版本交付给 QA 测试团队或者运营团队,如果评审通过,代码就进入发布、生产阶段。

    为了更加符合国内软件行业的实际情况,我在这里将 QA 和测试统称为“QA 测试”。持续交付可以看作是持续集成、持续测试的延续,它强调的是不管怎么更新,软件是随时随地可以交付的。通过严格的自动化测试,可以确保软件开发的质量和进度。因为通过完全的自动化过程把每个变更自动提交到测试环境中,所以当功能开发完成时,就有信心只需要点击一次按钮就能保证发布和部署应用的质量。

    (1)选择持续交付工具集

    在没有制定出工作流程时,便考虑选择持续交付工具集,是最不重要的决策。实际上,在设计好工作流程和业务流程之后再选择相匹配的工具集即可。考虑到满足实际业务和工作流程的情况,开发一些诸如 Shell、Python 等脚本程序是非常理性的做法。

    更重要的是,无论是 CI 还是 CT、CD 等,都可以使用众多的开源工具,这样做没有被锁定的风险,可以自由切换等。

    (2)持续交付的角色和方式

    产品持续交付的角色应由 QA 测试人员来担任,开发人员也需要参与到产品的某一轮系统测试中,并随时准备 Bug 审查和 Bug 修复。最后,由 QA 测试人员根据具体的产品缺陷、质量情况等做出产品是否适合面向用户交付、发布的决定。

    持续交付的目标不是要消灭缺陷,而是要规范开发和测试的流程,从根源上提高软件质量。总之,持续集成、持续测试、持续交付和持续部署的核心就是让不间断的“密集型、高强度、信息及时反馈的持续性改进”成为提高软件产品质量的驱动力。

    目前,持续交付的方式一般有:

    • 源代码交付——需要将源代码下载到所需要的环境中,如 Python 的 py、Java 的 java等程序文件。但这些文件都不是标准的软件包,不利于集中管理和运行。
    • Linux 标准包交付——通过 Linux deb 或者 RPM 对项目的依赖进行管理。这种方式相对更加规范和科学,但仍然需要解决包冲突问题。同时,这种方式会经常因服务器环境差异、构建环境初始化失败等问题导致无法部署 Linux 标准包。
    • 虚拟机镜像交付——在虚拟机中安装测试软件产品成功后,直接将该虚拟机镜像(比如VMware 的 vmdk、VirtualBox 的 ova、OpenStack 的 qcow2 等镜像)进行交付、发布。显然,这种方式部署成功率接近100%,而且隔离性好。但存在着虚拟机镜像对服务器资源的消耗,同时容量较大、不够灵活等问题。
    • Docker 镜像交付——这种方式是对虚拟机镜像交付的重大改进,在保证系统隔离的同时,Docker 镜像对服务器的资源消耗更低、更加轻量。这种交付方式将成为主流趋势。

    持续部署(Continuous Deployment)

    持续部署是持续交付的后续阶段,也是整个CI/CD环节中的最后一环,指的是软件通过测试后自动部署到生产环境中。持续部署的目标是软件在任何时刻都是可部署的,可进入生产阶段,发布给用户使用。 持续部署的前提是能够自动化完成集成、测试和交付等。它与持续交付的区别是,持续交付是将软件部署到线上环境中,使用的是手动方式;而持续部署强调的是自动化。换言之,持续部署是持续交付的更高阶段,所有通过了测试验证的软件都自动部署到生产环境中。如果没有制度的约束或其他条件的影响,团队都应该以持续部署为目标,如下图所示。

    image

    CI/CD在软件研发测试中的应用

    DevOps是一种工程文化、理论集,是一个抽象的概念;而其范畴下的 CI/CD 则是它的具体实现和方法,是一种化抽象为形象的工具集、流程图。如果说 DevOps 是云计算,那么 CI/CD 就是计算、存储和网络,OpenStack 则是其具体实现平台。

    软件项目的研发有两大难题:一是确定软件的需求,即确定目标;二是确定当前离目标还有多远,即确定剩余的工作量。后者是项目缺少可见性的问题。前者是持续性反馈的问题。比如来自用户需求的反馈、市场变化的反馈、项目研发测试过程的反馈等。

    自动化是软件研发测试中的重要方式。持续集成最大的优点就是降低风险,提高项目研发过程的效率和质量,迎合互联网时代信息快速更新的规律。持续集成本身并不能帮助开发工程师和 QA 工程师解决Bug,而是通过不断的测试和反馈来尽早地发现 Bug,问题发现得越早,处理问题的成本就越小,也越容易解决。由于无法证明通过了某次测试的代码永远无缺陷,因此,持续集成中的测试也就显得非常重要,好的测试能够更多、更快地发现当前版本中的错误。

    根据持续集成的作业流,代码从提交到生产环境,整个过程分为以下几步。

    提交(Commit)

    开发者向代码仓库提交代码。所有后面的步骤都始于本地代码的一次提交。

    测试(第1轮)

    整个 CI 系统配置了代码仓库系统、Jenkins 系统和代码评审系统的触发器,只要提交代码就会运行自动化测试。一般这里的测试有如下几种。

    • 单元测试:针对函数或模块的测试。
    • 集成测试:针对整个软件的功能测试。
    • 代码风格测试:针对代码的编写风格进行测试,比如 Python 的 PEP 8 等。
    • 其他测试。

    构建(Build)

    通过第1轮测试后,代码就可以合并进主干分支,推送到私有代码仓库 GitLab 系统中,进行下一阶段的构建了。

    从开发人员提交本地代码,到测试通过,代码合并到分支里,提交就算完成了。此时,就需要进行构建进入第2轮测试。所谓构建,指的是将源码转换为可以运行的 Linux 标准软件包,比如安装依赖、生成配置文件等,当然也可以构建成 Docker 镜像。

    测试(第2轮或多轮)

    构建完成后,就要进行第2轮或多轮测试。总之,构建部署在测试之前。

    此阶段的测试最全面,投入资源最多,包括系统测试、功能/集成测试、性能测试等都会运行。需要强调的是,每一个更新点都必须测试到。如果测试的覆盖率不高,进入后面的部署和生产阶段后,很可能会出现严重的问题。

    部署

    通过多轮测试后,当前代码就是一个可以直接部署的稳定版本。将这个版本的所有文件打包存档(比如 Fuel 的 iso,或其他自研产品的 iso、vmdk 乃至 Docker 镜像等),发布到成品库服务器上。这方面的自动化部署工具有 Ansible、Chef、Puppet 等。

    回滚

    一旦当前软件版本发生问题,就要回滚到上一次构建的版本,使用上一次构建并发布的版本。

    CI/CD 很好地解决了代码从自动化编译打包、自动化构建部署、自动化测试到快速交付产品4个阶段。为此,需要做到:

    • 软件包尽量轻。为了保证代码在编译、打包、构建等阶段做到快速、轻量,应尽可能实现软件的模块化处理。典型的,如将 OpenStack 的各个服务分别放在不同的 Docker 镜像中。
    • 重视管理教育。在公司中,一个团队/部门里的工程师素质是良莠不齐的,不能让少数人影响到一群人、一个整体,仅靠自觉是完全不够的,需要指导团队养成做事的好习惯。一个常见的问题就是开发人员不写单元测试代码,影响到整个软件研发测试的质量和周期。

    构建产出物

    构建工作主要产出两个东西:待测的软件包和应用系统(有的只有后者)。这两个产出物有所不同,前者是构建过程与环境无关,而且可重复,因此需要提供如 yum、npm、gem 等软件包服务,让构建号和软件包号建立关联,便于回溯。

    通过 IaaS 云或 Docker 容器将 CI/CD 环境以微服务的方式运行,做到开箱即用。CI/CD是软件工程的一个重要实践,可以帮助我们更早地发现问题、解决问题,降低成本,同时大大减少交付、发布过程中的不确定性,提高团队的生产效率。当然,CI/CD 与其他工具和方法也紧密相关。

    每日构建和每日测试

    每日构建(Daily Build)在反应速度上没有持续集成快,它更强调的是通过每天(通常是晚上)自动部署当天开发所累积的代码,并结合每日测试(Daily Test)方法进行自动化测试,用于评估和衡量项目的进度。

    持续集成特性决定了不可能在该阶段加入大量的测试,而每日构建则一般是在夜里执行的,那么这时就可以做更多的事情,比如代码质量检查、单元测试、测试覆盖率、集成测试等。每日构建会把当天所有的提交代码都一起做集成,而不像持续集成那样每提交一次代码就做一次集成。持续集成属于增量式构建,而每日构建则是一次完全构建。

    每日构建是项目的心跳线。一般而言,通过每日构建我们可以看到项目的进度。比如今天有10个 Bug 的修复代码都提交了,晚上进行自动部署并通过了自动化测试,第二天上班时通过查看邮件我们就可以发现每日构建成功了,那么项目就又往前迈了一大步,开发工作又有了具体的进展。

    具体的,每日构建就是在每天晚上自动化部署一个 OpenStack 环境(可以是all-in-one,也可以是 multi-node),然后运行大型或专项测试,比如 API 性能测试、API 接口测试、功能测试、部署测试等。

    相关文章

      网友评论

        本文标题:深入理解 OpenStack 社区 CI/CD

        本文链接:https://www.haomeiwen.com/subject/yryzqftx.html