第03课:深入理解

第03课:深入理解 OpenStack 社区 CI/CD

在介绍 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 系统运行工作流程如下图所示。

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

首先是 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 配置实例,请查看[这里](http://git.openstack.org/cgit/openstack-infra/project-config/tree/jenkins/jobs 与http://docs.openstack.org/infra/system-config/jenkins.html)。

集群任务分发系统(Gearman)

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

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 工作节点上执行。

任务组织系统(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 文件格式编写。这里是[链接地址](https://github.com/openstack-infra/project-config/ tree/master/jenkins/jobs)。

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

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

如何添加一个新的 Job

首先需要选择哪些项目属于这个 Job。在 projects.yaml 文件中可以看到所有项目的 Job。点击[这里](https://github.com/openstack-infra/project-config/blob/master/jenkins/jobs/ projects.yaml)获得链接地址:。

比如将一个名称为“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 运行状态如下图所示。

代码评审系统(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,如下图所示。

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

上一篇
下一篇
目录