简介
软件开发和交互本来就是一项复杂的工作,流程上来说常见的就有:代码开发->代码质量检查->单元测试->打包->部署->集成测试等等。在这个过程中,开发人员、测试人员、运维人员需要花费大量的时间和精力协同配合,即使这样都很难确保整个发布流程不会出错,好不容易所有环节都没有出错,却也很难保证开发环境、测试环境、准生产环境、线上环境的完全一致;本文使用jenkins的pipeline功能整合整个开发、测试、发布流程,并使用docker-swarm来进行服务的管理,实现一个简单的devops实践,做到这些流程可以一键自动完成。
本文所用到的工具主要有: jenkins、maven、junit、sonar、docker、docker swarm等,要完成本文示例,需要提前了解一下相关知识,主要有: jenkins pipeline的语法、Groovy 语法、docker容器技术、docker swarm容器集群管理技术、以及一点shell脚本编写能力。
- 如果对上面技术不是特别熟悉的朋友可以参考下以下几篇文章学习一下:
- jenkins pipeline流水线语法
- 使用 Jenkinsfile
- 最新docker实战入门教程
- java maven应用制作docker镜像教程
- Docker swarm容器集群使用教程
- Groovy 入门教程
- 原文传送门
本文旨在抛砖引玉,希望大家有更多的深入研究和分享!
1、流程和工具使用介绍
1.1 单元测试
使用junit进行单元测试,首先使用mvn test直接运行单元测试;再使用jenkins junit 插件生成测试报告
1.2 需要安装额外的jenkins 插件
- Git Parameter 自动获取代码分支、tag等进行选择
- Extended Choice Parameter 多选、复选参数控件
- Junit 插件,用于收集单元测试结果并进行自动统计分析
eg.代码片段生成示例:
- JaCoCo plugin 用于分析代码检测结果
eg.使用示例:- SonarQube Scanner for Jenkins 用于代码静态检查
- sonarqube server配置
- sonarqube 代码检测结果回调通知jenkins配置
菜单: 配置 -> 网络调用 -> 新增
> 回调地址格式:<http://[jenkins_url]/sonarqube-webhook/>
- Email 邮件发送配置
菜单路径: 系统管理 -> 配置 ->系统管理员邮件地址(这个地址会作为默认的邮件发送者)
邮件发送服务器配置:
1.3 需要使用的maven插件
- docker-maven-plugin 或者 dockerfile-maven-plugin 用于将服务打包成镜像并推送到远程仓库中
- sonar-maven-plugin 用于代码静态检查
- Junit 用于单元测试用例
- jacoco-maven-plugin 代码测试覆盖率分析
1.4 eclipse插件
- jenkins-editor 用于pipeline脚本的编写和语法静态检查
2、pipeline语法简单介绍
流水线是用户定义的一个CD流水线模型 。流水线的代码定义了整个的构建过程, 他通常包括构建, 测试和交付应用程序的阶段 。
pipeline有如下特性:
- Code: 流水线是在代码中实现的,通常会检查到源代码控制, 使团队有编辑, 审查和迭代他们的交付流水线的能力。
- Durable: 流水线可以从Jenkins的主分支的计划内和计划外的重启中存活下来。
- Pausable: 流水线可以有选择的停止或等待人工输入或批准,然后才能继续运行流水线。
- Versatile: 流水线支持复杂的现实世界的 CD 需求, 包括fork/join, 循环, 并行执行工作的能力。
- Extensible:流水线插件支持扩展到它的DSL [1]的惯例和与其他插件集成的多个选项。
2.1 pipeline脚本模式
- 声明式语法
推荐使用声明式语法
- 脚本式语法
2.2 pipeline主要元素
节点
节点是一个机器 ,它是Jenkins环境的一部分 and is capable of执行流水线。
另外, node
块是 脚本化流水线语法的关键部分。
在jenkins集群环境下可以使用此特性。
阶段
stage
块定义了在整个流水线的执行任务的按操作范围认为划分的一系列相关的操作,比如:代码打包、代码静态检查、单元测试、部署等等阶段
步骤
本质上 ,一个单一的操作,一个 step 告诉Jenkins 在特定的时间点要做什么(或过程中的步骤)。 举个例子,要执行shell命令 ,请使用 sh
步骤: sh 'make'
。当一个插件扩展了流水线DSL, [1] 通常意味着插件已经实现了一个新的 step,这个可以用于自定义pipeline插件。
2.3 pipeline语法格式
- 主体结构如下
pipeline {
agent any //在任何可用的代理上执行流水线阶段
environment { //全局环境变量示例
CC = 'clang'
}
parameters {//参数列表,可以直接在pipeline脚本中指定参数,后续步骤中可以使用这些参数,这种模式适用于脚本化参数构建,也就是说不需要再到jenkins中手动指定参数就可以进行构建,比如通过代码push事件触发构建操作
string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
}
options {
//配置整个构建任务的系统参数,用来控制构建任务的行为,如:构建失败后重试的次数,保留的构建结果数量等等
}
triggers {//触发器,用于触发自动构建操作
cron('H */4 * * 1-5')//按定时周期进行构建
}
tools {
//指定流水线依赖的插件版本(如maven、jdk等等),如果不指定,使用默认版本的工具
}
stages {//所有的阶段都需要包含在此标签内
stage('Build') {
environment { //步骤内环境变量示例
LOCAL_ENV_VAR = "hello local env variable!"
}
when {//有条件的执行当前阶段,满足条件才执行
branch 'production'//只有当前代码分支名匹配才执行此阶段内的步骤
}
steps {//包含该stage内的所有操作步骤,如果只有一条命令,可以省略这个标签
// 详细步骤
script{//steps下可以插入groovy脚本,类似于java代码
//groovy脚本
}
}
}
stage('Test') {
steps {
//
}
}
stage('Deploy') {
steps {
//
}
}
stage('Parallel Stage') {//并行执行的步骤示例
when {
branch 'master'
}
failFast true //只要有任意一个失败,则立即终止所有任务
parallel {//此标签下的stage都可以同时并行执行
stage('Branch A') {
agent {
label "for-branch-a"
}
steps {
echo "On Branch A"
}
}
stage('Branch B') {
agent {
label "for-branch-b"
}
steps {
echo "On Branch B"
}
}
}
}
}
}
post {//监听此任务执行状态变化,进行额外的处理,比如告警等等操作
always {
//无论流水线或阶段的完成状态如何,都允许在 post 部分运行该步骤。
script{//可以插入groovy脚本,类似于java代码
//groovy脚本
}
}
changed {
//只有当前流水线或阶段的完成状态与它之前的运行不同时,才允许在 post 部分运行该步骤。
}
success {
//只有当前流水线或阶段的完成状态为"success",才允许在 post 部分运行该步骤, 通常web UI是蓝色或绿色。
}
unstable {
//只有当前流水线或阶段的完成状态为"unstable",才允许在 post 部分运行该步骤, 通常由于测试失败,代码违规等造成。通常web UI是黄色。
}
failure {
//只有当前流水线或阶段的完成状态为"failure",才允许在 post 部分运行该步骤, 通常web UI是红色。
}
aborted {
//只有当前流水线或阶段的完成状态为"aborted",才允许在 post 部分运行该步骤, 通常由于流水线被手动的aborted。通常web UI是灰色。
}
}
}
- 组件使用说明
代理
agent
部分指定了整个流水线或特定的部分, 将会在Jenkins环境中执行的位置,这取决于 agent
区域的位置。该部分必须在 pipeline
块的顶层被定义, 但是 stage 级别的使用是可选的。
参数
为了支持作者可能有的各种各样的用例流水线, agent
部分支持一些不同类型的参数。这些参数应用在pipeline
块的顶层, 或 stage
指令内部。
- any
在任何可用的代理上执行流水线或阶段。例如:agent any
- none
当在pipeline
块的顶部没有全局代理, 该参数将会被分配到整个流水线的运行中并且每个stage
部分都需要包含他自己的agent
部分。比如:agent none
- label
在提供了标签的 Jenkins 环境中可用的代理上执行流水线或阶段。 例如:agent { label 'my-defined-label' }
- node
agent { node { label 'labelName' } }
和agent { label 'labelName' }
一样, 但是node
允许额外的选项 (比如customWorkspace
)。 - docker
使用给定的容器执行流水线或阶段。该容器将在预置的 node上,或在匹配可选定义的label
参数上,动态的供应来接受基于Docker的流水线。docker
也可以选择的接受args
参数,该参数可能包含直接传递到docker run
调用的参数, 以及alwaysPull
选项, 该选项强制docker pull
,即使镜像名称已经存在。 比如:agent { docker 'maven:3-alpine' }
或agent { docker { image 'maven:3-alpine' label 'my-defined-label' args '-v /tmp:/tmp' } }
- dockerfile
执行流水线或阶段, 使用从源代码库包含的Dockerfile
构建的容器。为了使用该选项,Jenkinsfile
必须从多个分支流水线中加载, 或者加载 “Pipeline from SCM.” 通常,这是源代码仓库的根目录下的Dockerfile
:agent { dockerfile true }
. 如果在另一个目录下构建Dockerfile
, 使用dir
选项:agent { dockerfile {dir 'someSubDir' } }
。如果Dockerfile
有另一个名称, 你可以使用filename
选项指定该文件名。你可以传递额外的参数到docker build ...
使用additionalBuildArgs
选项提交, 比如agent { dockerfile {additionalBuildArgs '--build-arg foo=bar' } }
。 例如, 一个带有build/Dockerfile.build
的仓库,期望一个构建参数version
:agent { // Equivalent to "docker build -f Dockerfile.build --build-arg version=1.0.2 ./build/ dockerfile { filename 'Dockerfile.build' dir 'build' label 'my-defined-label' additionalBuildArgs '--build-arg version=1.0.2' } }
常见选项
有一些应用于两个或更多 agent
的实现的选项。他们不被要求,除非特别规定。
- label
一个字符串。该标签用于运行流水线或个别的stage
。该选项对node
,docker
和dockerfile
可用,node
要求必须选择该选项。 - customWorkspace
一个字符串。在自定义工作区运行应用了agent
的流水线或个别的stage
, 而不是默认值。 它既可以是一个相对路径, 在这种情况下,自定义工作区会存在于节点工作区根目录下, 或者一个绝对路径。比如:agent { node { label 'my-defined-label' customWorkspace '/some/other/path' } }
该选项对node
,docker
和dockerfile
有用 。 - reuseNode
一个布尔值, 默认为false。 如果是true, 则在流水线的顶层指定的节点上运行该容器, 在同样的工作区, 而不是在一个全新的节点上。这个选项对docker
和dockerfile
有用, 并且只有当 使用在个别的stage
的agent
上才会有效。
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent { docker 'maven:3-alpine' }
stages {
stage('Example Build') {
steps {
sh 'mvn -B clean verify'
}
}
}
}
在一个给定名称和标签( | |
阶段级别的 agent
部分
Jenkinsfile (Declarative Pipeline)
pipeline {
agent none
stages {
stage('Example Build') {
agent { docker 'maven:3-alpine' }
steps {
echo 'Hello, Maven'
sh 'mvn --version'
}
}
stage('Example Test') {
agent { docker 'openjdk:8-jre' }
steps {
echo 'Hello, JDK'
sh 'java -version'
}
}
}
}
在流水线顶层定义 | |
使用镜像在一个新建的容器中执行该阶段的该步骤。 | |
使用一个与之前阶段不同的镜像在一个新建的容器中执行该阶段的该步骤。 |
post
post
部分定义一个或多个steps ,这些阶段根据流水线或阶段的完成情况而 运行(取决于流水线中 post
部分的位置). post
支持以下 post-condition 块中的其中之一: always
, changed
, failure
, success
, unstable
, 和 aborted
。这些条件块允许在post
部分的步骤的执行取决于流水线或阶段的完成状态。
Conditions
always
无论流水线或阶段的完成状态如何,都允许在post
部分运行该步骤。changed
只有当前流水线或阶段的完成状态与它之前的运行不同时,才允许在post
部分运行该步骤。failure
只有当前流水线或阶段的完成状态为"failure",才允许在post
部分运行该步骤, 通常web UI是红色。success
只有当前流水线或阶段的完成状态为"success",才允许在post
部分运行该步骤, 通常web UI是蓝色或绿色。unstable
只有当前流水线或阶段的完成状态为"unstable",才允许在post
部分运行该步骤, 通常由于测试失败,代码违规等造成。通常web UI是黄色。aborted
只有当前流水线或阶段的完成状态为"aborted",才允许在post
部分运行该步骤, 通常由于流水线被手动的aborted。通常web UI是灰色。
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
post {
always {
echo 'I will always say Hello again!'
}
}
}
按照惯例, | |
Post-condition 块包含与 steps 部分相同的steps。 |
stages
包含一系列一个或多个 stage 指令, stages
部分是流水线描述的大部分"work" 的位置。 建议 stages
至少包含一个 stage 指令用于连续交付过程的每个离散部分,比如构建, 测试, 和部署。
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
| |
steps
steps
部分在给定的 stage
指令中执行的定义了一系列的一个或多个steps。
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
| |
指令
environment
environment
指令制定一个 键-值对序列,该序列将被定义为所有步骤的环境变量,或者是特定于阶段的步骤, 这取决于 environment
指令在流水线内的位置。
该指令支持一个特殊的助手方法 credentials()
,该方法可用于在Jenkins环境中通过标识符访问预定义的凭证。对于类型为 "Secret Text"的凭证, credentials()
将确保指定的环境变量包含秘密文本内容。对于类型为 "SStandard username and password"的凭证, 指定的环境变量指定为 username:password
,并且两个额外的环境变量将被自动定义 :分别为 MYVARNAME_USR
和 MYVARNAME_PSW
。
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
environment {
CC = 'clang'
}
stages {
stage('Example') {
environment {
AN_ACCESS_KEY = credentials('my-prefined-secret-text')
}
steps {
sh 'printenv'
}
}
}
}
顶层流水线块中使用的 | |
在一个 | |
|
options
options
指令允许从流水线内部配置特定于流水线的选项。 流水线提供了许多这样的选项, 比如 buildDiscarder
,但也可以由插件提供, 比如 timestamps
.
可用选项
- buildDiscarder
为最近的流水线运行的特定数量保存组件和控制台输出。例如:options { buildDiscarder(logRotator(numToKeepStr: '1')) }
- disableConcurrentBuilds
不允许同时执行流水线。 可被用来防止同时访问共享资源等。 例如:options { disableConcurrentBuilds() }
- overrideIndexTriggers
允许覆盖分支索引触发器的默认处理。 如果分支索引触发器在多分支或组织标签中禁用,options { overrideIndexTriggers(true) }
将只允许它们用于促工作。否则,options { overrideIndexTriggers(false) }
只会禁用改作业的分支索引触发器。 - skipDefaultCheckout
在agent
指令中,跳过从源代码控制中检出代码的默认情况。例如:options { skipDefaultCheckout() }
- skipStagesAfterUnstable
一旦构建状态变得UNSTABLE,跳过该阶段。例如:options { skipStagesAfterUnstable() }
- checkoutToSubdirectory
在工作空间的子目录中自动地执行源代码控制检出。例如:options { checkoutToSubdirectory('foo') }
- timeout
设置流水线运行的超时时间, 在此之后,Jenkins将中止流水线。例如:options { timeout(time: 1, unit: 'HOURS') }
- retry
在失败时, 重新尝试整个流水线的指定次数。 For example:options { retry(3) }
- timestamps
预谋所有由流水线生成的控制台输出,与该流水线发出的时间一致。 例如:options { timestamps() }
Example
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
options {
timeout(time: 1, unit: 'HOURS')
}
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
指定一个小时的全局执行超时, 在此之后,Jenkins 将中止流水线运行。 | |
一个完整的可用选项列表正在等待完成第 INFRA-1503次。 | |
阶段选项
stage
的 options
指令类似于流水线根目录上的 options
指令。然而, stage
-级别 options
只能包括 retry
, timeout
, 或 timestamps
等步骤, 或与 stage
相关的声明式选项,如 skipDefaultCheckout
。
在stage
, options
指令中的步骤在进入 agent
之前被调用或在 when
条件出现时进行检查。
可选的阶段选项
- skipDefaultCheckout
在agent
指令中跳过默认的从源代码控制中检出代码。例如:options { skipDefaultCheckout() }
- timeout
设置此阶段的超时时间, 在此之后, Jenkins 会终止该阶段。 例如:options { timeout(time: 1, unit: 'HOURS') }
- retry
在失败时, 重试此阶段指定次数。 例如:options { retry(3) }
- timestamps
预谋此阶段生成的所有控制台输出以及该行发出的时间一致。例如:options { timestamps() }
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
echo 'Hello World'
}
}
}
}
参数
parameters
指令提供了一个用户在触发流水线时应该提供的参数列表。这些用户指定参数的值可通过 params
对象提供给流水线步骤, 了解更多请参考示例。
可用参数
- string
字符串类型的参数, 例如:parameters { string(name: 'DEPLOY_ENV', defaultValue: 'staging', description: '') }
- booleanParam
布尔参数, 例如:parameters { booleanParam(name: 'DEBUG_BUILD', defaultValue: true, description: '') }
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
parameters {
string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
}
stages {
stage('Example') {
steps {
echo "Hello ${params.PERSON}"
}
}
}
}
触发器
triggers
指令定义了流水线被重新触发的自动化方法。对于集成了源( 比如 GitHub 或 BitBucket)的流水线, 可能不需要 triggers
,因为基于 web 的集成很肯能已经存在。 当前可用的触发器是 cron
, pollSCM
和 upstream
。
- cron
接收 cron 样式的字符串来定义要重新触发流水线的常规间隔 ,比如:triggers { cron('H */4 * * 1-5') }
- pollSCM
接收 cron 样式的字符串来定义一个固定的间隔,在这个间隔中,Jenkins 会检查新的源代码更新。如果存在更改, 流水线就会被重新触发。例如:triggers { pollSCM('H */4 * * 1-5') }
- upstream
接受逗号分隔的工作字符串和阈值。 当字符串中的任何作业以最小阈值结束时,流水线被重新触发。例如:triggers { upstream(upstreamProjects: 'job1,job2', threshold: hudson.model.Result.SUCCESS) }
| |
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
triggers {
cron('H */4 * * 1-5')
}
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
stage
stage
指令在 stages
部分进行,应该包含一个 实际上, 流水巷所做的所有实际工作都将封装进一个或多个 stage
指令中。
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
工具
定义自动安装和放置 PATH
的工具的一部分。如果 agent none
指定,则忽略该操作。
支持工具
- maven
- jdk
- gradle
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
tools {
maven 'apache-maven-3.0.1'
}
stages {
stage('Example') {
steps {
sh 'mvn --version'
}
}
}
}
input
stage
的 input
指令允许你使用 input提示输入。 在应用了 options
后,进入 stage
的 agent
或评估 when
条件前,stage
将暂停。 如果 input
被批准, stage
将会继续。 作为 input
提交的一部分的任何参数都将在环境中用于其他 stage
。
配置项
- message
必需的。 这将在用户提交input
时呈现给用户。 - id
input
的可选标识符, 默认为stage
名称。 - ok
input
表单上的"ok" 按钮的可选文本。 - submitter
可选的以逗号分隔的用户列表或允许提交input
的外部组名。默认允许任何用户。 - submitterParameter
环境变量的可选名称。如果存在,用submitter
名称设置。 - parameters
提示提交者提供的一个可选的参数列表。 更多信息参见 [parameters]。
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example') {
input {
message "Should we continue?"
ok "Yes, we should."
submitter "alice,bob"
parameters {
string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
}
}
steps {
echo "Hello, ${PERSON}, nice to meet you."
}
}
}
}
when
when
指令允许流水线根据给定的条件决定是否应该执行阶段。 when
指令必须包含至少一个条件。 如果 when
指令包含多个条件, 所有的子条件必须返回True,阶段才能执行。 这与子条件在 allOf
条件下嵌套的情况相同 (参见下面的示例)。
使用诸如 not
, allOf
, 或 anyOf
的嵌套条件可以构建更复杂的条件结构 can be built 嵌套条件刻意潜逃到任意深度。
Required | No |
Parameters | None |
Allowed | Inside a |
内置条件
- branch
当正在构建的分支与模式给定的分支匹配时,执行这个阶段, 例如:when { branch 'master' }
。注意,这只适用于多分支流水线。 - environment
当指定的环境变量是给定的值时,执行这个步骤, 例如:when { environment name: 'DEPLOY_TO', value: 'production' }
- expression
当指定的Groovy表达式评估为true时,执行这个阶段, 例如:when { expression { return params.DEBUG_BUILD } }
- not
当嵌套条件是错误时,执行这个阶段,必须包含一个条件,例如:when { not { branch 'master' } }
- allOf
当所有的嵌套条件都正确时,执行这个阶段,必须包含至少一个条件,例如:when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }
- anyOf
当至少有一个嵌套条件为真时,执行这个阶段,必须包含至少一个条件,例如:when { anyOf { branch 'master'; branch 'staging' } }
在进入 stage
的 agent
前评估 when
默认情况下, 如果定义了某个阶段的代理,在进入该stage
的 agent
后该 stage
的 when
条件将会被评估。但是, 可以通过在 when
块中指定 beforeAgent
选项来更改此选项。 如果 beforeAgent
被设置为 true
, 那么就会首先对 when
条件进行评估 , 并且只有在 when
条件验证为真时才会进入 agent
。
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example Build') {
steps {
echo 'Hello World'
}
}
stage('Example Deploy') {
when {
branch 'production'
}
steps {
echo 'Deploying'
}
}
}
}
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example Build') {
steps {
echo 'Hello World'
}
}
stage('Example Deploy') {
when {
branch 'production'
environment name: 'DEPLOY_TO', value: 'production'
}
steps {
echo 'Deploying'
}
}
}
}
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example Build') {
steps {
echo 'Hello World'
}
}
stage('Example Deploy') {
when {
allOf {
branch 'production'
environment name: 'DEPLOY_TO', value: 'production'
}
}
steps {
echo 'Deploying'
}
}
}
}
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example Build') {
steps {
echo 'Hello World'
}
}
stage('Example Deploy') {
when {
branch 'production'
anyOf {
environment name: 'DEPLOY_TO', value: 'production'
environment name: 'DEPLOY_TO', value: 'staging'
}
}
steps {
echo 'Deploying'
}
}
}
}
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example Build') {
steps {
echo 'Hello World'
}
}
stage('Example Deploy') {
when {
expression { BRANCH_NAME ==~ /(production|staging)/ }
anyOf {
environment name: 'DEPLOY_TO', value: 'production'
environment name: 'DEPLOY_TO', value: 'staging'
}
}
steps {
echo 'Deploying'
}
}
}
}
Jenkinsfile (Declarative Pipeline)
pipeline {
agent none
stages {
stage('Example Build') {
steps {
echo 'Hello World'
}
}
stage('Example Deploy') {
agent {
label "some-label"
}
when {
beforeAgent true
branch 'production'
}
steps {
echo 'Deploying'
}
}
}
}
并行
声明式流水线的阶段可以在他们内部声明多隔嵌套阶段, 它们将并行执行。 注意,一个阶段必须只有一个 steps
或 parallel
的阶段。 嵌套阶段本身不能包含进一步的 parallel
阶段, 但是其他的阶段的行为与任何其他 stage
相同。任何包含 parallel
的阶段不能包含 agent
或 tools
阶段, 因为他们没有相关 steps
。
另外, 通过添加 failFast true
到包含 parallel
的 stage
中, 当其中一个进程失败时,你可以强制所有的 parallel
阶段都被终止。
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Non-Parallel Stage') {
steps {
echo 'This stage will be executed first.'
}
}
stage('Parallel Stage') {
when {
branch 'master'
}
failFast true
parallel {
stage('Branch A') {
agent {
label "for-branch-a"
}
steps {
echo "On Branch A"
}
}
stage('Branch B') {
agent {
label "for-branch-b"
}
steps {
echo "On Branch B"
}
}
}
}
}
}
步骤
声明式流水线可能使用在 流水线步骤引用中记录的所有可用的步骤, 它包含一个完整的步骤列表, 其中添加了下面列出的步骤,这些步骤只在声明式流水线中 only supported 。
脚本
script
步骤需要 [scripted-pipeline]块并在声明式流水线中执行。 对于大多数用例来说,应该声明式流水线中的“脚本”步骤是不必要的, 但是它可以提供一个有用的"逃生出口"。 非平凡的规模和/或复杂性的 script
块应该被转移到 共享库 。
示例
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
script {
def browsers = ['chrome', 'firefox']
for (int i = 0; i < browsers.size(); ++i) {
echo "Testing the ${browsers[i]} browser"
}
}
}
}
}
}
2.4 pipeline流水线脚本存储模式
- jenkins job内脚本模式
在jenkins 管理后台的job任务中直接编写pipeline脚本
- 项目源码仓库内脚本
项目源码SCM仓库中包含了pipeline脚本,用于复杂的流水线项目,其很难在流水线配置页面 经典 UI 的脚本文本区域进行编写和维护。而且可以把jenkins pipeline的构建脚本也纳入源码版本控制中,便于修改和版本回溯。
2.5 使用小技巧总结
- 脚本化参数和job界面上配置的参数二选一即可
如果习惯手动构建任务,建议直接使用配置界面上的参数配置,使用简单灵活
如果需要自动化触发构建,建议使用脚本化参数
- script脚本的插入位置说明
需要插入到具体执行步骤的位置,如 steps、success下等等
- stage动态生成能力
可以借助脚本动态生成多个自定义的stage,注意的是,这些stage必须包含在同一个父stage内才行,内部stage内不允许再写steps标签
- steps标签使用注意事项
steps标签不允许嵌套
- 获取shell脚本执行结果
def result = sh returnStdout: true, script: '<具体的shell命令>' //result用于存储返回结果
- 执行任务异常信息捕获
stage标签和script标签下允许使用try{}catch(Exception e){}进行执行异常的捕获操作
3、pipeline指令生成工具的使用
一些常用的jenkins步骤,如拉取代码、单元测试、发送邮件、远程shell操作等等,jenkins都提供了工具自动生成pipeline代码片段,我们只需要点击界面进行配置,然后生成脚本片段复制到我们的steps下即可。
操作截图如下所示:
我们以拉取git仓库中的代码为例:
4、java应用devops实践
4.1 项目结构
│
├─src
│ ├─main
│ │ ├─java
│ ├─main
│ │ ├─resources
│ ├─test
│ ├─java
│
├─subModule1 #子模块一
│
├─subModule2 #子模块二
│
├─Jenkinsfile #jenkins pipeline脚本
│
├─deploy.sh #docker swarm 服务部署脚本
│
└─pom.xml
4.2 devops相关脚本
4.2.1 依赖的maven插件配置
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>false</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.7.0.1746</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
</plugin>
</plugins>
</build>
4.2.2 pipeline代码
def PROJ_VERSION = "1.0.0"
def errMsg='';
pipeline{
agent any
tools {
maven "apache-maven-3.6.3-bin"
}
options {
//只保留最近三次构建的包
buildDiscarder(logRotator(numToKeepStr: '6'))
//跳过默认的代码检出操作
skipDefaultCheckout(true)
//如果整个流程执行异常导致失败可以自动重试
//retry(1)
}
stages {
stage('拉取最新代码') {
steps{
sh 'printenv'
echo "拉取[${SOURCE_TAG}]的最新代码......"
script{
try{
//提取分支或者tag名
// String sourceTag = params.SOURCE_TAG.substring(params.SOURCE_TAG.lastIndexOf("/")+1,params.SOURCE_TAG.length());
echo "checkout branches:[ ${SOURCE_TAG} ]"
checkout([$class: 'GitSCM', branches: [[name: params.SOURCE_TAG]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'xxxx-xxx-xxx-xxx-xxxxxxx', url: 'http://localhost:3000/test/helloworld.git']]])
if(params.PROJ_VERSION == null || params.PROJ_VERSION.trim().equals("")){
PROJ_VERSION = sh returnStdout: true, script: 'mvn help:evaluate -Dexpression=project.version -q -DforceStdout'
}
}catch(Exception e){
errMsg = e;
throw e
}
}
echo "PROJ_VERSION:${PROJ_VERSION}"
}
}
stage('代码检查') {
steps{
script{
try{
echo "mvn clean compile sonar:sonar -Dsonar.projectKey=bsp-ota -Dsonar.host.url=http://localhost:9191 -Dsonar.login=xxxxxxxxx 执行代码静态检查任务......"
sh "mvn clean compile sonar:sonar -Dsonar.projectKey=bsp-ota -Dsonar.host.url=http://localhost:9191 -Dsonar.login=xxxxxxxxx"
}catch(Exception e){
errMsg = e;
throw e
}
}
}
}
stage('构建依赖模块') {
steps{
script{
try{
echo "mvn clean install ${MODULE_IDS}和其依赖的基础模块......"
sh "mvn clean install -pl ${MODULE_IDS} -am -U -Dmaven.test.skip=true"
}catch(Exception e){
errMsg = e;
throw e
}
}
}
}
stage('动态生成子stage'){
failFast true
steps{
echo "开始打包镜像和发布服务......"
script{
try{
String[] modules = params.MODULE_IDS.split(",");
for(module in modules){
String testStageName='单元测试:'+module;
String pkgStageName='生成镜像:'+module;
String deployStageName='部署项目:'+module;
stage(testStageName) {
echo "${module}注入jacoco插件配置,clean test执行单元测试代码......并将结果上传到将SonarQaue"
dir (module){
withSonarQubeEnv('sonar') {
sh "mvn org.jacoco:jacoco-maven-plugin:prepare-agent test -U -Dautoconfig.skip=true -Dmaven.test.skip=false -Dmaven.test.failure.ignore=true sonar:sonar -Dsonar.projectKey=bsp-ota -Dsonar.host.url=http://localhost:9191 -Dsonar.login=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
//95%通过即为合格
junit allowEmptyResults: true, healthScaleFactor: 5.0, testResults: 'target/surefire-reports/*.xml'
}
echo "配置单元测试覆盖率要求,未达到要求pipeline将会fail,code coverage.LineCoverage>80%. 作用为将结果打印到jenkins 中,通过 jenkins jacoco plugin "
/*jacoco changeBuildStatus: true, maximumLineCoverage:"80"*/
jacoco buildOverBuild: true, deltaClassCoverage: '5', deltaLineCoverage: '10', deltaMethodCoverage: '10', maximumClassCoverage: '80', maximumLineCoverage: '80', maximumMethodCoverage: '80', minimumClassCoverage: '60', minimumLineCoverage: '60', minimumMethodCoverage: '60', runAlways: true, sourceInclusionPattern: '**/*.java'
timeout(3) { //一分钟
//利用sonar webhook功能通知pipeline代码检测结果,未通过质量阈,pipeline将会fail
def qg = waitForQualityGate()
if (qg.status != 'OK') {
error "未通过Sonarqube的代码质量阈检查,请及时修改!failure: ${qg.status}"
}
}
}
}
stage(pkgStageName) {
echo "构建${module}镜像并推送到镜像仓库......"
dir (module){
sh "mvn install -P docker docker:build docker:push -U -DskipTests=true"
}
}
stage(deployStageName) {
echo "部署项目${module}......"
script{
def remote = getServer(SWARM_HOST_IP);
echo "starting ${module}${PROJ_VERSION} docker swarm deploy ......"
def _command='sh deploy.sh '+module+" "+PROJ_VERSION
if(params.EXPOSE_PORT != null && !params.EXPOSE_PORT.trim().equals("")){
_command='sh deploy.sh '+module+" "+PROJ_VERSION +" "+params.EXPOSE_PORT+":"+params.INNER_PORT
}
echo "执行远程命令:"+_command
sshPut remote: remote, from: 'deploy.sh', into: 'deploy.sh'
sshCommand remote: remote, command: _command
sshRemove remote: remote, path: 'deploy.sh'
}
}
}
}catch(Exception e){
errMsg = e
throw e
}
}
echo "打包镜像和发布服务完成......"
}
}
//集成测试,可并行测试,节省时间
}
post {
failure { //执行流水线失败进行邮件提醒
script{
if(NEED_NOTIFY_EMAIL){
echo '执行任务失败,邮件通知对应的研发人员!'
String content='构建任务'+env.JOB_BASE_NAME+'失败,请及时查看!\n原因:'+errMsg
String title='[ERROR]-jenkins任务'+env.JOB_NAME+'-'+env.BUILD_NUMBER+'构建结果提醒'
mail bcc: '', body: content, cc: '', from: 'activate@skyroam.com', replyTo: '', subject: title, to: params.EMAILS
}
}
}
success { //执行成功进行邮件提醒
echo '执行任务成功,邮件通知对应的研发人员!'
script{
if(NEED_NOTIFY_EMAIL){
String content='执行任务'+env.JOB_BASE_NAME+'-'+env.BUILD_NUMBER+'成功,请及时查看!'
String title='[SUCCESS]-jenkins任务'+env.JOB_NAME+'-'+env.BUILD_NUMBER+'构建结果提醒'
mail bcc: '', body: content, cc: '', replyTo: '', subject: title, to: params.EMAILS
}
}
}
}
}
def getServer(ip){
def remote = [:]
remote.name = "server-${ip}"
remote.host = ip
remote.port = 52113
remote.allowAnyHosts = true
withCredentials([usernamePassword(credentialsId: SWRAM_MANAGER_HOST_ID, passwordVariable: 'password', usernameVariable: 'username')]) {
remote.user = "${username}"
remote.password = "${password}"
}
return remote
}
4.2.4 docker swarm 服务部署脚本 deploy.sh
#!/bin/bash
#项目名称
IMAGE_NAME=$1
#将要要发布的版本
IMAGE_VERSION=$2
PJ_PORT=$3
SERVICE_NAME=$IMAGE_NAME
#如果机器上环境变量未指定本地镜像仓库地址,则使用默认的地址
if [ ! -n "$REMOTE_IMAGE_REPO" ]; then
REMOTE_IMAGE_REPO=127.0.0.1:5000
fi
#检查
echo $(sudo -E docker service ls | grep $IMAGE_NAME)
_count=$(sudo -E docker service ls | grep $IMAGE_NAME | wc -l)
if [ $_count -gt 0 ]; then
#获取服务名
SERVICE_NAME=$(sudo -E docker service ls | grep $IMAGE_NAME | awk '{print $2}')
echo "sudo -E docker service update $SERVICE_NAME $REMOTE_IMAGE_REPO/$IMAGE_NAME:$IMAGE_VERSION"
sudo -E docker service update $SERVICE_NAME --image $REMOTE_IMAGE_REPO/$IMAGE_NAME:$IMAGE_VERSION
else
echo "sudo -E docker service create $IMAGE_NAME $REMOTE_IMAGE_REPO/$IMAGE_NAME:$IMAGE_VERSION"
if [ $PJ_PORT ]; then
sudo -E docker service create --name $IMAGE_NAME \
--network bridge --replicas 1 \
--publish PJ_PORT \
--restart-condition on-failure \
--mount type=bind,src=/home/release/conf/,dst=/home/release/conf/ \
--constraint 'node.role==worker' $REMOTE_IMAGE_REPO/$IMAGE_NAME:$IMAGE_VERSION
else
sudo -E docker service create --name $IMAGE_NAME \
--network bridge --replicas 1 \
--restart-condition on-failure \
--mount type=bind,src=/home/release/conf/,dst=/home/release/conf/ \
--constraint 'node.role==worker' $REMOTE_IMAGE_REPO/$IMAGE_NAME:$IMAGE_VERSION
fi
fi
4.2.5 jenkins中任务的参数
4.2.6 pipeline执行结果
4.2.7 sonarque检查结果