乐趣区

关于持续集成:如何用-CI-持续集成-保证研发质量

本文来自 OPPO 互联网根底技术团队,转载请注名作者。同时欢送关注咱们的公众号:OPPO_tech,与你分享 OPPO 前沿互联网技术及流动。

1. 背景

分布式技术的倒退粗浅地扭转了咱们编程的模式和思考软件的模式。分布式很好的解决性能扩大,可靠性,组件可用性等问题,然而单机转变成分布式却加大了零碎的复杂性,对于组件的开发,测试,部署,公布都提出更高的要求。那么,针对简单的分布式系统怎么保障软件品质和零碎的稳定性?首先看下,传统软件产品流动的大抵流程,简化流程大略是 3 大块:

开发 -> QA -> 灰度上线!

个别如下图:

流程有个很大的问题,品质全靠 QA 测,对接全靠人力,沟通老本大,脱漏问题多,个别有几个常见的问题:

  1. QA 很难每次都测试全面,毕竟 QA 毕竟是人,人的主观因素太大,有时候人为判断感觉简略,不必测的中央很可能有漏了。或者感觉批改点太简略,感觉不至于出问题,就不再全面的测试。以至于可能会有基本功能问题;
  2. 测试速度慢,效率太低,QA 资源节约,如果每次 QA 都须要全量的测试,那么反复工作太多,效率太低,成果也不好,对于这些反复工作,本能够更好,更快的解决,不至于 QA 就为了测试这么点货色,而没有精力去做更多的事件;
  3. 甚至说编译不过的代码都有可能脱漏到 QA;
  4. 闭环太慢,开发性能如果有问题,等到 QA 测进去,让后再反馈到开发这个闭环就太慢了。更不用说,问题漏到线上,再反馈到开发人员,那么戴江就更大了;
  5. 多个开发人员并行开发的时候,工作可能相互影响,小问题越积越多,性能集成的时候可能十分耗时;

咱们提出的改良点:

  1. 外围点之一:问题发现要早,发现越早,代价越小;
  2. 外围点之一:问题闭环要快,闭环越快,效率越高;
  3. 反复工作自动化,缩小人的有效劳作;
  4. 多开发人员的时候,性能继续集成,问题拆小,提前发现;

最外围的一点就是:”自动化闭环问题“。

以后的简单的软件系统对品质和效率提出了更高的要求,所以响应的软件流动必须要高度自动化能力达到要求。自动化触发、自动化测试、自动化闭环、自动化公布、自动化卡点等一系列的保障,所有可能当时预知且可固化的行为都应该自动化,把效率和品质晋升,而让人去做更聪慧的事件。

咱们的思考:近些年来,对于自动化有 Continuous Integration,Continuous Delivery,Continuous Deployment 的一些实践和实际。这三者来说,突出“继续”二字,“继续”是为了达到“快”的目标,“疾速迭代”,“疾速响应”,“疾速闭环”,“快”是外围竞争力。个别大家的共识流程分类如下:

对于开发者来说,接触到更多的是 Continuous Integration(继续集成),CI 把通过自动化,把流程固化下来,保障代码集成的有序、牢靠,确保版本可控,问题可追溯,代码的流动中通过自动化,升高了人为主观的出错率,进步速度,进步版本品质和效率。

2. CI 是什么?

CI 即是继续集成(Continuous Integration),是当今软件流动中至关重要的一环。CI 个别由开发人员递交代码改变所触发,CI 在两头环境做自动化验证,CI 验证过后,即通过了根本质量保证,那么就能够容许下一步的软件流动。

继续集成说白了就是一种软件开发实际,即团队开发成员尽可能的快的集成,每次集成通过自动化的构建 (包含编译,公布,自动化测试) 来验证,从而尽早地发现集成谬误。因为问题发现的越早,那么问题解决的老本就越少。

一般来说,继续集成须要买通几个环节:

  1. 代码提交(git)
  2. 工作构建(jenkins)
  3. 部署测试(ansible,shell,puppet)

划重点:CI 过程由代码流动触发

代码流动关注两个工夫点:

  • Pre-Merge:代码改变合入主干分支前夕触发。集成的对象是代码改变与骨干最新代码 Merge 之后的代码,目标是验证代码改变是否可能合入主干;
  • Post-Merge:代码改变合入主干分支之后触发。集成对象就是最新的骨干分支代码,目标是验证合入改变代码之后骨干是否可能失常工作;

Pre-Merge 和 Post-Merge 关注点不同,缺一不可。差别在哪里?如果只有一个开发者,那么 Pre-Merge 和 Post-Merge 的测试对象是雷同的。在多个开发者递交代码的时候,Pre-Merge 和 Post-Merge 就会出现差别,他们的 CI 测试对象不同。

换句话说,Pre-Merge 是并行的,每个开发分支想要合入主干都会触发 Pre-Merge CI,CI 的测试对象是 < 开发分支 + 骨干分支 >,Post-Merge 是串行的,测试对象永远都是最新的骨干分支代码。

3. CI 的四个思考

3.1 CI 怎么触发?

代码流动触发:

个别有两个触发点,Pre-Merge,Post-Merge,别离是代码合入主干之前,骨干代码合入之后。

定时触发。

3.2 CI 触发之后做什么?

CI 触发了之后做什么?说白了就是构建工作做了啥,个别有几个流程:

  1. Checkout,Pre-Merge 代码——校验 MR 合入是否非法;
  2. 代码编译 —— 校验代码编译是否非法;
  3. 动态查看 —— 校验动态语法是否非法;
  4. 单元测试 —— 回归测试函数单元非法;
  5. 冒烟测试 —— 简略测试零碎是否失常;
  6. 接口测试 —— 测试用户接口是否失常;
  7. 性能基准测试 —— 测试性能是否合乎预期;

3.3 怎么闭环问题?

先思考可能会有什么问题:

  1. Pre-Merge 代码抵触;
  2. 代码编译失败;
  3. 动态查看失败;
  4. 单元测试回归测试不通过;
  5. 冒烟测试步通过,接口测试失败。。。;

递交一个代码 MR 递交可能遇到以上问题,那么怎么能力疾速闭环这个问题呢?

首先,得有伎俩告诉到开发者

解决:

  1. MR 的 comment,CI 流动失败之后,间接以评论的形式主动增加到 MR;
  2. 邮件,触发的一次 MR,失败了以邮件的形式发送到相干人;

再者,得有伎俩让开发人员晓得问题

解决:

  1. 开发者晓得本人的 MR 触发 CI 失败之后,得晓得怎么去排查问题 —— 测试报告,

    • 比方单元测试失败,要有单元测试报告,接口测试失败要有接口测试报告;
  2. 每次构建工作保留归档线索,以便排查;

3.4 CI 构建流动输入什么?

  1. 单元测试报告;
  2. 接口测试报告;
  3. 代码覆盖率报告;
  4. 接口覆盖率报告;
  5. 构建版本包(继续化部署须要);

4. CI 平台选型

个别对少数的公司来说,不须要本人研发一个 CI 平台,有很多优良的开源 CI 平台工具,工具之间并没有相对的差别劣势,这里就不进行选型了,咱们以一个 Jenkins 残缺示例来阐明 CI 的应用办法和技巧。代码仓库咱们应用 Gitlab,CI 平台咱们应用开源的 Jenkins 作为演示。一步步实现咱们须要得几大模块性能。

平台选型:代码平台 Gitlab,CI 平台 Jenkins;

5. CI 的流程实际

CI 次要把关的是代码流动,个别有两个触发点:

  1. 代码合入主干前,触发 CI 测试,目标是校验本次合入是否合乎品质预期,如果不合乎,那么不准代码合入主干;
  2. 代码合入主干后,触发 CI 测试,目标是校验最新的骨干分支是否合乎品质预期;

Pre-Merge 触发过程

  1. 开发代码递交 Merge Request(github 上习惯叫做 PR,gitlab 上习惯叫做 MR)
  2. MR 主动触发 CI 构建事件
  3. 运行 动态查看,Merge 查看,单元测试,冒烟测试,集成测试,全副通过之后,代码才容许 Merge 合入主干分支;
  4. 进行下一步软件流动

Post-Merge 触发过程

  1. 管理员审核 Merge Request 通过,代码合入主干,触发 Post-Merge 事件;
  2. CI 平台收到事件,主动进行 CI 构建;
  3. 构建实现,进行下一步软件流动;

6. Jenkins 平台构建

6.1 Jenkins 平台搭建

jenkins 是 java 程序开发的,装置是十分不便的,去官网上下载一个 war 包,而后后盾拉起运行即可。运行命令如下:

启动

nohup /usr/bin/java -jar jenkins.war --httpPort=8888 >> jenkins.log 2>&1 &

这样 jenkins 平台就拉起来了,超简略。

6.1.1 初始化平台

初始密钥

平台第一次搭建须要做一些配置,在日志里找到一个“初始密钥”留神一下提醒:

*************************************************************
*************************************************************
*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

5ddce31f4a0d48b4b7d6d71ff41d94a8

This may also be found at: /root/software/jenkins/workhome/secrets/initialAdminPassword

*************************************************************
*************************************************************
*************************************************************

这个是最初始的超级用户明码,待会配置的 jenkins 时候会用上,所以连忙拿个小本本记下。

登录网页

当初关上浏览器,登录 jenkins 的网页,咱们上面做 jenkins 的初始化:

点击“Continue”,进行下一步,下一步就是定制一些插件装置了。

第一次定制插件装置

这个是可选的,这个依据本人需要抉择插件,为了省事,选第一种就好,之后也很不便在平台上下载插件。

装置插件过程(jenkins 简直齐全由插件组装起性能):

更新胜利的就会显示“绿色”。插件装置完之后,下一步就是配置第一个超级用户了。

配置实现之后,点击 “Save and Continue”,最初一步,配置 url,点击 finish 即可:

当初根本配置曾经实现,能够开始欢快的应用 jenkins 了。

6.1.2 应用小技巧

中文化配置

jenkins 平台搭建好之后,默认的是英文的,在国内的话可能没必要,咱们能够装置中文化插件来更敌对的展现咱们的 jenkins。分两个步骤:

步骤一:装置插件:”Locale“:

“Manage Jenkins” -> “Manage Plugins” -> “Available”

步骤二:装置完之后,配置 Configure

账号配置

6.2 Jenkins 插件装置

6.2.1 插件更新地址

这里举荐国内的插件源地址,因为官网的网络拜访不是很稳固。比方以下是清华的镜像源。

https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

6.2.2 必备插件装置

jenkins 性能都是由插件提供,有些插件是必须装备的,能力提供齐备的 CI 性能,比方流水线 Pipeline。这里列举几大要害插件的应用办法。

pipeline

jenkins 必备插件,流水线插件,是否十分不便的让你定义流程,调度节点,分配资源,处理结果等。

blue ocean

pipeline 的可视化插件,pipeline 还是申明式代码编写,如果要能让人更不便的应用,那么须要一个可视化的工具,blue ocean 就是为此而生。

junit

测试报告的一个解析插件,这也是一种较为通用的测试报告格局。

Cobertura Plugin

覆盖率展现的一个插件。单测跑完,须要有伎俩晓得覆盖率的状况,并且须要能不便的闭环解决。

  1. 显示覆盖率的状况;
  2. 代码的笼罩详情,不便开发人员闭环解决(细化到每一行代码);
GitLab

咱们的演示以 Gitlab 作为例子,须要和 GitLab 进行交互,所以须要装置插件用来承受 GitLab 事件,并反馈 CI 后果。

6.3 Jenkins 工作创立

6.3.1 创立工作(item)

item 就是 CI 的我的项目,item 由管理员动态创立配置好,触发起来就是 job 了。每触发一次,job 编号都是递增的。点击 New Item 创立一个”流水线“的我的项目。

6.3.2 创立视图(view)

View 是什么概念?View 能够把一些有业务意义的工作归纳起来,在一个列表中显示。能够点击”New View“进行创立。

视图会展现 item,你能够选择性的勾选。

6.4 Jenkins 流水线

流水线框架是通过 Pipeline 脚本来形容流程,Pipeline 有两种创立形式,两种语法:

  1. 申明式流水线语法
  2. 脚本化流水线语法

当初官网举荐的是申明式流水线语法。那么,申明式的语法是什么样子的?申明式语法特点之一:顶层必须以 pipeline {} 开始。

7. Jenkins 和 Gitlab 交互

这一步就是最要害的货色,Jenkins 搭建好了之后,如果只是一个孤岛平台,那么没有任何意义,它必须参加到软件开发流程中去能力施展成果。交互示意图如下:

咱们看到,Gitlab 的代码流动须要以事件的模式触发 Jenkins,Jenkins 执行完一系列流动之后,须要把后果反馈到 Gitlab,并且可能影响到 Gitlab 的下一步流动,所以 Gitlab 和 Jenkins 须要互相配置关联。

7.1 Gitlab 配置

为什么须要配置 gitlab?因为须要买通 gitlab 到 jenkins 的路。gitlab 作为代码仓库,次要产生我的项目代码相干的事件,比方 Merge Request,Push Commit 等,当 gitlab 产生这些事件的时候,须要主动把这个事件推送给 jenkins,这样就买通了触发交互。

7.1.1 配置 Web Hook 事件

操作步骤:

  1. 关上代码仓库
  2. 点击 setting -> integrations

    1. 填入 URL
    2. 填入 Secret Token
    3. 勾选 Trigger 事件

URL 和 Secret Token 怎么来的?这个是对应到 item。

在 Jenkins 平台上,关上对应的 item,关上 Configure,勾选 Build Triggers,找到”Build when a change is pushed to GitLab“,就是这个了。

再往下有一个 Secret token,点击 Generate。

把这两个正确填写好,那么就能买通第一个环节了:Gitlab 到 Jenkins 的触发。填写完之后,能够由 GitLab 发个测试事件测试下。

返回 200 即是胜利了。

7.1.2 配置 Pre-Merge 卡点

代码 Pre-Merge CI 没过不让合入主干 这个性能怎么实现?要害是 GitLab 要反对代码 Merge 前夕的 Hook 行为。

  1. 首先,咱们约定一个行为规范:所有合入主干的代码必须递交 MR,MR CI 测试通过才能够合入主干;
  2. 其次,勾选 Settings -> General -> Merge requests,把”
    Only allow merge requests to be merged if the pipeline succeeds“勾选上;

7.2 Jenkins 配置

Jenkins 次要看 Pipeline 的配置,Pipeline 配置关上 Configure 如下:

看一个残缺的 pipeline 架子定义各个阶段(能够间接把这个拷贝,运行看下成果):

pipeline {
    agent any
    stages {stage('代码 checkout') {
            steps {echo "------------"}
        }
        stage ("动态查看") {
            steps {echo "------------"}
        }
        stage ("代码编译") {
            steps {echo "------------"}
        }
        stage ("单元测试") {
            steps {echo "------------"}
        }
        stage ("打包") {
            steps {echo "------------"}
        }
        stage ("冒烟测试") {
            steps {echo "------------"}
        }
        stage ("集成测试") {
            steps {echo "------------"}
        }
        stage ("基准性能测试") {
            steps {echo "------------"}
        }
    }
    post {
        always {echo "------------"}
        success {echo "------------"}
        failure {echo "------------"}
        unstable {echo "------------"}
    }
}

跑进去的成果:

Blue Ocean 的成果:

接下来,咱们拆解几个要害的阶段来剖析。

7.2.1 代码 checkout

Checkout 的代码也就是咱们的测试对象,这个对于 Pre-merge 和 Post-merge 是不同的,Pre-merge 卡点由 Merge Request 事件触发,咱们须要 Checkout 出”代码批改“+”最新骨干分支“的代码。Post-merge 绝对简略,咱们只须要 Checkout 出最新的骨干分支即可。怎么做?

pipeline 间接反对这个应用。

stage('代码 checkout') {
    steps {dir(path: "${你想要搁置的代码门路}") {
            checkout changelog: true, poll: true, scm: [
                $class: 'GitSCM', 
                branches: [[name: "*/${env.gitlabSourceBranch}"]],
                doGenerateSubmoduleConfigurations: false, 
                extensions: [[$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: 'origin', mergeStrategy: 'DEFAULT', mergeTarget: "${env.gitlabTargetBranch}" ]],
                    [$class: 'UserIdentity', email: "${env.gitlabUserEmail}", name: "${env.gitlabUserName}" ]
                ],
                submoduleCfg: [],
                userRemoteConfigs: [[credentialsId: "${env.OCS_GITLAB_CredentialsId}", 
                    url: "${env.gitlabSourceRepoHttpUrl}"
                ]]
            ]
        }                
    }            
}

这里指明:

  • 代码 checkout 下来搁置的门路(dir path 配置);
  • 指明 checkout 的分支,MR 事件触发 CI 的时候 env.gitlabSourceBranch 是会主动设置上的;
  • 指明 checkout 的行为,PreBuildMerge 行为(其实 Post-Merge 触发 PUSH 事件的时候,也实用于下面的写法);
  • 指明 gitlab 认证凭证(credentialsId);

下面的语法同时实用于 Pre-merge 和 Post-merge。

7.2.2 动态查看

动态查看次要是对代码语法做一些动态查看,比方 golang,能够应用自带的 go vet,或者 go fmt 等查看,通过这一轮查看就能保障大家的代码打消了最根本的语法和格局谬误。

7.2.3 单元测试

对最小的函数做单元测试是必要的,通过单元测试能够拿到我的项目的一个覆盖率状况。这个阶段咱们取得两个货色:

  • 单元测试案例报告
  • 覆盖率报告

测试报告怎么拿?以 golang 为例,跑单测的时候,把覆盖率的开关关上,规范输入到一个文件:

go test -cover -coverprofile=cover.output xxx | tee ut.output

这里会产生两个文件:

  • ut.output:用来生成单元测试报告的文件;
  • cover.output:用来生成覆盖率报告的文件;

单测报告生成

首先解析这个文件成 xml 格局的文件,而后用 junit 上报给 jenkins 展现。

sh "go-junit-report < ut.output > ut.xml"
junit 'ut.xml'

go-junit-report 哪里来的?这是个开源的工具,就是专门用来做单元测试解析的。

在 jenkins 上展现的成果如下:

覆盖率报告生成

解析覆盖率输入文件,生成一个 xml 文件:

gocov convert cover.output | gocov-xml > cover.xml

上报这个 xml 文件,用于 jenkins 平台展现:

step([
        $class: 'CoberturaPublisher', 
        autoUpdateHealth: false, 
        autoUpdateStability: false, 
        coberturaReportFile: '**/cover.xml', 
        failUnhealthy: false, 
        failUnstable: false, 
        maxNumberOfBuilds: 0, 
        onlyStable: false, 
        sourceEncoding: 'ASCII', 
        zoomCoverageChart: false
    ]
)

点击进文件,能够看到代码笼罩的详情:

7.2.4 接口测试

对残缺的零碎做一些接口级别的测试,比方模仿用户行为,测试用户调用的接口,这样能保障最根本的性能。报告输入也能够用 junit 格局,能够上报给 jenkins,解析如图:

7.2.5 邮件发送

测试通过或者失败,须要发送有后果邮件。

configFileProvider([configFile(fileId: '5f1e288d-71ee-4d29-855f-f3b22eee376c', targetLocation: 'email.html', variable: 'content')]) {
    script {template = readFile encoding: 'UTF-8', file: "${content}"
        emailext(subject: "CI 构建后果: ${currentBuild.result?:'Unknow'}",
            to: "test@test.com",
            from: "push@test.com", 
            body: """${template}"""
        )
    }
}

7.2.6 Gitlab 状态交互

// 定义 Gitlab 流程
options {gitLabConnection('test-gitlab')
    gitlabBuilds(builds: ['jenkinsCI'])
}

            // 触发 gitlab pipeline
            updateGitlabCommitStatus name: 'jenkinsCI', state: 'success'
            addGitLabMRComment comment: """**CI Jenkins 主动构建详情 **\n
| 条目 | 值 |
| ------ | ------ |
| 后果 | ${currentBuild.result?: 'Unknow'} |
| MR LastCommit | ${env.gitlabMergeRequestLastCommit} | 
| MR id | ${env.gitlabMergeRequestIid} |
| Message Title | ${env.gitlabMergeRequestTitle} |
| 构建工作 ID | ${env.BUILD_NUMBER} |
| 构建详情链接 | [${env.RUN_DISPLAY_URL}](${env.RUN_DISPLAY_URL})"""

CI 胜利或失败,都须要把这个状态给到 gitlab,咱们以一个 Comment 展现后果,并且附上 jenkins 工作的跳转链接,这样能够最快的帮忙开发人员闭环。

胜利才容许合入 :

Gitlab CI

7.2.7 构建归档

打包日志:

// 先打一个 tar 包
sh "tar -czvf log.tar.gz ${SERVICEDIR}/run/*.log"
// jenkins 进行归档
archiveArtifacts allowEmptyArchive: true, artifacts: "log.tar.gz", followSymlinks: 
false

8. Jenkins 高级技巧

8.1 资源互斥

有时候多个工作跑的时候,可能会并发应用到某个资源,而如果这个资源无限,那么可能须要用到一些互斥伎俩来保障。比方,两个工作可能都用到了 mongodb,而 mongodb 如果只有一套,那么就必须让多个工作串行执行才行,不然就会跑错了逻辑。怎么做?

这个能够在“Configure System”->“Lockable Resources Manager”定义好锁资源:

而后再 Pipeline 脚本里应用这个锁资源:

stage ("单元测试") {
    steps {lock(resource: "UT_TEST", quantity:1) {
            echo "====== 单元测试 ============"
            echo "====== 单元测试实现 ============"     
        }
    }
}

并且还能够在界面上(Dashboard -> Lockable Resources)看到哪些资源被哪些工作占用:

通过正当定义锁资源,咱们就能做到工作能够并发,然而要害的竞态资源做互斥,这样 CI 构建工作更灵便,更有效率(这个能够类比成代码外面锁粒度的一个影响,如果你不必 Lock Resource 这种形式,那么很可能只能配置成 node 并发度为 1 能力爱护到竞态资源)。

8.2 节点调度

jenkins 容许你调度指定的工作到适合的节点。当有多个节点的时候,可能会想要工作 A 固定到 node1 上执行,那么能够应用 agent 命令指定。

定义节点的时候每个节点都会赋有一个 label 名称,而后运行的时候,就能够指定节点了:

agent {label "slave_node_1"}

8.3 节点间文件传输

咱们应用 stash, unstash 来实现,上面的例子就是把 build/ 目录在 node1 和 node2 间接做无损传输:

stage ("打包") {agent { label "slave_node_1"}
    steps {
        // 节点 1 上,把 Build 目录下的都打包;stash (name: "buildPkg", includes: "build/**/*")
    }
}

stage ("冒烟部署") {agent { label "slave_node_2"}
    steps {
        // 节点 2 上,解包
        unstash ("buildPkg")
    }
}

8.4 节点的后置清理

在流水线多节点切换的时候,须要留神下你所在的节点是哪个,千万别晕头了。

pipeline {agent { label "master"}
    stages {stage ("测试") {agent { label "slave_node_1"}
            steps { }
            // 阶段后置
            post {
                always {
                    // 清理 slave_node_1 的构建空间
                    cleanWs()}
            }
        }

    }
    // 流水线总后置
    post {
        always {
            // 只清理 master 节点的构建空间
            cleanWs()}
    }
}

多节点的时候,肯定要记得别离清理节点。

9. 总结

通过应用正当的技术平台,把人与事正当的关联,古代软件开发流动中,CI 是必不可少的流程,开发人员身在其中,CI 以代码流动为终点,构建后果能能疾速响应到对应人,并提供伎俩让对应人疾速解决,最初提供直观的报告。咱们通过 Jenkins(CI 平台)+ Gitlab(仓库)来演示残缺搭建流程,展现一个可实际的过程。一切都是为了软件开发效率和版本品质。

退出移动版